Implement context-sensitive keybinding support.

Allow defining commands with guard conditions, and binding
one or more commands to alphabetic and function keys. When
the relevant key is pressed, the first listed command with
successfully evaluated guard is chosen.

For consistency, the guard is also checked when the command
is invoked from the console; this requires suspending the
core inside PluginManager, before invoking plugin code.
develop
Alexander Gavrilov 2011-12-31 13:25:46 +04:00
parent 4aa77f5530
commit b652ec4132
10 changed files with 474 additions and 104 deletions

@ -51,16 +51,20 @@ using namespace DFHack;
#include "dfhack/SDL_fakes/events.h" #include "dfhack/SDL_fakes/events.h"
#include "dfhack/df/ui.h"
#include "dfhack/df/world.h" #include "dfhack/df/world.h"
#include "dfhack/df/world_data.h" #include "dfhack/df/world_data.h"
#include "dfhack/df/interface.h"
#include "dfhack/df/viewscreen_dwarfmodest.h"
#include <stdio.h> #include <stdio.h>
#include <iomanip> #include <iomanip>
#include <stdlib.h> #include <stdlib.h>
#include <fstream> #include <fstream>
#include "tinythread.h" #include "tinythread.h"
using namespace tthread;
using namespace tthread;
using namespace df::enums;
struct Core::Cond struct Core::Cond
{ {
@ -204,6 +208,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
" cls - Clear the console.\n" " cls - Clear the console.\n"
" fpause - Force DF to pause.\n" " fpause - Force DF to pause.\n"
" die - Force DF to close immediately\n" " die - Force DF to close immediately\n"
" keybinding - Modify bindings of commands to keys\n"
"Plugin management (useful for developers):\n" "Plugin management (useful for developers):\n"
//" belongs COMMAND - Tell which plugin a command belongs to.\n" //" belongs COMMAND - Tell which plugin a command belongs to.\n"
" plug [PLUGIN|v] - List plugin state and description.\n" " plug [PLUGIN|v] - List plugin state and description.\n"
@ -212,6 +217,27 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
" reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" " reload PLUGIN|all - Reload a plugin or all loaded plugins.\n"
); );
} }
else if (parts.size() == 1)
{
Plugin *plug = plug_mgr->getPluginByCommand(parts[0]);
if (plug) {
for (int j = 0; j < plug->size();j++)
{
const PluginCommand & pcmd = (plug->operator[](j));
if (pcmd.name != parts[0])
continue;
if (pcmd.isHotkeyCommand())
con.color(Console::COLOR_CYAN);
con.print("%s: %s\n",pcmd.name.c_str(), pcmd.description.c_str());
con.reset_color();
if (!pcmd.usage.empty())
con << "Usage:\n" << pcmd.usage << flush;
return;
}
}
con.printerr("Unknown command: %s\n", parts[0].c_str());
}
else else
{ {
con.printerr("not implemented yet\n"); con.printerr("not implemented yet\n");
@ -311,7 +337,10 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
else for (int j = 0; j < plug->size();j++) else for (int j = 0; j < plug->size();j++)
{ {
const PluginCommand & pcmd = (plug->operator[](j)); const PluginCommand & pcmd = (plug->operator[](j));
if (pcmd.isHotkeyCommand())
con.color(Console::COLOR_CYAN);
con.print(" %-22s - %s\n",pcmd.name.c_str(), pcmd.description.c_str()); con.print(" %-22s - %s\n",pcmd.name.c_str(), pcmd.description.c_str());
con.reset_color();
} }
} }
else else
@ -339,7 +368,10 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
for (int j = 0; j < plug->size();j++) for (int j = 0; j < plug->size();j++)
{ {
const PluginCommand & pcmd = (plug->operator[](j)); const PluginCommand & pcmd = (plug->operator[](j));
if (pcmd.isHotkeyCommand())
con.color(Console::COLOR_CYAN);
con.print(" %-22s- %s\n",pcmd.name.c_str(), pcmd.description.c_str()); con.print(" %-22s- %s\n",pcmd.name.c_str(), pcmd.description.c_str());
con.reset_color();
} }
} }
} }
@ -354,6 +386,49 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
con.print("%s\n", plug->getName().c_str()); con.print("%s\n", plug->getName().c_str());
} }
} }
else if(first == "keybinding")
{
if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add"))
{
std::string keystr = parts[1];
if (parts[0] == "set")
core->ClearKeyBindings(keystr);
for (int i = parts.size()-1; i >= 2; i--)
{
if (!core->AddKeyBinding(keystr, parts[i])) {
con.printerr("Invalid key spec: %s\n", keystr.c_str());
break;
}
}
}
else if (parts.size() >= 2 && parts[0] == "clear")
{
for (unsigned i = 1; i < parts.size(); i++)
{
if (!core->ClearKeyBindings(parts[i])) {
con.printerr("Invalid key spec: %s\n", parts[i].c_str());
break;
}
}
}
else if (parts.size() == 2 && parts[0] == "list")
{
std::vector<std::string> list = core->ListKeyBindings(parts[1]);
if (list.empty())
con << "No bindings." << endl;
for (unsigned i = 0; i < list.size(); i++)
con << " " << list[i] << endl;
}
else
{
con << "Usage:" << endl
<< " keybinding list <key>" << endl
<< " keybinding clear <key> <key>..." << endl
<< " keybinding set <key> \"cmdline\" \"cmdline\"..." << endl
<< " keybinding add <key> \"cmdline\" \"cmdline\"..." << endl
<< "Later adds, and earlier items within one command have priority." << endl;
}
}
else if(first == "fpause") else if(first == "fpause")
{ {
World * w = core->getWorld(); World * w = core->getWorld();
@ -482,12 +557,12 @@ Core::Core()
StackMutex = 0; StackMutex = 0;
core_cond = 0; core_cond = 0;
// set up hotkey capture // set up hotkey capture
memset(hotkey_states,0,sizeof(hotkey_states));
hotkey_set = false; hotkey_set = false;
HotkeyMutex = 0; HotkeyMutex = 0;
HotkeyCond = 0; HotkeyCond = 0;
misc_data_mutex=0; misc_data_mutex=0;
last_world_data_ptr = NULL; last_world_data_ptr = NULL;
top_viewscreen = NULL;
}; };
void Core::fatal (std::string output, bool deactivate) void Core::fatal (std::string output, bool deactivate)
@ -694,6 +769,19 @@ int Core::Update()
plug_mgr->OnStateChange(new_wdata ? SC_GAME_LOADED : SC_GAME_UNLOADED); plug_mgr->OnStateChange(new_wdata ? SC_GAME_LOADED : SC_GAME_UNLOADED);
} }
// detect if the viewscreen changed
if (df::global::gview)
{
df::viewscreen *screen = &df::global::gview->view;
while (screen->child)
screen = screen->child;
if (screen != top_viewscreen)
{
top_viewscreen = screen;
plug_mgr->OnStateChange(SC_VIEWSCREEN_CHANGED);
}
}
// notify all the plugins that a game tick is finished // notify all the plugins that a game tick is finished
plug_mgr->OnUpdate(); plug_mgr->OnUpdate();
@ -785,37 +873,166 @@ int Core::SDL_Event(SDL::Event* ev, int orig_return)
if(ev && ev->type == SDL::ET_KEYDOWN || ev->type == SDL::ET_KEYUP) if(ev && ev->type == SDL::ET_KEYDOWN || ev->type == SDL::ET_KEYUP)
{ {
SDL::KeyboardEvent * ke = (SDL::KeyboardEvent *)ev; SDL::KeyboardEvent * ke = (SDL::KeyboardEvent *)ev;
bool shift = ke->ksym.mod & SDL::KMOD_SHIFT;
// consuming F1 .. F8 if(ke->state == SDL::BTN_PRESSED && !hotkey_states[ke->ksym.sym])
int idx = ke->ksym.sym - SDL::K_F1;
if(idx < 0 || idx > 7)
return orig_return;
idx += 8*shift;
// now we have the real index...
if(ke->state == SDL::BTN_PRESSED && !hotkey_states[idx])
{ {
hotkey_states[idx] = 1; hotkey_states[ke->ksym.sym] = true;
Gui * g = getGui();
if(g->hotkeys && g->df_interface && g->df_menu_state) int mod = 0;
if (ke->ksym.mod & SDL::KMOD_SHIFT) mod |= 1;
if (ke->ksym.mod & SDL::KMOD_CTRL) mod |= 2;
if (ke->ksym.mod & SDL::KMOD_ALT) mod |= 4;
SelectHotkey(ke->ksym.sym, mod);
}
else if(ke->state == SDL::BTN_RELEASED)
{ {
t_viewscreen * ws = g->GetCurrentScreen(); hotkey_states[ke->ksym.sym] = false;
// FIXME: put hardcoded values into memory.xml }
if(ws->getClassName() == "viewscreen_dwarfmodest" && *g->df_menu_state == 0x23) }
return orig_return; return orig_return;
else // do stuff with the events...
}
bool Core::SelectHotkey(int sym, int modifiers)
{
// Find the topmost viewscreen
if (!df::global::gview || !df::global::ui)
return false;
df::viewscreen *screen = &df::global::gview->view;
while (screen->child)
screen = screen->child;
std::string cmd;
{ {
t_hotkey & hotkey = (*g->hotkeys)[idx]; tthread::lock_guard<tthread::mutex> lock(*HotkeyMutex);
setHotkeyCmd(hotkey.name);
// Check the internal keybindings
std::vector<KeyBinding> &bindings = key_bindings[sym];
for (int i = bindings.size()-1; i >= 0; --i) {
if (bindings[i].modifiers != modifiers)
continue;
if (!plug_mgr->CanInvokeHotkey(bindings[i].command[0], screen))
continue;
cmd = bindings[i].cmdline;
break;
} }
if (cmd.empty()) {
// Check the hotkey keybindings
int idx = sym - SDL::K_F1;
if(idx >= 0 && idx < 8)
{
if (modifiers & 1)
idx += 8;
if (!strict_virtual_cast<df::viewscreen_dwarfmodest>(screen) ||
df::global::ui->main.mode != ui_sidebar_mode::Hotkeys)
{
cmd = df::global::ui->main.hotkeys[idx].name;
} }
} }
else if(ke->state == SDL::BTN_RELEASED)
{
hotkey_states[idx] = 0;
} }
} }
return orig_return;
// do stuff with the events... if (!cmd.empty()) {
setHotkeyCmd(cmd);
return true;
}
else
return false;
}
static bool parseKeySpec(std::string keyspec, int *psym, int *pmod)
{
*pmod = 0;
// ugh, ugly
for (;;) {
if (keyspec.size() > 6 && keyspec.substr(0, 6) == "Shift-") {
*pmod |= 1;
keyspec = keyspec.substr(6);
} else if (keyspec.size() > 5 && keyspec.substr(0, 5) == "Ctrl-") {
*pmod |= 2;
keyspec = keyspec.substr(5);
} else if (keyspec.size() > 4 && keyspec.substr(0, 4) == "Alt-") {
*pmod |= 4;
keyspec = keyspec.substr(4);
} else
break;
}
if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') {
*psym = SDL::K_a + (keyspec[0]-'A');
return true;
} else if (keyspec.size() == 2 && keyspec[0] == 'F' && keyspec[1] >= '1' && keyspec[1] <= '9') {
*psym = SDL::K_F1 + (keyspec[1]-'1');
return true;
} else
return false;
}
bool Core::ClearKeyBindings(std::string keyspec)
{
int sym, mod;
if (!parseKeySpec(keyspec, &sym, &mod))
return false;
tthread::lock_guard<tthread::mutex> lock(*HotkeyMutex);
std::vector<KeyBinding> &bindings = key_bindings[sym];
for (int i = bindings.size()-1; i >= 0; --i) {
if (bindings[i].modifiers == mod)
bindings.erase(bindings.begin()+i);
}
return true;
}
bool Core::AddKeyBinding(std::string keyspec, std::string cmdline)
{
int sym;
KeyBinding binding;
if (!parseKeySpec(keyspec, &sym, &binding.modifiers))
return false;
cheap_tokenise(cmdline, binding.command);
if (binding.command.empty())
return false;
tthread::lock_guard<tthread::mutex> lock(*HotkeyMutex);
// Don't add duplicates
std::vector<KeyBinding> &bindings = key_bindings[sym];
for (int i = bindings.size()-1; i >= 0; --i) {
if (bindings[i].modifiers == binding.modifiers &&
bindings[i].cmdline == cmdline)
return true;
}
binding.cmdline = cmdline;
bindings.push_back(binding);
return true;
}
std::vector<std::string> Core::ListKeyBindings(std::string keyspec)
{
int sym, mod;
std::vector<std::string> rv;
if (!parseKeySpec(keyspec, &sym, &mod))
return rv;
tthread::lock_guard<tthread::mutex> lock(*HotkeyMutex);
std::vector<KeyBinding> &bindings = key_bindings[sym];
for (int i = bindings.size()-1; i >= 0; --i) {
if (bindings[i].modifiers == mod)
rv.push_back(bindings[i].cmdline);
}
return rv;
} }
//////////////// ////////////////

