Petr Mrázek 2011-12-31 01:40:08 +01:00
commit 1284b30f79
7 changed files with 509 additions and 104 deletions

@ -51,13 +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_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
{ {
@ -145,8 +152,17 @@ void fHKthread(void * iodata)
std::string stuff = core->getHotkeyCmd(); // waits on mutex! std::string stuff = core->getHotkeyCmd(); // waits on mutex!
if(!stuff.empty()) if(!stuff.empty())
{ {
vector <string> crap; vector <string> args;
command_result cr = plug_mgr->InvokeCommand(stuff, crap, false); cheap_tokenise(stuff, args);
if (args.empty()) {
core->con.printerr("Empty hotkey command.\n");
continue;
}
string first = args[0];
args.erase(args.begin());
command_result cr = plug_mgr->InvokeCommand(first, args, false);
if(cr == CR_WOULD_BREAK) if(cr == CR_WOULD_BREAK)
{ {
core->con.printerr("It isn't possible to run an interactive command outside the console.\n"); core->con.printerr("It isn't possible to run an interactive command outside the console.\n");
@ -155,53 +171,25 @@ void fHKthread(void * iodata)
} }
} }
// A thread function... for the interactive console. static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clueless_counter, const string &command)
void fIOthread(void * iodata)
{ {
IODATA * iod = ((IODATA*) iodata);
Core * core = iod->core;
PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr;
CommandHistory main_history;
main_history.load("dfhack.history");
Console & con = core->con; Console & con = core->con;
if(plug_mgr == 0 || core == 0)
{ if (!command.empty())
con.printerr("Something horrible happened in Core's constructor...\n");
return;
}
con.print("DFHack is ready. Have a nice day!\n"
"Type in '?' or 'help' for general help, 'ls' to see all commands.\n");
int clueless_counter = 0;
while (true)
{
string command = "";
int ret = con.lineedit("[DFHack]# ",command, main_history);
if(ret == -2)
{
cerr << "Console is shutting down properly." << endl;
return;
}
else if(ret == -1)
{
cerr << "Console caught an unspecified error." << endl;
continue;
}
else if(ret)
{ {
// a proper, non-empty command was entered
main_history.add(command);
main_history.save("dfhack.history");
}
// cut the input into parts // cut the input into parts
vector <string> parts; vector <string> parts;
cheap_tokenise(command,parts); cheap_tokenise(command,parts);
if(parts.size() == 0) if(parts.size() == 0)
{ {
clueless_counter ++; clueless_counter ++;
continue; return;
} }
string first = parts[0]; string first = parts[0];
parts.erase(parts.begin()); parts.erase(parts.begin());
if (first[0] == '#') return;
cerr << "Invoking: " << command << endl; cerr << "Invoking: " << command << endl;
// let's see what we actually got // let's see what we actually got
@ -220,6 +208,7 @@ void fIOthread(void * iodata)
" 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"
@ -370,6 +359,40 @@ void fIOthread(void * iodata)
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
{
con << "Usage:" << 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();
@ -410,6 +433,70 @@ void fIOthread(void * iodata)
*/ */
} }
} }
}
}
static void loadInitFile(Core *core, PluginManager *plug_mgr, string fname)
{
ifstream init(fname);
if (init.bad())
return;
int tmp = 0;
string command;
while (getline(init, command))
{
if (!command.empty())
runInteractiveCommand(core, plug_mgr, tmp, command);
}
}
// A thread function... for the interactive console.
void fIOthread(void * iodata)
{
IODATA * iod = ((IODATA*) iodata);
Core * core = iod->core;
PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr;
CommandHistory main_history;
main_history.load("dfhack.history");
Console & con = core->con;
if(plug_mgr == 0 || core == 0)
{
con.printerr("Something horrible happened in Core's constructor...\n");
return;
}
loadInitFile(core, plug_mgr, "dfhack.init");
con.print("DFHack is ready. Have a nice day!\n"
"Type in '?' or 'help' for general help, 'ls' to see all commands.\n");
int clueless_counter = 0;
while (true)
{
string command = "";
int ret = con.lineedit("[DFHack]# ",command, main_history);
if(ret == -2)
{
cerr << "Console is shutting down properly." << endl;
return;
}
else if(ret == -1)
{
cerr << "Console caught an unspecified error." << endl;
continue;
}
else if(ret)
{
// a proper, non-empty command was entered
main_history.add(command);
main_history.save("dfhack.history");
}
runInteractiveCommand(core, plug_mgr, clueless_counter, command);
if(clueless_counter == 3) if(clueless_counter == 3)
{ {
con.print("Do 'help' or '?' for the list of available commands.\n"); con.print("Do 'help' or '?' for the list of available commands.\n");
@ -434,11 +521,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;
top_viewscreen = NULL;
}; };
void Core::fatal (std::string output, bool deactivate) void Core::fatal (std::string output, bool deactivate)
@ -631,8 +719,36 @@ int Core::Update()
if(errorstate) if(errorstate)
return -1; return -1;
// detect if the game was loaded or unloaded in the meantime
void *new_wdata = NULL;
if (df::global::world) {
df::world_data *wdata = df::global::world->world_data;
// when the game is unloaded, world_data isn't deleted, but its contents are
if (wdata && !wdata->sites.empty())
new_wdata = wdata;
}
if (new_wdata != last_world_data_ptr) {
last_world_data_ptr = new_wdata;
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();
// wake waiting tools // wake waiting tools
// do not allow more tools to join in while we process stuff here // do not allow more tools to join in while we process stuff here
StackMutex->lock(); StackMutex->lock();
@ -721,37 +837,149 @@ 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)
{ {
t_hotkey & hotkey = (*g->hotkeys)[idx]; // Find the topmost viewscreen
setHotkeyCmd(hotkey.name); 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;
{
tthread::lock_guard<tthread::mutex> lock(*HotkeyMutex);
// 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) }
}
if (!cmd.empty()) {
setHotkeyCmd(cmd);
return true;
}
else
return false;
}
static bool parseKeySpec(std::string keyspec, int *psym, int *pmod)
{ {
hotkey_states[idx] = 0; *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;
} }
return orig_return;
// do stuff with the events... 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);
binding.cmdline = cmdline;
key_bindings[sym].push_back(binding);
return true;
}
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;
} }
//////////////// ////////////////

@ -27,6 +27,10 @@ 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"
#include "dfhack/df/viewscreen.h"
using namespace DFHack; using namespace DFHack;
#include <string> #include <string>
@ -135,6 +139,7 @@ Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _f
plugin_shutdown = 0; plugin_shutdown = 0;
plugin_status = 0; plugin_status = 0;
plugin_onupdate = 0; plugin_onupdate = 0;
plugin_onstatechange = 0;
state = PS_UNLOADED; state = PS_UNLOADED;
access = new RefLock(); access = new RefLock();
} }
@ -192,6 +197,7 @@ bool Plugin::load()
plugin_status = (command_result (*)(Core *, std::string &)) LookupPlugin(plug, "plugin_status"); plugin_status = (command_result (*)(Core *, std::string &)) LookupPlugin(plug, "plugin_status");
plugin_onupdate = (command_result (*)(Core *)) LookupPlugin(plug, "plugin_onupdate"); plugin_onupdate = (command_result (*)(Core *)) LookupPlugin(plug, "plugin_onupdate");
plugin_shutdown = (command_result (*)(Core *)) LookupPlugin(plug, "plugin_shutdown"); plugin_shutdown = (command_result (*)(Core *)) LookupPlugin(plug, "plugin_shutdown");
plugin_onstatechange = (command_result (*)(Core *, state_change_event)) LookupPlugin(plug, "plugin_onstatechange");
//name = _PlugName(); //name = _PlugName();
plugin_lib = plug; plugin_lib = plug;
if(plugin_init(&c,commands) == CR_OK) if(plugin_init(&c,commands) == CR_OK)
@ -271,13 +277,69 @@ 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.viewscreen_type && !cmd.viewscreen_type->is_instance(top))
|| !cmd.guard(&c, top))
{
c.con.printerr("Could not invoke %s: unsuitable UI state.\n", command.c_str());
cr = CR_FAILURE;
}
else
{
cr = cmd.function(&c, parameters);
}
}
else
{
cr = cmd.function(&c, parameters);
}
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.viewscreen_type || cmd.viewscreen_type->is_instance(top))
&& cmd.guard(&c, top);
}
else else
cr = commands[i].function(&c, parameters); {
cr = default_hotkey(&c, top);
}
break; break;
} }
} }
@ -299,6 +361,19 @@ command_result Plugin::on_update()
return cr; return cr;
} }
command_result Plugin::on_state_change(state_change_event event)
{
Core & c = Core::getInstance();
command_result cr = CR_NOT_IMPLEMENTED;
access->lock_add();
if(state == PS_LOADED && plugin_onstatechange)
{
cr = plugin_onstatechange(&c, event);
}
access->lock_sub();
return cr;
}
Plugin::plugin_state Plugin::getState() const Plugin::plugin_state Plugin::getState() const
{ {
return state; return state;
@ -348,19 +423,27 @@ Plugin *PluginManager::getPluginByName (const std::string & name)
return 0; return 0;
} }
// FIXME: handle name collisions... Plugin *PluginManager::getPluginByCommand(const std::string &command)
command_result PluginManager::InvokeCommand( std::string & command, std::vector <std::string> & parameters, bool interactive)
{ {
command_result cr = CR_NOT_IMPLEMENTED; tthread::lock_guard<tthread::mutex> lock(*cmdlist_mutex);
Core * c = &Core::getInstance();
cmdlist_mutex->lock();
map <string, Plugin *>::iterator iter = belongs.find(command); map <string, Plugin *>::iterator iter = belongs.find(command);
if (iter != belongs.end()) if (iter != belongs.end())
return iter->second;
else
return NULL;
}
// FIXME: handle name collisions...
command_result PluginManager::InvokeCommand( std::string & command, std::vector <std::string> & parameters, bool interactive)
{ {
cr = iter->second->invoke(command, parameters, interactive); Plugin *plugin = getPluginByCommand(command);
return plugin ? plugin->invoke(command, parameters, interactive) : CR_NOT_IMPLEMENTED;
} }
cmdlist_mutex->unlock();
return cr; bool PluginManager::CanInvokeHotkey(std::string &command, df::viewscreen *top)
{
Plugin *plugin = getPluginByCommand(command);
return plugin ? plugin->can_invoke_hotkey(command, top) : false;
} }
void PluginManager::OnUpdate( void ) void PluginManager::OnUpdate( void )
@ -370,6 +453,15 @@ void PluginManager::OnUpdate( void )
all_plugins[i]->on_update(); all_plugins[i]->on_update();
} }
} }
void PluginManager::OnStateChange( state_change_event event )
{
for(int i = 0; i < all_plugins.size(); i++)
{
all_plugins[i]->on_state_change(event);
}
}
// FIXME: doesn't check name collisions! // FIXME: doesn't check name collisions!
void PluginManager::registerCommands( Plugin * p ) void PluginManager::registerCommands( Plugin * p )
{ {

@ -43,6 +43,11 @@ namespace tthread
class thread; class thread;
} }
namespace df
{
struct viewscreen;
}
namespace DFHack namespace DFHack
{ {
class Process; class Process;
@ -134,6 +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);
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;
DFHack::Console con; DFHack::Console con;
@ -178,12 +189,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
df::viewscreen *top_viewscreen;
// Very important! // Very important!
bool started; bool started;

@ -35,10 +35,16 @@ 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;
struct virtual_identity;
enum command_result enum command_result
{ {
CR_WOULD_BREAK = -2, CR_WOULD_BREAK = -2,
@ -46,33 +52,48 @@ namespace DFHack
CR_FAILURE = 0, CR_FAILURE = 0,
CR_OK = 1 CR_OK = 1
}; };
struct PluginCommand enum state_change_event
{ {
SC_GAME_LOADED,
SC_GAME_UNLOADED,
SC_VIEWSCREEN_CHANGED
};
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
) )
: name(_name), description(_description),
function(function_), interactive(interactive_),
guard(NULL), viewscreen_type(NULL)
{ {
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_,
virtual_identity *viewscreen_type_ = NULL)
: name(_name), description(_description),
function(function_), interactive(false),
guard(guard_), viewscreen_type(viewscreen_type_)
{ {
name = rhs.name;
description = rhs.description;
function = rhs.function;
interactive = rhs.interactive;
} }
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;
virtual_identity *viewscreen_type;
}; };
class Plugin class Plugin
{ {
@ -87,11 +108,13 @@ namespace DFHack
Plugin(DFHack::Core* core, const std::string& filepath, const std::string& filename, PluginManager * pm); Plugin(DFHack::Core* core, const std::string& filepath, const std::string& filename, PluginManager * pm);
~Plugin(); ~Plugin();
command_result on_update(); command_result on_update();
command_result on_state_change(state_change_event event);
public: public:
bool load(); bool load();
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
{ {
@ -117,6 +140,7 @@ namespace DFHack
command_result (*plugin_status)(Core *, std::string &); command_result (*plugin_status)(Core *, std::string &);
command_result (*plugin_shutdown)(Core *); command_result (*plugin_shutdown)(Core *);
command_result (*plugin_onupdate)(Core *); command_result (*plugin_onupdate)(Core *);
command_result (*plugin_onstatechange)(Core *, state_change_event);
}; };
class DFHACK_EXPORT PluginManager class DFHACK_EXPORT PluginManager
{ {
@ -126,12 +150,15 @@ namespace DFHack
PluginManager(Core * core); PluginManager(Core * core);
~PluginManager(); ~PluginManager();
void OnUpdate( void ); void OnUpdate( void );
void OnStateChange( state_change_event event );
void registerCommands( Plugin * p ); void registerCommands( Plugin * p );
void unregisterCommands( Plugin * p ); void unregisterCommands( Plugin * p );
// 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())
@ -149,5 +176,7 @@ namespace DFHack
std::vector <Plugin *> all_plugins; std::vector <Plugin *> all_plugins;
std::string plugin_path; std::string plugin_path;
}; };
DFHACK_EXPORT bool default_hotkey(Core *, df::viewscreen *);
} }

