From 32a2d9f9b5c94e3651943542d84c89a82ff4b77f Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 18 Sep 2023 12:49:36 +0300 Subject: [PATCH 1/9] Removed redundant uppercase in mental stability formula. Reworked thresholds for combat skill effectiveness formulas to have a higher 100 cap (more descriptive about very strong warriors). --- plugins/lua/sort.lua | 74 ++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index 15d9ebabb..778bc87b9 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -212,7 +212,7 @@ local function melee_skill_effectiveness(unit) end local function get_melee_skill_effectiveness_rating(unit) - return get_rating(melee_skill_effectiveness(unit), 350000, 2350000, 78, 64, 49, 35) + return get_rating(melee_skill_effectiveness(unit), 350000, 2750000, 64, 52, 40, 28) end local function make_sort_by_melee_skill_effectiveness_desc() @@ -272,7 +272,7 @@ local function ranged_skill_effectiveness(unit) end local function get_ranged_skill_effectiveness_rating(unit) - return get_rating(ranged_skill_effectiveness(unit), 0, 500000, 90, 62, 44, 27) + return get_rating(ranged_skill_effectiveness(unit), 0, 800000, 72, 52, 31, 11) end local function make_sort_by_ranged_skill_effectiveness_desc(list) @@ -345,41 +345,41 @@ end -- Statistical rating that is higher for dwarves that are mentally stable local function get_mental_stability(unit) - local ALTRUISM = unit.status.current_soul.personality.traits.ALTRUISM - local ANXIETY_PROPENSITY = unit.status.current_soul.personality.traits.ANXIETY_PROPENSITY - local BRAVERY = unit.status.current_soul.personality.traits.BRAVERY - local CHEER_PROPENSITY = unit.status.current_soul.personality.traits.CHEER_PROPENSITY - local CURIOUS = unit.status.current_soul.personality.traits.CURIOUS - local DISCORD = unit.status.current_soul.personality.traits.DISCORD - local DUTIFULNESS = unit.status.current_soul.personality.traits.DUTIFULNESS - local EMOTIONALLY_OBSESSIVE = unit.status.current_soul.personality.traits.EMOTIONALLY_OBSESSIVE - local HUMOR = unit.status.current_soul.personality.traits.HUMOR - local LOVE_PROPENSITY = unit.status.current_soul.personality.traits.LOVE_PROPENSITY - local PERSEVERENCE = unit.status.current_soul.personality.traits.PERSEVERENCE - local POLITENESS = unit.status.current_soul.personality.traits.POLITENESS - local PRIVACY = unit.status.current_soul.personality.traits.PRIVACY - local STRESS_VULNERABILITY = unit.status.current_soul.personality.traits.STRESS_VULNERABILITY - local TOLERANT = unit.status.current_soul.personality.traits.TOLERANT - - local CRAFTSMANSHIP = setbelief.getUnitBelief(unit, df.value_type['CRAFTSMANSHIP']) - local FAMILY = setbelief.getUnitBelief(unit, df.value_type['FAMILY']) - local HARMONY = setbelief.getUnitBelief(unit, df.value_type['HARMONY']) - local INDEPENDENCE = setbelief.getUnitBelief(unit, df.value_type['INDEPENDENCE']) - local KNOWLEDGE = setbelief.getUnitBelief(unit, df.value_type['KNOWLEDGE']) - local LEISURE_TIME = setbelief.getUnitBelief(unit, df.value_type['LEISURE_TIME']) - local NATURE = setbelief.getUnitBelief(unit, df.value_type['NATURE']) - local SKILL = setbelief.getUnitBelief(unit, df.value_type['SKILL']) - - -- Calculate the rating using the defined variables - local rating = (CRAFTSMANSHIP * -0.01) + (FAMILY * -0.09) + (HARMONY * 0.05) - + (INDEPENDENCE * 0.06) + (KNOWLEDGE * -0.30) + (LEISURE_TIME * 0.24) - + (NATURE * 0.27) + (SKILL * -0.21) + (ALTRUISM * 0.13) - + (ANXIETY_PROPENSITY * -0.06) + (BRAVERY * 0.06) - + (CHEER_PROPENSITY * 0.41) + (CURIOUS * -0.06) + (DISCORD * 0.14) - + (DUTIFULNESS * -0.03) + (EMOTIONALLY_OBSESSIVE * -0.13) - + (HUMOR * -0.05) + (LOVE_PROPENSITY * 0.15) + (PERSEVERENCE * -0.07) - + (POLITENESS * -0.14) + (PRIVACY * 0.03) + (STRESS_VULNERABILITY * -0.20) - + (TOLERANT * -0.11) + local altruism = unit.status.current_soul.personality.traits.ALTRUISM + local anxiety_propensity = unit.status.current_soul.personality.traits.ANXIETY_PROPENSITY + local bravery = unit.status.current_soul.personality.traits.BRAVERY + local cheer_propensity = unit.status.current_soul.personality.traits.CHEER_PROPENSITY + local curious = unit.status.current_soul.personality.traits.CURIOUS + local discord = unit.status.current_soul.personality.traits.DISCORD + local dutifulness = unit.status.current_soul.personality.traits.DUTIFULNESS + local emotionally_obsessive = unit.status.current_soul.personality.traits.EMOTIONALLY_OBSESSIVE + local humor = unit.status.current_soul.personality.traits.HUMOR + local love_propensity = unit.status.current_soul.personality.traits.LOVE_PROPENSITY + local perseverence = unit.status.current_soul.personality.traits.PERSEVERENCE + local politeness = unit.status.current_soul.personality.traits.POLITENESS + local privacy = unit.status.current_soul.personality.traits.PRIVACY + local stress_vulnerability = unit.status.current_soul.personality.traits.STRESS_VULNERABILITY + local tolerant = unit.status.current_soul.personality.traits.TOLERANT + + local craftsmanship = setbelief.getUnitBelief(unit, df.value_type['CRAFTSMANSHIP']) + local family = setbelief.getUnitBelief(unit, df.value_type['FAMILY']) + local harmony = setbelief.getUnitBelief(unit, df.value_type['HARMONY']) + local independence = setbelief.getUnitBelief(unit, df.value_type['INDEPENDENCE']) + local knowledge = setbelief.getUnitBelief(unit, df.value_type['KNOWLEDGE']) + local leisure_time = setbelief.getUnitBelief(unit, df.value_type['LEISURE_TIME']) + local nature = setbelief.getUnitBelief(unit, df.value_type['NATURE']) + local skill = setbelief.getUnitBelief(unit, df.value_type['SKILL']) + + -- calculate the rating using the defined variables + local rating = (craftsmanship * -0.01) + (family * -0.09) + (harmony * 0.05) + + (independence * 0.06) + (knowledge * -0.30) + (leisure_time * 0.24) + + (nature * 0.27) + (skill * -0.21) + (altruism * 0.13) + + (anxiety_propensity * -0.06) + (bravery * 0.06) + + (cheer_propensity * 0.41) + (curious * -0.06) + (discord * 0.14) + + (dutifulness * -0.03) + (emotionally_obsessive * -0.13) + + (humor * -0.05) + (love_propensity * 0.15) + (perseverence * -0.07) + + (politeness * -0.14) + (privacy * 0.03) + (stress_vulnerability * -0.20) + + (tolerant * -0.11) return rating end From 73fed1e833b6ce5d76459721d77bd08c1c3660c6 Mon Sep 17 00:00:00 2001 From: Mikhail Panov Date: Tue, 26 Sep 2023 17:54:34 +0300 Subject: [PATCH 2/9] Updated changelog.txt --- docs/changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 9efb86430..f5907f4cc 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -75,6 +75,9 @@ Template for new versions: - Linux launcher: allow Steam Overlay and game streaming to function - `autobutcher`: don't ignore semi-wild units when marking units for slaughter +## Misc Improvements +- 'sort': Improve combat skill scale thresholds + # 50.09-r4 ## New Features From 6ad724b4831f19f46ae62bede574e1817c3ba21c Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 26 Sep 2023 03:45:15 -0700 Subject: [PATCH 3/9] add detailed focus strings for setupdwarfgame --- library/modules/Gui.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 9eee284ac..58763262f 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -87,6 +87,7 @@ using namespace DFHack; #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_legendsst.h" #include "df/viewscreen_new_regionst.h" +#include "df/viewscreen_setupdwarfgamest.h" #include "df/viewscreen_titlest.h" #include "df/world.h" @@ -174,6 +175,45 @@ DEFINE_GET_FOCUS_STRING_HANDLER(new_region) focusStrings.push_back(baseFocus); } +DEFINE_GET_FOCUS_STRING_HANDLER(setupdwarfgame) +{ + if (screen->doing_custom_settings) + focusStrings.push_back(baseFocus + "/CustomSettings"); + else if (game->main_interface.options.open) + focusStrings.push_back(baseFocus + "/Abort"); + else if (screen->initial_selection == 1) + focusStrings.push_back(baseFocus + "/Default"); + else if (game->main_interface.name_creator.open) { + switch (game->main_interface.name_creator.context) { + case df::name_creator_context_type::EMBARK_FORT_NAME: + focusStrings.push_back(baseFocus + "/FortName"); + break; + case df::name_creator_context_type::EMBARK_GROUP_NAME: + focusStrings.push_back(baseFocus + "/GroupName"); + break; + default: + break; + } + } + else if (game->main_interface.image_creator.open) { + focusStrings.push_back(baseFocus + "/GroupSymbol"); + } + else if (screen->viewing_objections != 0) + focusStrings.push_back(baseFocus + "/Objections"); + else { + switch (screen->mode) { + case 0: focusStrings.push_back(baseFocus + "/Dwarves"); break; + case 1: focusStrings.push_back(baseFocus + "/Items"); break; + case 2: focusStrings.push_back(baseFocus + "/Animals"); break; + default: + break; + } + } + + if (focusStrings.empty()) + focusStrings.push_back(baseFocus + "/Default"); +} + DEFINE_GET_FOCUS_STRING_HANDLER(legends) { if (screen->init_stage != -1) From 5c670d20db0b7d1387c60c9d4ecb32d488f20792 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 26 Sep 2023 03:52:24 -0700 Subject: [PATCH 4/9] align mouse button semantics to DF we, um, had it backwards --- docs/changelog.txt | 1 + docs/dev/Lua API.rst | 4 +- library/LuaTools.cpp | 72 +++++++++---------- library/lua/gui.lua | 23 ++---- library/lua/gui/dialogs.lua | 8 +-- library/lua/gui/widgets.lua | 37 +++++----- library/modules/Screen.cpp | 10 +++ plugins/lua/buildingplan/inspectoroverlay.lua | 4 +- plugins/lua/buildingplan/itemselection.lua | 4 +- plugins/lua/buildingplan/planneroverlay.lua | 8 +-- plugins/lua/hotkeys.lua | 8 +-- plugins/lua/overlay.lua | 3 - plugins/lua/sort.lua | 4 +- plugins/lua/zone.lua | 9 ++- test/library/gui/widgets.EditField.lua | 6 +- test/library/gui/widgets.Scrollbar.lua | 18 ++--- test/library/gui/widgets.lua | 4 +- 17 files changed, 110 insertions(+), 113 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index f5907f4cc..aec12a96f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -66,6 +66,7 @@ Template for new versions: ## API ## Lua +- mouse key events are now aligned with internal DF semantics: ``_MOUSE_L`` indicates that the left mouse button has just been pressed and ``_MOUSE_L_DOWN`` indicates that the left mouse button is being held down. similar for ``_MOUSE_R`` and ``_MOUSE_M``. 3rd party scripts may have to adjust. ## Removed diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 1cba8284e..6eeb2dbe7 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -2528,10 +2528,10 @@ Supported callbacks and fields are: Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience. ``_MOUSE_L, _MOUSE_R, _MOUSE_M`` - If the left, right, and/or middle mouse button is being pressed. + If the left, right, and/or middle mouse button was just pressed. ``_MOUSE_L_DOWN, _MOUSE_R_DOWN, _MOUSE_M_DOWN`` - If the left, right, and/or middle mouse button was just pressed. + If the left, right, and/or middle mouse button is being held down. If this method is omitted, the screen is dismissed on reception of the ``LEAVESCREEN`` key. diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index ffeb4980a..87699641d 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -131,12 +131,12 @@ void DFHack::Lua::GetVector(lua_State *state, std::vector &pvec, in } } -static bool trigger_inhibit_l_down = false; -static bool trigger_inhibit_r_down = false; -static bool trigger_inhibit_m_down = false; -static bool inhibit_l_down = false; -static bool inhibit_r_down = false; -static bool inhibit_m_down = false; +static bool trigger_inhibit_l = false; +static bool trigger_inhibit_r = false; +static bool trigger_inhibit_m = false; +static bool inhibit_l = false; +static bool inhibit_r = false; +static bool inhibit_m = false; void DFHack::Lua::PushInterfaceKeys(lua_State *L, const std::set &keys) { @@ -161,32 +161,32 @@ void DFHack::Lua::PushInterfaceKeys(lua_State *L, } if (df::global::enabler) { - if (!inhibit_l_down && df::global::enabler->mouse_lbut_down) { + if (!inhibit_l && df::global::enabler->mouse_lbut) { lua_pushboolean(L, true); - lua_setfield(L, -2, "_MOUSE_L_DOWN"); - trigger_inhibit_l_down = true; + lua_setfield(L, -2, "_MOUSE_L"); + trigger_inhibit_l = true; } - if (!inhibit_r_down && df::global::enabler->mouse_rbut_down) { + if (!inhibit_r && df::global::enabler->mouse_rbut) { lua_pushboolean(L, true); - lua_setfield(L, -2, "_MOUSE_R_DOWN"); - trigger_inhibit_r_down = true; + lua_setfield(L, -2, "_MOUSE_R"); + trigger_inhibit_r = true; } - if (!inhibit_m_down && df::global::enabler->mouse_mbut_down) { + if (!inhibit_m && df::global::enabler->mouse_mbut) { lua_pushboolean(L, true); - lua_setfield(L, -2, "_MOUSE_M_DOWN"); - trigger_inhibit_m_down = true; + lua_setfield(L, -2, "_MOUSE_M"); + trigger_inhibit_m = true; } - if (df::global::enabler->mouse_lbut) { + if (df::global::enabler->mouse_lbut_down) { lua_pushboolean(L, true); - lua_setfield(L, -2, "_MOUSE_L"); + lua_setfield(L, -2, "_MOUSE_L_DOWN"); } - if (df::global::enabler->mouse_rbut) { + if (df::global::enabler->mouse_rbut_down) { lua_pushboolean(L, true); - lua_setfield(L, -2, "_MOUSE_R"); + lua_setfield(L, -2, "_MOUSE_R_DOWN"); } - if (df::global::enabler->mouse_mbut) { + if (df::global::enabler->mouse_mbut_down) { lua_pushboolean(L, true); - lua_setfield(L, -2, "_MOUSE_M"); + lua_setfield(L, -2, "_MOUSE_M_DOWN"); } } } @@ -2159,25 +2159,25 @@ void DFHack::Lua::Core::Reset(color_ostream &out, const char *where) lua_settop(State, 0); } - if (trigger_inhibit_l_down) { - trigger_inhibit_l_down = false; - inhibit_l_down = true; + if (trigger_inhibit_l) { + trigger_inhibit_l = false; + inhibit_l = true; } - if (trigger_inhibit_r_down) { - trigger_inhibit_r_down = false; - inhibit_r_down = true; + if (trigger_inhibit_r) { + trigger_inhibit_r = false; + inhibit_r = true; } - if (trigger_inhibit_m_down) { - trigger_inhibit_m_down = false; - inhibit_m_down = true; + if (trigger_inhibit_m) { + trigger_inhibit_m = false; + inhibit_m = true; } if (df::global::enabler) { - if (!df::global::enabler->mouse_lbut) - inhibit_l_down = false; - if (!df::global::enabler->mouse_rbut) - inhibit_r_down = false; - if (!df::global::enabler->mouse_mbut) - inhibit_m_down = false; + if (!df::global::enabler->mouse_lbut_down) + inhibit_l = false; + if (!df::global::enabler->mouse_rbut_down) + inhibit_r = false; + if (!df::global::enabler->mouse_mbut_down) + inhibit_m = false; } } diff --git a/library/lua/gui.lua b/library/lua/gui.lua index bba7222b9..ba49e0cdc 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -696,16 +696,12 @@ end DEFAULT_INITIAL_PAUSE = true -local zscreen_inhibit_mouse_l = false - -- ensure underlying DF screens don't also react to handled clicks function markMouseClicksHandled(keys) - if keys._MOUSE_L_DOWN then - -- note we can't clear mouse_lbut here. otherwise we break dragging, - df.global.enabler.mouse_lbut_down = 0 - zscreen_inhibit_mouse_l = true + if keys._MOUSE_L then + df.global.enabler.mouse_lbut = 0 end - if keys._MOUSE_R_DOWN then + if keys._MOUSE_R then df.global.enabler.mouse_rbut_down = 0 df.global.enabler.mouse_rbut = 0 end @@ -789,7 +785,7 @@ function ZScreen:onInput(keys) local has_mouse = self:isMouseOver() if not self:hasFocus() then if has_mouse and - (keys._MOUSE_L_DOWN or keys._MOUSE_R_DOWN or + (keys._MOUSE_L or keys._MOUSE_R or keys.CONTEXT_SCROLL_UP or keys.CONTEXT_SCROLL_DOWN or keys.CONTEXT_SCROLL_PAGEUP or keys.CONTEXT_SCROLL_PAGEDOWN) then self:raise() @@ -804,22 +800,15 @@ function ZScreen:onInput(keys) return end - if self.pass_mouse_clicks and keys._MOUSE_L_DOWN and not has_mouse then + if self.pass_mouse_clicks and keys._MOUSE_L and not has_mouse then self.defocused = self.defocusable self:sendInputToParent(keys) return - elseif keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + elseif keys.LEAVESCREEN or keys._MOUSE_R then self:dismiss() markMouseClicksHandled(keys) return else - if zscreen_inhibit_mouse_l then - if keys._MOUSE_L then - return - else - zscreen_inhibit_mouse_l = false - end - end local passit = self.pass_pause and keys.D_PAUSE if not passit and self.pass_mouse_clicks then if keys.CONTEXT_SCROLL_UP or keys.CONTEXT_SCROLL_DOWN or diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 499fa6305..95a56d0c4 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -57,11 +57,11 @@ function MessageBox:onDestroy() end function MessageBox:onInput(keys) - if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R then self:dismiss() if keys.SELECT and self.on_accept then self.on_accept() - elseif (keys.LEAVESCREEN or keys._MOUSE_R_DOWN) and self.on_cancel then + elseif (keys.LEAVESCREEN or keys._MOUSE_R) and self.on_cancel then self.on_cancel() end gui.markMouseClicksHandled(keys) @@ -129,7 +129,7 @@ function InputBox:onInput(keys) self.on_input(self.subviews.edit.text) end return true - elseif keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + elseif keys.LEAVESCREEN or keys._MOUSE_R then self:dismiss() if self.on_cancel then self.on_cancel() @@ -231,7 +231,7 @@ function ListBox:getWantedFrameSize() end function ListBox:onInput(keys) - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then self:dismiss() if self.on_cancel then self.on_cancel() diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 6a0a0091b..05d237f35 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -273,7 +273,7 @@ end function Panel:onInput(keys) if self.kbd_get_pos then - if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R then Panel_end_drag(self, not keys.SELECT and self.saved_frame or nil, not not keys.SELECT) return true @@ -281,7 +281,6 @@ function Panel:onInput(keys) for code in pairs(keys) do local dx, dy = guidm.get_movement_delta(code, 1, 10) if dx then - local frame_rect = self.frame_rect local kbd_pos = self.kbd_get_pos() kbd_pos.x = kbd_pos.x + dx kbd_pos.y = kbd_pos.y + dy @@ -292,9 +291,9 @@ function Panel:onInput(keys) return end if self.drag_offset then - if keys._MOUSE_R_DOWN then + if keys._MOUSE_R then Panel_end_drag(self, self.saved_frame) - elseif keys._MOUSE_L then + elseif keys._MOUSE_L_DOWN then Panel_update_frame(self, Panel_make_frame(self)) end return true @@ -302,7 +301,7 @@ function Panel:onInput(keys) if Panel.super.onInput(self, keys) then return true end - if not keys._MOUSE_L_DOWN then return end + if not keys._MOUSE_L then return end local x,y = self:getMouseFramePos() if not x then return end @@ -489,7 +488,7 @@ function Panel:onRenderFrame(dc, rect) dc:seek(pos.x, pos.y):pen(pen):char(string.char(0xDB)) end if self.drag_offset and not self.kbd_get_pos - and df.global.enabler.mouse_lbut == 0 then + and df.global.enabler.mouse_lbut_down == 0 then Panel_end_drag(self, nil, true) end end @@ -718,7 +717,7 @@ function EditField:onInput(keys) end end - if self.key and (keys.LEAVESCREEN or keys._MOUSE_R_DOWN) then + if self.key and (keys.LEAVESCREEN or keys._MOUSE_R) then self:setText(self.saved_text) self:setFocus(false) return true @@ -740,8 +739,8 @@ function EditField:onInput(keys) end end return not not self.key - elseif keys._MOUSE_L then - local mouse_x, mouse_y = self:getMousePos() + elseif keys._MOUSE_L_DOWN then + local mouse_x = self:getMousePos() if mouse_x then self:setCursor(self.start_pos + mouse_x - (self.text_offset or 0)) return true @@ -986,7 +985,7 @@ function Scrollbar:onRenderBody(dc) if self.is_dragging then scrollbar_do_drag(self) end - if df.global.enabler.mouse_lbut == 0 then + if df.global.enabler.mouse_lbut_down == 0 then self.last_scroll_ms = 0 self.is_dragging = false self.scroll_spec = nil @@ -1023,7 +1022,7 @@ function Scrollbar:onInput(keys) return true end end - if not keys._MOUSE_L_DOWN then return false end + if not keys._MOUSE_L then return false end local _,y = self:getMousePos() if not y then return false end local scroll_spec = nil @@ -1386,11 +1385,11 @@ function Label:onInput(keys) if self:inputToSubviews(keys) then return true end - if keys._MOUSE_L_DOWN and self:getMousePos() and self.on_click then + if keys._MOUSE_L and self:getMousePos() and self.on_click then self.on_click() return true end - if keys._MOUSE_R_DOWN and self:getMousePos() and self.on_rclick then + if keys._MOUSE_R and self:getMousePos() and self.on_rclick then self.on_rclick() return true end @@ -1498,7 +1497,7 @@ end function HotkeyLabel:onInput(keys) if HotkeyLabel.super.onInput(self, keys) then return true - elseif keys._MOUSE_L_DOWN and self:getMousePos() and self.on_activate + elseif keys._MOUSE_L and self:getMousePos() and self.on_activate and not is_disabled(self) then self.on_activate() return true @@ -1658,7 +1657,7 @@ end function CycleHotkeyLabel:onInput(keys) if CycleHotkeyLabel.super.onInput(self, keys) then return true - elseif keys._MOUSE_L_DOWN and self:getMousePos() and not is_disabled(self) then + elseif keys._MOUSE_L and self:getMousePos() and not is_disabled(self) then self:cycle() return true end @@ -1962,7 +1961,7 @@ function List:onInput(keys) return self:submit() elseif keys.CUSTOM_SHIFT_ENTER then return self:submit2() - elseif keys._MOUSE_L_DOWN then + elseif keys._MOUSE_L then local idx = self:getIdxUnderMouse() if idx then local now_ms = dfhack.getTickCount() @@ -2317,7 +2316,7 @@ end function Tab:onInput(keys) if Tab.super.onInput(self, keys) then return true end - if keys._MOUSE_L_DOWN and self:getMousePos() then + if keys._MOUSE_L and self:getMousePos() then self.on_select(self.id) return true end @@ -2419,7 +2418,7 @@ local function rangeslider_get_width_per_idx(self) end function RangeSlider:onInput(keys) - if not keys._MOUSE_L_DOWN then return false end + if not keys._MOUSE_L then return false end local x = self:getMousePos() if not x then return false end local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn() @@ -2527,7 +2526,7 @@ function RangeSlider:onRenderBody(dc, rect) if self.is_dragging_target then rangeslider_do_drag(self, width_per_idx) end - if df.global.enabler.mouse_lbut == 0 then + if df.global.enabler.mouse_lbut_down == 0 then self.is_dragging_target = nil self.is_dragging_idx = nil end diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 5f98c40e5..a5b347493 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -1004,6 +1004,8 @@ dfhack_lua_viewscreen::~dfhack_lua_viewscreen() void dfhack_lua_viewscreen::render() { + using df::global::enabler; + if (Screen::isDismissed(this)) { if (parent) @@ -1011,6 +1013,14 @@ void dfhack_lua_viewscreen::render() return; } + if (enabler && + (enabler->mouse_lbut_down || enabler->mouse_rbut_down || enabler->mouse_mbut_down)) + { + // synthesize feed events for held mouse buttons + std::set keys; + feed(&keys); + } + dfhack_viewscreen::render(); safe_call_lua(do_render, 0, 0); diff --git a/plugins/lua/buildingplan/inspectoroverlay.lua b/plugins/lua/buildingplan/inspectoroverlay.lua index 1fcf19028..3c6f0ed5e 100644 --- a/plugins/lua/buildingplan/inspectoroverlay.lua +++ b/plugins/lua/buildingplan/inspectoroverlay.lua @@ -127,9 +127,9 @@ function InspectorOverlay:onInput(keys) if not require('plugins.buildingplan').isPlannedBuilding(dfhack.gui.getSelectedBuilding(true)) then return false end - if keys._MOUSE_L_DOWN and mouse_is_over_resume_button(self.frame_parent_rect) then + if keys._MOUSE_L and mouse_is_over_resume_button(self.frame_parent_rect) then return true - elseif keys._MOUSE_L_DOWN or keys._MOUSE_R_DOWN or keys.LEAVESCREEN then + elseif keys._MOUSE_L or keys._MOUSE_R or keys.LEAVESCREEN then self:reset() end return InspectorOverlay.super.onInput(self, keys) diff --git a/plugins/lua/buildingplan/itemselection.lua b/plugins/lua/buildingplan/itemselection.lua index 84e866502..9dfd0cc69 100644 --- a/plugins/lua/buildingplan/itemselection.lua +++ b/plugins/lua/buildingplan/itemselection.lua @@ -366,10 +366,10 @@ function ItemSelection:submit(choices) end function ItemSelection:onInput(keys) - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then self.on_cancel() return true - elseif keys._MOUSE_L_DOWN then + elseif keys._MOUSE_L then local list = self.subviews.flist.list local idx = list:getIdxUnderMouse() if idx then diff --git a/plugins/lua/buildingplan/planneroverlay.lua b/plugins/lua/buildingplan/planneroverlay.lua index ebd8e6e02..2cc15dfde 100644 --- a/plugins/lua/buildingplan/planneroverlay.lua +++ b/plugins/lua/buildingplan/planneroverlay.lua @@ -272,7 +272,7 @@ function ItemLine:reset() end function ItemLine:onInput(keys) - if keys._MOUSE_L_DOWN and self:getMousePos() then + if keys._MOUSE_L and self:getMousePos() then self.on_select(self.idx) end return ItemLine.super.onInput(self, keys) @@ -739,7 +739,7 @@ end function PlannerOverlay:onInput(keys) if not is_plannable() then return false end - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then if uibs.selection_pos:isValid() then uibs.selection_pos:clear() return true @@ -758,7 +758,7 @@ function PlannerOverlay:onInput(keys) return true end if self:is_minimized() then return false end - if keys._MOUSE_L_DOWN then + if keys._MOUSE_L then if is_over_options_panel() then return false end local detect_rect = copyall(self.frame_rect) detect_rect.height = self.subviews.main.frame_rect.height + @@ -828,7 +828,7 @@ function PlannerOverlay:onInput(keys) end end end - return keys._MOUSE_L or keys.SELECT + return keys._MOUSE_L_DOWN or keys.SELECT end function PlannerOverlay:render(dc) diff --git a/plugins/lua/hotkeys.lua b/plugins/lua/hotkeys.lua index 8eff17aae..80f8816e8 100644 --- a/plugins/lua/hotkeys.lua +++ b/plugins/lua/hotkeys.lua @@ -269,24 +269,24 @@ function Menu:onSubmit2(_, choice) end function Menu:onInput(keys) - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if keys.LEAVESCREEN or keys._MOUSE_R then return false elseif keys.KEYBOARD_CURSOR_RIGHT then self:onSubmit2(self.subviews.list:getSelected()) return true - elseif keys._MOUSE_L_DOWN then + elseif keys._MOUSE_L then local list = self.subviews.list local x = list:getMousePos() if x == 0 then -- clicked on icon self:onSubmit2(list:getSelected()) - df.global.enabler.mouse_lbut = 0 + gui.markMouseClicksHandled(keys) return true end if not self:getMouseFramePos() then self.parent_view:dismiss() return true end - df.global.enabler.mouse_lbut = 0 + gui.markMouseClicksHandled(keys) end self:inputToSubviews(keys) return true -- we're modal diff --git a/plugins/lua/overlay.lua b/plugins/lua/overlay.lua index d3f0b9c9d..cd5286d0d 100644 --- a/plugins/lua/overlay.lua +++ b/plugins/lua/overlay.lua @@ -507,9 +507,6 @@ function feed_viewscreen_widgets(vs_name, vs, keys) return false end gui.markMouseClicksHandled(keys) - if keys._MOUSE_L_DOWN then - df.global.enabler.mouse_lbut = 0 - end return true end diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index 778bc87b9..6d8c8a298 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -1148,9 +1148,7 @@ function SquadAssignmentOverlay:refresh_list(sort_widget, sort_fn) end function SquadAssignmentOverlay:onInput(keys) - if keys._MOUSE_R_DOWN or - keys._MOUSE_L_DOWN and not self:getMouseFramePos() - then + if keys._MOUSE_R or (keys._MOUSE_L and not self:getMouseFramePos()) then -- if any click is made outside of our window, we may need to refresh our list self.dirty = true end diff --git a/plugins/lua/zone.lua b/plugins/lua/zone.lua index 3c07b609f..13182cef1 100644 --- a/plugins/lua/zone.lua +++ b/plugins/lua/zone.lua @@ -801,10 +801,12 @@ end function AssignAnimalScreen:onInput(keys) local handled = AssignAnimalScreen.super.onInput(self, keys) if not self.is_valid_ui_state() then - view:dismiss() + if view then + view:dismiss() + end return end - if keys._MOUSE_L_DOWN then + if keys._MOUSE_L then -- if any click is made outside of our window, we need to recheck unit properties local window = self.subviews[1] if not window:getMouseFramePos() then @@ -818,7 +820,7 @@ function AssignAnimalScreen:onInput(keys) end function AssignAnimalScreen:onRenderFrame() - if not self.is_valid_ui_state() then + if view and not self.is_valid_ui_state() then view:dismiss() end end @@ -1072,6 +1074,7 @@ function CageChainOverlay:init() frame={t=0, l=0, r=0, h=1}, label='DFHack assign', key='CUSTOM_CTRL_T', + visible=is_valid_building, on_activate=function() view = view and view:raise() or show_cage_chain_screen() end, }, } diff --git a/test/library/gui/widgets.EditField.lua b/test/library/gui/widgets.EditField.lua index 88625a7bf..8418b67d4 100644 --- a/test/library/gui/widgets.EditField.lua +++ b/test/library/gui/widgets.EditField.lua @@ -42,17 +42,17 @@ function test.editfield_click() expect.eq(5, e.cursor) mock.patch(e, 'getMousePos', mock.func(0), function() - e:onInput{_MOUSE_L=true} + e:onInput{_MOUSE_L_DOWN=true} expect.eq(1, e.cursor) end) mock.patch(e, 'getMousePos', mock.func(20), function() - e:onInput{_MOUSE_L=true} + e:onInput{_MOUSE_L_DOWN=true} expect.eq(5, e.cursor, 'should only seek to end of text') end) mock.patch(e, 'getMousePos', mock.func(2), function() - e:onInput{_MOUSE_L=true} + e:onInput{_MOUSE_L_DOWN=true} expect.eq(3, e.cursor) end) end diff --git a/test/library/gui/widgets.Scrollbar.lua b/test/library/gui/widgets.Scrollbar.lua index 548792b3d..dd490256c 100644 --- a/test/library/gui/widgets.Scrollbar.lua +++ b/test/library/gui/widgets.Scrollbar.lua @@ -59,37 +59,37 @@ function test.onInput() s:update(23, 10, 50) expect.false_(s:onInput{}, 'no mouse down') - expect.false_(s:onInput{_MOUSE_L_DOWN=true}, 'no y coord') + expect.false_(s:onInput{_MOUSE_L=true}, 'no y coord') spec, y = nil, 0 - expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.true_(s:onInput{_MOUSE_L=true}) expect.eq('up_small', spec, 'on up arrow') spec, y = nil, 1 - expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.true_(s:onInput{_MOUSE_L=true}) expect.eq('up_large', spec, 'on body above bar') spec, y = nil, 44 - expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.true_(s:onInput{_MOUSE_L=true}) expect.eq('up_large', spec, 'on body just above bar') spec, y = nil, 45 - expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.true_(s:onInput{_MOUSE_L=true}) expect.nil_(spec, 'on top of bar') spec, y = nil, 63 - expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.true_(s:onInput{_MOUSE_L=true}) expect.nil_(spec, 'on bottom of bar') spec, y = nil, 64 - expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.true_(s:onInput{_MOUSE_L=true}) expect.eq('down_large', spec, 'on body just below bar') spec, y = nil, 98 - expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.true_(s:onInput{_MOUSE_L=true}) expect.eq('down_large', spec, 'on body below bar') spec, y = nil, 99 - expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.true_(s:onInput{_MOUSE_L=true}) expect.eq('down_small', spec, 'on down arrow') end diff --git a/test/library/gui/widgets.lua b/test/library/gui/widgets.lua index 88d3ac952..b37fbe04d 100644 --- a/test/library/gui/widgets.lua +++ b/test/library/gui/widgets.lua @@ -7,7 +7,7 @@ function test.hotkeylabel_click() local l = widgets.HotkeyLabel{key='SELECT', on_activate=func} mock.patch(l, 'getMousePos', mock.func(0), function() - l:onInput{_MOUSE_L_DOWN=true} + l:onInput{_MOUSE_L=true} expect.eq(1, func.call_count) end) end @@ -33,7 +33,7 @@ function test.togglehotkeylabel_click() local l = widgets.ToggleHotkeyLabel{} expect.true_(l:getOptionValue()) mock.patch(l, 'getMousePos', mock.func(0), function() - l:onInput{_MOUSE_L_DOWN=true} + l:onInput{_MOUSE_L=true} expect.false_(l:getOptionValue()) end) end From 888c88dfcf7399663ae4066dd419292c9e836b70 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 27 Sep 2023 02:23:08 +0000 Subject: [PATCH 5/9] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index e6d83ccae..52fe6b277 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e6d83ccaee5b5a3c663b56046ae55a7389742da8 +Subproject commit 52fe6b27723ba7e3064ae03c1a7ae5712c3dc0ec diff --git a/scripts b/scripts index a8aacf9b3..4032be431 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a8aacf9b3e8d71c338f8d087792ee8aa39a85220 +Subproject commit 4032be431a669ac822583dc3ccca3c6c07c8e3aa From f5aab7ee45f2d14542b793a6972271d95e84408d Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Wed, 27 Sep 2023 20:32:46 +0100 Subject: [PATCH 6/9] pulled event handler calls out of the loops over global vectors to avoid potential iterator invalidation by an event callback, and did some tidying --- library/modules/EventManager.cpp | 279 ++++++++++++++++++------------- 1 file changed, 164 insertions(+), 115 deletions(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 02b1892e9..ddc46942d 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -43,6 +43,7 @@ #include #include #include +#include namespace DFHack { DBG_DECLARE(eventmanager, log, DebugCategory::LINFO); @@ -246,6 +247,15 @@ static int32_t reportToRelevantUnitsTime = -1; //interaction static int32_t lastReportInteraction; +struct hash_pair { + template + size_t operator()(const std::pair& p) const { + auto h1 = std::hash{}(p.first); + auto h2 = std::hash{}(p.second); + return h1 ^ (h2 << 1); + } +}; + void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { static bool doOnce = false; // const string eventNames[] = {"world loaded", "world unloaded", "map loaded", "map unloaded", "viewscreen changed", "core initialized", "begin unload", "paused", "unpaused"}; @@ -276,9 +286,9 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event gameLoaded = false; multimap copy(handlers[EventType::UNLOAD].begin(), handlers[EventType::UNLOAD].end()); - for (auto &key_value : copy) { + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for map unloaded state change event\n"); - key_value.second.eventHandler(out, nullptr); + handle.eventHandler(out, nullptr); } } else if ( event == DFHack::SC_MAP_LOADED ) { /* @@ -375,8 +385,7 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { continue; int32_t eventFrequency = -100; if ( a != EventType::TICK ) - for (auto &key_value : handlers[a]) { - EventHandler &handle = key_value.second; + for (auto &[_,handle] : handlers[a]) { if (handle.freq < eventFrequency || eventFrequency == -100 ) eventFrequency = handle.freq; } @@ -439,8 +448,7 @@ static void manageJobInitiatedEvent(color_ostream& out) { continue; if ( link->item->id <= lastJobId ) continue; - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for job initiated event\n"); handle.eventHandler(out, (void*)link->item); } @@ -455,18 +463,21 @@ static void manageJobStartedEvent(color_ostream& out) { static unordered_set startedJobs; + vector new_started_jobs; // iterate event handler callbacks multimap copy(handlers[EventType::JOB_STARTED].begin(), handlers[EventType::JOB_STARTED].end()); for (df::job_list_link* link = df::global::world->jobs.list.next; link != nullptr; link = link->next) { df::job* job = link->item; if (job && Job::getWorker(job) && !startedJobs.count(job->id)) { startedJobs.emplace(job->id); - for (auto &key_value : copy) { - auto &handler = key_value.second; - // the jobs must have a worker to start - DEBUG(log,out).print("calling handler for job started event\n"); - handler.eventHandler(out, job); - } + new_started_jobs.emplace_back(job); + } + } + for (df::job* job : new_started_jobs) { + for (auto &[_,handle] : copy) { + // the jobs must have a worker to start + DEBUG(log,out).print("calling handler for job started event\n"); + handle.eventHandler(out, job); } } } @@ -556,6 +567,8 @@ static void manageJobCompletedEvent(color_ostream& out) { } #endif + vector new_jobs_completed; + vector new_jobs_completed_repeats; for (auto &prevJob : prevJobs) { //if it happened within a tick, must have been cancelled by the user or a plugin: not completed if ( tick1 <= tick0 ) @@ -573,11 +586,7 @@ static void manageJobCompletedEvent(color_ostream& out) { continue; //still false positive if cancelled at EXACTLY the right time, but experiments show this doesn't happen - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; - DEBUG(log,out).print("calling handler for repeated job completed event\n"); - handle.eventHandler(out, (void*)&job0); - } + new_jobs_completed_repeats.emplace_back(&job0); continue; } @@ -586,10 +595,19 @@ static void manageJobCompletedEvent(color_ostream& out) { if ( job0.flags.bits.repeat || job0.completion_timer != 0 ) continue; - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; + new_jobs_completed.emplace_back(&job0); + } + + for (df::job* job : new_jobs_completed_repeats) { + for (auto &[_,handle] : copy) { + DEBUG(log,out).print("calling handler for repeated job completed event\n"); + handle.eventHandler(out, (void*) job); + } + } + for (df::job* job : new_jobs_completed) { + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for job completed event\n"); - handle.eventHandler(out, (void*)&job0); + handle.eventHandler(out, (void*) job); } } @@ -617,15 +635,17 @@ static void manageNewUnitActiveEvent(color_ostream& out) { multimap copy(handlers[EventType::UNIT_NEW_ACTIVE].begin(), handlers[EventType::UNIT_NEW_ACTIVE].end()); // iterate event handler callbacks - for (auto &key_value : copy) { - auto &handler = key_value.second; - for (df::unit* unit : df::global::world->units.active) { - int32_t id = unit->id; - if (!activeUnits.count(id)) { - activeUnits.emplace(id); - DEBUG(log,out).print("calling handler for new unit event\n"); - handler.eventHandler(out, (void*) intptr_t(id)); // intptr_t() avoids cast from smaller type warning - } + vector new_active_unit_ids; + for (df::unit* unit : df::global::world->units.active) { + if (!activeUnits.count(unit->id)) { + activeUnits.emplace(unit->id); + new_active_unit_ids.emplace_back(unit->id); + } + } + for (int32_t unit_id : new_active_unit_ids) { + for (auto &[_,handle] : copy) { + DEBUG(log,out).print("calling handler for new unit event\n"); + handle.eventHandler(out, (void*) intptr_t(unit_id)); // intptr_t() avoids cast from smaller type warning } } } @@ -635,6 +655,7 @@ static void manageUnitDeathEvent(color_ostream& out) { if (!df::global::world) return; multimap copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end()); + vector dead_unit_ids; for (auto unit : df::global::world->units.all) { //if ( unit->counters.death_id == -1 ) { if ( Units::isActive(unit) ) { @@ -644,13 +665,15 @@ static void manageUnitDeathEvent(color_ostream& out) { //dead: if dead since last check, trigger events if ( livingUnits.find(unit->id) == livingUnits.end() ) continue; + livingUnits.erase(unit->id); + dead_unit_ids.emplace_back(unit->id); + } - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; + for (int32_t unit_id : dead_unit_ids) { + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for unit death event\n"); - handle.eventHandler(out, (void*)intptr_t(unit->id)); + handle.eventHandler(out, (void*)intptr_t(unit_id)); } - livingUnits.erase(unit->id); } } @@ -666,6 +689,8 @@ static void manageItemCreationEvent(color_ostream& out) { multimap copy(handlers[EventType::ITEM_CREATED].begin(), handlers[EventType::ITEM_CREATED].end()); size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false); if ( index != 0 ) index--; + + std::vector created_items; for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) { df::item* item = df::global::world->items.all[a]; //already processed @@ -683,12 +708,17 @@ static void manageItemCreationEvent(color_ostream& out) { //spider webs don't count if ( item->flags.bits.spider_web ) continue; - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; + created_items.push_back(item->id); + } + + // handle all created items + for (int32_t item_id : created_items) { + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for item created event\n"); - handle.eventHandler(out, (void*)intptr_t(item->id)); + handle.eventHandler(out, (void*)intptr_t(item_id)); } } + nextItem = *df::global::item_next_id; } @@ -703,6 +733,7 @@ static void manageBuildingEvent(color_ostream& out) { **/ multimap copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end()); //first alert people about new buildings + vector new_buildings; for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) { int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a); if ( index == -1 ) { @@ -711,29 +742,32 @@ static void manageBuildingEvent(color_ostream& out) { continue; } buildings.insert(a); - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; - DEBUG(log,out).print("calling handler for created building event\n"); - handle.eventHandler(out, (void*)intptr_t(a)); - } + new_buildings.emplace_back(a); + } nextBuilding = *df::global::building_next_id; + std::for_each(new_buildings.begin(), new_buildings.end(), [&](int32_t building){ + for (auto &[_,handle] : copy) { + DEBUG(log,out).print("calling handler for created building event\n"); + handle.eventHandler(out, (void*)intptr_t(building)); + } + }); + //now alert people about destroyed buildings - for ( auto a = buildings.begin(); a != buildings.end(); ) { - int32_t id = *a; + for ( auto it = buildings.begin(); it != buildings.end(); ) { + int32_t id = *it; int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id); if ( index != -1 ) { - a++; + ++it; continue; } - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for destroyed building event\n"); handle.eventHandler(out, (void*)intptr_t(id)); } - a = buildings.erase(a); + it = buildings.erase(it); } } @@ -743,35 +777,41 @@ static void manageConstructionEvent(color_ostream& out) { //unordered_set constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end()); multimap copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end()); - // find & send construction removals - for (auto iter = constructions.begin(); iter != constructions.end();) { - auto &construction = *iter; - // if we can't find it, it was removed - if (df::construction::find(construction.pos) != nullptr) { - ++iter; - continue; + + unordered_set next_construction_set; // will be swapped with constructions + next_construction_set.reserve(constructions.bucket_count()); + vector new_constructions; + + // find new constructions - swapping found constructions over from constructions to next_construction_set + for (auto c : df::global::world->constructions) { + auto &construction = *c; + auto it = constructions.find(construction); + if (it == constructions.end()) { + // handle new construction event later + new_constructions.emplace_back(construction); } - // send construction to handlers, because it was removed - for (const auto &key_value: copy) { - EventHandler handle = key_value.second; + else { + constructions.erase(it); + } + next_construction_set.emplace(construction); + } + + constructions.swap(next_construction_set); + + // now next_construction_set contains all the constructions that were removed (not found in df::global::world->constructions) + for (auto& construction : next_construction_set) { + // handle construction removed event + for (const auto &[_,handle]: copy) { DEBUG(log,out).print("calling handler for destroyed construction event\n"); handle.eventHandler(out, (void*) &construction); } - // erase from existent constructions - iter = constructions.erase(iter); } - // find & send construction additions - for (auto c: df::global::world->constructions) { - auto &construction = *c; - // add construction to constructions, if it isn't already present - if (constructions.emplace(construction).second) { - // send construction to handlers, because it is new - for (const auto &key_value: copy) { - EventHandler handle = key_value.second; - DEBUG(log,out).print("calling handler for created construction event\n"); - handle.eventHandler(out, (void*) &construction); - } + // now handle all the new constructions + for (auto& construction : new_constructions) { + for (const auto &[_,handle]: copy) { + DEBUG(log,out).print("calling handler for created construction event\n"); + handle.eventHandler(out, (void*) &construction); } } } @@ -781,6 +821,8 @@ static void manageSyndromeEvent(color_ostream& out) { return; multimap copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end()); int32_t highestTime = -1; + + std::vector new_syndrome_data; for (auto unit : df::global::world->units.all) { /* @@ -795,14 +837,16 @@ static void manageSyndromeEvent(color_ostream& out) { if ( startTime <= lastSyndromeTime ) continue; - SyndromeData data(unit->id, b); - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; - DEBUG(log,out).print("calling handler for syndrome event\n"); - handle.eventHandler(out, (void*)&data); - } + new_syndrome_data.emplace_back(unit->id, b); + } + } + for (auto& data : new_syndrome_data) { + for (auto &[_,handle] : copy) { + DEBUG(log,out).print("calling handler for syndrome event\n"); + handle.eventHandler(out, (void*)&data); } } + lastSyndromeTime = highestTime; } @@ -815,8 +859,7 @@ static void manageInvasionEvent(color_ostream& out) { return; nextInvasion = df::global::plotinfo->invasions.next_id; - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for invasion event\n"); handle.eventHandler(out, (void*)intptr_t(nextInvasion-1)); } @@ -829,6 +872,11 @@ static void manageEquipmentEvent(color_ostream& out) { unordered_map itemIdToInventoryItem; unordered_set currentlyEquipped; + + vector equipment_pickups; + vector equipment_drops; + vector equipment_changes; + for (auto unit : df::global::world->units.all) { itemIdToInventoryItem.clear(); currentlyEquipped.clear(); @@ -856,12 +904,7 @@ static void manageEquipmentEvent(color_ostream& out) { auto c = itemIdToInventoryItem.find(dfitem_new->item->id); if ( c == itemIdToInventoryItem.end() ) { //new item equipped (probably just picked up) - InventoryChangeData data(unit->id, nullptr, &item_new); - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; - DEBUG(log,out).print("calling handler for new item equipped inventory change event\n"); - handle.eventHandler(out, (void*)&data); - } + equipment_pickups.emplace_back(unit->id, nullptr, &item_new); continue; } InventoryItem item_old = (*c).second; @@ -872,24 +915,14 @@ static void manageEquipmentEvent(color_ostream& out) { continue; //some sort of change in how it's equipped - InventoryChangeData data(unit->id, &item_old, &item_new); - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; - DEBUG(log,out).print("calling handler for inventory change event\n"); - handle.eventHandler(out, (void*)&data); - } + equipment_changes.emplace_back(unit->id, nullptr, &item_new); } //check for dropped items for (auto i : v) { if ( currentlyEquipped.find(i.itemId) != currentlyEquipped.end() ) continue; //TODO: delete ptr if invalid - InventoryChangeData data(unit->id, &i, nullptr); - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; - DEBUG(log,out).print("calling handler for dropped item inventory change event\n"); - handle.eventHandler(out, (void*)&data); - } + equipment_drops.emplace_back(unit->id, &i, nullptr); } if ( !hadEquipment ) delete temp; @@ -902,6 +935,26 @@ static void manageEquipmentEvent(color_ostream& out) { equipment.push_back(item); } } + + // now handle events + std::for_each(equipment_pickups.begin(), equipment_drops.end(), [&](InventoryChangeData& data) { + for (auto &[_, handle] : copy) { + DEBUG(log,out).print("calling handler for new item equipped inventory change event\n"); + handle.eventHandler(out, (void*) &data); + } + }); + std::for_each(equipment_drops.begin(), equipment_drops.end(), [&](InventoryChangeData& data) { + for (auto &[_, handle] : copy) { + DEBUG(log,out).print("calling handler for dropped item inventory change event\n"); + handle.eventHandler(out, (void*) &data); + } + }); + std::for_each(equipment_changes.begin(), equipment_changes.end(), [&](InventoryChangeData& data) { + for (auto &[_, handle] : copy) { + DEBUG(log,out).print("calling handler for inventory change event\n"); + handle.eventHandler(out, (void*) &data); + } + }); } static void updateReportToRelevantUnits() { @@ -939,8 +992,7 @@ static void manageReportEvent(color_ostream& out) { for ( ; idx < reports.size(); idx++ ) { df::report* report = reports[idx]; - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for report event\n"); handle.eventHandler(out, (void*)intptr_t(report->id)); } @@ -981,7 +1033,7 @@ static void manageUnitAttackEvent(color_ostream& out) { if ( strikeReports.empty() ) return; updateReportToRelevantUnits(); - map > alreadyDone; + unordered_set, hash_pair> already_done; for (int reportId : strikeReports) { df::report* report = df::report::find(reportId); if ( !report ) @@ -1011,27 +1063,25 @@ static void manageUnitAttackEvent(color_ostream& out) { UnitAttackData data{}; data.report_id = report->id; - if ( wound1 && !alreadyDone[unit1->id][unit2->id] ) { + if ( wound1 && already_done.find(std::make_pair(unit1->id,unit2->id)) == already_done.end() ) { data.attacker = unit1->id; data.defender = unit2->id; data.wound = wound1->id; - alreadyDone[data.attacker][data.defender] = 1; - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; + already_done.emplace(unit1->id, unit2->id); + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for unit1 attack unit attack event\n"); handle.eventHandler(out, (void*)&data); } } - if ( wound2 && !alreadyDone[unit1->id][unit2->id] ) { + if ( wound2 && already_done.find(std::make_pair(unit1->id,unit2->id)) == already_done.end() ) { data.attacker = unit2->id; data.defender = unit1->id; data.wound = wound2->id; - alreadyDone[data.attacker][data.defender] = 1; - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; + already_done.emplace(unit1->id, unit2->id); + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for unit2 attack unit attack event\n"); handle.eventHandler(out, (void*)&data); } @@ -1041,9 +1091,9 @@ static void manageUnitAttackEvent(color_ostream& out) { data.attacker = unit2->id; data.defender = unit1->id; data.wound = -1; - alreadyDone[data.attacker][data.defender] = 1; - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; + + already_done.emplace(unit1->id, unit2->id); + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for unit1 killed unit attack event\n"); handle.eventHandler(out, (void*)&data); } @@ -1053,9 +1103,9 @@ static void manageUnitAttackEvent(color_ostream& out) { data.attacker = unit1->id; data.defender = unit2->id; data.wound = -1; - alreadyDone[data.attacker][data.defender] = 1; - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; + + already_done.emplace(unit1->id, unit2->id); + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for unit2 killed unit attack event\n"); handle.eventHandler(out, (void*)&data); } @@ -1313,8 +1363,7 @@ static void manageInteractionEvent(color_ostream& out) { lastAttacker = df::unit::find(data.attacker); //lastDefender = df::unit::find(data.defender); //fire event - for (auto &key_value : copy) { - EventHandler &handle = key_value.second; + for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for interaction event\n"); handle.eventHandler(out, (void*)&data); } From 78bea2a55089d08173d955a750fd778cc214c4f8 Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Wed, 27 Sep 2023 23:34:19 +0100 Subject: [PATCH 7/9] fixed a typo brain fart moment --- library/modules/EventManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index ddc46942d..cb3f3e76f 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -937,7 +937,7 @@ static void manageEquipmentEvent(color_ostream& out) { } // now handle events - std::for_each(equipment_pickups.begin(), equipment_drops.end(), [&](InventoryChangeData& data) { + std::for_each(equipment_pickups.begin(), equipment_pickups.end(), [&](InventoryChangeData& data) { for (auto &[_, handle] : copy) { DEBUG(log,out).print("calling handler for new item equipped inventory change event\n"); handle.eventHandler(out, (void*) &data); From 70ff728fba68e18d1eb4f0a3146dbc9146d4eaaf Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Thu, 28 Sep 2023 16:19:32 +0100 Subject: [PATCH 8/9] manageJobStartedEvent handle moved to inner loop and iterates from previous in linked list in case it is removed. fix a bug with item change events and using invalidated stack pointer as item data. swapped order of destroyed and created building event calls, and a couple other improvements --- library/modules/EventManager.cpp | 98 ++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index cb3f3e76f..14fac42b3 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -466,18 +466,22 @@ static void manageJobStartedEvent(color_ostream& out) { vector new_started_jobs; // iterate event handler callbacks multimap copy(handlers[EventType::JOB_STARTED].begin(), handlers[EventType::JOB_STARTED].end()); - for (df::job_list_link* link = df::global::world->jobs.list.next; link != nullptr; link = link->next) { - df::job* job = link->item; + + for (df::job_list_link* link = &df::global::world->jobs.list; link->next != nullptr; link = link->next) { + df::job* job = link->next->item; + int32_t j_id = job->id; if (job && Job::getWorker(job) && !startedJobs.count(job->id)) { startedJobs.emplace(job->id); - new_started_jobs.emplace_back(job); + for (auto &[_,handle] : copy) { + // the jobs must have a worker to start + DEBUG(log,out).print("calling handler for job started event\n"); + handle.eventHandler(out, job); + } } - } - for (df::job* job : new_started_jobs) { - for (auto &[_,handle] : copy) { - // the jobs must have a worker to start - DEBUG(log,out).print("calling handler for job started event\n"); - handle.eventHandler(out, job); + if (link->next == nullptr || link->next->item->id != j_id) { + if ( Once::doOnce("EventManager jobstarted job removed") ) { + out.print("%s,%d: job %u removed from jobs linked list\n", __FILE__, __LINE__, j_id); + } } } } @@ -498,11 +502,11 @@ static void manageJobCompletedEvent(color_ostream& out) { int32_t tick1 = df::global::world->frame_counter; multimap copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end()); - map nowJobs; + unordered_map nowJobs; for ( df::job_list_link* link = &df::global::world->jobs.list; link != nullptr; link = link->next ) { if ( link->item == nullptr ) continue; - nowJobs[link->item->id] = link->item; + nowJobs.emplace(link->item->id, link->item); } #if 0 @@ -567,8 +571,6 @@ static void manageJobCompletedEvent(color_ostream& out) { } #endif - vector new_jobs_completed; - vector new_jobs_completed_repeats; for (auto &prevJob : prevJobs) { //if it happened within a tick, must have been cancelled by the user or a plugin: not completed if ( tick1 <= tick0 ) @@ -586,7 +588,10 @@ static void manageJobCompletedEvent(color_ostream& out) { continue; //still false positive if cancelled at EXACTLY the right time, but experiments show this doesn't happen - new_jobs_completed_repeats.emplace_back(&job0); + for (auto &[_,handle] : copy) { + DEBUG(log,out).print("calling handler for repeated job completed event\n"); + handle.eventHandler(out, (void*) &job0); + } continue; } @@ -595,37 +600,27 @@ static void manageJobCompletedEvent(color_ostream& out) { if ( job0.flags.bits.repeat || job0.completion_timer != 0 ) continue; - new_jobs_completed.emplace_back(&job0); - } - - for (df::job* job : new_jobs_completed_repeats) { - for (auto &[_,handle] : copy) { - DEBUG(log,out).print("calling handler for repeated job completed event\n"); - handle.eventHandler(out, (void*) job); - } - } - for (df::job* job : new_jobs_completed) { for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for job completed event\n"); - handle.eventHandler(out, (void*) job); + handle.eventHandler(out, (void*) &job0); } } //erase old jobs, copy over possibly altered jobs - for (auto &prevJob : prevJobs) { - Job::deleteJobStruct(prevJob.second, true); + for (auto &[_,prev_job] : prevJobs) { + Job::deleteJobStruct(prev_job, true); } prevJobs.clear(); //create new jobs - for (auto &nowJob : nowJobs) { + for (auto &[_,now_job] : nowJobs) { /*map::iterator i = prevJobs.find((*j).first); if ( i != prevJobs.end() ) { continue; }*/ - df::job* newJob = Job::cloneJobStruct(nowJob.second, true); - prevJobs[newJob->id] = newJob; + df::job* newJob = Job::cloneJobStruct(now_job, true); + prevJobs.emplace(newJob->id, newJob); } } @@ -662,6 +657,7 @@ static void manageUnitDeathEvent(color_ostream& out) { livingUnits.insert(unit->id); continue; } + if (!Units::isDead(unit)) continue; // for units that have left the map but aren't dead //dead: if dead since last check, trigger events if ( livingUnits.find(unit->id) == livingUnits.end() ) continue; @@ -747,13 +743,6 @@ static void manageBuildingEvent(color_ostream& out) { } nextBuilding = *df::global::building_next_id; - std::for_each(new_buildings.begin(), new_buildings.end(), [&](int32_t building){ - for (auto &[_,handle] : copy) { - DEBUG(log,out).print("calling handler for created building event\n"); - handle.eventHandler(out, (void*)intptr_t(building)); - } - }); - //now alert people about destroyed buildings for ( auto it = buildings.begin(); it != buildings.end(); ) { int32_t id = *it; @@ -769,6 +758,14 @@ static void manageBuildingEvent(color_ostream& out) { } it = buildings.erase(it); } + + //alert people about newly created buildings + std::for_each(new_buildings.begin(), new_buildings.end(), [&](int32_t building){ + for (auto &[_,handle] : copy) { + DEBUG(log,out).print("calling handler for created building event\n"); + handle.eventHandler(out, (void*)intptr_t(building)); + } + }); } static void manageConstructionEvent(color_ostream& out) { @@ -877,6 +874,13 @@ static void manageEquipmentEvent(color_ostream& out) { vector equipment_drops; vector equipment_changes; + + // This vector stores the pointers to newly created changed items + // needed as the stack allocated temporary (in the loop) is lost when we go to + // handle the event calls, so we move that data to the heap if its needed, + // and then once we are done we delete everything. + vector changed_items; + for (auto unit : df::global::world->units.all) { itemIdToInventoryItem.clear(); currentlyEquipped.clear(); @@ -904,25 +908,30 @@ static void manageEquipmentEvent(color_ostream& out) { auto c = itemIdToInventoryItem.find(dfitem_new->item->id); if ( c == itemIdToInventoryItem.end() ) { //new item equipped (probably just picked up) - equipment_pickups.emplace_back(unit->id, nullptr, &item_new); + changed_items.emplace_back(new InventoryItem(item_new)); + equipment_pickups.emplace_back(unit->id, nullptr, changed_items.back()); continue; } - InventoryItem item_old = (*c).second; + InventoryItem item_old = c->second; df::unit_inventory_item& item0 = item_old.item; df::unit_inventory_item& item1 = item_new.item; if ( item0.mode == item1.mode && item0.body_part_id == item1.body_part_id && item0.wound_id == item1.wound_id ) continue; //some sort of change in how it's equipped - - equipment_changes.emplace_back(unit->id, nullptr, &item_new); + changed_items.emplace_back(new InventoryItem(item_new)); + InventoryItem* item_new_ptr = changed_items.back(); + changed_items.emplace_back(new InventoryItem(item_old)); + InventoryItem* item_old_ptr = changed_items.back(); + equipment_changes.emplace_back(unit->id, item_old_ptr, item_new_ptr); } //check for dropped items for (auto i : v) { if ( currentlyEquipped.find(i.itemId) != currentlyEquipped.end() ) continue; //TODO: delete ptr if invalid - equipment_drops.emplace_back(unit->id, &i, nullptr); + changed_items.emplace_back(new InventoryItem(i)); + equipment_drops.emplace_back(unit->id, changed_items.back(), nullptr); } if ( !hadEquipment ) delete temp; @@ -955,6 +964,11 @@ static void manageEquipmentEvent(color_ostream& out) { handle.eventHandler(out, (void*) &data); } }); + + // clean up changed items list + std::for_each(changed_items.begin(), changed_items.end(), [](InventoryItem* p){ + delete p; + }); } static void updateReportToRelevantUnits() { From feaf0d6bb35d1e81bb7f70a40c4512913687a914 Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Fri, 29 Sep 2023 15:36:16 +0100 Subject: [PATCH 9/9] update changelog --- docs/changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index f819587de..315f37841 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -58,11 +58,14 @@ Template for new versions: ## Fixes - `autolabor`: now unconditionally re-enables vanilla work details when the fort or the plugin is unloaded - ``dfhack.TranslateName()``: fixed crash on certain invalid names, which affected `warn-starving` +- EventManager: Unit death event no longer misfires on units leaving the map ## Misc Improvements - `dig`: `digtype` command now has options to choose between designating only visible tiles or hidden tiles, as well as "auto" dig mode. Z-level options adjusted to allow choosing z-levels above, below, or the same as the cursor. - `hotkeys`: make the DFHack logo brighten on hover in ascii mode to indicate that it is clickable - `hotkeys`: use vertical bars instead of "!" symbols for the DFHack logo to make it easier to read +- EventManager: guarded against potential iterator invalidation if one of the event listeners modified the global data structure being iterated over +- EventManager: changed firing order of building created and building destroyed events to improve performance in the building location cache. ## Documentation