@ -27,6 +27,9 @@ distribution.
#include "dfhack/Process.h" #include "dfhack/Process.h"
#include "dfhack/PluginManager.h" #include "dfhack/PluginManager.h"
#include "dfhack/Console.h" #include "dfhack/Console.h"
#include "dfhack/DataDefs.h"
using namespace DFHack; using namespace DFHack;
#include <string> #include <string>
@ -273,13 +276,67 @@ command_result Plugin::invoke( std::string & command, std::vector <std::string>
{ {
for (int i = 0; i < commands.size();i++) for (int i = 0; i < commands.size();i++)
{ {
if(commands[i].name == command) PluginCommand &cmd = commands[i];
if(cmd.name == command)
{ {
// running interactive things from some other source than the console would break it // running interactive things from some other source than the console would break it
if(!interactive_ && commands[i].interactive) if(!interactive_ && cmd.interactive)
cr = CR_WOULD_BREAK; cr = CR_WOULD_BREAK;
else if (cmd.guard)
{
// Execute hotkey commands in a way where they can
// expect their guard conditions to be matched,
// so as to avoid duplicating checks.
// This means suspending the core beforehand.
CoreSuspender suspend(&c);
df::viewscreen *top = c.getTopViewscreen();
if (!cmd.guard(&c, top))
{
c.con.printerr("Could not invoke %s: unsuitable UI state.\n", command.c_str());
cr = CR_WRONG_USAGE;
}
else else
cr = commands[i].function(&c, parameters); {
cr = cmd.function(&c, parameters);
}
}
else
{
cr = cmd.function(&c, parameters);
}
if (cr == CR_WRONG_USAGE && !cmd.usage.empty())
c.con << "Usage:\n" << cmd.usage << flush;
break;
}
}
}
access->lock_sub();
return cr;
}
bool Plugin::can_invoke_hotkey( std::string & command, df::viewscreen *top )
{
Core & c = Core::getInstance();
bool cr = false;
access->lock_add();
if(state == PS_LOADED)
{
for (int i = 0; i < commands.size();i++)
{
PluginCommand &cmd = commands[i];
if(cmd.name == command)
{
if (cmd.interactive)
cr = false;
else if (cmd.guard)
cr = cmd.guard(&c, top);
else
cr = default_hotkey(&c, top);
break; break;
} }
} }
@ -363,19 +420,27 @@ Plugin *PluginManager::getPluginByName (const std::string & name)
return 0; return 0;
} }
Plugin *PluginManager::getPluginByCommand(const std::string &command)
{
tthread::lock_guard<tthread::mutex> lock(*cmdlist_mutex);
map <string, Plugin *>::iterator iter = belongs.find(command);
if (iter != belongs.end())
return iter->second;
else
return NULL;
}
// FIXME: handle name collisions... // FIXME: handle name collisions...
command_result PluginManager::InvokeCommand( std::string & command, std::vector <std::string> & parameters, bool interactive) command_result PluginManager::InvokeCommand( std::string & command, std::vector <std::string> & parameters, bool interactive)
{ {
command_result cr = CR_NOT_IMPLEMENTED; Plugin *plugin = getPluginByCommand(command);
Core * c = &Core::getInstance(); return plugin ? plugin->invoke(command, parameters, interactive) : CR_NOT_IMPLEMENTED;
cmdlist_mutex->lock(); }
map <string, Plugin *>::iterator iter = belongs.find(command);
if(iter != belongs.end()) bool PluginManager::CanInvokeHotkey(std::string &command, df::viewscreen *top)
{ {
cr = iter->second->invoke(command, parameters, interactive); Plugin *plugin = getPluginByCommand(command);
} return plugin ? plugin->can_invoke_hotkey(command, top) : false;
cmdlist_mutex->unlock();
return cr;
} }
void PluginManager::OnUpdate( void ) void PluginManager::OnUpdate( void )

@ -43,6 +43,11 @@ namespace tthread
class thread; class thread;
} }
namespace df
{
struct viewscreen;
}
namespace DFHack namespace DFHack
{ {
class Process; class Process;
@ -134,7 +139,12 @@ namespace DFHack
/// returns a named pointer. /// returns a named pointer.
void *GetData(std::string key); void *GetData(std::string key);
bool ClearKeyBindings(std::string keyspec);
bool AddKeyBinding(std::string keyspec, std::string cmdline);
std::vector<std::string> ListKeyBindings(std::string keyspec);
bool isWorldLoaded() { return (last_world_data_ptr != NULL); } bool isWorldLoaded() { return (last_world_data_ptr != NULL); }
df::viewscreen *getTopViewscreen() { return top_viewscreen; }
DFHack::Process * p; DFHack::Process * p;
DFHack::VersionInfo * vinfo; DFHack::VersionInfo * vinfo;
@ -180,13 +190,25 @@ namespace DFHack
} s_mods; } s_mods;
std::vector <Module *> allModules; std::vector <Module *> allModules;
DFHack::PluginManager * plug_mgr; DFHack::PluginManager * plug_mgr;
// hotkey-related stuff // hotkey-related stuff
int hotkey_states[16]; struct KeyBinding {
int modifiers;
std::vector<std::string> command;
std::string cmdline;
};
std::map<int, std::vector<KeyBinding> > key_bindings;
std::map<int, bool> hotkey_states;
std::string hotkey_cmd; std::string hotkey_cmd;
bool hotkey_set; bool hotkey_set;
tthread::mutex * HotkeyMutex; tthread::mutex * HotkeyMutex;
tthread::condition_variable * HotkeyCond; tthread::condition_variable * HotkeyCond;
bool SelectHotkey(int key, int modifiers);
void *last_world_data_ptr; // for state change tracking void *last_world_data_ptr; // for state change tracking
df::viewscreen *top_viewscreen;
// Very important! // Very important!
bool started; bool started;