@ -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>
<!-- <!--

@ -288,10 +288,32 @@ DFhackCExport DFHack::command_result plugin_init(DFHack::Core* pCore, std::vecto
return DFHack::CR_OK; return DFHack::CR_OK;
} }
DFhackCExport DFHack::command_result plugin_onstatechange(DFHack::Core* pCore, DFHack::state_change_event event)
{
switch (event) {
case DFHack::SC_GAME_LOADED:
case DFHack::SC_GAME_UNLOADED:
if (running)
pCore->con.printerr("seedwatch deactivated due to game load/unload\n");
running = false;
break;
default:
break;
}
return DFHack::CR_OK;
}
DFhackCExport DFHack::command_result plugin_onupdate(DFHack::Core* pCore) DFhackCExport DFHack::command_result plugin_onupdate(DFHack::Core* pCore)
{ {
if (running) if (running)
{ {
// reduce processing rate
static int counter = 0;
if (++counter < 500)
return DFHack::CR_OK;
counter = 0;
DFHack::Core& core = *pCore; DFHack::Core& core = *pCore;
DFHack::World *w = core.getWorld(); DFHack::World *w = core.getWorld();
DFHack::t_gamemodes gm; DFHack::t_gamemodes gm;

@ -8,6 +8,7 @@
#include <dfhack/df/ui.h> #include <dfhack/df/ui.h>
#include <dfhack/df/building_stockpilest.h> #include <dfhack/df/building_stockpilest.h>
#include <dfhack/df/selection_rect.h> #include <dfhack/df/selection_rect.h>
#include <dfhack/df/viewscreen_dwarfmodest.h>
using std::vector; using std::vector;
using std::string; using std::string;
@ -21,7 +22,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 +34,12 @@ 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, &df::viewscreen_dwarfmodest::_identity
)
);
} }
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 +51,24 @@ DFhackCExport command_result plugin_shutdown ( Core * c )
return CR_OK; return CR_OK;
} }
bool inSelectMode() { static bool copystock_guard(Core *c, df::viewscreen *)
{
using namespace ui_sidebar_mode; using namespace ui_sidebar_mode;
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 +80,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_FAILURE;
} }
ui->stockpile.custom_settings = sp->settings; ui->stockpile.custom_settings = sp->settings;