From 28b00d9f210fca0fa6810a0aa61cbeb3f87263c0 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 16 Sep 2023 01:24:47 +0300 Subject: [PATCH 01/28] Tweaked military formula to be more informative about strong warriors. Added options to sort and reset manager orders to the 'o' screen. --- plugins/lua/orders.lua | 37 +++++++++++- plugins/lua/sort.lua | 74 +++++++++++------------ plugins/orders.cpp | 131 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 39 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 7a87e529f..91fd3bef6 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -62,12 +62,24 @@ local function do_export() }:show() end +local function do_reset() + dfhack.run_command('orders', 'reset') +end + +local function do_sort_type() + dfhack.run_command('orders', 'sort_type') +end + +local function do_sort_mat() + dfhack.run_command('orders', 'sort_material') +end + OrdersOverlay = defclass(OrdersOverlay, overlay.OverlayWidget) OrdersOverlay.ATTRS{ default_pos={x=53,y=-6}, default_enabled=true, viewscreens='dwarfmode/Info/WORK_ORDERS/Default', - frame={w=30, h=4}, + frame={w=75, h=4}, } function OrdersOverlay:init() @@ -95,13 +107,34 @@ function OrdersOverlay:init() }, widgets.HotkeyLabel{ frame={t=0, l=15}, - label='sort', + label='sort by freq', key='CUSTOM_CTRL_O', auto_width=true, on_activate=do_sort, }, widgets.HotkeyLabel{ frame={t=1, l=15}, + label='sort by type', + key='CUSTOM_CTRL_T', + auto_width=true, + on_activate=do_sort_type, + }, + widgets.HotkeyLabel{ + frame={t=0, l=35}, + label='sort by mat', + key='CUSTOM_CTRL_T', + auto_width=true, + on_activate=do_sort_mat, + }, + widgets.HotkeyLabel{ + frame={t=1, l=35}, + label='reset', + key='CUSTOM_CTRL_R', + auto_width=true, + on_activate=do_reset, + }, + widgets.HotkeyLabel{ + frame={t=1, l=55}, label='clear', key='CUSTOM_CTRL_C', auto_width=true, 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 diff --git a/plugins/orders.cpp b/plugins/orders.cpp index e3c57d0f1..ceaf84196 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -64,6 +64,9 @@ static command_result orders_export_command(color_ostream & out, const std::stri static command_result orders_import_command(color_ostream & out, const std::string & name); static command_result orders_clear_command(color_ostream & out); static command_result orders_sort_command(color_ostream & out); +static command_result orders_sort_type_command(color_ostream & out); +static command_result orders_sort_material_command(color_ostream & out); +static command_result orders_reset_command(color_ostream & out); static command_result orders_command(color_ostream & out, std::vector & parameters) { @@ -111,6 +114,21 @@ static command_result orders_command(color_ostream & out, std::vectorreaction_name.empty(); + bool b_has_reaction_name = !b->reaction_name.empty(); + + if (a_has_reaction_name != b_has_reaction_name) + { + return a_has_reaction_name; + } + else if (a_has_reaction_name && b_has_reaction_name) + { + if (a->reaction_name != b->reaction_name) + { + return a->reaction_name < b->reaction_name; + } + } + + // Compare job_type + return enum_item_key(a->job_type) < enum_item_key(b->job_type); +} + +static command_result orders_sort_type_command(color_ostream & out) +{ + CoreSuspender suspend; + if (!std::is_sorted(world->manager_orders.begin(), + world->manager_orders.end(), + compare_type)) + { + std::stable_sort(world->manager_orders.begin(), + world->manager_orders.end(), + compare_type); + out << "Manager orders are sorted by job type." << std::endl; + } + + return CR_OK; +} + +static bool compare_material(df::manager_order *a, df::manager_order *b) +{ + // Goal: Sort orders to easily find them in the list and to see dupclicated orders. + // Sorting by materials + + // Determine if only one of the orders has mat_type + bool a_has_material = (a->mat_type != -1 || a->mat_index != -1); + bool b_has_material = (b->mat_type != -1 || b->mat_index != -1); + + if (a_has_material != b_has_material) + { + return a_has_material; + } + else if (a_has_material && b_has_material) + { + // Compare mat_type using MaterialInfo + if (MaterialInfo(a).getToken() != MaterialInfo(b).getToken()) + { + return MaterialInfo(a).getToken() < MaterialInfo(b).getToken(); + } + } + + // Determine if only one order has material_category + bool a_has_material_category = (a->material_category.whole != 0); + bool b_has_material_category = (b->material_category.whole != 0); + + if (a_has_material_category != b_has_material_category) + { + return a_has_material_category; + } + else if (a_has_material_category && b_has_material_category) + { + std::vector a_mats, b_mats; + bitfield_to_string(&a_mats, a->material_category); + bitfield_to_string(&b_mats, b->material_category); + + // Checking that mats are not empty just in case + if (!a_mats.empty() && !b_mats.empty() && a_mats[0] != b_mats[0]) + { + return a_mats[0] < b_mats[0]; + } + } + + // By default orders are equal + return false; +} +static command_result orders_sort_material_command(color_ostream & out) +{ + CoreSuspender suspend; + if (!std::is_sorted(world->manager_orders.begin(), + world->manager_orders.end(), + compare_material)) + { + std::stable_sort(world->manager_orders.begin(), + world->manager_orders.end(), + compare_material); + out << "Manager orders are sorted by material." << std::endl; + } + + return CR_OK; +} + +static command_result orders_reset_command(color_ostream & out) +{ + for (auto it : world->manager_orders) + { + it->status.bits.active = false; + it->status.bits.validated = false; + } + return CR_OK; +} \ No newline at end of file From 69be3be35963569f9edf56f0e75b2a6ef8407276 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 16 Sep 2023 14:18:06 +0300 Subject: [PATCH 02/28] Added sorting by job type and by material for manager orders. Added shortcuts to the manager menu for new functions. --- docs/plugins/orders.rst | 12 ++++++++++++ plugins/lua/orders.lua | 34 +++++++++++++++++----------------- plugins/orders.cpp | 29 ++++++++++++++++++++++++++--- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index 46a004081..4affa01b3 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -17,10 +17,22 @@ Usage manager orders. It will not clear the orders that already exist. ``orders clear`` Deletes all manager orders in the current embark. +``orders reset`` + Invalidates manager orders forcing material conditions recheck. ``orders sort`` Sorts current manager orders by repeat frequency so repeating orders don't prevent one-time orders from ever being completed. The sorting order is: one-time orders first, then yearly, seasonally, monthly, and finally, daily. +``orders sort_type`` + Sorts current manager orders by job type, making it easier to locate orders + that produce similar items. The sorting is done by reaction name, job type + and item subtype. If orders are equal by these fields the sorting falls back + to sort by frequency. +``orders sort_material`` + Sorts current manager orders by material, making it easier to locate orders + that produce items of the same material. The sorting is done by material type + and material category. If orders are equal by these fields the sorting falls back + to sort by job type. You can keep your orders automatically sorted by adding the following command to your ``dfhack-config/init/onMapLoad.init`` file:: diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 91fd3bef6..510e88b8c 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -79,7 +79,7 @@ OrdersOverlay.ATTRS{ default_pos={x=53,y=-6}, default_enabled=true, viewscreens='dwarfmode/Info/WORK_ORDERS/Default', - frame={w=75, h=4}, + frame={w=73, h=4}, } function OrdersOverlay:init() @@ -107,39 +107,39 @@ function OrdersOverlay:init() }, widgets.HotkeyLabel{ frame={t=0, l=15}, + label='reset', + key='CUSTOM_CTRL_R', + auto_width=true, + on_activate=do_reset, + }, + widgets.HotkeyLabel{ + frame={t=1, l=15}, + label='clear', + key='CUSTOM_CTRL_C', + auto_width=true, + on_activate=do_clear, + }, + widgets.HotkeyLabel{ + frame={t=0, l=30}, label='sort by freq', key='CUSTOM_CTRL_O', auto_width=true, on_activate=do_sort, }, widgets.HotkeyLabel{ - frame={t=1, l=15}, + frame={t=1, l=30}, label='sort by type', key='CUSTOM_CTRL_T', auto_width=true, on_activate=do_sort_type, }, widgets.HotkeyLabel{ - frame={t=0, l=35}, + frame={t=0, l=52}, label='sort by mat', key='CUSTOM_CTRL_T', auto_width=true, on_activate=do_sort_mat, }, - widgets.HotkeyLabel{ - frame={t=1, l=35}, - label='reset', - key='CUSTOM_CTRL_R', - auto_width=true, - on_activate=do_reset, - }, - widgets.HotkeyLabel{ - frame={t=1, l=55}, - label='clear', - key='CUSTOM_CTRL_C', - auto_width=true, - on_activate=do_clear, - }, }, } diff --git a/plugins/orders.cpp b/plugins/orders.cpp index ceaf84196..7fcefa745 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -1056,7 +1056,29 @@ static bool compare_type(df::manager_order *a, df::manager_order *b) } // Compare job_type - return enum_item_key(a->job_type) < enum_item_key(b->job_type); + if (enum_item_key(a->job_type) != enum_item_key(b->job_type)) + { + return enum_item_key(a->job_type) < enum_item_key(b->job_type); + } + + // Compare item subtype + bool a_has_item_subtype = (a->item_subtype != -1); + bool b_has_item_subtype = (b->item_subtype != -1); + + if (a_has_item_subtype != b_has_item_subtype) + { + return a_has_item_subtype; + } + else if (a_has_item_subtype && b_has_item_subtype) + { + if (a->item_subtype != b->item_subtype) + { + return a->item_subtype < b->item_subtype; + } + } + + // Fall back to freq sort + return compare_freq(a, b); } static command_result orders_sort_type_command(color_ostream & out) @@ -1118,9 +1140,10 @@ static bool compare_material(df::manager_order *a, df::manager_order *b) } } - // By default orders are equal - return false; + // Fall back to material sort + return compare_type(a, b); } + static command_result orders_sort_material_command(color_ostream & out) { CoreSuspender suspend; From ebb190e3569daa534fe8363b03fe7027d695b427 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 16 Sep 2023 11:37:11 +0000 Subject: [PATCH 03/28] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- plugins/orders.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/orders.cpp b/plugins/orders.cpp index 7fcefa745..12eb08640 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -1060,7 +1060,7 @@ static bool compare_type(df::manager_order *a, df::manager_order *b) { return enum_item_key(a->job_type) < enum_item_key(b->job_type); } - + // Compare item subtype bool a_has_item_subtype = (a->item_subtype != -1); bool b_has_item_subtype = (b->item_subtype != -1); @@ -1168,4 +1168,4 @@ static command_result orders_reset_command(color_ostream & out) it->status.bits.validated = false; } return CR_OK; -} \ No newline at end of file +} From 7d3786586acd087aceb5cd005e8e732fe2d8f792 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 18 Sep 2023 12:28:34 +0300 Subject: [PATCH 04/28] Reverted sort.lua. Changed orders sort by material keybing from T to M. --- plugins/lua/orders.lua | 2 +- plugins/lua/sort.lua | 74 +++++++++++++++++++++--------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 510e88b8c..a4f3c459f 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -136,7 +136,7 @@ function OrdersOverlay:init() widgets.HotkeyLabel{ frame={t=0, l=52}, label='sort by mat', - key='CUSTOM_CTRL_T', + key='CUSTOM_CTRL_M', auto_width=true, on_activate=do_sort_mat, }, diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index 778bc87b9..15d9ebabb 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, 2750000, 64, 52, 40, 28) + return get_rating(melee_skill_effectiveness(unit), 350000, 2350000, 78, 64, 49, 35) 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, 800000, 72, 52, 31, 11) + return get_rating(ranged_skill_effectiveness(unit), 0, 500000, 90, 62, 44, 27) 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 8826c27fa0f941ee50a866eae74ed969414f4113 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 18 Sep 2023 13:41:02 +0300 Subject: [PATCH 05/28] Changed 'reset' to recheck for clarity. --- docs/plugins/orders.rst | 2 +- plugins/lua/orders.lua | 8 ++++---- plugins/orders.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index 4affa01b3..351456e2e 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -17,7 +17,7 @@ Usage manager orders. It will not clear the orders that already exist. ``orders clear`` Deletes all manager orders in the current embark. -``orders reset`` +``orders recheck`` Invalidates manager orders forcing material conditions recheck. ``orders sort`` Sorts current manager orders by repeat frequency so repeating orders don't diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index a4f3c459f..b50c28694 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -62,8 +62,8 @@ local function do_export() }:show() end -local function do_reset() - dfhack.run_command('orders', 'reset') +local function do_recheck() + dfhack.run_command('orders', 'recheck') end local function do_sort_type() @@ -107,10 +107,10 @@ function OrdersOverlay:init() }, widgets.HotkeyLabel{ frame={t=0, l=15}, - label='reset', + label='recheck', key='CUSTOM_CTRL_R', auto_width=true, - on_activate=do_reset, + on_activate=do_recheck, }, widgets.HotkeyLabel{ frame={t=1, l=15}, diff --git a/plugins/orders.cpp b/plugins/orders.cpp index 12eb08640..4b93cff2d 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -66,7 +66,7 @@ static command_result orders_clear_command(color_ostream & out); static command_result orders_sort_command(color_ostream & out); static command_result orders_sort_type_command(color_ostream & out); static command_result orders_sort_material_command(color_ostream & out); -static command_result orders_reset_command(color_ostream & out); +static command_result orders_recheck_command(color_ostream & out); static command_result orders_command(color_ostream & out, std::vector & parameters) { @@ -124,9 +124,9 @@ static command_result orders_command(color_ostream & out, std::vectormanager_orders) { From 3fc289cefadcde4ebca15d08991d3ff1cef27a1c Mon Sep 17 00:00:00 2001 From: Mikhail Panov Date: Fri, 22 Sep 2023 16:26:23 +0300 Subject: [PATCH 06/28] Added single order recheck option to orders recheck. Sorting by material and job type consider frequency as a higher priority. --- plugins/orders.cpp | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/plugins/orders.cpp b/plugins/orders.cpp index 4b93cff2d..0bb89a0bc 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -67,6 +67,7 @@ static command_result orders_sort_command(color_ostream & out); static command_result orders_sort_type_command(color_ostream & out); static command_result orders_sort_material_command(color_ostream & out); static command_result orders_recheck_command(color_ostream & out); +static command_result orders_recheck_current_command(color_ostream & out); static command_result orders_command(color_ostream & out, std::vector & parameters) { @@ -129,6 +130,14 @@ static command_result orders_command(color_ostream & out, std::vectorfrequency != b->frequency) + { + return compare_freq(a, b); + } + // Determine if only one order has reaction_name bool a_has_reaction_name = !a->reaction_name.empty(); bool b_has_reaction_name = !b->reaction_name.empty(); @@ -1077,8 +1092,8 @@ static bool compare_type(df::manager_order *a, df::manager_order *b) } } - // Fall back to freq sort - return compare_freq(a, b); + // By default orders are the same + return false; } static command_result orders_sort_type_command(color_ostream & out) @@ -1102,6 +1117,12 @@ static bool compare_material(df::manager_order *a, df::manager_order *b) // Goal: Sort orders to easily find them in the list and to see dupclicated orders. // Sorting by materials + // Divide orders by frequency first + if (a->frequency != b->frequency) + { + return compare_freq(a, b); + } + // Determine if only one of the orders has mat_type bool a_has_material = (a->mat_type != -1 || a->mat_index != -1); bool b_has_material = (b->mat_type != -1 || b->mat_index != -1); @@ -1169,3 +1190,17 @@ static command_result orders_recheck_command(color_ostream & out) } return CR_OK; } + +static command_result orders_recheck_current_command(color_ostream & out) +{ + if (game->main_interface.info.work_orders.conditions.open) + { + game->main_interface.info.work_orders.conditions.wq.status.active = false; + } + else + { + out << COLOR_LIGHTRED << "Order conditions is not open" << std::endl; + return CR_FAILURE; + } + return CR_OK; +} From a500233e0baf78ab28b89150baf92babcaef6509 Mon Sep 17 00:00:00 2001 From: Mikhail Panov Date: Fri, 22 Sep 2023 18:29:45 +0300 Subject: [PATCH 07/28] Moved onde order recheck to orders plugin. --- plugins/lua/orders.lua | 84 ++++++++++++++++++++++++++++++++++++++++++ plugins/orders.cpp | 3 ++ 2 files changed, 87 insertions(+) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index b50c28694..1dd7e8160 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -190,7 +190,91 @@ function OrdersOverlay:render(dc) OrdersOverlay.super.render(self, dc) end +-- Resets the selected work order to the `Checking` state + +local function set_current_inactive() + local scrConditions = df.global.game.main_interface.info.work_orders.conditions + if scrConditions.open then + dfhack.run_command('orders', 'recheck', 'this') + else + qerror("Order conditions is not open") + end +end + +local function is_current_active() + local scrConditions = df.global.game.main_interface.info.work_orders.conditions + local order = scrConditions.wq + return order.status.active +end + +-- ------------------- +-- RecheckOverlay +-- + +local focusString = 'dwarfmode/Info/WORK_ORDERS/Conditions' + +RecheckOverlay = defclass(RecheckOverlay, overlay.OverlayWidget) +RecheckOverlay.ATTRS{ + default_pos={x=6,y=8}, + default_enabled=true, + viewscreens=focusString, + -- width is the sum of lengths of `[` + `Ctrl+A` + `: ` + button.label + `]` + frame={w=1 + 6 + 2 + 16 + 1, h=3}, +} + +local function areTabsInTwoRows() + -- get the tile above the order status icon + local pen = dfhack.screen.readTile(7, 7, false) + -- in graphics mode, `0` when one row, something else when two (`67` aka 'C' from "Creatures") + -- in ASCII mode, `32` aka ' ' when one row, something else when two (`196` aka '-' from tab frame's top) + return (pen.ch ~= 0 and pen.ch ~= 32) +end + +function RecheckOverlay:updateTextButtonFrame() + local twoRows = areTabsInTwoRows() + if (self._twoRows == twoRows) then return false end + + self._twoRows = twoRows + local frame = twoRows + and {b=0, l=0, r=0, h=1} + or {t=0, l=0, r=0, h=1} + self.subviews.button.frame = frame + + return true +end + +function RecheckOverlay:init() + self:addviews{ + widgets.TextButton{ + view_id = 'button', + -- frame={t=0, l=0, r=0, h=1}, -- is set in `updateTextButtonFrame()` + label='request re-check', + key='CUSTOM_CTRL_A', + on_activate=set_current_inactive, + enabled=is_current_active, + }, + } + + self:updateTextButtonFrame() +end + +function RecheckOverlay:onRenderBody(dc) + if (self.frame_rect.y1 == 7) then + -- only apply this logic if the overlay is on the same row as + -- originally thought: just above the order status icon + + if self:updateTextButtonFrame() then + self:updateLayout() + end + end + + RecheckOverlay.super.onRenderBody(self, dc) +end + +-- ------------------- + OVERLAY_WIDGETS = { + recheck=RecheckOverlay, overlay=OrdersOverlay, } diff --git a/plugins/orders.cpp b/plugins/orders.cpp index 0bb89a0bc..bfbe03480 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -10,6 +10,7 @@ #include "json/json.h" #include "df/building.h" +#include "df/gamest.h" #include "df/historical_figure.h" #include "df/itemdef_ammost.h" #include "df/itemdef_armorst.h" @@ -36,6 +37,8 @@ using namespace DFHack; using namespace df::enums; +using df::global::game; + DFHACK_PLUGIN("orders"); REQUIRE_GLOBAL(world); From a236722a752ebd665ae75ee26ff89fd1ef1a504e Mon Sep 17 00:00:00 2001 From: Mikhail Panov Date: Fri, 22 Sep 2023 18:37:14 +0300 Subject: [PATCH 08/28] Changed hotkeys + orders.cpp compilation error fix. --- plugins/lua/orders.lua | 4 ++-- plugins/orders.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 1dd7e8160..c84dfa5f7 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -129,14 +129,14 @@ function OrdersOverlay:init() widgets.HotkeyLabel{ frame={t=1, l=30}, label='sort by type', - key='CUSTOM_CTRL_T', + key='CUSTOM_CTRL_J', auto_width=true, on_activate=do_sort_type, }, widgets.HotkeyLabel{ frame={t=0, l=52}, label='sort by mat', - key='CUSTOM_CTRL_M', + key='CUSTOM_CTRL_T', auto_width=true, on_activate=do_sort_mat, }, diff --git a/plugins/orders.cpp b/plugins/orders.cpp index bfbe03480..77e8d5fa8 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -1198,7 +1198,7 @@ static command_result orders_recheck_current_command(color_ostream & out) { if (game->main_interface.info.work_orders.conditions.open) { - game->main_interface.info.work_orders.conditions.wq.status.active = false; + game->main_interface.info.work_orders.conditions.wq->status.bits.active = false; } else { From 8a424de7ffb90f868c3f67112637137fdeeab3d7 Mon Sep 17 00:00:00 2001 From: Mikhail Panov Date: Fri, 22 Sep 2023 19:04:49 +0300 Subject: [PATCH 09/28] Updated orders.rst --- docs/plugins/orders.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index 351456e2e..9bf220a06 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -17,8 +17,13 @@ Usage manager orders. It will not clear the orders that already exist. ``orders clear`` Deletes all manager orders in the current embark. -``orders recheck`` - Invalidates manager orders forcing material conditions recheck. +``orders recheck [this]`` + Sets the status to ``Checking`` (from ``Active``) of all work orders or one + currently viewed if 'this' option is passed. Work order conditions screen + should be open in this case. This makes the manager reevaluate its conditions. + This is especially useful for an order that had its conditions met when it + was started, but the requisite items have since disappeared and the workorder + is now generating job cancellation spam. ``orders sort`` Sorts current manager orders by repeat frequency so repeating orders don't prevent one-time orders from ever being completed. The sorting order is: From 303ce1fdc3005ccbef47fa415713243241e3b6bf Mon Sep 17 00:00:00 2001 From: donhth <> Date: Sat, 23 Sep 2023 15:54:46 -0400 Subject: [PATCH 10/28] re-enable tubefill. --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index b96606284..bb2794011 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -167,7 +167,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua) #dfhack_plugin(title-folder title-folder.cpp) #dfhack_plugin(trackstop trackstop.cpp) - #dfhack_plugin(tubefill tubefill.cpp) + dfhack_plugin(tubefill tubefill.cpp) #add_subdirectory(tweak) dfhack_plugin(workflow workflow.cpp LINK_LIBRARIES lua) dfhack_plugin(work-now work-now.cpp) From 437f96f3c03e7cecab8732f203a9c3e72d839aeb Mon Sep 17 00:00:00 2001 From: donhth <> Date: Sun, 24 Sep 2023 07:30:31 -0400 Subject: [PATCH 11/28] add changelog entry, remove unavailable tag for tubefill --- docs/changelog.txt | 2 ++ docs/plugins/tubefill.rst | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 044cb1ca4..eb54766f2 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -53,6 +53,8 @@ Template for new versions: ## New Tools +- `tubefill`: (reinstated) replenishes mined-out adamantine + ## New Features ## Fixes diff --git a/docs/plugins/tubefill.rst b/docs/plugins/tubefill.rst index a8684c765..80282f6d9 100644 --- a/docs/plugins/tubefill.rst +++ b/docs/plugins/tubefill.rst @@ -3,7 +3,7 @@ tubefill .. dfhack-tool:: :summary: Replenishes mined-out adamantine. - :tags: unavailable fort armok map + :tags: fort armok map Veins that were originally hollow will be left alone. From de5b88c8c76541bff29831fc336bcc3dbd46101e Mon Sep 17 00:00:00 2001 From: Mikhail Panov Date: Wed, 27 Sep 2023 22:47:05 +0300 Subject: [PATCH 12/28] Added info about workorder-recheck removal to Removed.rst. --- docs/about/Removed.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/about/Removed.rst b/docs/about/Removed.rst index 4b61c951e..4d8f8fc6e 100644 --- a/docs/about/Removed.rst +++ b/docs/about/Removed.rst @@ -10,6 +10,13 @@ work (e.g. links from the `changelog`). :local: :depth: 1 +.. _workorder-recheck: + +workorder-recheck +================= +Tool to set 'Checking' status of the selected work order forcing manager to reevaluate its +conditions. Merged into `orders`. + .. _autohauler: autohauler From 32a2d9f9b5c94e3651943542d84c89a82ff4b77f Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 18 Sep 2023 12:49:36 +0300 Subject: [PATCH 13/28] 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 14/28] 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 15/28] 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 16/28] 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 17/28] 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 18/28] 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 19/28] 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 20/28] 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 51173fb9fea6002c8777ea3ceb30f5a4b674080e Mon Sep 17 00:00:00 2001 From: Mikhail Panov Date: Fri, 29 Sep 2023 17:34:48 +0300 Subject: [PATCH 21/28] Removed material and job type sortings. --- docs/plugins/orders.rst | 10 --- plugins/lua/orders.lua | 26 ++----- plugins/orders.cpp | 150 ---------------------------------------- 3 files changed, 6 insertions(+), 180 deletions(-) diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index 9bf220a06..d8d3fe585 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -28,16 +28,6 @@ Usage Sorts current manager orders by repeat frequency so repeating orders don't prevent one-time orders from ever being completed. The sorting order is: one-time orders first, then yearly, seasonally, monthly, and finally, daily. -``orders sort_type`` - Sorts current manager orders by job type, making it easier to locate orders - that produce similar items. The sorting is done by reaction name, job type - and item subtype. If orders are equal by these fields the sorting falls back - to sort by frequency. -``orders sort_material`` - Sorts current manager orders by material, making it easier to locate orders - that produce items of the same material. The sorting is done by material type - and material category. If orders are equal by these fields the sorting falls back - to sort by job type. You can keep your orders automatically sorted by adding the following command to your ``dfhack-config/init/onMapLoad.init`` file:: diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index c84dfa5f7..1a3208336 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -79,7 +79,7 @@ OrdersOverlay.ATTRS{ default_pos={x=53,y=-6}, default_enabled=true, viewscreens='dwarfmode/Info/WORK_ORDERS/Default', - frame={w=73, h=4}, + frame={w=46, h=4}, } function OrdersOverlay:init() @@ -114,31 +114,17 @@ function OrdersOverlay:init() }, widgets.HotkeyLabel{ frame={t=1, l=15}, - label='clear', - key='CUSTOM_CTRL_C', - auto_width=true, - on_activate=do_clear, - }, - widgets.HotkeyLabel{ - frame={t=0, l=30}, - label='sort by freq', + label='sort', key='CUSTOM_CTRL_O', auto_width=true, on_activate=do_sort, }, widgets.HotkeyLabel{ - frame={t=1, l=30}, - label='sort by type', - key='CUSTOM_CTRL_J', - auto_width=true, - on_activate=do_sort_type, - }, - widgets.HotkeyLabel{ - frame={t=0, l=52}, - label='sort by mat', - key='CUSTOM_CTRL_T', + frame={t=0, l=31}, + label='clear', + key='CUSTOM_CTRL_C', auto_width=true, - on_activate=do_sort_mat, + on_activate=do_clear, }, }, } diff --git a/plugins/orders.cpp b/plugins/orders.cpp index 77e8d5fa8..a84f14172 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -67,8 +67,6 @@ static command_result orders_export_command(color_ostream & out, const std::stri static command_result orders_import_command(color_ostream & out, const std::string & name); static command_result orders_clear_command(color_ostream & out); static command_result orders_sort_command(color_ostream & out); -static command_result orders_sort_type_command(color_ostream & out); -static command_result orders_sort_material_command(color_ostream & out); static command_result orders_recheck_command(color_ostream & out); static command_result orders_recheck_current_command(color_ostream & out); @@ -118,16 +116,6 @@ static command_result orders_command(color_ostream & out, std::vectorfrequency != b->frequency) - { - return compare_freq(a, b); - } - - // Determine if only one order has reaction_name - bool a_has_reaction_name = !a->reaction_name.empty(); - bool b_has_reaction_name = !b->reaction_name.empty(); - - if (a_has_reaction_name != b_has_reaction_name) - { - return a_has_reaction_name; - } - else if (a_has_reaction_name && b_has_reaction_name) - { - if (a->reaction_name != b->reaction_name) - { - return a->reaction_name < b->reaction_name; - } - } - - // Compare job_type - if (enum_item_key(a->job_type) != enum_item_key(b->job_type)) - { - return enum_item_key(a->job_type) < enum_item_key(b->job_type); - } - - // Compare item subtype - bool a_has_item_subtype = (a->item_subtype != -1); - bool b_has_item_subtype = (b->item_subtype != -1); - - if (a_has_item_subtype != b_has_item_subtype) - { - return a_has_item_subtype; - } - else if (a_has_item_subtype && b_has_item_subtype) - { - if (a->item_subtype != b->item_subtype) - { - return a->item_subtype < b->item_subtype; - } - } - - // By default orders are the same - return false; -} - -static command_result orders_sort_type_command(color_ostream & out) -{ - CoreSuspender suspend; - if (!std::is_sorted(world->manager_orders.begin(), - world->manager_orders.end(), - compare_type)) - { - std::stable_sort(world->manager_orders.begin(), - world->manager_orders.end(), - compare_type); - out << "Manager orders are sorted by job type." << std::endl; - } - - return CR_OK; -} - -static bool compare_material(df::manager_order *a, df::manager_order *b) -{ - // Goal: Sort orders to easily find them in the list and to see dupclicated orders. - // Sorting by materials - - // Divide orders by frequency first - if (a->frequency != b->frequency) - { - return compare_freq(a, b); - } - - // Determine if only one of the orders has mat_type - bool a_has_material = (a->mat_type != -1 || a->mat_index != -1); - bool b_has_material = (b->mat_type != -1 || b->mat_index != -1); - - if (a_has_material != b_has_material) - { - return a_has_material; - } - else if (a_has_material && b_has_material) - { - // Compare mat_type using MaterialInfo - if (MaterialInfo(a).getToken() != MaterialInfo(b).getToken()) - { - return MaterialInfo(a).getToken() < MaterialInfo(b).getToken(); - } - } - - // Determine if only one order has material_category - bool a_has_material_category = (a->material_category.whole != 0); - bool b_has_material_category = (b->material_category.whole != 0); - - if (a_has_material_category != b_has_material_category) - { - return a_has_material_category; - } - else if (a_has_material_category && b_has_material_category) - { - std::vector a_mats, b_mats; - bitfield_to_string(&a_mats, a->material_category); - bitfield_to_string(&b_mats, b->material_category); - - // Checking that mats are not empty just in case - if (!a_mats.empty() && !b_mats.empty() && a_mats[0] != b_mats[0]) - { - return a_mats[0] < b_mats[0]; - } - } - - // Fall back to material sort - return compare_type(a, b); -} - -static command_result orders_sort_material_command(color_ostream & out) -{ - CoreSuspender suspend; - if (!std::is_sorted(world->manager_orders.begin(), - world->manager_orders.end(), - compare_material)) - { - std::stable_sort(world->manager_orders.begin(), - world->manager_orders.end(), - compare_material); - out << "Manager orders are sorted by material." << std::endl; - } - - return CR_OK; -} - static command_result orders_recheck_command(color_ostream & out) { for (auto it : world->manager_orders) From feaf0d6bb35d1e81bb7f70a40c4512913687a914 Mon Sep 17 00:00:00 2001 From: Najeeb Al-Shabibi Date: Fri, 29 Sep 2023 15:36:16 +0100 Subject: [PATCH 22/28] 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 From e868612985ca2608409e042dbb8d33f88b76c732 Mon Sep 17 00:00:00 2001 From: Mikhail Panov Date: Fri, 29 Sep 2023 17:41:18 +0300 Subject: [PATCH 23/28] Rephrased description in rst files. --- docs/about/Removed.rst | 4 ++-- docs/plugins/orders.rst | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/about/Removed.rst b/docs/about/Removed.rst index 4d8f8fc6e..86d7e0f75 100644 --- a/docs/about/Removed.rst +++ b/docs/about/Removed.rst @@ -14,8 +14,8 @@ work (e.g. links from the `changelog`). workorder-recheck ================= -Tool to set 'Checking' status of the selected work order forcing manager to reevaluate its -conditions. Merged into `orders`. +Tool to set 'Checking' status of the selected work order, allowing conditions to be +reevaluated. Merged into `orders`. .. _autohauler: diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index d8d3fe585..acb25fe99 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -18,9 +18,9 @@ Usage ``orders clear`` Deletes all manager orders in the current embark. ``orders recheck [this]`` - Sets the status to ``Checking`` (from ``Active``) of all work orders or one - currently viewed if 'this' option is passed. Work order conditions screen - should be open in this case. This makes the manager reevaluate its conditions. + Sets the status to ``Checking`` (from ``Active``) for all work orders. if the + "this" option is passed, only sets the status for the workorder whose condition + details page is open. This makes the manager reevaluate its conditions. This is especially useful for an order that had its conditions met when it was started, but the requisite items have since disappeared and the workorder is now generating job cancellation spam. From 7b8721965753f19cf07c391f960a046b8048a1b5 Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 29 Sep 2023 09:40:52 -0700 Subject: [PATCH 24/28] Update plugins/lua/orders.lua --- plugins/lua/orders.lua | 8 -------- 1 file changed, 8 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 1a3208336..df8710739 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -66,14 +66,6 @@ local function do_recheck() dfhack.run_command('orders', 'recheck') end -local function do_sort_type() - dfhack.run_command('orders', 'sort_type') -end - -local function do_sort_mat() - dfhack.run_command('orders', 'sort_material') -end - OrdersOverlay = defclass(OrdersOverlay, overlay.OverlayWidget) OrdersOverlay.ATTRS{ default_pos={x=53,y=-6}, From a52386474ae7b8133ec5b9761f71be13152d2b39 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 29 Sep 2023 16:42:47 +0000 Subject: [PATCH 25/28] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 76f5a2b10..73eac0413 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 76f5a2b101f0817857c9dfa8dd3d9f076137aafc +Subproject commit 73eac04134b3e7d89b7be1bc355dddab38c5656d From c6ad1dd24d86439a89d1c9840a733bc56fdcb575 Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 29 Sep 2023 09:58:33 -0700 Subject: [PATCH 26/28] Update tubefill.rst --- docs/plugins/tubefill.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/plugins/tubefill.rst b/docs/plugins/tubefill.rst index 80282f6d9..ff668f8df 100644 --- a/docs/plugins/tubefill.rst +++ b/docs/plugins/tubefill.rst @@ -5,7 +5,11 @@ tubefill :summary: Replenishes mined-out adamantine. :tags: fort armok map -Veins that were originally hollow will be left alone. +This tool replaces mined-out tiles of adamantine spires with fresh, undug +adamantine walls, ready to be re-harvested. Empty tiles within the spire that +used to contain special gemstones, obsidian, water, or magma will also be +replaced with fresh adamantine. Adamantine spires that were originally hollow +will be left hollow. See below for more details. Usage ----- From ffd166a41fc01ae76ad8fbc890a7c7abb00e483d Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 29 Sep 2023 18:26:00 +0000 Subject: [PATCH 27/28] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 9312906c5..bcfb0fb3b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 9312906c5a33feea50c0c32cd683ad22fb87822c +Subproject commit bcfb0fb3ba82939f353bda9908d39877661d9c81 From 56e782d9bb10bb9c02376f5e37edb25763b88721 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 30 Sep 2023 04:47:59 +0000 Subject: [PATCH 28/28] 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 bcfb0fb3b..dc1d5c80f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit bcfb0fb3ba82939f353bda9908d39877661d9c81 +Subproject commit dc1d5c80f03a68f6d58ff4f8dbbdf6855a7e9781 diff --git a/scripts b/scripts index 73eac0413..57c859394 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 73eac04134b3e7d89b7be1bc355dddab38c5656d +Subproject commit 57c859394936f9183d95426f75a5d3e9b305dd2a