diff --git a/library/Core.cpp b/library/Core.cpp index f8af357cf..95a05ee6b 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -217,6 +217,27 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue " 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 { con.printerr("not implemented yet\n"); @@ -316,7 +337,10 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue else for (int j = 0; j < plug->size();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.reset_color(); } } else @@ -344,7 +368,10 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue for (int j = 0; j < plug->size();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.reset_color(); } } } @@ -384,9 +411,18 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue } } } + else if (parts.size() == 2 && parts[0] == "list") + { + std::vector 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 " << endl << " keybinding clear ..." << endl << " keybinding set \"cmdline\" \"cmdline\"..." << endl << " keybinding add \"cmdline\" \"cmdline\"..." << endl @@ -968,18 +1004,35 @@ bool Core::AddKeyBinding(std::string keyspec, std::string cmdline) tthread::lock_guard lock(*HotkeyMutex); + // Don't add duplicates + std::vector &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; - key_bindings[sym].push_back(binding); + bindings.push_back(binding); return true; } -bool DFHack::default_hotkey(Core *, df::viewscreen *top) +std::vector Core::ListKeyBindings(std::string keyspec) { - // Default hotkey guard function - for (;top ;top = top->parent) - if (strict_virtual_cast(top)) - return true; - return false; + int sym, mod; + std::vector rv; + if (!parseKeySpec(keyspec, &sym, &mod)) + return rv; + + tthread::lock_guard lock(*HotkeyMutex); + + std::vector &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; } //////////////// diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 29cc992b7..9acd05aa4 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -29,7 +29,6 @@ distribution. #include "Console.h" #include "DataDefs.h" -#include "df/viewscreen.h" using namespace DFHack; @@ -292,21 +291,22 @@ command_result Plugin::invoke( std::string & command, std::vector CoreSuspender suspend(&c); df::viewscreen *top = c.getTopViewscreen(); - if ((cmd.viewscreen_type && !cmd.viewscreen_type->is_instance(top)) - || !cmd.guard(&c, top)) + if (!cmd.guard(&c, top)) { c.con.printerr("Could not invoke %s: unsuitable UI state.\n", command.c_str()); - cr = CR_FAILURE; + cr = CR_WRONG_USAGE; } - else + else { cr = cmd.function(&c, parameters); } } - else + else { cr = cmd.function(&c, parameters); } + if (cr == CR_WRONG_USAGE && !cmd.usage.empty()) + c.con << "Usage:\n" << cmd.usage << flush; break; } } @@ -325,20 +325,14 @@ bool Plugin::can_invoke_hotkey( std::string & command, df::viewscreen *top ) 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 - { + cr = cmd.guard(&c, top); + else cr = default_hotkey(&c, top); - } break; } } diff --git a/library/include/Core.h b/library/include/Core.h index 53dbc3d8c..a368e360d 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -141,6 +141,7 @@ namespace DFHack bool ClearKeyBindings(std::string keyspec); bool AddKeyBinding(std::string keyspec, std::string cmdline); + std::vector ListKeyBindings(std::string keyspec); bool isWorldLoaded() { return (last_world_data_ptr != NULL); } df::viewscreen *getTopViewscreen() { return top_viewscreen; } diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 4d5319dfd..726734baa 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -50,7 +50,8 @@ namespace DFHack CR_WOULD_BREAK = -2, CR_NOT_IMPLEMENTED = -1, CR_FAILURE = 0, - CR_OK = 1 + CR_OK = 1, + CR_WRONG_USAGE = 2 }; enum state_change_event { @@ -62,18 +63,19 @@ namespace DFHack { typedef command_result (*command_function)(Core *, std::vector &); typedef bool (*command_hotkey_guard)(Core *, df::viewscreen *); - + /// create a command with a name, description, function pointer to its code /// and saying if it needs an interactive terminal /// Most commands shouldn't require an interactive terminal! PluginCommand(const char * _name, const char * _description, command_function function_, - bool interactive_ = false + bool interactive_ = false, + const char * usage_ = "" ) : name(_name), description(_description), function(function_), interactive(interactive_), - guard(NULL), viewscreen_type(NULL) + guard(NULL), usage(usage_) { } @@ -81,19 +83,21 @@ namespace DFHack const char * _description, command_function function_, command_hotkey_guard guard_, - virtual_identity *viewscreen_type_ = NULL) + const char * usage_ = "") : name(_name), description(_description), function(function_), interactive(false), - guard(guard_), viewscreen_type(viewscreen_type_) + guard(guard_), usage(usage_) { } + bool isHotkeyCommand() const { return guard != NULL; } + std::string name; std::string description; command_function function; bool interactive; command_hotkey_guard guard; - virtual_identity *viewscreen_type; + std::string usage; }; class Plugin { @@ -177,6 +181,9 @@ namespace DFHack 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 *); } diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 6749e9f01..cd6f46f25 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -37,8 +37,44 @@ using namespace std; #include "Error.h" #include "ModuleFactory.h" #include "Core.h" +#include "PluginManager.h" using namespace DFHack; +#include "DataDefs.h" +#include "df/cursor.h" +#include "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(top)) + return true; + return false; +} + +bool DFHack::dwarfmode_hotkey(Core *, df::viewscreen *top) +{ + // Require the main dwarf mode screen + return !!strict_virtual_cast(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() { return new Gui(); diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index 35ec69ea3..8992b2078 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -27,7 +27,7 @@ DFhackCExport command_result plugin_init ( Core * c, std::vector { commands.clear(); 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; } @@ -165,7 +165,7 @@ command_result cleanunits (Core * c) DFhackCExport command_result spotclean (Core * c, vector & parameters) { - c->Suspend(); + // HOTKEY COMMAND: CORE ALREADY SUSPENDED vector splatter; DFHack::Maps *Mapz = c->getMaps(); DFHack::Gui *Gui = c->getGui(); @@ -173,7 +173,6 @@ DFhackCExport command_result spotclean (Core * c, vector & parameters) if(!Mapz->Start()) { c->con.printerr("Can't init map.\n"); - c->Resume(); return CR_FAILURE; } int32_t cursorX, cursorY, cursorZ; @@ -181,7 +180,6 @@ DFhackCExport command_result spotclean (Core * c, vector & parameters) if(cursorX == -30000) { c->con.printerr("The cursor is not active.\n"); - c->Resume(); return CR_FAILURE; } int32_t blockX = cursorX / 16, blockY = cursorY / 16; @@ -193,7 +191,6 @@ DFhackCExport command_result spotclean (Core * c, vector & parameters) { spatters[i]->intensity[tileX][tileY] = 0; } - c->Resume(); return CR_OK; } diff --git a/plugins/initflags.cpp b/plugins/initflags.cpp index 29d8144a8..5671619e8 100644 --- a/plugins/initflags.cpp +++ b/plugins/initflags.cpp @@ -26,8 +26,10 @@ DFhackCExport command_result plugin_init (Core *c, std::vector & { commands.clear(); if (d_init) { - commands.push_back(PluginCommand("twaterlvl", "Toggle display of water/magma depth.", twaterlvl)); - commands.push_back(PluginCommand("tidlers", "Toggle display of idlers.", tidlers)); + commands.push_back(PluginCommand("twaterlvl", "Toggle display of water/magma depth.", + twaterlvl, dwarfmode_hotkey)); + commands.push_back(PluginCommand("tidlers", "Toggle display of idlers.", + tidlers, dwarfmode_hotkey)); } std::cerr << "d_init: " << sizeof(df::d_init) << endl; return CR_OK; @@ -40,21 +42,19 @@ DFhackCExport command_result plugin_shutdown ( Core * c ) DFhackCExport command_result twaterlvl(Core * c, vector & parameters) { - c->Suspend(); + // HOTKEY COMMAND: CORE ALREADY SUSPENDED df::global::d_init->flags1.toggle(d_init_flags1::SHOW_FLOW_AMOUNTS); c->con << "Toggled the display of water/magma depth." << endl; - c->Resume(); return CR_OK; } DFhackCExport command_result tidlers(Core * c, vector & parameters) { - c->Suspend(); + // HOTKEY COMMAND: CORE ALREADY SUSPENDED df::d_init_idlers iv = df::d_init_idlers(int(d_init->idlers) + 1); if (!d_init_idlers::is_valid(iv)) iv = ENUM_FIRST_ITEM(d_init_idlers); d_init->idlers = iv; c->con << "Toggled the display of idlers to " << ENUM_KEY_STR(d_init_idlers, iv) << endl; - c->Resume(); return CR_OK; } diff --git a/plugins/stockpiles.cpp b/plugins/stockpiles.cpp index fb79f3e59..e104ec908 100644 --- a/plugins/stockpiles.cpp +++ b/plugins/stockpiles.cpp @@ -36,8 +36,12 @@ DFhackCExport command_result plugin_init (Core *c, std::vector & if (world && ui) { commands.push_back( PluginCommand( - "copystock", "Copy stockpile under cursor.", copystock, - copystock_guard, &df::viewscreen_dwarfmodest::_identity + "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" ) ); } @@ -51,10 +55,13 @@ DFhackCExport command_result plugin_shutdown ( Core * c ) return CR_OK; } -static bool copystock_guard(Core *c, df::viewscreen *) +static bool copystock_guard(Core *c, df::viewscreen *top) { using namespace ui_sidebar_mode; + if (!dwarfmode_hotkey(c,top)) + return false; + switch (ui->main.mode) { case Stockpiles: return true; @@ -63,12 +70,12 @@ static bool copystock_guard(Core *c, df::viewscreen *) return !!virtual_cast(world->selected_building); default: return false; - } + } } static command_result copystock(Core * c, vector & parameters) { - /* HOTKEY COMMAND: CORE ALREADY SUSPENDED */ + // HOTKEY COMMAND: CORE ALREADY SUSPENDED // For convenience: when used in the stockpiles mode, switch to 'q' if (ui->main.mode == ui_sidebar_mode::Stockpiles) { @@ -81,9 +88,10 @@ static command_result copystock(Core * c, vector & parameters) } building_stockpilest *sp = virtual_cast(world->selected_building); - if (!sp) { + if (!sp) + { c->con.printerr("Selected building isn't a stockpile.\n"); - return CR_FAILURE; + return CR_WRONG_USAGE; } ui->stockpile.custom_settings = sp->settings; diff --git a/plugins/vdig.cpp b/plugins/vdig.cpp index 392f0ea98..290b3b933 100644 --- a/plugins/vdig.cpp +++ b/plugins/vdig.cpp @@ -30,8 +30,17 @@ DFhackCExport const char * plugin_name ( void ) DFhackCExport command_result plugin_init ( Core * c, std::vector &commands) { commands.clear(); - commands.push_back(PluginCommand("vdig","Dig a whole vein.",vdig)); - commands.push_back(PluginCommand("vdigx","Dig a whole vein, follow vein through z-levels with stairs.",vdigx)); + commands.push_back(PluginCommand( + "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("digcircle","Dig desingate a circle (filled or hollow) with given radius.",digcircle)); //commands.push_back(PluginCommand("autodig","Mark a tile for continuous digging.",autodig)); @@ -760,18 +769,10 @@ DFhackCExport command_result expdig (Core * c, vector & parameters) c->Resume(); return CR_OK; } + DFhackCExport command_result vdigx (Core * c, vector & parameters) { - for(int i = 0; i < parameters.size();i++) - { - 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; - } - } + // HOTKEY COMMAND: CORE ALREADY SUSPENDED vector lol; lol.push_back("x"); return vdig(c,lol); @@ -779,32 +780,25 @@ DFhackCExport command_result vdigx (Core * c, vector & parameters) DFhackCExport command_result vdig (Core * c, vector & parameters) { + // HOTKEY COMMAND: CORE ALREADY SUSPENDED uint32_t x_max,y_max,z_max; bool updown = false; for(int i = 0; i < parameters.size();i++) { if(parameters.size() && parameters[0]=="x") updown = true; - else if(parameters[i] == "help" || parameters[i] == "?") - { - 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; - } + else + return CR_WRONG_USAGE; } Console & con = c->con; - c->Suspend(); DFHack::Maps * Maps = c->getMaps(); DFHack::Gui * Gui = c->getGui(); // init the map if(!Maps->Start()) { con.printerr("Can't init map. Make sure you have a map loaded in DF.\n"); - c->Resume(); return CR_FAILURE; } @@ -816,14 +810,12 @@ DFhackCExport command_result vdig (Core * c, vector & parameters) while(cx == -30000) { con.printerr("Cursor is not active. Point the cursor at a vein.\n"); - c->Resume(); return CR_FAILURE; } 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) { con.printerr("I won't dig the borders. That would be cheating!\n"); - c->Resume(); return CR_FAILURE; } MapExtras::MapCache * MCache = new MapExtras::MapCache(Maps); @@ -834,7 +826,6 @@ DFhackCExport command_result vdig (Core * c, vector & parameters) { con.printerr("This tile is not a vein.\n"); delete MCache; - c->Resume(); return CR_FAILURE; } 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 & parameters) } } MCache->WriteAll(); - c->Resume(); return CR_OK; }