@ -35,49 +35,68 @@ namespace tthread
class mutex; class mutex;
class condition_variable; class condition_variable;
} }
namespace df
{
struct viewscreen;
}
namespace DFHack namespace DFHack
{ {
class Core; class Core;
class PluginManager; class PluginManager;
enum command_result enum command_result
{ {
CR_WOULD_BREAK = -2, CR_WOULD_BREAK = -2,
CR_NOT_IMPLEMENTED = -1, CR_NOT_IMPLEMENTED = -1,
CR_FAILURE = 0, CR_FAILURE = 0,
CR_OK = 1 CR_OK = 1,
CR_WRONG_USAGE = 2
}; };
enum state_change_event enum state_change_event
{ {
SC_GAME_LOADED, SC_GAME_LOADED,
SC_GAME_UNLOADED SC_GAME_UNLOADED,
SC_VIEWSCREEN_CHANGED
}; };
struct PluginCommand struct DFHACK_EXPORT PluginCommand
{ {
typedef command_result (*command_function)(Core *, std::vector <std::string> &);
typedef bool (*command_hotkey_guard)(Core *, df::viewscreen *);
/// create a command with a name, description, function pointer to its code /// create a command with a name, description, function pointer to its code
/// and saying if it needs an interactive terminal /// and saying if it needs an interactive terminal
/// Most commands shouldn't require an interactive terminal! /// Most commands shouldn't require an interactive terminal!
PluginCommand(const char * _name, PluginCommand(const char * _name,
const char * _description, const char * _description,
command_result (*function_)(Core *, std::vector <std::string> &), command_function function_,
bool interactive_ = false bool interactive_ = false,
const char * usage_ = ""
) )
: name(_name), description(_description),
function(function_), interactive(interactive_),
guard(NULL), usage(usage_)
{ {
name = _name;
description = _description;
function = function_;
interactive = interactive_;
} }
PluginCommand (const PluginCommand & rhs)
PluginCommand(const char * _name,
const char * _description,
command_function function_,
command_hotkey_guard guard_,
const char * usage_ = "")
: name(_name), description(_description),
function(function_), interactive(false),
guard(guard_), usage(usage_)
{ {
name = rhs.name;
description = rhs.description;
function = rhs.function;
interactive = rhs.interactive;
} }
bool isHotkeyCommand() const { return guard != NULL; }
std::string name; std::string name;
std::string description; std::string description;
command_result (*function)(Core *, std::vector <std::string> &); command_function function;
bool interactive; bool interactive;
command_hotkey_guard guard;
std::string usage;
}; };
class Plugin class Plugin
{ {
@ -98,6 +117,7 @@ namespace DFHack
bool unload(); bool unload();
bool reload(); bool reload();
command_result invoke( std::string & command, std::vector <std::string> & parameters, bool interactive ); command_result invoke( std::string & command, std::vector <std::string> & parameters, bool interactive );
bool can_invoke_hotkey( std::string & command, df::viewscreen *top );
plugin_state getState () const; plugin_state getState () const;
const PluginCommand& operator[] (std::size_t index) const const PluginCommand& operator[] (std::size_t index) const
{ {
@ -139,7 +159,9 @@ namespace DFHack
// PUBLIC METHODS // PUBLIC METHODS
public: public:
Plugin *getPluginByName (const std::string & name); Plugin *getPluginByName (const std::string & name);
Plugin *getPluginByCommand (const std::string &command);
command_result InvokeCommand( std::string & command, std::vector <std::string> & parameters, bool interactive = true ); command_result InvokeCommand( std::string & command, std::vector <std::string> & parameters, bool interactive = true );
bool CanInvokeHotkey(std::string &command, df::viewscreen *top);
Plugin* operator[] (std::size_t index) Plugin* operator[] (std::size_t index)
{ {
if(index >= all_plugins.size()) if(index >= all_plugins.size())
@ -157,5 +179,10 @@ namespace DFHack
std::vector <Plugin *> all_plugins; std::vector <Plugin *> all_plugins;
std::string plugin_path; std::string plugin_path;
}; };
// Predefined hotkey guards
DFHACK_EXPORT bool default_hotkey(Core *, df::viewscreen *);
DFHACK_EXPORT bool dwarfmode_hotkey(Core *, df::viewscreen *);
DFHACK_EXPORT bool cursor_hotkey(Core *, df::viewscreen *);
} }

@ -37,8 +37,44 @@ using namespace std;
#include "dfhack/Error.h" #include "dfhack/Error.h"
#include "ModuleFactory.h" #include "ModuleFactory.h"
#include "dfhack/Core.h" #include "dfhack/Core.h"
#include "dfhack/PluginManager.h"
using namespace DFHack; using namespace DFHack;
#include "dfhack/DataDefs.h"
#include "dfhack/df/cursor.h"
#include "dfhack/df/viewscreen_dwarfmodest.h"
// Predefined common guard functions
bool DFHack::default_hotkey(Core *, df::viewscreen *top)
{
// Default hotkey guard function
for (;top ;top = top->parent)
if (strict_virtual_cast<df::viewscreen_dwarfmodest>(top))
return true;
return false;
}
bool DFHack::dwarfmode_hotkey(Core *, df::viewscreen *top)
{
// Require the main dwarf mode screen
return !!strict_virtual_cast<df::viewscreen_dwarfmodest>(top);
}
bool DFHack::cursor_hotkey(Core *c, df::viewscreen *top)
{
if (!dwarfmode_hotkey(c, top))
return false;
// Also require the cursor.
if (!df::global::cursor || df::global::cursor->x == -30000)
return false;
return true;
}
//
Module* DFHack::createGui() Module* DFHack::createGui()
{ {
return new Gui(); return new Gui();

@ -91,6 +91,11 @@
<stl-string name="str_visit"/> <stl-string name="str_visit"/>
<stl-string name="str_site"/> <stl-string name="str_site"/>
</class-type> </class-type>
<class-type type-name='viewscreen_dwarfmodest' inherits-from='viewscreen'>
todo
</class-type>
</data-definition> </data-definition>
<!-- <!--

@ -27,7 +27,7 @@ DFhackCExport command_result plugin_init ( Core * c, std::vector <PluginCommand>
{ {
commands.clear(); commands.clear();
commands.push_back(PluginCommand("clean","Removes contaminants from map tiles, items and creatures.",clean)); commands.push_back(PluginCommand("clean","Removes contaminants from map tiles, items and creatures.",clean));
commands.push_back(PluginCommand("spotclean","Cleans map tile under cursor.",spotclean)); commands.push_back(PluginCommand("spotclean","Cleans map tile under cursor.",spotclean,cursor_hotkey));
return CR_OK; return CR_OK;
} }
@ -165,7 +165,7 @@ command_result cleanunits (Core * c)
DFhackCExport command_result spotclean (Core * c, vector <string> & parameters) DFhackCExport command_result spotclean (Core * c, vector <string> & parameters)
{ {
c->Suspend(); // HOTKEY COMMAND: CORE ALREADY SUSPENDED
vector<DFHack::t_spattervein *> splatter; vector<DFHack::t_spattervein *> splatter;
DFHack::Maps *Mapz = c->getMaps(); DFHack::Maps *Mapz = c->getMaps();
DFHack::Gui *Gui = c->getGui(); DFHack::Gui *Gui = c->getGui();
@ -173,7 +173,6 @@ DFhackCExport command_result spotclean (Core * c, vector <string> & parameters)
if(!Mapz->Start()) if(!Mapz->Start())
{ {
c->con.printerr("Can't init map.\n"); c->con.printerr("Can't init map.\n");
c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
int32_t cursorX, cursorY, cursorZ; int32_t cursorX, cursorY, cursorZ;
@ -181,7 +180,6 @@ DFhackCExport command_result spotclean (Core * c, vector <string> & parameters)
if(cursorX == -30000) if(cursorX == -30000)
{ {
c->con.printerr("The cursor is not active.\n"); c->con.printerr("The cursor is not active.\n");
c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
int32_t blockX = cursorX / 16, blockY = cursorY / 16; int32_t blockX = cursorX / 16, blockY = cursorY / 16;
@ -193,7 +191,6 @@ DFhackCExport command_result spotclean (Core * c, vector <string> & parameters)
{ {
spatters[i]->intensity[tileX][tileY] = 0; spatters[i]->intensity[tileX][tileY] = 0;
} }
c->Resume();
return CR_OK; return CR_OK;
} }

@ -26,8 +26,10 @@ DFhackCExport command_result plugin_init (Core *c, std::vector <PluginCommand> &
{ {
commands.clear(); commands.clear();
if (d_init) { if (d_init) {
commands.push_back(PluginCommand("twaterlvl", "Toggle display of water/magma depth.", twaterlvl)); commands.push_back(PluginCommand("twaterlvl", "Toggle display of water/magma depth.",
commands.push_back(PluginCommand("tidlers", "Toggle display of idlers.", tidlers)); twaterlvl, dwarfmode_hotkey));
commands.push_back(PluginCommand("tidlers", "Toggle display of idlers.",
tidlers, dwarfmode_hotkey));
} }
std::cerr << "d_init: " << sizeof(df::d_init) << endl; std::cerr << "d_init: " << sizeof(df::d_init) << endl;
return CR_OK; return CR_OK;
@ -40,21 +42,19 @@ DFhackCExport command_result plugin_shutdown ( Core * c )
DFhackCExport command_result twaterlvl(Core * c, vector <string> & parameters) DFhackCExport command_result twaterlvl(Core * c, vector <string> & parameters)
{ {
c->Suspend(); // HOTKEY COMMAND: CORE ALREADY SUSPENDED
df::global::d_init->flags1.toggle(d_init_flags1::SHOW_FLOW_AMOUNTS); df::global::d_init->flags1.toggle(d_init_flags1::SHOW_FLOW_AMOUNTS);
c->con << "Toggled the display of water/magma depth." << endl; c->con << "Toggled the display of water/magma depth." << endl;
c->Resume();
return CR_OK; return CR_OK;
} }
DFhackCExport command_result tidlers(Core * c, vector <string> & parameters) DFhackCExport command_result tidlers(Core * c, vector <string> & parameters)
{ {
c->Suspend(); // HOTKEY COMMAND: CORE ALREADY SUSPENDED
df::d_init_idlers iv = df::d_init_idlers(int(d_init->idlers) + 1); df::d_init_idlers iv = df::d_init_idlers(int(d_init->idlers) + 1);
if (!d_init_idlers::is_valid(iv)) if (!d_init_idlers::is_valid(iv))
iv = ENUM_FIRST_ITEM(d_init_idlers); iv = ENUM_FIRST_ITEM(d_init_idlers);
d_init->idlers = iv; d_init->idlers = iv;
c->con << "Toggled the display of idlers to " << ENUM_KEY_STR(d_init_idlers, iv) << endl; c->con << "Toggled the display of idlers to " << ENUM_KEY_STR(d_init_idlers, iv) << endl;
c->Resume();
return CR_OK; return CR_OK;
} }

@ -21,7 +21,8 @@ using df::global::selection_rect;
using df::building_stockpilest; using df::building_stockpilest;
DFhackCExport command_result copystock(Core * c, vector <string> & parameters); static command_result copystock(Core *c, vector <string> & parameters);
static bool copystock_guard(Core *c, df::viewscreen *top);
DFhackCExport const char * plugin_name ( void ) DFhackCExport const char * plugin_name ( void )
{ {
@ -32,7 +33,16 @@ DFhackCExport command_result plugin_init (Core *c, std::vector <PluginCommand> &
{ {
commands.clear(); commands.clear();
if (world && ui) { if (world && ui) {
commands.push_back(PluginCommand("copystock", "Copy stockpile under cursor.", copystock)); commands.push_back(
PluginCommand(
"copystock", "Copy stockpile under cursor.",
copystock, copystock_guard,
" - In 'q' or 't' mode: select a stockpile and invoke in order\n"
" to switch to the 'p' stockpile creation mode, and initialize\n"
" the custom settings from the selected stockpile.\n"
" - In 'p': invoke in order to switch back to 'q'.\n"
)
);
} }
std::cerr << "world: " << sizeof(df::world) << " ui: " << sizeof(df::ui) std::cerr << "world: " << sizeof(df::world) << " ui: " << sizeof(df::ui)
<< " b_stock: " << sizeof(building_stockpilest) << endl; << " b_stock: " << sizeof(building_stockpilest) << endl;
@ -44,21 +54,27 @@ DFhackCExport command_result plugin_shutdown ( Core * c )
return CR_OK; return CR_OK;
} }
bool inSelectMode() { static bool copystock_guard(Core *c, df::viewscreen *top)
{
using namespace ui_sidebar_mode; using namespace ui_sidebar_mode;
if (!dwarfmode_hotkey(c,top))
return false;
switch (ui->main.mode) { switch (ui->main.mode) {
case Stockpiles:
return true;
case BuildingItems: case BuildingItems:
case QueryBuilding: case QueryBuilding:
return true; return !!virtual_cast<building_stockpilest>(world->selected_building);
default: default:
return false; return false;
} }
} }
DFhackCExport command_result copystock(Core * c, vector <string> & parameters) static command_result copystock(Core * c, vector <string> & parameters)
{ {
CoreSuspender suspend(c); // HOTKEY COMMAND: CORE ALREADY SUSPENDED
// For convenience: when used in the stockpiles mode, switch to 'q' // For convenience: when used in the stockpiles mode, switch to 'q'
if (ui->main.mode == ui_sidebar_mode::Stockpiles) { if (ui->main.mode == ui_sidebar_mode::Stockpiles) {
@ -70,15 +86,10 @@ DFhackCExport command_result copystock(Core * c, vector <string> & parameters)
return CR_OK; return CR_OK;
} }
if (!inSelectMode()) {
c->con << "Cannot copy stockpile in mode " << ENUM_KEY_STR(ui_sidebar_mode, ui->main.mode) << endl;
return CR_OK;
}
building_stockpilest *sp = virtual_cast<building_stockpilest>(world->selected_building); building_stockpilest *sp = virtual_cast<building_stockpilest>(world->selected_building);
if (!sp) { if (!sp) {
c->con << "Selected building isn't a stockpile." << endl; c->con.printerr("Selected building isn't a stockpile.\n");
return CR_OK; return CR_WRONG_USAGE;
} }
ui->stockpile.custom_settings = sp->settings; ui->stockpile.custom_settings = sp->settings;

@ -30,8 +30,17 @@ DFhackCExport const char * plugin_name ( void )
DFhackCExport command_result plugin_init ( Core * c, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( Core * c, std::vector <PluginCommand> &commands)
{ {
commands.clear(); commands.clear();
commands.push_back(PluginCommand("vdig","Dig a whole vein.",vdig)); commands.push_back(PluginCommand(
commands.push_back(PluginCommand("vdigx","Dig a whole vein, follow vein through z-levels with stairs.",vdigx)); "vdig","Dig a whole vein.",vdig,cursor_hotkey,
" Designates a whole vein under the cursor for digging.\n"
"Options:\n"
" x - follow veins through z-levels with stairs.\n"
));
commands.push_back(PluginCommand(
"vdigx","Dig a whole vein, following through z-levels.",vdigx,cursor_hotkey,
" Designates a whole vein under the cursor for digging.\n"
" Also follows the vein between z-levels with stairs, like 'vdig x' would.\n"
));
commands.push_back(PluginCommand("expdig","Select or designate an exploratory pattern. Use 'expdig ?' for help.",expdig)); commands.push_back(PluginCommand("expdig","Select or designate an exploratory pattern. Use 'expdig ?' for help.",expdig));
commands.push_back(PluginCommand("digcircle","Dig desingate a circle (filled or hollow) with given radius.",digcircle)); commands.push_back(PluginCommand("digcircle","Dig desingate a circle (filled or hollow) with given radius.",digcircle));
//commands.push_back(PluginCommand("autodig","Mark a tile for continuous digging.",autodig)); //commands.push_back(PluginCommand("autodig","Mark a tile for continuous digging.",autodig));
@ -760,18 +769,10 @@ DFhackCExport command_result expdig (Core * c, vector <string> & parameters)
c->Resume(); c->Resume();
return CR_OK; return CR_OK;
} }
DFhackCExport command_result vdigx (Core * c, vector <string> & parameters) DFhackCExport command_result vdigx (Core * c, vector <string> & parameters)
{ {
for(int i = 0; i < parameters.size();i++) // HOTKEY COMMAND: CORE ALREADY SUSPENDED
{
if(parameters[i] == "help" || parameters[i] == "?")
{
c->con.print("Designates a whole vein under the cursor for digging.\n"
"Also follows the vein between z-levels with stairs, like 'vdig x' would.\n"
);
return CR_OK;
}
}
vector <string> lol; vector <string> lol;
lol.push_back("x"); lol.push_back("x");
return vdig(c,lol); return vdig(c,lol);
@ -779,32 +780,25 @@ DFhackCExport command_result vdigx (Core * c, vector <string> & parameters)
DFhackCExport command_result vdig (Core * c, vector <string> & parameters) DFhackCExport command_result vdig (Core * c, vector <string> & parameters)
{ {
// HOTKEY COMMAND: CORE ALREADY SUSPENDED
uint32_t x_max,y_max,z_max; uint32_t x_max,y_max,z_max;
bool updown = false; bool updown = false;
for(int i = 0; i < parameters.size();i++) for(int i = 0; i < parameters.size();i++)
{ {
if(parameters.size() && parameters[0]=="x") if(parameters.size() && parameters[0]=="x")
updown = true; updown = true;
else if(parameters[i] == "help" || parameters[i] == "?") else
{ return CR_WRONG_USAGE;
c->con.print("Designates a whole vein under the cursor for digging.\n"
"Options:\n"
"x - follow veins through z-levels with stairs.\n"
);
return CR_OK;
}
} }
Console & con = c->con; Console & con = c->con;
c->Suspend();
DFHack::Maps * Maps = c->getMaps(); DFHack::Maps * Maps = c->getMaps();
DFHack::Gui * Gui = c->getGui(); DFHack::Gui * Gui = c->getGui();
// init the map // init the map
if(!Maps->Start()) if(!Maps->Start())
{ {
con.printerr("Can't init map. Make sure you have a map loaded in DF.\n"); con.printerr("Can't init map. Make sure you have a map loaded in DF.\n");
c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
@ -816,14 +810,12 @@ DFhackCExport command_result vdig (Core * c, vector <string> & parameters)
while(cx == -30000) while(cx == -30000)
{ {
con.printerr("Cursor is not active. Point the cursor at a vein.\n"); con.printerr("Cursor is not active. Point the cursor at a vein.\n");
c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz); DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz);
if(xy.x == 0 || xy.x == tx_max - 1 || xy.y == 0 || xy.y == ty_max - 1) if(xy.x == 0 || xy.x == tx_max - 1 || xy.y == 0 || xy.y == ty_max - 1)
{ {
con.printerr("I won't dig the borders. That would be cheating!\n"); con.printerr("I won't dig the borders. That would be cheating!\n");
c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
MapExtras::MapCache * MCache = new MapExtras::MapCache(Maps); MapExtras::MapCache * MCache = new MapExtras::MapCache(Maps);
@ -834,7 +826,6 @@ DFhackCExport command_result vdig (Core * c, vector <string> & parameters)
{ {
con.printerr("This tile is not a vein.\n"); con.printerr("This tile is not a vein.\n");
delete MCache; delete MCache;
c->Resume();
return CR_FAILURE; return CR_FAILURE;
} }
con.print("%d/%d/%d tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, des.whole); con.print("%d/%d/%d tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, des.whole);
@ -944,7 +935,6 @@ DFhackCExport command_result vdig (Core * c, vector <string> & parameters)
} }
} }
MCache->WriteAll(); MCache->WriteAll();
c->Resume();
return CR_OK; return CR_OK;
} }