From b6196e91e4469ae4e0d05df905305d816a7a6ff0 Mon Sep 17 00:00:00 2001 From: TaxiService Date: Sun, 5 Feb 2023 19:00:38 +0100 Subject: [PATCH 01/12] increase min scrollbar height to 2 (from 1) rationale: - vanilla scrollbars dont get shorter than 2. - 2-tall 'bars are easier to click on than 1-tall 'bars. - this avoids having to make short custom graphics for 1-tall tiles, which most of the time look terrible... cons: - short scrollbars are cool : ( --- library/lua/gui/widgets.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 429f28e34..71d89846d 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -808,7 +808,7 @@ local function scrollbar_get_max_pos_and_height(scrollbar) local frame_body = scrollbar.frame_body local scrollbar_body_height = (frame_body and frame_body.height or 3) - 2 - local height = math.max(1, math.floor( + local height = math.max(2, math.floor( (math.min(scrollbar.elems_per_page, scrollbar.num_elems) * scrollbar_body_height) / scrollbar.num_elems)) From 526d7c1726a08dc34eabd65906dcfe6bdc6b14be Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 5 Feb 2023 18:01:46 -0800 Subject: [PATCH 02/12] allow focus string generation to fall through unfocuses ZScreens --- docs/dev/Lua API.rst | 5 +++-- library/include/modules/Gui.h | 2 +- library/include/modules/Screen.h | 3 +++ library/modules/Gui.cpp | 31 +++++++++++++++++++++++++------ library/modules/Screen.cpp | 4 ++++ 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 242e84037..5a1b562ee 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -974,10 +974,11 @@ Screens the specified type (e.g. ``df.viewscreen_titlest``), or ``nil`` if none match. If ``depth`` is not specified or is less than 1, all viewscreens are checked. -* ``dfhack.gui.getDFViewscreen([skip_dismissed])`` +* ``dfhack.gui.getDFViewscreen([skip_dismissed[, viewscreen]])`` Returns the topmost viewscreen not owned by DFHack. If ``skip_dismissed`` is - ``true``, ignores screens already marked to be removed. + ``true``, ignores screens already marked to be removed. If ``viewscreen`` is + specified, starts the scan at the given viewscreen. General-purpose selections ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index cfb8925e3..aeb763e4a 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -189,7 +189,7 @@ namespace DFHack DFHACK_EXPORT df::viewscreen *getViewscreenByIdentity(virtual_identity &id, int n = 1); /// Get the top-most underlying DF viewscreen (not owned by DFHack) - DFHACK_EXPORT df::viewscreen *getDFViewscreen(bool skip_dismissed = false); + DFHACK_EXPORT df::viewscreen *getDFViewscreen(bool skip_dismissed = false, df::viewscreen *top = NULL); /// Get the top-most viewscreen of the given type from the top `n` viewscreens (or all viewscreens if n < 1) /// returns NULL if none match diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 0f0afd6e2..c21e3ad32 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -351,6 +351,7 @@ namespace DFHack virtual bool is_lua_screen() { return false; } + virtual bool isFocused() { return true; } virtual std::string getFocusString() = 0; virtual void onShow() {}; virtual void onDismiss() {}; @@ -365,6 +366,7 @@ namespace DFHack class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen { std::string focus; + bool defocused = false; void update_focus(lua_State *L, int idx); @@ -384,6 +386,7 @@ namespace DFHack static df::viewscreen *get_pointer(lua_State *L, int idx, bool make); virtual bool is_lua_screen() { return true; } + virtual bool isFocused() { return !defocused; } virtual std::string getFocusString() { return focus; } virtual void render(); diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 7d56977a6..1008b0972 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -484,6 +484,12 @@ bool Gui::matchFocusString(std::string focusString, bool prefixMatch) { }) != currentFocusStrings.end(); } +static void push_dfhack_focus_string(dfhack_viewscreen *vs, std::vector &focusStrings) +{ + auto name = vs->getFocusString(); + focusStrings.push_back(name.empty() ? "dfhack" : "dfhack/" + name); +} + std::vector Gui::getFocusStrings(df::viewscreen* top) { std::vector focusStrings; @@ -493,10 +499,21 @@ std::vector Gui::getFocusStrings(df::viewscreen* top) if (dfhack_viewscreen::is_instance(top)) { - auto name = static_cast(top)->getFocusString(); - focusStrings.push_back(name.empty() ? "dfhack" : "dfhack/" + name); + dfhack_viewscreen *vs = static_cast(top); + if (vs->isFocused()) + { + push_dfhack_focus_string(vs, focusStrings); + return focusStrings; + } + top = Gui::getDFViewscreen(top); + if (dfhack_viewscreen::is_instance(top)) + { + push_dfhack_focus_string(static_cast(top), focusStrings); + return focusStrings; + } } - else if (virtual_identity *id = virtual_identity::get(top)) + + if (virtual_identity *id = virtual_identity::get(top)) { std::string name = getNameChunk(id, 11, 2); @@ -504,7 +521,8 @@ std::vector Gui::getFocusStrings(df::viewscreen* top) if (handler) handler(name, focusStrings, top); } - else + + if (!focusStrings.size()) { Core &core = Core::getInstance(); std::string name = core.p->readClassName(*(void**)top); @@ -1865,8 +1883,9 @@ df::viewscreen *Gui::getViewscreenByIdentity (virtual_identity &id, int n) return NULL; } -df::viewscreen *Gui::getDFViewscreen(bool skip_dismissed) { - df::viewscreen *screen = Gui::getCurViewscreen(skip_dismissed); +df::viewscreen *Gui::getDFViewscreen(bool skip_dismissed, df::viewscreen *screen) { + if (!screen) + screen = Gui::getCurViewscreen(skip_dismissed); while (screen && dfhack_viewscreen::is_instance(screen)) { screen = screen->parent; if (skip_dismissed) diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 13dbcb204..a78a36a3f 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -865,6 +865,9 @@ void dfhack_lua_viewscreen::update_focus(lua_State *L, int idx) lua_getfield(L, idx, "allow_options"); allow_options = lua_toboolean(L, -1); lua_pop(L, 1); + lua_getfield(L, idx, "defocused"); + defocused = lua_toboolean(L, -1); + lua_pop(L, 1); lua_getfield(L, idx, "focus_path"); auto str = lua_tostring(L, -1); @@ -1081,6 +1084,7 @@ using df::identity_traits; #define CUR_STRUCT dfhack_viewscreen static const struct_field_info dfhack_viewscreen_fields[] = { { METHOD(OBJ_METHOD, is_lua_screen), 0, 0 }, + { METHOD(OBJ_METHOD, isFocused), 0, 0 }, { METHOD(OBJ_METHOD, getFocusString), 0, 0 }, { METHOD(OBJ_METHOD, onShow), 0, 0 }, { METHOD(OBJ_METHOD, onDismiss), 0, 0 }, From d6c4d4417e99ac16106b83dc76de6f4175bf975e Mon Sep 17 00:00:00 2001 From: Myk Date: Sun, 5 Feb 2023 21:11:07 -0800 Subject: [PATCH 03/12] Add dig to the build Ref: #2743 --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 9ce25a4cc..3389303fd 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -103,7 +103,7 @@ dfhack_plugin(cursecheck cursecheck.cpp) dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) #dfhack_plugin(deramp deramp.cpp) dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_static) -#dfhack_plugin(dig dig.cpp) +dfhack_plugin(dig dig.cpp) dfhack_plugin(dig-now dig-now.cpp LINK_LIBRARIES lua) #dfhack_plugin(digFlood digFlood.cpp) #add_subdirectory(diggingInvaders) From 032b62dcbece4fa7bd09986de95d3b7b687ee642 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 6 Feb 2023 07:14:56 +0000 Subject: [PATCH 04/12] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index dc11839b6..88e7eb472 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit dc11839b673019e9dac0e63de0b05dedd3aea786 +Subproject commit 88e7eb47291c25623bd8ef97d726fbfab3bd66d2 From 2ddd23e45ddda1baa21fe7c257054ffb60b13942 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 6 Feb 2023 00:57:33 -0800 Subject: [PATCH 05/12] prevent rclick from bleeding through (again) --- docs/changelog.txt | 1 + library/lua/gui.lua | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index ea140f451..3ff4233b3 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - ``Units::isFortControlled``: Account for agitated wildlife +- Fix right click sometimes closing both a DFHack window and a vanilla panel ## Misc Improvements - `automelt`: is now more resistent to savegame corruption diff --git a/library/lua/gui.lua b/library/lua/gui.lua index a5489c5a6..c571c2389 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -789,6 +789,7 @@ function ZScreen:onInput(keys) end if keys._MOUSE_R_DOWN then df.global.enabler.mouse_rbut_down = 0 + df.global.enabler.mouse_rbut = 0 end return end From 9e318842a429a6a85392fe146cc911a505eee9a6 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 6 Feb 2023 00:58:57 -0800 Subject: [PATCH 06/12] implement some Gui module hotkey guards and clean up focus string matching logic --- docs/dev/Lua API.rst | 7 +- library/include/modules/Gui.h | 2 +- library/modules/Gui.cpp | 204 ++++++++-------------------------- plugins/confirm.cpp | 7 +- 4 files changed, 56 insertions(+), 164 deletions(-) diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 5a1b562ee..8dc448660 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -958,17 +958,18 @@ Screens [1] = "dwarfmode/Info/CREATURES/CITIZEN" [2] = "dwardmode/Squads" -* ``dfhack.gui.matchFocusString(focus_string)`` +* ``dfhack.gui.matchFocusString(focus_string[, viewscreen])`` Returns ``true`` if the given ``focus_string`` is found in the current focus strings, or as a prefix to any of the focus strings, or ``false`` - if no match is found. Matching is case insensitive. + if no match is found. Matching is case insensitive. If ``viewscreen`` is + specified, gets the focus strings to match from the given viewscreen. * ``dfhack.gui.getCurFocus([skip_dismissed])`` Returns the focus string of the current viewscreen. -* ``dfhack.gui.getViewscreenByType(type [, depth])`` +* ``dfhack.gui.getViewscreenByType(type[, depth])`` Returns the topmost viewscreen out of the top ``depth`` viewscreens with the specified type (e.g. ``df.viewscreen_titlest``), or ``nil`` if none match. diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index aeb763e4a..b579dcc97 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -67,7 +67,7 @@ namespace DFHack namespace Gui { DFHACK_EXPORT std::vector getFocusStrings(df::viewscreen *top); - DFHACK_EXPORT bool matchFocusString(std::string focusString, bool prefixMatch = true); + DFHACK_EXPORT bool matchFocusString(std::string focus_string, df::viewscreen *top = NULL); // Full-screen item details view diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 1008b0972..56f8bc301 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -57,6 +57,7 @@ using namespace DFHack; #include "df/building_trapst.h" #include "df/building_type.h" #include "df/building_workshopst.h" +#include "df/cri_unitst.h" #include "df/d_init.h" #include "df/game_mode.h" #include "df/general_ref.h" @@ -459,7 +460,7 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) } if (!newFocusString.size()) { - focusStrings.push_back(baseFocus); + focusStrings.push_back(baseFocus + "/Default"); } } @@ -475,12 +476,14 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dungeonmode) } */ -bool Gui::matchFocusString(std::string focusString, bool prefixMatch) { - focusString = toLower(focusString); - std::vector currentFocusStrings = getFocusStrings(getCurViewscreen(true)); +bool Gui::matchFocusString(std::string focus_string, df::viewscreen *top) { + focus_string = toLower(focus_string); + if (!top) + top = getCurViewscreen(true); + std::vector currentFocusStrings = getFocusStrings(top); - return std::find_if(currentFocusStrings.begin(), currentFocusStrings.end(), [&focusString, &prefixMatch](std::string item) { - return prefixMatch ? prefix_matches(focusString, toLower(item)) : focusString == toLower(item); + return std::find_if(currentFocusStrings.begin(), currentFocusStrings.end(), [&focus_string](std::string item) { + return prefix_matches(focus_string, toLower(item)); }) != currentFocusStrings.end(); } @@ -535,17 +538,7 @@ std::vector Gui::getFocusStrings(df::viewscreen* top) bool Gui::default_hotkey(df::viewscreen *top) { - // Default hotkey guard function - for (;top ;top = top->parent) - { - if (strict_virtual_cast(top)) - return true; -/* TODO: understand how this changes for v50 - if (strict_virtual_cast(top)) - return true; -*/ - } - return false; + return World::isFortressMode() || World::isAdventureMode(); } bool Gui::anywhere_hotkey(df::viewscreen *) { @@ -553,24 +546,7 @@ bool Gui::anywhere_hotkey(df::viewscreen *) { } bool Gui::dwarfmode_hotkey(df::viewscreen *top) { - return World::isFortressMode(); -} - -bool Gui::unitjobs_hotkey(df::viewscreen *top) -{ -/* TODO: understand how this changes for v50 - // Require the unit or jobs list - return !!strict_virtual_cast(top) || - !!strict_virtual_cast(top); -*/ return false; -} - -bool Gui::item_details_hotkey(df::viewscreen *top) -{ -/* TODO: understand how this changes for v50 - // Require the main dwarf mode screen - return !!strict_virtual_cast(top); -*/ return false; + return matchFocusString("dwarfmode", top); } static bool has_cursor() @@ -595,164 +571,82 @@ bool Gui::workshop_job_hotkey(df::viewscreen *top) if (!dwarfmode_hotkey(top)) return false; -/* TODO: understand how this changes for v50 - using namespace ui_sidebar_mode; - using df::global::ui_workshop_in_add; - using df::global::ui_workshop_job_cursor; - - switch (plotinfo->main.mode) { - case QueryBuilding: - { - if (!ui_workshop_job_cursor) // allow missing - return false; - - df::building *selected = world->selected_building; - if (!virtual_cast(selected) && - !virtual_cast(selected)) - return false; - - // No jobs? - if (selected->jobs.empty() || - selected->jobs[0]->job_type == job_type::DestroyBuilding) - return false; - - // Add job gui activated? - if (ui_workshop_in_add && *ui_workshop_in_add) - return false; + df::building *selected = getAnyBuilding(top); + if (!virtual_cast(selected) && + !virtual_cast(selected)) + return false; - return true; - }; - default: + if (selected->jobs.empty() || + selected->jobs[0]->job_type == job_type::DestroyBuilding) return false; - } -*/ return false; + + return true; } bool Gui::build_selector_hotkey(df::viewscreen *top) { + using df::global::buildreq; + if (!dwarfmode_hotkey(top)) return false; -/* TODO: understand how this changes for v50 - using namespace ui_sidebar_mode; - using df::global::ui_build_selector; - - switch (plotinfo->main.mode) { - case Build: - { - if (!ui_build_selector) // allow missing - return false; - - // Not selecting, or no choices? - if (ui_build_selector->building_type < 0 || - ui_build_selector->stage != 2 || - ui_build_selector->choices.empty()) - return false; - - return true; - }; - default: + if (buildreq->building_type < 0 || + buildreq->stage != 2 || + buildreq->choices.empty()) return false; - } -*/ return false; + + return true; } bool Gui::view_unit_hotkey(df::viewscreen *top) { if (!dwarfmode_hotkey(top)) return false; -/* TODO: understand how this changes for v50 - using df::global::ui_selected_unit; - - if (plotinfo->main.mode != ui_sidebar_mode::ViewUnits) - return false; - if (!ui_selected_unit) // allow missing - return false; - return vector_get(world->units.active, *ui_selected_unit) != NULL; -*/ return false; + return !!getAnyUnit(top); } -bool Gui::unit_inventory_hotkey(df::viewscreen *top) +bool Gui::any_job_hotkey(df::viewscreen *top) { - using df::global::ui_unit_view_mode; - - if (!view_unit_hotkey(top)) - return false; - if (!ui_unit_view_mode) - return false; - - return ui_unit_view_mode->value == df::ui_unit_view_mode::Inventory; + return matchFocusString("dwarfmode/Info/JOBS", top) + || matchFocusString("dwarfmode/Info/CREATURES/CITIZEN", top) + || workshop_job_hotkey(top); } df::job *Gui::getSelectedWorkshopJob(color_ostream &out, bool quiet) { - using df::global::ui_workshop_job_cursor; - - if (!workshop_job_hotkey(Core::getTopViewscreen())) { - if (!quiet) - out.printerr("Not in a workshop, or no job is highlighted.\n"); - return NULL; - } - - df::building *selected = world->selected_building; - int idx = *ui_workshop_job_cursor; - - if (size_t(idx) >= selected->jobs.size()) - { - out.printerr("Invalid job cursor index: %d\n", idx); + auto bld = getSelectedBuilding(out, true); + if (!bld) return NULL; - } - - return selected->jobs[idx]; -} - -bool Gui::any_job_hotkey(df::viewscreen *top) -{ -/* TODO: understand how this changes for v50 - if (VIRTUAL_CAST_VAR(screen, df::viewscreen_joblistst, top)) - return vector_get(screen->jobs, screen->cursor_pos) != NULL; - if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitlistst, top)) - return vector_get(screen->jobs[screen->page], screen->cursor_pos[screen->page]) != NULL; - - return workshop_job_hotkey(top); -*/ return false; + // no way to select a specific job; just get the first one + return bld->jobs.size() ? bld->jobs[0] : NULL; } df::job *Gui::getSelectedJob(color_ostream &out, bool quiet) { -/* TODO: understand how this changes for v50 - df::viewscreen *top = Core::getTopViewscreen(); - - if (VIRTUAL_CAST_VAR(screen, df::viewscreen_jobst, top)) - { - return screen->job; - } - if (VIRTUAL_CAST_VAR(joblist, df::viewscreen_joblistst, top)) - { - df::job *job = vector_get(joblist->jobs, joblist->cursor_pos); + using df::global::game; - if (!job && !quiet) - out.printerr("Selected unit has no job\n"); + auto top = Core::getTopViewscreen(); + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) + return dfscreen->getSelectedJob(); - return job; + if (matchFocusString("dwarfmode/Info/JOBS")) { + auto &cri_job = game->main_interface.info.jobs.cri_job; + // no way to select specific jobs; just get the first one + return cri_job.size() ? cri_job[0]->jb : NULL; } - else if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, top)) - { - int page = unitlist->page; - df::job *job = vector_get(unitlist->jobs[page], unitlist->cursor_pos[page]); + + if (auto unit = getAnyUnit(top)) { + df::job *job = unit->job.current_job; if (!job && !quiet) out.printerr("Selected unit has no job\n"); return job; } - else if (auto dfscreen = dfhack_viewscreen::try_cast(top)) - return dfscreen->getSelectedJob(); - else - return getSelectedWorkshopJob(out, quiet); -*/ return getSelectedWorkshopJob(out, quiet); + + return getSelectedWorkshopJob(out, quiet); } df::unit *Gui::getAnyUnit(df::viewscreen *top) @@ -767,7 +661,7 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) return df::unit::find(game->main_interface.view_sheets.active_id); /* TODO: understand how this changes for v50 - using namespace ui_sidebar_mode; + using namespace ui_sidebar_mode; using df::global::ui_look_cursor; using df::global::ui_look_list; using df::global::ui_selected_unit; diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 9b3f78285..aad1c17bc 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -69,7 +69,6 @@ public: enum cstate { INACTIVE, ACTIVE, SELECTED }; virtual string get_id() = 0; virtual string get_focus_string() = 0; - virtual bool match_prefix() = 0; virtual bool set_state(cstate) = 0; static bool set_state(string id, cstate state) @@ -305,7 +304,7 @@ public: conf_wrapper *wrapper = confirmations[this->get_id()]; if(wrapper->is_paused()) { std::string concernedFocus = this->get_focus_string(); - if(!Gui::matchFocusString(this->get_focus_string(), this->match_prefix())) + if(!Gui::matchFocusString(this->get_focus_string())) wrapper->set_paused(false); return false; } else if (state == INACTIVE) @@ -469,7 +468,6 @@ public: } string get_id() override = 0; string get_focus_string() override = 0; - bool match_prefix() override = 0; #define CONF_LUA_START using namespace conf_lua; Lua::StackUnwinder unwind(l_state); push(screen); push(get_id()); bool intercept_key (df::interface_key key) { @@ -560,7 +558,6 @@ static int conf_register_##cls = conf_register(&cls##_instance, {\ class confirmation_##cls : public confirmation { \ virtual string get_id() { static string id = char_replace(#cls, '_', '-'); return id; } \ virtual string get_focus_string() { return focusString; } \ - virtual bool match_prefix() { return focusString[strlen(focusString) - 1] == '*'; } \ }; \ IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, 0); @@ -585,7 +582,7 @@ DEFINE_CONFIRMATION(haul_delete_stop, viewscreen_dwarfmodest, "dwarfmode/Hau DEFINE_CONFIRMATION(depot_remove, viewscreen_dwarfmodest, "dwarfmode/ViewSheets/BUILDING"); DEFINE_CONFIRMATION(squad_disband, viewscreen_dwarfmodest, "dwarfmode/Squads"); DEFINE_CONFIRMATION(order_remove, viewscreen_dwarfmodest, "dwarfmode/Info/WORK_ORDERS"); -DEFINE_CONFIRMATION(zone_remove, viewscreen_dwarfmodest, "dwarfmode/Zone*"); +DEFINE_CONFIRMATION(zone_remove, viewscreen_dwarfmodest, "dwarfmode/Zone"); DEFINE_CONFIRMATION(burrow_remove, viewscreen_dwarfmodest, "dwarfmode/Burrow"); DEFINE_CONFIRMATION(stockpile_remove, viewscreen_dwarfmodest, "dwarfmode/Some/Stockpile"); From 5747e9f3f083cea51687b80098e572cdfa3e29b9 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 6 Feb 2023 01:23:52 -0800 Subject: [PATCH 07/12] set up some keybindings that are ready to go and reinstate autodump-destroy-item --- data/init/dfhack.keybindings.init | 19 ++++++------------- docs/changelog.txt | 4 ++++ plugins/autodump.cpp | 6 ++---- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/data/init/dfhack.keybindings.init b/data/init/dfhack.keybindings.init index 5847a42c7..3a94aea2a 100644 --- a/data/init/dfhack.keybindings.init +++ b/data/init/dfhack.keybindings.init @@ -25,9 +25,6 @@ keybinding add Ctrl-Shift-K gui/cp437-table # customizable quick command list keybinding add Ctrl-Shift-A gui/quickcmd -# an in-game init file editor -#keybinding add Alt-S@title|dwarfmode/Default|dungeonmode gui/settings-manager - ###################### # dwarfmode bindings # @@ -36,25 +33,21 @@ keybinding add Ctrl-Shift-A gui/quickcmd # quicksave keybinding add Ctrl-Alt-S@dwarfmode quicksave -# toggle the display of water level as 1-7 tiles -#keybinding add Ctrl-W@dwarfmode|dungeonmode twaterlvl - # designate the whole vein for digging -#keybinding add Ctrl-V@dwarfmode digv -#keybinding add Ctrl-Shift-V@dwarfmode "digv x" +keybinding add Ctrl-V@dwarfmode digv +keybinding add Ctrl-Shift-V@dwarfmode "digv x" # clean the selected tile of blood etc -#keybinding add Ctrl-C spotclean +keybinding add Ctrl-C spotclean # destroy the selected item -#keybinding add Ctrl-K@dwarfmode autodump-destroy-item +keybinding add Ctrl-K@dwarfmode autodump-destroy-item # destroy items designated for dump in the selected tile -#keybinding add Ctrl-Shift-K@dwarfmode autodump-destroy-here +keybinding add Ctrl-H@dwarfmode autodump-destroy-here -# apply blueprints to the map (Alt-F for compatibility with LNP Quickfort) +# apply blueprints to the map keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort -#keybinding add Alt-F@dwarfmode gui/quickfort # show information collected by dwarfmonitor #keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs" diff --git a/docs/changelog.txt b/docs/changelog.txt index ea140f451..29fc72cf0 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -40,6 +40,10 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `automelt`: is now more resistent to savegame corruption +- `autodump`: reinstate ``autodump-destroy-item``, hotkey: Ctrl-K +- `autodump`: new hotkey for ``autodump-destroy-here``: Ctrl-H +- `dig`: new hotkeys for vein designation on z-level (Ctrl-V) and vein designation across z-levels (Ctrl-Shift-V) +- `clean`: new hotkey for `spotclean`: Ctrl-C ## Documentation diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index aed6fa0ea..61e83d63e 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -287,13 +287,11 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector & parame return CR_WRONG_USAGE; } - //DFHack::VersionInfo *mem = Core::getInstance().vinfo; if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); @@ -461,10 +458,11 @@ static int last_frame = 0; command_result df_autodump_destroy_item(color_ostream &out, vector & parameters) { - // HOTKEY COMMAND; CORE ALREADY SUSPENDED if (!parameters.empty()) return CR_WRONG_USAGE; + CoreSuspender suspend; + df::item *item = Gui::getSelectedItem(out); if (!item) return CR_FAILURE; From bf91ffb1fe3081a8ff32cebc43627f990a8970c6 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 6 Feb 2023 02:37:32 -0800 Subject: [PATCH 08/12] support focus paths for overlay widgets and migrate existing widgets to use them --- docs/changelog.txt | 1 + docs/dev/overlay-dev-guide.rst | 6 +++- plugins/lua/autolabor.lua | 11 ++----- plugins/lua/orders.lua | 17 +--------- plugins/lua/overlay.lua | 59 +++++++++++++++++++++++++++------- 5 files changed, 56 insertions(+), 38 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index ea140f451..d52212f1b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -46,6 +46,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API ## Lua +- `overlay`: overlay widgets can now specify focus paths for the viewscreens they attach to so they only appear in specific contexts. see `overlay-dev-guide` for details. ## Removed diff --git a/docs/dev/overlay-dev-guide.rst b/docs/dev/overlay-dev-guide.rst index c7e094299..d31ce246d 100644 --- a/docs/dev/overlay-dev-guide.rst +++ b/docs/dev/overlay-dev-guide.rst @@ -109,7 +109,11 @@ The ``overlay.OverlayWidget`` superclass defines the following class attributes: ``dwarfmode`` and the adventure mode map viewscreen would be ``dungeonmode``. If there is only one viewscreen that this widget is associated with, it can be specified as a string instead of a list of - strings with a single element. + strings with a single element. If you only want your widget to appear in + certain contexts, you can specify a focus path, in the same syntax as the + `keybinding` command. For example, ``dwarfmode/Info/CREATURES/CITIZEN`` will + ensure the overlay widget is only displayed when the "Citizens" subtab under + the "Units" panel is active. - ``hotspot`` (default: ``false``) If set to ``true``, your widget's ``overlay_onupdate`` function will be called whenever the `overlay` plugin's ``plugin_onupdate()`` function is diff --git a/plugins/lua/autolabor.lua b/plugins/lua/autolabor.lua index b91227650..6ef4d7279 100644 --- a/plugins/lua/autolabor.lua +++ b/plugins/lua/autolabor.lua @@ -4,16 +4,11 @@ local gui = require('gui') local overlay = require('plugins.overlay') local widgets = require('gui.widgets') -local function is_labor_panel_visible() - local info = df.global.game.main_interface.info - return info.open and info.current_mode == df.info_interface_mode_type.LABOR -end - AutolaborOverlay = defclass(AutolaborOverlay, overlay.OverlayWidget) AutolaborOverlay.ATTRS{ default_pos={x=7,y=-13}, default_enabled=true, - viewscreens='dwarfmode', + viewscreens='dwarfmode/Info/LABOR', frame={w=29, h=5}, frame_style=gui.MEDIUM_FRAME, frame_background=gui.CLEAR_PEN, @@ -34,9 +29,7 @@ function AutolaborOverlay:init() end function AutolaborOverlay:render(dc) - if not is_labor_panel_visible() or not isEnabled() then - return false - end + if not isEnabled() then return false end AutolaborOverlay.super.render(self, dc) end diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 972edcfea..72bb6185e 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -9,11 +9,6 @@ local widgets = require('gui.widgets') -- OrdersOverlay -- -local function is_orders_panel_visible() - local info = df.global.game.main_interface.info - return info.open and info.current_mode == df.info_interface_mode_type.WORK_ORDERS -end - local function do_sort() dfhack.run_command('orders', 'sort') end @@ -49,7 +44,7 @@ OrdersOverlay = defclass(OrdersOverlay, overlay.OverlayWidget) OrdersOverlay.ATTRS{ default_pos={x=53,y=-6}, default_enabled=true, - viewscreens='dwarfmode', + viewscreens='dwarfmode/Info/WORK_ORDERS', frame={w=30, h=4}, frame_style=gui.MEDIUM_FRAME, frame_background=gui.CLEAR_PEN, @@ -84,16 +79,6 @@ function OrdersOverlay:init() } end -function OrdersOverlay:render(dc) - if not is_orders_panel_visible() then return false end - OrdersOverlay.super.render(self, dc) -end - -function OrdersOverlay:onInput(keys) - if not is_orders_panel_visible() then return false end - OrdersOverlay.super.onInput(self, keys) -end - OVERLAY_WIDGETS = { overlay=OrdersOverlay, } diff --git a/plugins/lua/overlay.lua b/plugins/lua/overlay.lua index bf944d7e9..c3522c204 100644 --- a/plugins/lua/overlay.lua +++ b/plugins/lua/overlay.lua @@ -81,19 +81,18 @@ function normalize_list(element_or_list) return {element_or_list} end --- normalize "short form" viewscreen names to "long form" +-- normalize "short form" viewscreen names to "long form" and remove any focus local function normalize_viewscreen_name(vs_name) - if vs_name == 'all' or vs_name:match('viewscreen_.*st') then - return vs_name + if vs_name == 'all' or vs_name:match('^viewscreen_.*st') then + return vs_name:match('^[^/]+') end - return 'viewscreen_' .. vs_name .. 'st' + return 'viewscreen_' .. vs_name:match('^[^/]+') .. 'st' end --- reduce "long form" viewscreen names to "short form" +-- reduce "long form" viewscreen names to "short form"; keep focus function simplify_viewscreen_name(vs_name) - _,_,short_name = vs_name:find('^viewscreen_(.*)st$') - if short_name then return short_name end - return vs_name + local short_name = vs_name:match('^viewscreen_([^/]+)st') + return short_name or vs_name end local function is_empty(tbl) @@ -241,10 +240,23 @@ local function do_list(args) end end +local function get_focus_strings(viewscreens) + local focus_strings = nil + for _,vs in ipairs(viewscreens) do + if vs:match('/') then + focus_strings = focus_strings or {} + vs = simplify_viewscreen_name(vs) + table.insert(focus_strings, vs) + end + end + return focus_strings +end + local function load_widget(name, widget_class) local widget = widget_class{name=name} widget_db[name] = { widget=widget, + focus_strings=get_focus_strings(normalize_list(widget.viewscreens)), next_update_ms=widget.overlay_onupdate and 0 or math.huge, } if not overlay_config[name] then overlay_config[name] = {} end @@ -426,12 +438,30 @@ function update_hotspot_widgets() end end +local function matches_focus_strings(db_entry, vs_name) + if not db_entry.focus_strings then return true end + local matched = true + local simple_vs_name = simplify_viewscreen_name(vs_name) + for _,fs in ipairs(db_entry.focus_strings) do + if fs:startswith(simple_vs_name) then + matched = false + if dfhack.gui.matchFocusString(fs, vs) then + return true + end + end + end + return matched +end + local function _update_viewscreen_widgets(vs_name, vs, now_ms) local vs_widgets = active_viewscreen_widgets[vs_name] if not vs_widgets then return end now_ms = now_ms or dfhack.getTickCount() for name,db_entry in pairs(vs_widgets) do - if do_update(name, db_entry, now_ms, vs) then return end + if matches_focus_strings(db_entry, vs_name) and + do_update(name, db_entry, now_ms, vs) then + return + end end return now_ms end @@ -439,7 +469,9 @@ end function update_viewscreen_widgets(vs_name, vs) if triggered_screen_has_lock() then return end local now_ms = _update_viewscreen_widgets(vs_name, vs, nil) - _update_viewscreen_widgets('all', vs, now_ms) + if now_ms then + _update_viewscreen_widgets('all', vs, now_ms) + end end local function _feed_viewscreen_widgets(vs_name, keys) @@ -447,7 +479,8 @@ local function _feed_viewscreen_widgets(vs_name, keys) if not vs_widgets then return false end for _,db_entry in pairs(vs_widgets) do local w = db_entry.widget - if detect_frame_change(w, function() return w:onInput(keys) end) then + if matches_focus_strings(db_entry, vs_name) and + detect_frame_change(w, function() return w:onInput(keys) end) then return true end end @@ -465,7 +498,9 @@ local function _render_viewscreen_widgets(vs_name, dc) dc = dc or gui.Painter.new() for _,db_entry in pairs(vs_widgets) do local w = db_entry.widget - detect_frame_change(w, function() w:render(dc) end) + if matches_focus_strings(db_entry, vs_name) then + detect_frame_change(w, function() w:render(dc) end) + end end end From 397a64c4b599236f20c27fb58489fdc4748378df Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 6 Feb 2023 02:58:11 -0800 Subject: [PATCH 09/12] don't print out how many items there are in the world --- plugins/cleanowned.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/cleanowned.cpp b/plugins/cleanowned.cpp index 86ef0a2e1..3fece8bfe 100644 --- a/plugins/cleanowned.cpp +++ b/plugins/cleanowned.cpp @@ -74,8 +74,6 @@ command_result df_cleanowned (color_ostream &out, vector & parameters) return CR_FAILURE; } - out.print("Found total %zd items.\n", world->items.all.size()); - for (std::size_t i=0; i < world->items.all.size(); i++) { df::item * item = world->items.all[i]; From e285ee31a406b4aba936615d4788e6b9f129980e Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 6 Feb 2023 03:28:19 -0800 Subject: [PATCH 10/12] tweak defaults, load initial races immediately --- docs/changelog.txt | 2 ++ docs/plugins/autobutcher.rst | 7 +++---- plugins/autobutcher.cpp | 12 +++++++----- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index ea140f451..626191f9f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -40,6 +40,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `automelt`: is now more resistent to savegame corruption +- `autobutcher`: changed defaults from 5 females / 1 male to 4 females / 2 males so a single unfortunate accident doesn't leave players without a mating pair +- `autobutcher`: now immediately loads races available at game start into the watchlist ## Documentation diff --git a/docs/plugins/autobutcher.rst b/docs/plugins/autobutcher.rst index 1a4aa4f91..7e5b6b024 100644 --- a/docs/plugins/autobutcher.rst +++ b/docs/plugins/autobutcher.rst @@ -18,8 +18,8 @@ watch list. Units will be ignored if they are: Creatures who will not reproduce (because they're not interested in the opposite sex or have been gelded) will be butchered before those who will. Older adults and younger children will be butchered first if the population -is above the target (defaults are: 1 male kid, 5 female kids, 1 male adult, -5 female adults). Note that you may need to set a target above 1 to have a +is above the target (defaults are: 2 male kids, 4 female kids, 2 male adults, +4 female adults). Note that you may need to set a target above 1 to have a reliable breeding population due to asexuality etc. See `fix-ster` if this is a problem. @@ -34,7 +34,7 @@ Usage ``autobutcher autowatch`` Automatically add all new races (animals you buy from merchants, tame yourself, or get from migrants) to the watch list using the default target - counts. + counts. This option is enabled by default. ``autobutcher noautowatch`` Stop auto-adding new races to the watch list. ``autobutcher target all|new| [ ...]`` @@ -108,4 +108,3 @@ fortress:: autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA autobutcher target 5 5 6 2 PIG autobutcher target 0 0 0 0 new - autobutcher autowatch diff --git a/plugins/autobutcher.cpp b/plugins/autobutcher.cpp index 3e76796cb..28667bbe7 100644 --- a/plugins/autobutcher.cpp +++ b/plugins/autobutcher.cpp @@ -107,6 +107,8 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { DEBUG(status,out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); set_config_bool(CONFIG_IS_ENABLED, is_enabled); + if (enable) + autobutcher_cycle(out); } else { DEBUG(status,out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", @@ -130,11 +132,11 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) { config = World::AddPersistentData(CONFIG_KEY); set_config_bool(CONFIG_IS_ENABLED, is_enabled); set_config_val(CONFIG_CYCLE_TICKS, 6000); - set_config_bool(CONFIG_AUTOWATCH, false); - set_config_val(CONFIG_DEFAULT_FK, 5); - set_config_val(CONFIG_DEFAULT_MK, 1); - set_config_val(CONFIG_DEFAULT_FA, 5); - set_config_val(CONFIG_DEFAULT_MA, 1); + set_config_bool(CONFIG_AUTOWATCH, true); + set_config_val(CONFIG_DEFAULT_FK, 4); + set_config_val(CONFIG_DEFAULT_MK, 2); + set_config_val(CONFIG_DEFAULT_FA, 4); + set_config_val(CONFIG_DEFAULT_MA, 2); } // we have to copy our enabled flag into the global plugin variable, but From 6dbf7b83bd51a9f0f5ed23f86d04dea507c2d6c3 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 6 Feb 2023 04:02:18 -0800 Subject: [PATCH 11/12] update auto plugin example, use new best practices --- plugins/autochop.cpp | 2 + plugins/autoslab.cpp | 2 + .../examples/persistent_per_save_example.cpp | 72 ++++++++++++++----- plugins/nestboxes.cpp | 2 + plugins/seedwatch.cpp | 23 +++--- 5 files changed, 74 insertions(+), 27 deletions(-) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 5b1cdf97e..bf7540e29 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -143,6 +143,8 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { DEBUG(status,out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); + if (enable) + do_cycle(out, true); } else { DEBUG(status,out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", diff --git a/plugins/autoslab.cpp b/plugins/autoslab.cpp index 9b0848470..e17ce72c8 100644 --- a/plugins/autoslab.cpp +++ b/plugins/autoslab.cpp @@ -90,6 +90,8 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) is_enabled = enable; DEBUG(status, out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); set_config_bool(CONFIG_IS_ENABLED, is_enabled); + if (enable) + do_cycle(out); } else { diff --git a/plugins/examples/persistent_per_save_example.cpp b/plugins/examples/persistent_per_save_example.cpp index a8421949b..f066be6ef 100644 --- a/plugins/examples/persistent_per_save_example.cpp +++ b/plugins/examples/persistent_per_save_example.cpp @@ -6,6 +6,7 @@ // savegame that had this plugin enabled is loaded. #include +#include #include #include "df/world.h" @@ -18,6 +19,7 @@ #include "modules/World.h" using std::string; +using std::unordered_map; using std::vector; using namespace DFHack; @@ -38,25 +40,50 @@ namespace DFHack { } static const string CONFIG_KEY = string(plugin_name) + "/config"; +static const string ELEM_CONFIG_KEY_PREFIX = string(plugin_name) + "/elem/"; static PersistentDataItem config; +static unordered_map elems; + enum ConfigValues { CONFIG_IS_ENABLED = 0, CONFIG_SOMETHING_ELSE = 1, }; -static int get_config_val(int index) { - if (!config.isValid()) + +enum ElemConfigValues { + ELEM_CONFIG_ID = 0, + ELEM_CONFIG_SOMETHING_ELSE = 1, +}; + +static int get_config_val(PersistentDataItem &c, int index) { + if (!c.isValid()) return -1; - return config.ival(index); + return c.ival(index); +} +static bool get_config_bool(PersistentDataItem &c, int index) { + return get_config_val(c, index) == 1; } -static bool get_config_bool(int index) { - return get_config_val(index) == 1; +static void set_config_val(PersistentDataItem &c, int index, int value) { + if (c.isValid()) + c.ival(index) = value; } -static void set_config_val(int index, int value) { - if (config.isValid()) - config.ival(index) = value; +static void set_config_bool(PersistentDataItem &c, int index, bool value) { + set_config_val(c, index, value ? 1 : 0); } -static void set_config_bool(int index, bool value) { - set_config_val(index, value ? 1 : 0); + +static PersistentDataItem & ensure_elem_config(color_ostream &out, int id) { + if (elems.count(id)) + return elems[id]; + string keyname = ELEM_CONFIG_KEY_PREFIX + int_to_string(id); + DEBUG(config,out).print("creating new persistent key for elem id %d\n", id); + elems.emplace(id, World::GetPersistentData(keyname, NULL)); + return elems[id]; +} +static void remove_elem_config(color_ostream &out, int id) { + if (!elems.count(id)) + return; + DEBUG(config,out).print("removing persistent key for elem id %d\n", id); + World::DeletePersistentData(elems[id]); + elems.erase(id); } static const int32_t CYCLE_TICKS = 1200; // one day @@ -87,7 +114,9 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { is_enabled = enable; DEBUG(config,out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); - set_config_bool(CONFIG_IS_ENABLED, is_enabled); + set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); + if (enable) + do_cycle(out); } else { DEBUG(config,out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", @@ -109,16 +138,27 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) { if (!config.isValid()) { DEBUG(config,out).print("no config found in this save; initializing\n"); config = World::AddPersistentData(CONFIG_KEY); - set_config_bool(CONFIG_IS_ENABLED, is_enabled); - set_config_val(CONFIG_SOMETHING_ELSE, 6000); + set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); + set_config_val(config, CONFIG_SOMETHING_ELSE, 6000); } // we have to copy our enabled flag into the global plugin variable, but // all the other state we can directly read/modify from the persistent // data structure. - is_enabled = get_config_bool(CONFIG_IS_ENABLED); + is_enabled = get_config_bool(config, CONFIG_IS_ENABLED); DEBUG(config,out).print("loading persisted enabled state: %s\n", is_enabled ? "true" : "false"); + + // load other config elements, if applicable + elems.clear(); + vector elem_configs; + World::GetPersistentData(&elem_configs, ELEM_CONFIG_KEY_PREFIX, true); + const size_t num_elem_configs = elem_configs.size(); + for (size_t idx = 0; idx < num_elem_configs; ++idx) { + auto &c = elem_configs[idx]; + elems.emplace(get_config_val(c, ELEM_CONFIG_ID), c); + } + return CR_OK; } @@ -150,8 +190,8 @@ static command_result do_command(color_ostream &out, vector ¶meters) // TODO: configuration logic // simple commandline parsing can be done in C++, but there are lua libraries - // that can easily handle more complex commandlines. see the blueprint plugin - // for an example. + // that can easily handle more complex commandlines. see the seedwatch plugin + // for a simple example. return CR_OK; } diff --git a/plugins/nestboxes.cpp b/plugins/nestboxes.cpp index 98e557075..b67101614 100644 --- a/plugins/nestboxes.cpp +++ b/plugins/nestboxes.cpp @@ -70,6 +70,8 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { DEBUG(config,out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); + if (enable) + do_cycle(out); } else { DEBUG(config,out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index aee167258..551ec36dc 100644 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -99,7 +99,7 @@ static const int32_t CYCLE_TICKS = 1200; static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle static command_result do_command(color_ostream &out, vector ¶meters); -static void do_cycle(color_ostream &out, int32_t *num_enabled_seeds, int32_t *num_disabled_seeds); +static void do_cycle(color_ostream &out, int32_t *num_enabled_seeds = NULL, int32_t *num_disabled_seeds = NULL); static void seedwatch_setTarget(color_ostream &out, string name, int32_t num); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { @@ -149,7 +149,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { is_enabled ? "enabled" : "disabled"); set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); if (enable) - seedwatch_setTarget(out, "all", DEFAULT_TARGET); + do_cycle(out); } else { DEBUG(config,out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", @@ -174,26 +174,27 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) { world_plant_ids[plant->id] = i; } + watched_seeds.clear(); + vector seed_configs; + World::GetPersistentData(&seed_configs, SEED_CONFIG_KEY_PREFIX, true); + const size_t num_seed_configs = seed_configs.size(); + for (size_t idx = 0; idx < num_seed_configs; ++idx) { + auto &c = seed_configs[idx]; + watched_seeds.emplace(get_config_val(c, SEED_CONFIG_ID), c); + } + config = World::GetPersistentData(CONFIG_KEY); if (!config.isValid()) { DEBUG(config,out).print("no config found in this save; initializing\n"); config = World::AddPersistentData(CONFIG_KEY); set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); + seedwatch_setTarget(out, "all", DEFAULT_TARGET); } is_enabled = get_config_bool(config, CONFIG_IS_ENABLED); DEBUG(config,out).print("loading persisted enabled state: %s\n", is_enabled ? "true" : "false"); - watched_seeds.clear(); - vector seed_configs; - World::GetPersistentData(&seed_configs, SEED_CONFIG_KEY_PREFIX, true); - const size_t num_seed_configs = seed_configs.size(); - for (size_t idx = 0; idx < num_seed_configs; ++idx) { - auto &c = seed_configs[idx]; - watched_seeds.emplace(get_config_val(c, SEED_CONFIG_ID), c); - } - return CR_OK; } From c0cd37ff6fb65f9e3474aebf2b8175e68441c0fc Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 6 Feb 2023 09:04:50 -0800 Subject: [PATCH 12/12] protect against NULLs and invalid stockpiles --- plugins/automelt.cpp | 66 +++++++++++----------------------------- plugins/lua/automelt.lua | 8 +++-- 2 files changed, 23 insertions(+), 51 deletions(-) diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp index 14c62a80a..e8001c48b 100644 --- a/plugins/automelt.cpp +++ b/plugins/automelt.cpp @@ -1,37 +1,21 @@ - #include "Debug.h" #include "LuaTools.h" #include "PluginManager.h" -#include "TileTypes.h" #include "modules/Buildings.h" -#include "modules/Maps.h" #include "modules/Items.h" #include "modules/World.h" -#include "modules/Designations.h" #include "modules/Persistence.h" -#include "modules/Units.h" -#include "modules/Screen.h" #include "modules/Gui.h" -// #include "uicommon.h" - #include "df/world.h" -#include "df/building.h" -#include "df/world_raws.h" -#include "df/building_def.h" -#include "df/viewscreen_dwarfmodest.h" #include "df/building_stockpilest.h" -#include "df/plotinfost.h" #include "df/item_quality.h" #include #include -using df::building_stockpilest; using std::map; -using std::multimap; -using std::pair; using std::string; using std::unordered_map; using std::vector; @@ -41,10 +25,7 @@ using namespace df::enums; DFHACK_PLUGIN("automelt"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); -REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); -REQUIRE_GLOBAL(cursor); -REQUIRE_GLOBAL(plotinfo); namespace DFHack { @@ -57,16 +38,12 @@ static const string CONFIG_KEY = string(plugin_name) + "/config"; static const string STOCKPILE_CONFIG_KEY_PREFIX = string(plugin_name) + "/stockpile/"; static PersistentDataItem config; -// static vector watched_stockpiles; -// static unordered_map watched_stockpiles_indices; - static unordered_map watched_stockpiles; enum StockpileConfigValues { STOCKPILE_CONFIG_ID = 0, STOCKPILE_CONFIG_MONITORED = 1, - }; static int get_config_val(PersistentDataItem &c, int index) @@ -115,8 +92,8 @@ static void remove_stockpile_config(color_ostream &out, int id) watched_stockpiles.erase(id); } -static bool isStockpile(df::building * building) { - return building->getType() == df::building_type::Stockpile; +static bool isStockpile(df::building * bld) { + return bld && bld->getType() == df::building_type::Stockpile; } static void validate_stockpile_configs(color_ostream &out) @@ -124,7 +101,7 @@ static void validate_stockpile_configs(color_ostream &out) for (auto &c : watched_stockpiles) { int id = get_config_val(c.second, STOCKPILE_CONFIG_ID); auto bld = df::building::find(id); - if (!bld || !isStockpile(bld)) + if (!isStockpile(bld)) remove_stockpile_config(out, id); } } @@ -135,7 +112,7 @@ static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle static command_result do_command(color_ostream &out, vector ¶meters); static int32_t do_cycle(color_ostream &out); -DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) +DFhackCExport command_result plugin_init(color_ostream &out, vector &commands) { DEBUG(status, out).print("initializing %s\n", plugin_name); @@ -222,7 +199,6 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) return CR_OK; } - static bool call_automelt_lua(color_ostream *out, const char *fn_name, int nargs = 0, int nres = 0, Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA, @@ -268,6 +244,8 @@ static command_result do_command(color_ostream &out, vector ¶meters) static inline bool is_metal_item(df::item *item) { + if (!item) + return false; MaterialInfo mat(item); return (mat.getCraftClass() == craft_material_class::Metal); } @@ -307,6 +285,9 @@ static inline bool can_melt(df::item *item) { static const BadFlagsCanMelt bad_flags; + if (!is_metal_item(item)) + return false; + if (item->flags.whole & bad_flags.whole) return false; @@ -315,9 +296,6 @@ static inline bool can_melt(df::item *item) if (t == df::enums::item_type::BOX || t == df::enums::item_type::BAR) return false; - if (!is_metal_item(item)) - return false; - for (auto &g : item->general_refs) { switch (g->getType()) @@ -372,7 +350,7 @@ static int mark_item(color_ostream &out, df::item *item, BadFlagsMarkItem bad_fl { DEBUG(perf,out).print("assignedToStockpile\n"); size_t marked_count = 0; - std::vector contents; + vector contents; Items::getContainedItems(item, &contents); for (auto child = contents.begin(); child != contents.end(); child++) { @@ -414,7 +392,6 @@ static int mark_item(color_ostream &out, df::item *item, BadFlagsMarkItem bad_fl } - static int32_t mark_all_in_stockpile(color_ostream &out, PersistentDataItem & stockpile, int32_t &premarked_item_count, int32_t &item_count, map &tracked_item_map, bool should_melt) { DEBUG(perf,out).print("%s running mark_all_in_stockpile\nshould_melt=%d\n", plugin_name, should_melt); @@ -429,10 +406,8 @@ static int32_t mark_all_in_stockpile(color_ostream &out, PersistentDataItem & st int spid = get_config_val(stockpile, STOCKPILE_CONFIG_ID); auto found = df::building::find(spid); - if (!isStockpile(found)){ - + if (!isStockpile(found)) return 0; - } df::building_stockpilest * pile_cast = virtual_cast(found); @@ -451,7 +426,6 @@ static int32_t mark_all_in_stockpile(color_ostream &out, PersistentDataItem & st return marked_count; } - static int32_t scan_stockpiles(color_ostream &out, bool should_melt, map &item_count_piles, map &premarked_item_count_piles, map &marked_item_count_piles, map &tracked_item_map) { DEBUG(perf,out).print("running scan_stockpiles\n"); @@ -518,8 +492,6 @@ static int32_t scan_count_all(color_ostream &out, bool should_melt, int32_t &mar map tracked_item_map_piles; - tracked_item_map_piles.clear(); - newly_marked_items_piles = scan_stockpiles(out, should_melt, item_count_piles, premarked_item_count_piles, marked_item_count_piles, tracked_item_map_piles); marked_item_count_global = scan_all_melt_designated(out, tracked_item_map_piles); @@ -557,20 +529,18 @@ static int32_t do_cycle(color_ostream &out) { } static int getSelectedStockpile(color_ostream &out) { - df::building *selected_bldg = NULL; - selected_bldg = Gui::getSelectedBuilding(out, true); - if (selected_bldg->getType() != df::building_type::Stockpile) { + df::building *bld = Gui::getSelectedBuilding(out, true); + if (!isStockpile(bld)) { DEBUG(status,out).print("Selected building is not stockpile\n"); return -1; } - return selected_bldg->id; + return bld->id; } static PersistentDataItem *getSelectedStockpileConfig(color_ostream &out) { int32_t bldg_id = getSelectedStockpile(out); if (bldg_id == -1) { - DEBUG(status,out).print("Selected bldg invalid\n"); return NULL; } @@ -579,11 +549,10 @@ static PersistentDataItem *getSelectedStockpileConfig(color_ostream &out) { if (watched_stockpiles.count(bldg_id)) { c = &(watched_stockpiles[bldg_id]); return c; - } else { - DEBUG(status,out).print("No existing config\n"); - return NULL; } + DEBUG(status,out).print("No existing config\n"); + return NULL; } static void push_stockpile_config(lua_State *L, int id, bool monitored) { @@ -671,7 +640,7 @@ static void automelt_setStockpileConfig(color_ostream &out, int id, bool monitor DEBUG(status,out).print("entering automelt_setStockpileConfig for id=%d and monitored=%d\n", id, monitored); validate_stockpile_configs(out); auto bldg = df::building::find(id); - bool isInvalidStockpile = !bldg || !isStockpile(bldg); + bool isInvalidStockpile = !isStockpile(bldg); bool hasNoData = !monitored; if (isInvalidStockpile || hasNoData) { DEBUG(cycle,out).print("calling remove_stockpile_config with id=%d monitored=%d\n", id, monitored); @@ -767,7 +736,6 @@ static int automelt_getSelectedStockpileConfig(lua_State *L){ return 1; } -//TODO static int automelt_getItemCountsAndStockpileConfigs(lua_State *L) { color_ostream *out = Lua::GetOutput(L); if (!out) diff --git a/plugins/lua/automelt.lua b/plugins/lua/automelt.lua index 4f1d82ad9..b49a434a1 100644 --- a/plugins/lua/automelt.lua +++ b/plugins/lua/automelt.lua @@ -17,8 +17,12 @@ end local function do_set_stockpile_config(var_name, val, stockpiles) for _,bspec in ipairs(argparse.stringList(stockpiles)) do local config = automelt_getStockpileConfig(bspec) - config[var_name] = val - automelt_setStockpileConfig(config.id, config.monitor, config.melt) + if not config then + dfhack.printerr('invalid stockpile: '..tostring(bspec)) + else + config[var_name] = val + automelt_setStockpileConfig(config.id, config.monitor, config.melt) + end end end