From a1d68286cd351896c48dc0588d9c07ae05b1fdea Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 24 Aug 2023 16:26:09 +0300 Subject: [PATCH 01/11] Added melee rating, ranged rating, mental stability rating and stress to sorting options on military screen in sort.lua. Added info about new sorting methods to sort.rst. Added name to Authors.rst. --- docs/about/Authors.rst | 1 + docs/plugins/sort.rst | 20 +++- plugins/lua/sort.lua | 260 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 278 insertions(+), 3 deletions(-) diff --git a/docs/about/Authors.rst b/docs/about/Authors.rst index cf74c6412..e6d24f6e5 100644 --- a/docs/about/Authors.rst +++ b/docs/about/Authors.rst @@ -129,6 +129,7 @@ Michael Crouch creidieki Michon van Dooren MaienM miffedmap miffedmap Mike Stewart thewonderidiot +Mikhail Panov Halifay Mikko Juola Noeda Adeon Milo Christiansen milochristiansen MithrilTuxedo MithrilTuxedo diff --git a/docs/plugins/sort.rst b/docs/plugins/sort.rst index 4ddef85cd..e8e280f61 100644 --- a/docs/plugins/sort.rst +++ b/docs/plugins/sort.rst @@ -14,7 +14,7 @@ Searching and sorting functionality is provided by `overlay` widgets, and widget Squad assignment overlay ------------------------ -The squad assignment screen can be sorted by name, by migrant wave, or by various military-related skills. +The squad assignment screen can be sorted by name, by migrant wave, by stress, by various military-related skills or by specific potential ratings. If sorted by "any melee", then the citizen is sorted according to the highest skill they have in axes, short swords, maces, warhammers, spears, or general @@ -26,6 +26,24 @@ skill they have in crossbows or general ranged combat. If sorted by "leadership", then the citizen is sorted according to the highest skill they have in leader, teacher, or military tactics. +If sorting is done by "melee potential" citizens are arranged based on their +melee potential rating. This rating is a heuristic measure that takes into +account genetic predispositions in physical and mental attributes, as +well as body size. The goal is to assign a higher value to dwarves (and other +humanoid creatures) that are expected to be more effective in melee combat +against opponents of similar skill levels. + +If sorting is done by "ranged potential" citizens are arranged based on their +ranged potential rating. This rating is a heuristic measure that takes into +account genetic predispositions in physical and mental attributes. +The goal is to assign a higher value to dwarves (and other humanoid creatures) +that are expected to be more effective in ranged combat. + +If sorting is done by "mental stability" citizens are arranged based on their +mental stability rating. This rating is a measure that takes into account +facets and values of an individual and correlates to better stress values. +It is designed to be higher for more stress-resistant citizens. + You can search for a dwarf by name by typing in the Search field. You can also type in the name of any job skill (military-related or not) and dwarves with any experience in that skill will be shown. For example, to only see citizens diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index 743820103..8cb1e7127 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -5,6 +5,8 @@ local overlay = require('plugins.overlay') local utils = require('utils') local widgets = require('gui.widgets') +local setbelief = reqscript("modtools/set-belief") + local CH_UP = string.char(30) local CH_DN = string.char(31) @@ -73,6 +75,28 @@ local function sort_by_migrant_wave_asc(unit_id_1, unit_id_2) return utils.compare(unit1.id, unit2.id) end +local function sort_by_stress_desc(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local happiness1 = unit1.status.current_soul.personality.stress + local happiness2 = unit2.status.current_soul.personality.stress + return utils.compare(happiness2, happiness1) +end + +local function sort_by_stress_asc(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local happiness1 = unit1.status.current_soul.personality.stress + local happiness2 = unit2.status.current_soul.personality.stress + return utils.compare(happiness1, happiness2) +end + local function get_skill(unit_id, skill, unit) unit = unit or df.unit.find(unit_id) return unit and @@ -181,6 +205,181 @@ local function make_sort_by_skill_asc(sort_skill) end end +-- Heuristic rating that is bigger for more potent dwarves in long run melee military training +-- Wounds are not considered! +local function melee_potential(unit) + -- Physical attributes + local strength = unit.body.physical_attrs.STRENGTH.max_value + local agility = unit.body.physical_attrs.AGILITY.max_value + local toughness = unit.body.physical_attrs.TOUGHNESS.max_value + local endurance = unit.body.physical_attrs.ENDURANCE.max_value + local recuperation = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.RECUPERATION) + local diseaseResistance = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.DISEASE_RESISTANCE) + local bodySize = unit.body.size_info.size_base + + -- Mental attributes + local focus = unit.status.current_soul.mental_attrs.FOCUS.max_value + local willpower = unit.status.current_soul.mental_attrs.WILLPOWER.max_value + local spatialSense = unit.status.current_soul.mental_attrs.SPATIAL_SENSE.max_value + local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value + + -- strength/bodysize is a dirty approximation of momentum formula + local rating = 10000*strength/bodySize + strength*4 + agility*3 + toughness + + endurance + recuperation/3 + diseaseResistance/3 + + kinestheticSense*3 + willpower/2 + spatialSense/2 + focus/4 + return rating +end + +local function sort_by_melee_potential_desc(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local rating1 = melee_potential(unit1) + local rating2 = melee_potential(unit2) + if rating1 == rating2 then + return sort_by_name_desc(unit_id_1, unit_id_2) + end + return utils.compare(rating2, rating1) +end + +local function sort_by_melee_potential_asc(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local rating1 = melee_potential(unit1) + local rating2 = melee_potential(unit2) + if rating1 == rating2 then + return sort_by_name_asc(unit_id_1, unit_id_2) + end + return utils.compare(rating1, rating2) +end + +-- Heuristic rating that is bigger for more potent dwarves in long run ranged military training +-- Wounds are not considered! +local function ranged_potential(unit) + -- Physical attributes + local strength = unit.body.physical_attrs.STRENGTH.max_value + local agility = unit.body.physical_attrs.AGILITY.max_value + local toughness = unit.body.physical_attrs.TOUGHNESS.max_value + local endurance = unit.body.physical_attrs.ENDURANCE.max_value + local recuperation = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.RECUPERATION) + local diseaseResistance = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.DISEASE_RESISTANCE) + local bodySize = unit.body.size_info.size_base + + -- Mental attributes + local focus = unit.status.current_soul.mental_attrs.FOCUS.max_value + local willpower = unit.status.current_soul.mental_attrs.WILLPOWER.max_value + local spatialSense = unit.status.current_soul.mental_attrs.SPATIAL_SENSE.max_value + local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value + + local rating = strength + agility*4 + toughness + endurance + recuperation/2 + + diseaseResistance/2 - bodySize/2 + + kinestheticSense + willpower + spatialSense*3 + focus + return rating +end + +local function sort_by_ranged_potential_desc(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local rating1 = ranged_potential(unit1) + local rating2 = ranged_potential(unit2) + if rating1 == rating2 then + return sort_by_name_desc(unit_id_1, unit_id_2) + end + return utils.compare(rating2, rating1) +end + +local function sort_by_ranged_potential_asc(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local rating1 = ranged_potential(unit1) + local rating2 = ranged_potential(unit2) + if rating1 == rating2 then + return sort_by_name_asc(unit_id_1, unit_id_2) + end + return utils.compare(rating1, rating2) +end + +-- Statistical rating that is bigger for dwarves that are mentally stable +local function 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) + + return rating +end + + +local function sort_by_mental_stability_desc(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local rating1 = mental_stability(unit1) + local rating2 = mental_stability(unit2) + if rating1 == rating2 then + return sort_by_name_desc(unit_id_1, unit_id_2) + end + return utils.compare(rating2, rating1) +end + +local function sort_by_mental_stability_asc(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local rating1 = mental_stability(unit1) + local rating2 = mental_stability(unit2) + if rating1 == rating2 then + return sort_by_name_asc(unit_id_1, unit_id_2) + end + return utils.compare(rating1, rating2) +end + local SORT_FNS = { sort_by_any_melee_desc=make_sort_by_skill_list_desc(MELEE_WEAPON_SKILLS), sort_by_any_melee_asc=make_sort_by_skill_list_asc(MELEE_WEAPON_SKILLS), @@ -211,7 +410,7 @@ SquadAssignmentOverlay.ATTRS{ default_pos={x=-33, y=40}, default_enabled=true, viewscreens='dwarfmode/UnitSelector/SQUAD_FILL_POSITION', - frame={w=63, h=7}, + frame={w=74, h=9}, frame_style=gui.FRAME_PANEL, frame_background=gui.CLEAR_PEN, } @@ -236,6 +435,8 @@ function SquadAssignmentOverlay:init() {label='name'..CH_UP, value=sort_by_name_asc, pen=COLOR_YELLOW}, {label='migrant wave'..CH_DN, value=sort_by_migrant_wave_desc, pen=COLOR_GREEN}, {label='migrant wave'..CH_UP, value=sort_by_migrant_wave_asc, pen=COLOR_YELLOW}, + {label='stress'..CH_DN, value=sort_by_stress_desc, pen=COLOR_GREEN}, + {label='stress'..CH_UP, value=sort_by_stress_asc, pen=COLOR_YELLOW}, {label='axe skill'..CH_DN, value=SORT_FNS.sort_by_axe_desc, pen=COLOR_GREEN}, {label='axe skill'..CH_UP, value=SORT_FNS.sort_by_axe_asc, pen=COLOR_YELLOW}, {label='sword skill'..CH_DN, value=SORT_FNS.sort_by_sword_desc, pen=COLOR_GREEN}, @@ -248,6 +449,12 @@ function SquadAssignmentOverlay:init() {label='spear skill'..CH_UP, value=SORT_FNS.sort_by_spear_asc, pen=COLOR_YELLOW}, {label='crossbow skill'..CH_DN, value=SORT_FNS.sort_by_crossbow_desc, pen=COLOR_GREEN}, {label='crossbow skill'..CH_UP, value=SORT_FNS.sort_by_crossbow_asc, pen=COLOR_YELLOW}, + {label='melee potential'..CH_DN, value=sort_by_melee_potential_desc, pen=COLOR_GREEN}, + {label='melee potential'..CH_UP, value=sort_by_melee_potential_asc, pen=COLOR_YELLOW}, + {label='ranged potential'..CH_DN, value=sort_by_ranged_potential_desc, pen=COLOR_GREEN}, + {label='ranged potential'..CH_UP, value=sort_by_ranged_potential_asc, pen=COLOR_YELLOW}, + {label='mental stability'..CH_DN, value=sort_by_mental_stability_desc, pen=COLOR_GREEN}, + {label='mental stability'..CH_UP, value=sort_by_mental_stability_asc, pen=COLOR_YELLOW}, }, initial_option=SORT_FNS.sort_by_any_melee_desc, on_change=self:callback('refresh_list', 'sort'), @@ -309,7 +516,7 @@ function SquadAssignmentOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='sort_migrant_wave', - frame={t=0, l=48, w=13}, + frame={t=0, l=49, w=13}, options={ {label='migrant wave', value=sort_noop}, {label='migrant wave'..CH_DN, value=sort_by_migrant_wave_desc, pen=COLOR_GREEN}, @@ -318,6 +525,17 @@ function SquadAssignmentOverlay:init() option_gap=0, on_change=self:callback('refresh_list', 'sort_migrant_wave'), }, + widgets.CycleHotkeyLabel{ + view_id='sort_stress', + frame={t=0, l=65, w=7}, + options={ + {label='stress', value=sort_noop}, + {label='stress'..CH_DN, value=sort_by_stress_desc, pen=COLOR_GREEN}, + {label='stress'..CH_UP, value=sort_by_stress_asc, pen=COLOR_YELLOW}, + }, + option_gap=0, + on_change=self:callback('refresh_list', 'sort_stress'), + }, widgets.CycleHotkeyLabel{ view_id='sort_axe', frame={t=2, l=2, w=4}, @@ -384,6 +602,39 @@ function SquadAssignmentOverlay:init() option_gap=0, on_change=self:callback('refresh_list', 'sort_crossbow'), }, + widgets.CycleHotkeyLabel{ + view_id='sort_melee_potential', + frame={t=4, l=2, w=16}, + options={ + {label='melee potential', value=sort_noop}, + {label='melee potential'..CH_DN, value=sort_by_melee_potential_desc, pen=COLOR_GREEN}, + {label='melee potential'..CH_UP, value=sort_by_melee_potential_asc, pen=COLOR_YELLOW}, + }, + option_gap=0, + on_change=self:callback('refresh_list', 'sort_melee_potential'), + }, + widgets.CycleHotkeyLabel{ + view_id='sort_ranged_potential', + frame={t=4, l=21, w=17}, + options={ + {label='ranged potential', value=sort_noop}, + {label='ranged potential'..CH_DN, value=sort_by_ranged_potential_desc, pen=COLOR_GREEN}, + {label='ranged potential'..CH_UP, value=sort_by_ranged_potential_asc, pen=COLOR_YELLOW}, + }, + option_gap=0, + on_change=self:callback('refresh_list', 'sort_ranged_potential'), + }, + widgets.CycleHotkeyLabel{ + view_id='sort_mental_stability', + frame={t=4, l=41, w=17}, + options={ + {label='mental stability', value=sort_noop}, + {label='mental stability'..CH_DN, value=sort_by_mental_stability_desc, pen=COLOR_GREEN}, + {label='mental stability'..CH_UP, value=sort_by_mental_stability_asc, pen=COLOR_YELLOW}, + }, + option_gap=0, + on_change=self:callback('refresh_list', 'sort_mental_stability'), + }, } }, } @@ -454,12 +705,16 @@ local SORT_WIDGET_NAMES = { 'sort_leadership', 'sort_name', 'sort_migrant_wave', + 'sort_stress', 'sort_axe', 'sort_sword', 'sort_mace', 'sort_hammer', 'sort_spear', 'sort_crossbow', + 'sort_melee_potential', + 'sort_ranged_potential', + 'sort_mental_stability', } function SquadAssignmentOverlay:refresh_list(sort_widget, sort_fn) @@ -555,3 +810,4 @@ make_sort_order = utils.make_sort_order ]] return _ENV + From aef60649fe130961ed4b6446ce539f165c277bf9 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 24 Aug 2023 17:18:46 +0300 Subject: [PATCH 02/11] Trim trailing whitespace. --- plugins/lua/sort.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index 8cb1e7127..94f4098ce 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -810,4 +810,3 @@ make_sort_order = utils.make_sort_order ]] return _ENV - From 30488dc3104240cb56f22223cd14efba31bdf8f6 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 24 Aug 2023 18:47:42 +0300 Subject: [PATCH 03/11] Trailing space again. My bad. --- plugins/lua/sort.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index 94f4098ce..ae6ea4eeb 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -809,4 +809,4 @@ end make_sort_order = utils.make_sort_order ]] -return _ENV +return _ENV \ No newline at end of file From 771d7f6c6c9d1e76cb153fc3579ab387b5c5ec8a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 15:49:53 +0000 Subject: [PATCH 04/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/plugins/sort.rst | 24 ++++++++++++------------ plugins/lua/sort.lua | 26 +++++++++++++------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/plugins/sort.rst b/docs/plugins/sort.rst index e8e280f61..84f7359e4 100644 --- a/docs/plugins/sort.rst +++ b/docs/plugins/sort.rst @@ -26,22 +26,22 @@ skill they have in crossbows or general ranged combat. If sorted by "leadership", then the citizen is sorted according to the highest skill they have in leader, teacher, or military tactics. -If sorting is done by "melee potential" citizens are arranged based on their -melee potential rating. This rating is a heuristic measure that takes into -account genetic predispositions in physical and mental attributes, as -well as body size. The goal is to assign a higher value to dwarves (and other -humanoid creatures) that are expected to be more effective in melee combat +If sorting is done by "melee potential" citizens are arranged based on their +melee potential rating. This rating is a heuristic measure that takes into +account genetic predispositions in physical and mental attributes, as +well as body size. The goal is to assign a higher value to dwarves (and other +humanoid creatures) that are expected to be more effective in melee combat against opponents of similar skill levels. -If sorting is done by "ranged potential" citizens are arranged based on their -ranged potential rating. This rating is a heuristic measure that takes into -account genetic predispositions in physical and mental attributes. -The goal is to assign a higher value to dwarves (and other humanoid creatures) +If sorting is done by "ranged potential" citizens are arranged based on their +ranged potential rating. This rating is a heuristic measure that takes into +account genetic predispositions in physical and mental attributes. +The goal is to assign a higher value to dwarves (and other humanoid creatures) that are expected to be more effective in ranged combat. -If sorting is done by "mental stability" citizens are arranged based on their -mental stability rating. This rating is a measure that takes into account -facets and values of an individual and correlates to better stress values. +If sorting is done by "mental stability" citizens are arranged based on their +mental stability rating. This rating is a measure that takes into account +facets and values of an individual and correlates to better stress values. It is designed to be higher for more stress-resistant citizens. You can search for a dwarf by name by typing in the Search field. You can also diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index ae6ea4eeb..db132b855 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -224,8 +224,8 @@ local function melee_potential(unit) local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value -- strength/bodysize is a dirty approximation of momentum formula - local rating = 10000*strength/bodySize + strength*4 + agility*3 + toughness - + endurance + recuperation/3 + diseaseResistance/3 + local rating = 10000*strength/bodySize + strength*4 + agility*3 + toughness + + endurance + recuperation/3 + diseaseResistance/3 + kinestheticSense*3 + willpower/2 + spatialSense/2 + focus/4 return rating end @@ -277,7 +277,7 @@ local function ranged_potential(unit) local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value local rating = strength + agility*4 + toughness + endurance + recuperation/2 - + diseaseResistance/2 - bodySize/2 + + diseaseResistance/2 - bodySize/2 + kinestheticSense + willpower + spatialSense*3 + focus return rating end @@ -338,16 +338,16 @@ local function mental_stability(unit) 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) + 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 @@ -809,4 +809,4 @@ end make_sort_order = utils.make_sort_order ]] -return _ENV \ No newline at end of file +return _ENV From 25f2c1746a8169fb8f1d0f13e1007f41f9ea84c2 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 25 Aug 2023 16:18:20 +0300 Subject: [PATCH 05/11] Changed melee and ranged ratings to statistically proven linear formulas. --- docs/plugins/sort.rst | 32 ++++++++++++++++---------------- plugins/lua/sort.lua | 24 ++++++++---------------- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/docs/plugins/sort.rst b/docs/plugins/sort.rst index e8e280f61..8ec9fefcc 100644 --- a/docs/plugins/sort.rst +++ b/docs/plugins/sort.rst @@ -26,22 +26,22 @@ skill they have in crossbows or general ranged combat. If sorted by "leadership", then the citizen is sorted according to the highest skill they have in leader, teacher, or military tactics. -If sorting is done by "melee potential" citizens are arranged based on their -melee potential rating. This rating is a heuristic measure that takes into -account genetic predispositions in physical and mental attributes, as -well as body size. The goal is to assign a higher value to dwarves (and other -humanoid creatures) that are expected to be more effective in melee combat -against opponents of similar skill levels. - -If sorting is done by "ranged potential" citizens are arranged based on their -ranged potential rating. This rating is a heuristic measure that takes into -account genetic predispositions in physical and mental attributes. -The goal is to assign a higher value to dwarves (and other humanoid creatures) -that are expected to be more effective in ranged combat. - -If sorting is done by "mental stability" citizens are arranged based on their -mental stability rating. This rating is a measure that takes into account -facets and values of an individual and correlates to better stress values. +If sorting is done by "melee potential" citizens are arranged based on their +melee potential rating. This rating is a heuristic measure that takes into +account genetic predispositions in physical and mental attributes, as +well as body size. Dwarves (and other humanoid creatures) with bigger rating +are expected to be more effective in melee combat against opponents of +similar skill levels. + +If sorting is done by "ranged potential" citizens are arranged based on their +ranged potential rating. This rating is a statistical measure that takes into +account genetic predispositions in physical and mental attributes. +Dwarves (and other humanoid creatures) with bigger rating are expected to be +more effective in ranged combat. + +If sorting is done by "mental stability" citizens are arranged based on their +mental stability rating. This rating is a measure that takes into account +facets and values of an individual and correlates to better stress values. It is designed to be higher for more stress-resistant citizens. You can search for a dwarf by name by typing in the Search field. You can also diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index ae6ea4eeb..ee2f38c34 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -205,7 +205,7 @@ local function make_sort_by_skill_asc(sort_skill) end end --- Heuristic rating that is bigger for more potent dwarves in long run melee military training +-- Statistical rating that is bigger for more potent dwarves in long run melee military training -- Wounds are not considered! local function melee_potential(unit) -- Physical attributes @@ -213,20 +213,16 @@ local function melee_potential(unit) local agility = unit.body.physical_attrs.AGILITY.max_value local toughness = unit.body.physical_attrs.TOUGHNESS.max_value local endurance = unit.body.physical_attrs.ENDURANCE.max_value - local recuperation = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.RECUPERATION) - local diseaseResistance = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.DISEASE_RESISTANCE) local bodySize = unit.body.size_info.size_base -- Mental attributes - local focus = unit.status.current_soul.mental_attrs.FOCUS.max_value local willpower = unit.status.current_soul.mental_attrs.WILLPOWER.max_value local spatialSense = unit.status.current_soul.mental_attrs.SPATIAL_SENSE.max_value local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value - -- strength/bodysize is a dirty approximation of momentum formula - local rating = 10000*strength/bodySize + strength*4 + agility*3 + toughness - + endurance + recuperation/3 + diseaseResistance/3 - + kinestheticSense*3 + willpower/2 + spatialSense/2 + focus/4 + -- Melee potential rating + local rating = strength*5.8 + kinestheticSense*3.7 + bodySize*2 + agility*2 + endurance*1.8 + + willpower*1.5 * spatialSense*1.5 + toughness*1.5 return rating end @@ -258,17 +254,13 @@ local function sort_by_melee_potential_asc(unit_id_1, unit_id_2) return utils.compare(rating1, rating2) end --- Heuristic rating that is bigger for more potent dwarves in long run ranged military training +-- Statistical rating that is bigger for more potent dwarves in long run ranged military training -- Wounds are not considered! local function ranged_potential(unit) -- Physical attributes - local strength = unit.body.physical_attrs.STRENGTH.max_value local agility = unit.body.physical_attrs.AGILITY.max_value local toughness = unit.body.physical_attrs.TOUGHNESS.max_value local endurance = unit.body.physical_attrs.ENDURANCE.max_value - local recuperation = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.RECUPERATION) - local diseaseResistance = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.DISEASE_RESISTANCE) - local bodySize = unit.body.size_info.size_base -- Mental attributes local focus = unit.status.current_soul.mental_attrs.FOCUS.max_value @@ -276,9 +268,9 @@ local function ranged_potential(unit) local spatialSense = unit.status.current_soul.mental_attrs.SPATIAL_SENSE.max_value local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value - local rating = strength + agility*4 + toughness + endurance + recuperation/2 - + diseaseResistance/2 - bodySize/2 - + kinestheticSense + willpower + spatialSense*3 + focus + -- Ranged potential formula + local rating = agility*3.9 + kinestheticSense*3 + spatialSense*2.9 + toughness*0.9 + + focus*0.7 + endurance*0.7 + willpower*0.6 return rating end From 6fcbf48c8412bbdfdee51e713e70f23930bf11b0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 25 Aug 2023 13:28:36 +0000 Subject: [PATCH 06/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- plugins/lua/sort.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index 57992b6dd..c4610ecc6 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -269,7 +269,7 @@ local function ranged_potential(unit) local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value -- Ranged potential formula - local rating = agility*3.9 + kinestheticSense*3 + spatialSense*2.9 + toughness*0.9 + local rating = agility*3.9 + kinestheticSense*3 + spatialSense*2.9 + toughness*0.9 + focus*0.7 + endurance*0.7 + willpower*0.6 return rating end From c91a02ca6a9498b24a9644ecf4afd63800455be2 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sun, 27 Aug 2023 17:52:09 +0300 Subject: [PATCH 07/11] Reworked fallback from rating sortings. Renamed melee potential to solo combat potential. Added group combat potential. --- docs/plugins/sort.rst | 23 ++-- plugins/lua/sort.lua | 269 ++++++++++++++++++++++++++---------------- 2 files changed, 180 insertions(+), 112 deletions(-) diff --git a/docs/plugins/sort.rst b/docs/plugins/sort.rst index 8ec9fefcc..dd8b206ce 100644 --- a/docs/plugins/sort.rst +++ b/docs/plugins/sort.rst @@ -26,12 +26,22 @@ skill they have in crossbows or general ranged combat. If sorted by "leadership", then the citizen is sorted according to the highest skill they have in leader, teacher, or military tactics. -If sorting is done by "melee potential" citizens are arranged based on their -melee potential rating. This rating is a heuristic measure that takes into +If sorting is done by "mental stability" citizens are arranged based on their +mental stability rating. This rating is a measure that takes into account +facets and values of an individual and correlates to better stress values. +It is designed to be higher for more stress-resistant citizens. + +If sorting is done by "solo combat potential" citizens are arranged based on their +solo combat potential rating. This rating is a measure that takes into account genetic predispositions in physical and mental attributes, as well as body size. Dwarves (and other humanoid creatures) with bigger rating -are expected to be more effective in melee combat against opponents of -similar skill levels. +are expected to be more effective in melee combat against strong opponents. + +If sorting is done by "group combat potential" citizens are arranged based on their +group combat potential rating. Similar with solo combat rating except this rating +taking into account efficiency in a fight against multiple opponents. This rating +is valid only for dwarves because it considers martial trance which only dwarves +are capable of. If sorting is done by "ranged potential" citizens are arranged based on their ranged potential rating. This rating is a statistical measure that takes into @@ -39,11 +49,6 @@ account genetic predispositions in physical and mental attributes. Dwarves (and other humanoid creatures) with bigger rating are expected to be more effective in ranged combat. -If sorting is done by "mental stability" citizens are arranged based on their -mental stability rating. This rating is a measure that takes into account -facets and values of an individual and correlates to better stress values. -It is designed to be higher for more stress-resistant citizens. - You can search for a dwarf by name by typing in the Search field. You can also type in the name of any job skill (military-related or not) and dwarves with any experience in that skill will be shown. For example, to only see citizens diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index c4610ecc6..c418b1f5a 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -205,9 +205,82 @@ local function make_sort_by_skill_asc(sort_skill) end end +-- Statistical rating that is bigger for dwarves that are mentally stable +local function 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) + + return rating +end + + +local function sort_by_mental_stability_desc(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local rating1 = mental_stability(unit1) + local rating2 = mental_stability(unit2) + if rating1 == rating2 then + -- sorting by stress is opposite + -- more mental stable dwarves should have less stress + return sort_by_stress_asc(unit_id_1, unit_id_2) + end + return utils.compare(rating2, rating1) +end + +local function sort_by_mental_stability_asc(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local rating1 = mental_stability(unit1) + local rating2 = mental_stability(unit2) + if rating1 == rating2 then + return sort_by_stress_desc(unit_id_1, unit_id_2) + end + return utils.compare(rating1, rating2) +end + -- Statistical rating that is bigger for more potent dwarves in long run melee military training +-- Rating considers fighting solo opponents -- Wounds are not considered! -local function melee_potential(unit) +local function solo_combat_potential(unit) -- Physical attributes local strength = unit.body.physical_attrs.STRENGTH.max_value local agility = unit.body.physical_attrs.AGILITY.max_value @@ -220,154 +293,130 @@ local function melee_potential(unit) local spatialSense = unit.status.current_soul.mental_attrs.SPATIAL_SENSE.max_value local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value - -- Melee potential rating + -- solo combat potential rating local rating = strength*5.8 + kinestheticSense*3.7 + bodySize*2 + agility*2 + endurance*1.8 + willpower*1.5 * spatialSense*1.5 + toughness*1.5 return rating end -local function sort_by_melee_potential_desc(unit_id_1, unit_id_2) +local function sort_by_solo_combat_potential_desc(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end local unit1 = df.unit.find(unit_id_1) local unit2 = df.unit.find(unit_id_2) if not unit1 then return -1 end if not unit2 then return 1 end - local rating1 = melee_potential(unit1) - local rating2 = melee_potential(unit2) + local rating1 = solo_combat_potential(unit1) + local rating2 = solo_combat_potential(unit2) if rating1 == rating2 then - return sort_by_name_desc(unit_id_1, unit_id_2) + return sort_by_mental_stability_desc(unit_id_1, unit_id_2) end return utils.compare(rating2, rating1) end -local function sort_by_melee_potential_asc(unit_id_1, unit_id_2) +local function sort_by_solo_combat_potential_asc(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end local unit1 = df.unit.find(unit_id_1) local unit2 = df.unit.find(unit_id_2) if not unit1 then return -1 end if not unit2 then return 1 end - local rating1 = melee_potential(unit1) - local rating2 = melee_potential(unit2) + local rating1 = solo_combat_potential(unit1) + local rating2 = solo_combat_potential(unit2) if rating1 == rating2 then - return sort_by_name_asc(unit_id_1, unit_id_2) + return sort_by_mental_stability_asc(unit_id_1, unit_id_2) end return utils.compare(rating1, rating2) end --- Statistical rating that is bigger for more potent dwarves in long run ranged military training +-- Statistical rating that is bigger for more potent dwarves in long run melee military training +-- Rating considers fighting group of opponents -- Wounds are not considered! -local function ranged_potential(unit) +local function group_combat_potential(unit) -- Physical attributes - local agility = unit.body.physical_attrs.AGILITY.max_value - local toughness = unit.body.physical_attrs.TOUGHNESS.max_value + local strength = unit.body.physical_attrs.STRENGTH.max_value local endurance = unit.body.physical_attrs.ENDURANCE.max_value + local bodySize = unit.body.size_info.size_base -- Mental attributes - local focus = unit.status.current_soul.mental_attrs.FOCUS.max_value - local willpower = unit.status.current_soul.mental_attrs.WILLPOWER.max_value local spatialSense = unit.status.current_soul.mental_attrs.SPATIAL_SENSE.max_value local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value - -- Ranged potential formula - local rating = agility*3.9 + kinestheticSense*3 + spatialSense*2.9 + toughness*0.9 - + focus*0.7 + endurance*0.7 + willpower*0.6 + -- group combat potential rating + local rating = strength*8.3 + endurance*3 + bodySize*2.8 + kinestheticSense*0.6 + spatialSense*0.4 return rating end -local function sort_by_ranged_potential_desc(unit_id_1, unit_id_2) +local function sort_by_group_combat_potential_desc(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end local unit1 = df.unit.find(unit_id_1) local unit2 = df.unit.find(unit_id_2) if not unit1 then return -1 end if not unit2 then return 1 end - local rating1 = ranged_potential(unit1) - local rating2 = ranged_potential(unit2) + local rating1 = group_combat_potential(unit1) + local rating2 = group_combat_potential(unit2) if rating1 == rating2 then - return sort_by_name_desc(unit_id_1, unit_id_2) + return sort_by_mental_stability_desc(unit_id_1, unit_id_2) end return utils.compare(rating2, rating1) end -local function sort_by_ranged_potential_asc(unit_id_1, unit_id_2) +local function sort_by_group_combat_potential_asc(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end local unit1 = df.unit.find(unit_id_1) local unit2 = df.unit.find(unit_id_2) if not unit1 then return -1 end if not unit2 then return 1 end - local rating1 = ranged_potential(unit1) - local rating2 = ranged_potential(unit2) + local rating1 = group_combat_potential(unit1) + local rating2 = group_combat_potential(unit2) if rating1 == rating2 then - return sort_by_name_asc(unit_id_1, unit_id_2) + return sort_by_mental_stability_asc(unit_id_1, unit_id_2) end return utils.compare(rating1, rating2) end --- Statistical rating that is bigger for dwarves that are mentally stable -local function 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']) +-- Statistical rating that is bigger for more potent dwarves in long run ranged military training +-- Wounds are not considered! +local function ranged_potential(unit) + -- Physical attributes + local agility = unit.body.physical_attrs.AGILITY.max_value + local toughness = unit.body.physical_attrs.TOUGHNESS.max_value + local endurance = unit.body.physical_attrs.ENDURANCE.max_value - -- 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) + -- Mental attributes + local focus = unit.status.current_soul.mental_attrs.FOCUS.max_value + local willpower = unit.status.current_soul.mental_attrs.WILLPOWER.max_value + local spatialSense = unit.status.current_soul.mental_attrs.SPATIAL_SENSE.max_value + local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value + -- Ranged potential formula + local rating = agility*3.9 + kinestheticSense*3 + spatialSense*2.9 + toughness*0.9 + + focus*0.7 + endurance*0.7 + willpower*0.6 return rating end - -local function sort_by_mental_stability_desc(unit_id_1, unit_id_2) +local function sort_by_ranged_potential_desc(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end local unit1 = df.unit.find(unit_id_1) local unit2 = df.unit.find(unit_id_2) if not unit1 then return -1 end if not unit2 then return 1 end - local rating1 = mental_stability(unit1) - local rating2 = mental_stability(unit2) + local rating1 = ranged_potential(unit1) + local rating2 = ranged_potential(unit2) if rating1 == rating2 then - return sort_by_name_desc(unit_id_1, unit_id_2) + return sort_by_mental_stability_desc(unit_id_1, unit_id_2) end return utils.compare(rating2, rating1) end -local function sort_by_mental_stability_asc(unit_id_1, unit_id_2) +local function sort_by_ranged_potential_asc(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end local unit1 = df.unit.find(unit_id_1) local unit2 = df.unit.find(unit_id_2) if not unit1 then return -1 end if not unit2 then return 1 end - local rating1 = mental_stability(unit1) - local rating2 = mental_stability(unit2) + local rating1 = ranged_potential(unit1) + local rating2 = ranged_potential(unit2) if rating1 == rating2 then - return sort_by_name_asc(unit_id_1, unit_id_2) + return sort_by_mental_stability_asc(unit_id_1, unit_id_2) end return utils.compare(rating1, rating2) end @@ -402,7 +451,7 @@ SquadAssignmentOverlay.ATTRS{ default_pos={x=-33, y=40}, default_enabled=true, viewscreens='dwarfmode/UnitSelector/SQUAD_FILL_POSITION', - frame={w=74, h=9}, + frame={w=84, h=9}, frame_style=gui.FRAME_PANEL, frame_background=gui.CLEAR_PEN, } @@ -441,12 +490,14 @@ function SquadAssignmentOverlay:init() {label='spear skill'..CH_UP, value=SORT_FNS.sort_by_spear_asc, pen=COLOR_YELLOW}, {label='crossbow skill'..CH_DN, value=SORT_FNS.sort_by_crossbow_desc, pen=COLOR_GREEN}, {label='crossbow skill'..CH_UP, value=SORT_FNS.sort_by_crossbow_asc, pen=COLOR_YELLOW}, - {label='melee potential'..CH_DN, value=sort_by_melee_potential_desc, pen=COLOR_GREEN}, - {label='melee potential'..CH_UP, value=sort_by_melee_potential_asc, pen=COLOR_YELLOW}, - {label='ranged potential'..CH_DN, value=sort_by_ranged_potential_desc, pen=COLOR_GREEN}, - {label='ranged potential'..CH_UP, value=sort_by_ranged_potential_asc, pen=COLOR_YELLOW}, {label='mental stability'..CH_DN, value=sort_by_mental_stability_desc, pen=COLOR_GREEN}, {label='mental stability'..CH_UP, value=sort_by_mental_stability_asc, pen=COLOR_YELLOW}, + {label='solo combat potential'..CH_DN, value=sort_by_solo_combat_potential_desc, pen=COLOR_GREEN}, + {label='solo combat potential'..CH_UP, value=sort_by_solo_combat_potential_asc, pen=COLOR_YELLOW}, + {label='group combat potential'..CH_DN, value=sort_by_group_combat_potential_desc, pen=COLOR_GREEN}, + {label='group combat potential'..CH_UP, value=sort_by_group_combat_potential_asc, pen=COLOR_YELLOW}, + {label='ranged potential'..CH_DN, value=sort_by_ranged_potential_desc, pen=COLOR_GREEN}, + {label='ranged potential'..CH_UP, value=sort_by_ranged_potential_asc, pen=COLOR_YELLOW}, }, initial_option=SORT_FNS.sort_by_any_melee_desc, on_change=self:callback('refresh_list', 'sort'), @@ -530,7 +581,7 @@ function SquadAssignmentOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='sort_axe', - frame={t=2, l=2, w=4}, + frame={t=2, l=0, w=4}, options={ {label='axe', value=sort_noop}, {label='axe'..CH_DN, value=SORT_FNS.sort_by_axe_desc, pen=COLOR_GREEN}, @@ -541,7 +592,7 @@ function SquadAssignmentOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='sort_sword', - frame={t=2, l=9, w=6}, + frame={t=2, l=7, w=6}, options={ {label='sword', value=sort_noop}, {label='sword'..CH_DN, value=SORT_FNS.sort_by_sword_desc, pen=COLOR_GREEN}, @@ -552,7 +603,7 @@ function SquadAssignmentOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='sort_mace', - frame={t=2, l=18, w=5}, + frame={t=2, l=16, w=5}, options={ {label='mace', value=sort_noop}, {label='mace'..CH_DN, value=SORT_FNS.sort_by_mace_desc, pen=COLOR_GREEN}, @@ -563,7 +614,7 @@ function SquadAssignmentOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='sort_hammer', - frame={t=2, l=25, w=7}, + frame={t=2, l=23, w=7}, options={ {label='hammer', value=sort_noop}, {label='hammer'..CH_DN, value=SORT_FNS.sort_by_hammer_desc, pen=COLOR_GREEN}, @@ -574,7 +625,7 @@ function SquadAssignmentOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='sort_spear', - frame={t=2, l=36, w=6}, + frame={t=2, l=34, w=6}, options={ {label='spear', value=sort_noop}, {label='spear'..CH_DN, value=SORT_FNS.sort_by_spear_desc, pen=COLOR_GREEN}, @@ -585,7 +636,7 @@ function SquadAssignmentOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='sort_crossbow', - frame={t=2, l=45, w=9}, + frame={t=2, l=43, w=9}, options={ {label='crossbow', value=sort_noop}, {label='crossbow'..CH_DN, value=SORT_FNS.sort_by_crossbow_desc, pen=COLOR_GREEN}, @@ -595,37 +646,48 @@ function SquadAssignmentOverlay:init() on_change=self:callback('refresh_list', 'sort_crossbow'), }, widgets.CycleHotkeyLabel{ - view_id='sort_melee_potential', - frame={t=4, l=2, w=16}, + view_id='sort_mental_stability', + frame={t=4, l=0, w=17}, + options={ + {label='mental stability', value=sort_noop}, + {label='mental stability'..CH_DN, value=sort_by_mental_stability_desc, pen=COLOR_GREEN}, + {label='mental stability'..CH_UP, value=sort_by_mental_stability_asc, pen=COLOR_YELLOW}, + }, + option_gap=0, + on_change=self:callback('refresh_list', 'sort_mental_stability'), + }, + widgets.CycleHotkeyLabel{ + view_id='sort_solo_combat_potential', + frame={t=4, l=18, w=22}, options={ - {label='melee potential', value=sort_noop}, - {label='melee potential'..CH_DN, value=sort_by_melee_potential_desc, pen=COLOR_GREEN}, - {label='melee potential'..CH_UP, value=sort_by_melee_potential_asc, pen=COLOR_YELLOW}, + {label='solo combat potential', value=sort_noop}, + {label='solo combat potential'..CH_DN, value=sort_by_solo_combat_potential_desc, pen=COLOR_GREEN}, + {label='solo combat potential'..CH_UP, value=sort_by_solo_combat_potential_asc, pen=COLOR_YELLOW}, }, option_gap=0, - on_change=self:callback('refresh_list', 'sort_melee_potential'), + on_change=self:callback('refresh_list', 'sort_solo_combat_potential'), }, widgets.CycleHotkeyLabel{ - view_id='sort_ranged_potential', - frame={t=4, l=21, w=17}, + view_id='sort_group_combat_potential', + frame={t=4, l=41, w=23}, options={ - {label='ranged potential', value=sort_noop}, - {label='ranged potential'..CH_DN, value=sort_by_ranged_potential_desc, pen=COLOR_GREEN}, - {label='ranged potential'..CH_UP, value=sort_by_ranged_potential_asc, pen=COLOR_YELLOW}, + {label='group combat potential', value=sort_noop}, + {label='group combat potential'..CH_DN, value=sort_by_group_combat_potential_desc, pen=COLOR_GREEN}, + {label='group combat potential'..CH_UP, value=sort_by_group_combat_potential_asc, pen=COLOR_YELLOW}, }, option_gap=0, - on_change=self:callback('refresh_list', 'sort_ranged_potential'), + on_change=self:callback('refresh_list', 'sort_group_combat_potential'), }, widgets.CycleHotkeyLabel{ - view_id='sort_mental_stability', - frame={t=4, l=41, w=17}, + view_id='sort_ranged_potential', + frame={t=4, l=65, w=17}, options={ - {label='mental stability', value=sort_noop}, - {label='mental stability'..CH_DN, value=sort_by_mental_stability_desc, pen=COLOR_GREEN}, - {label='mental stability'..CH_UP, value=sort_by_mental_stability_asc, pen=COLOR_YELLOW}, + {label='ranged potential', value=sort_noop}, + {label='ranged potential'..CH_DN, value=sort_by_ranged_potential_desc, pen=COLOR_GREEN}, + {label='ranged potential'..CH_UP, value=sort_by_ranged_potential_asc, pen=COLOR_YELLOW}, }, option_gap=0, - on_change=self:callback('refresh_list', 'sort_mental_stability'), + on_change=self:callback('refresh_list', 'sort_ranged_potential'), }, } }, @@ -704,9 +766,10 @@ local SORT_WIDGET_NAMES = { 'sort_hammer', 'sort_spear', 'sort_crossbow', - 'sort_melee_potential', - 'sort_ranged_potential', 'sort_mental_stability', + 'sort_solo_combat_potential', + 'sort_group_combat_potential', + 'sort_ranged_potential', } function SquadAssignmentOverlay:refresh_list(sort_widget, sort_fn) From 73af533a906c3b3d6ec4e75f3fcf4d6ed9e6b821 Mon Sep 17 00:00:00 2001 From: Mikhail Panov Date: Tue, 29 Aug 2023 10:59:28 +0300 Subject: [PATCH 08/11] Update docs/plugins/sort.rst Co-authored-by: Myk --- docs/plugins/sort.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/plugins/sort.rst b/docs/plugins/sort.rst index dd8b206ce..c4e7654ef 100644 --- a/docs/plugins/sort.rst +++ b/docs/plugins/sort.rst @@ -14,7 +14,8 @@ Searching and sorting functionality is provided by `overlay` widgets, and widget Squad assignment overlay ------------------------ -The squad assignment screen can be sorted by name, by migrant wave, by stress, by various military-related skills or by specific potential ratings. +The squad assignment screen can be sorted by name, by migrant wave, by stress, +by various military-related skills or by long-term military potential. If sorted by "any melee", then the citizen is sorted according to the highest skill they have in axes, short swords, maces, warhammers, spears, or general From 26b81cfbbd8c9ac6625bb62ed821a94a834681a7 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Wed, 30 Aug 2023 09:39:13 +0300 Subject: [PATCH 09/11] Merged military rating back into one. Adjusted formulas for melee and ranged rating. Added skill effectiveness ratings for ranged and melee. Changed any melee and any ranged to skill effectiveness ratings. Added info about new ratings and research links to the docs. --- docs/plugins/sort.rst | 45 ++++---- plugins/lua/sort.lua | 263 ++++++++++++++++++++++++------------------ 2 files changed, 177 insertions(+), 131 deletions(-) diff --git a/docs/plugins/sort.rst b/docs/plugins/sort.rst index c4e7654ef..f142534db 100644 --- a/docs/plugins/sort.rst +++ b/docs/plugins/sort.rst @@ -17,12 +17,16 @@ Squad assignment overlay The squad assignment screen can be sorted by name, by migrant wave, by stress, by various military-related skills or by long-term military potential. -If sorted by "any melee", then the citizen is sorted according to the highest -skill they have in axes, short swords, maces, warhammers, spears, or general -fighting. +If sorted by "any melee", then the citizen is sorted according to the "melee +skill effectiveness". This rating uses the highest skill they have in axes, short +swords, maces, warhammers or spears along with physical and mental attributes and +general fighting skill. Citizens with higher rating are expected to be more +effective in melee combat with their corresponding weapon. -If sorted by "any ranged", then the citizen is sorted according to the highest -skill they have in crossbows or general ranged combat. +If sorted by "any ranged", then the citizen is sorted according to the "ranged +skill effectiveness". This rating uses crossbow and general archery skills +along with mental and physical attributes. Citizens with higher rating are +expected to be more effective in ranged combat. If sorted by "leadership", then the citizen is sorted according to the highest skill they have in leader, teacher, or military tactics. @@ -32,29 +36,30 @@ mental stability rating. This rating is a measure that takes into account facets and values of an individual and correlates to better stress values. It is designed to be higher for more stress-resistant citizens. -If sorting is done by "solo combat potential" citizens are arranged based on their -solo combat potential rating. This rating is a measure that takes into -account genetic predispositions in physical and mental attributes, as -well as body size. Dwarves (and other humanoid creatures) with bigger rating -are expected to be more effective in melee combat against strong opponents. - -If sorting is done by "group combat potential" citizens are arranged based on their -group combat potential rating. Similar with solo combat rating except this rating -taking into account efficiency in a fight against multiple opponents. This rating -is valid only for dwarves because it considers martial trance which only dwarves -are capable of. +If sorting is done by "melee potential" citizens are arranged based on +their "melee combat potential" rating. This rating is a statistical measure +that takes into account genetic predispositions in physical and mental +attributes, as well as body size. Dwarves (and other humanoid creatures) with +higher rating are expected to be more effective in melee combat if they train +their attributes to their genetic maximum. If sorting is done by "ranged potential" citizens are arranged based on their -ranged potential rating. This rating is a statistical measure that takes into -account genetic predispositions in physical and mental attributes. -Dwarves (and other humanoid creatures) with bigger rating are expected to be -more effective in ranged combat. +ranged combat potential rating. This rating is a statistical measure that takes into +account genetic predispositions in physical and mental attributes. Dwarves +(and other humanoid creatures) with higher rating are expected to be more +effective in ranged combat if they train their attributes to the maximum. You can search for a dwarf by name by typing in the Search field. You can also type in the name of any job skill (military-related or not) and dwarves with any experience in that skill will be shown. For example, to only see citizens with military tactics skill, type in "tactics". +"Melee skill effectiveness", "ranged skill effectiveness", "melee combat potential" +and "ranged combat potential" are explained in detail here: +https://www.reddit.com/r/dwarffortress/comments/163kczo/enhancing_military_candidate_selection_part_3/ +"Mental stability" is explained here: +https://www.reddit.com/r/dwarffortress/comments/1617s11/enhancing_military_candidate_selection_part_2/ + You can see all the job skill names that you can search for by running:: :lua @df.job_skill diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index c418b1f5a..02431bd7f 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -16,12 +16,12 @@ local MELEE_WEAPON_SKILLS = { df.job_skill.MACE, df.job_skill.HAMMER, df.job_skill.SPEAR, - df.job_skill.MELEE_COMBAT, --Fighter + -- df.job_skill.MELEE_COMBAT, --Fighter } local RANGED_WEAPON_SKILLS = { df.job_skill.CROSSBOW, - df.job_skill.RANGED_COMBAT, + -- df.job_skill.RANGED_COMBAT, } local LEADERSHIP_SKILLS = { @@ -125,6 +125,109 @@ local function get_max_skill(unit_id, list) return max end +local function melee_skill_effectiveness(unit_id, skill_list) + local unit = df.unit.find(unit_id) + + -- Physical attributes + local strength = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.STRENGTH) + local agility = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.AGILITY) + local toughness = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.TOUGHNESS) + local endurance = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.ENDURANCE) + local body_size_base = unit.body.size_info.size_base + + -- Mental attributes + local willpower = dfhack.units.getMentalAttrValue(unit, df.mental_attribute_type.WILLPOWER) + local spatial_sense = dfhack.units.getMentalAttrValue(unit, df.mental_attribute_type.SPATIAL_SENSE) + local kinesthetic_sense = dfhack.units.getMentalAttrValue(unit, df.mental_attribute_type.KINESTHETIC_SENSE) + + -- Skills + local melee_skill = get_max_skill(unit_id, skill_list) + if melee_skill then + melee_skill = melee_skill.rating + else + melee_skill = 0 + end + local melee_combat = dfhack.units.getNominalSkill(unit, df.job_skill.MELEE_COMBAT, true) + + local rating = melee_skill * 27000 + melee_combat * 9000 + + strength * 180 + body_size_base * 100 + kinesthetic_sense * 50 + endurance * 50 + + agility * 30 + toughness * 20 + willpower * 20 + spatial_sense * 20 + return rating +end + +local function make_sort_by_melee_skill_effectiveness_desc(list) + return function(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + if unit_id_1 == -1 then return -1 end + if unit_id_2 == -1 then return 1 end + local rating1 = melee_skill_effectiveness(unit_id_1, list) + local rating2 = melee_skill_effectiveness(unit_id_2, list) + if rating1 == rating2 then return sort_by_name_desc(unit_id_1, unit_id_2) end + if rating1 ~= rating2 then return utils.compare(rating2, rating1) end + end +end + +local function make_sort_by_melee_skill_effectiveness_asc(list) + return function(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + if unit_id_1 == -1 then return -1 end + if unit_id_2 == -1 then return 1 end + local rating1 = melee_skill_effectiveness(unit_id_1, list) + local rating2 = melee_skill_effectiveness(unit_id_2, list) + if rating1 == rating2 then return sort_by_name_desc(unit_id_1, unit_id_2) end + if rating1 ~= rating2 then return utils.compare(rating1, rating2) end + end +end + +local function ranged_skill_effectiveness(unit_id, skill_list) + local unit = df.unit.find(unit_id) + + -- Physical attributes + local agility = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.AGILITY) + + -- Mental attributes + local spatial_sense = dfhack.units.getMentalAttrValue(unit, df.mental_attribute_type.SPATIAL_SENSE) + local kinesthetic_sense = dfhack.units.getMentalAttrValue(unit, df.mental_attribute_type.KINESTHETIC_SENSE) + local focus = dfhack.units.getMentalAttrValue(unit, df.mental_attribute_type.FOCUS) + + -- Skills + local ranged_skill = get_max_skill(unit_id, skill_list) + if ranged_skill then + ranged_skill = ranged_skill.rating + else + ranged_skill = 0 + end + local ranged_combat = dfhack.units.getNominalSkill(unit, df.job_skill.RANGED_COMBAT, true) + + local rating = ranged_skill * 24000 + ranged_combat * 8000 + + agility * 15 + spatial_sense * 15 + kinesthetic_sense * 6 + focus * 6 + return rating +end + +local function make_sort_by_ranged_skill_effectiveness_desc(list) + return function(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + if unit_id_1 == -1 then return -1 end + if unit_id_2 == -1 then return 1 end + local rating1 = ranged_skill_effectiveness(unit_id_1, list) + local rating2 = ranged_skill_effectiveness(unit_id_2, list) + if rating1 == rating2 then return sort_by_name_desc(unit_id_1, unit_id_2) end + if rating1 ~= rating2 then return utils.compare(rating2, rating1) end + end +end + +local function make_sort_by_ranged_skill_effectiveness_asc(list) + return function(unit_id_1, unit_id_2) + if unit_id_1 == unit_id_2 then return 0 end + if unit_id_1 == -1 then return -1 end + if unit_id_2 == -1 then return 1 end + local rating1 = ranged_skill_effectiveness(unit_id_1, list) + local rating2 = ranged_skill_effectiveness(unit_id_2, list) + if rating1 == rating2 then return sort_by_name_desc(unit_id_1, unit_id_2) end + if rating1 ~= rating2 then return utils.compare(rating1, rating2) end + end +end + local function make_sort_by_skill_list_desc(list) return function(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end @@ -246,7 +349,6 @@ local function mental_stability(unit) return rating end - local function sort_by_mental_stability_desc(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end local unit1 = df.unit.find(unit_id_1) @@ -278,95 +380,49 @@ local function sort_by_mental_stability_asc(unit_id_1, unit_id_2) end -- Statistical rating that is bigger for more potent dwarves in long run melee military training --- Rating considers fighting solo opponents +-- Rating considers fighting melee opponents -- Wounds are not considered! -local function solo_combat_potential(unit) +local function melee_combat_potential(unit) -- Physical attributes local strength = unit.body.physical_attrs.STRENGTH.max_value local agility = unit.body.physical_attrs.AGILITY.max_value local toughness = unit.body.physical_attrs.TOUGHNESS.max_value local endurance = unit.body.physical_attrs.ENDURANCE.max_value - local bodySize = unit.body.size_info.size_base + local body_size_base = unit.body.size_info.size_base -- Mental attributes local willpower = unit.status.current_soul.mental_attrs.WILLPOWER.max_value - local spatialSense = unit.status.current_soul.mental_attrs.SPATIAL_SENSE.max_value - local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value - - -- solo combat potential rating - local rating = strength*5.8 + kinestheticSense*3.7 + bodySize*2 + agility*2 + endurance*1.8 - + willpower*1.5 * spatialSense*1.5 + toughness*1.5 - return rating -end - -local function sort_by_solo_combat_potential_desc(unit_id_1, unit_id_2) - if unit_id_1 == unit_id_2 then return 0 end - local unit1 = df.unit.find(unit_id_1) - local unit2 = df.unit.find(unit_id_2) - if not unit1 then return -1 end - if not unit2 then return 1 end - local rating1 = solo_combat_potential(unit1) - local rating2 = solo_combat_potential(unit2) - if rating1 == rating2 then - return sort_by_mental_stability_desc(unit_id_1, unit_id_2) - end - return utils.compare(rating2, rating1) -end - -local function sort_by_solo_combat_potential_asc(unit_id_1, unit_id_2) - if unit_id_1 == unit_id_2 then return 0 end - local unit1 = df.unit.find(unit_id_1) - local unit2 = df.unit.find(unit_id_2) - if not unit1 then return -1 end - if not unit2 then return 1 end - local rating1 = solo_combat_potential(unit1) - local rating2 = solo_combat_potential(unit2) - if rating1 == rating2 then - return sort_by_mental_stability_asc(unit_id_1, unit_id_2) - end - return utils.compare(rating1, rating2) -end - --- Statistical rating that is bigger for more potent dwarves in long run melee military training --- Rating considers fighting group of opponents --- Wounds are not considered! -local function group_combat_potential(unit) - -- Physical attributes - local strength = unit.body.physical_attrs.STRENGTH.max_value - local endurance = unit.body.physical_attrs.ENDURANCE.max_value - local bodySize = unit.body.size_info.size_base - - -- Mental attributes - local spatialSense = unit.status.current_soul.mental_attrs.SPATIAL_SENSE.max_value - local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value + local spatial_sense = unit.status.current_soul.mental_attrs.SPATIAL_SENSE.max_value + local kinesthetic_sense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value - -- group combat potential rating - local rating = strength*8.3 + endurance*3 + bodySize*2.8 + kinestheticSense*0.6 + spatialSense*0.4 + -- melee combat potential rating + local rating = strength * 264 + endurance * 84 + body_size_base * 77 + kinesthetic_sense * 74 + + agility * 33 + willpower * 31 + spatial_sense * 27 + toughness * 25 return rating end -local function sort_by_group_combat_potential_desc(unit_id_1, unit_id_2) +local function sort_by_melee_combat_potential_desc(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end local unit1 = df.unit.find(unit_id_1) local unit2 = df.unit.find(unit_id_2) if not unit1 then return -1 end if not unit2 then return 1 end - local rating1 = group_combat_potential(unit1) - local rating2 = group_combat_potential(unit2) + local rating1 = melee_combat_potential(unit1) + local rating2 = melee_combat_potential(unit2) if rating1 == rating2 then return sort_by_mental_stability_desc(unit_id_1, unit_id_2) end return utils.compare(rating2, rating1) end -local function sort_by_group_combat_potential_asc(unit_id_1, unit_id_2) +local function sort_by_melee_combat_potential_asc(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end local unit1 = df.unit.find(unit_id_1) local unit2 = df.unit.find(unit_id_2) if not unit1 then return -1 end if not unit2 then return 1 end - local rating1 = group_combat_potential(unit1) - local rating2 = group_combat_potential(unit2) + local rating1 = melee_combat_potential(unit1) + local rating2 = melee_combat_potential(unit2) if rating1 == rating2 then return sort_by_mental_stability_asc(unit_id_1, unit_id_2) end @@ -375,7 +431,7 @@ end -- Statistical rating that is bigger for more potent dwarves in long run ranged military training -- Wounds are not considered! -local function ranged_potential(unit) +local function ranged_combat_potential(unit) -- Physical attributes local agility = unit.body.physical_attrs.AGILITY.max_value local toughness = unit.body.physical_attrs.TOUGHNESS.max_value @@ -384,37 +440,36 @@ local function ranged_potential(unit) -- Mental attributes local focus = unit.status.current_soul.mental_attrs.FOCUS.max_value local willpower = unit.status.current_soul.mental_attrs.WILLPOWER.max_value - local spatialSense = unit.status.current_soul.mental_attrs.SPATIAL_SENSE.max_value - local kinestheticSense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value + local spatial_sense = unit.status.current_soul.mental_attrs.SPATIAL_SENSE.max_value + local kinesthetic_sense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value - -- Ranged potential formula - local rating = agility*3.9 + kinestheticSense*3 + spatialSense*2.9 + toughness*0.9 - + focus*0.7 + endurance*0.7 + willpower*0.6 + -- ranged combat potential formula + local rating = agility * 5 + kinesthetic_sense * 5 + spatial_sense * 2 + focus * 2 return rating end -local function sort_by_ranged_potential_desc(unit_id_1, unit_id_2) +local function sort_by_ranged_combat_potential_desc(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end local unit1 = df.unit.find(unit_id_1) local unit2 = df.unit.find(unit_id_2) if not unit1 then return -1 end if not unit2 then return 1 end - local rating1 = ranged_potential(unit1) - local rating2 = ranged_potential(unit2) + local rating1 = ranged_combat_potential(unit1) + local rating2 = ranged_combat_potential(unit2) if rating1 == rating2 then return sort_by_mental_stability_desc(unit_id_1, unit_id_2) end return utils.compare(rating2, rating1) end -local function sort_by_ranged_potential_asc(unit_id_1, unit_id_2) +local function sort_by_ranged_combat_potential_asc(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end local unit1 = df.unit.find(unit_id_1) local unit2 = df.unit.find(unit_id_2) if not unit1 then return -1 end if not unit2 then return 1 end - local rating1 = ranged_potential(unit1) - local rating2 = ranged_potential(unit2) + local rating1 = ranged_combat_potential(unit1) + local rating2 = ranged_combat_potential(unit2) if rating1 == rating2 then return sort_by_mental_stability_asc(unit_id_1, unit_id_2) end @@ -422,10 +477,10 @@ local function sort_by_ranged_potential_asc(unit_id_1, unit_id_2) end local SORT_FNS = { - sort_by_any_melee_desc=make_sort_by_skill_list_desc(MELEE_WEAPON_SKILLS), - sort_by_any_melee_asc=make_sort_by_skill_list_asc(MELEE_WEAPON_SKILLS), - sort_by_any_ranged_desc=make_sort_by_skill_list_desc(RANGED_WEAPON_SKILLS), - sort_by_any_ranged_asc=make_sort_by_skill_list_asc(RANGED_WEAPON_SKILLS), + sort_by_any_melee_desc=make_sort_by_melee_skill_effectiveness_desc(MELEE_WEAPON_SKILLS), + sort_by_any_melee_asc=make_sort_by_melee_skill_effectiveness_asc(MELEE_WEAPON_SKILLS), + sort_by_any_ranged_desc=make_sort_by_ranged_skill_effectiveness_desc(RANGED_WEAPON_SKILLS), + sort_by_any_ranged_asc=make_sort_by_ranged_skill_effectiveness_asc(RANGED_WEAPON_SKILLS), sort_by_leadership_desc=make_sort_by_skill_list_desc(LEADERSHIP_SKILLS), sort_by_leadership_asc=make_sort_by_skill_list_asc(LEADERSHIP_SKILLS), sort_by_axe_desc=make_sort_by_skill_desc(df.job_skill.AXE), @@ -451,7 +506,7 @@ SquadAssignmentOverlay.ATTRS{ default_pos={x=-33, y=40}, default_enabled=true, viewscreens='dwarfmode/UnitSelector/SQUAD_FILL_POSITION', - frame={w=84, h=9}, + frame={w=65, h=9}, frame_style=gui.FRAME_PANEL, frame_background=gui.CLEAR_PEN, } @@ -492,12 +547,10 @@ function SquadAssignmentOverlay:init() {label='crossbow skill'..CH_UP, value=SORT_FNS.sort_by_crossbow_asc, pen=COLOR_YELLOW}, {label='mental stability'..CH_DN, value=sort_by_mental_stability_desc, pen=COLOR_GREEN}, {label='mental stability'..CH_UP, value=sort_by_mental_stability_asc, pen=COLOR_YELLOW}, - {label='solo combat potential'..CH_DN, value=sort_by_solo_combat_potential_desc, pen=COLOR_GREEN}, - {label='solo combat potential'..CH_UP, value=sort_by_solo_combat_potential_asc, pen=COLOR_YELLOW}, - {label='group combat potential'..CH_DN, value=sort_by_group_combat_potential_desc, pen=COLOR_GREEN}, - {label='group combat potential'..CH_UP, value=sort_by_group_combat_potential_asc, pen=COLOR_YELLOW}, - {label='ranged potential'..CH_DN, value=sort_by_ranged_potential_desc, pen=COLOR_GREEN}, - {label='ranged potential'..CH_UP, value=sort_by_ranged_potential_asc, pen=COLOR_YELLOW}, + {label='melee potential'..CH_DN, value=sort_by_melee_combat_potential_desc, pen=COLOR_GREEN}, + {label='melee potential'..CH_UP, value=sort_by_melee_combat_potential_asc, pen=COLOR_YELLOW}, + {label='ranged potential'..CH_DN, value=sort_by_ranged_combat_potential_desc, pen=COLOR_GREEN}, + {label='ranged potential'..CH_UP, value=sort_by_ranged_combat_potential_asc, pen=COLOR_YELLOW}, }, initial_option=SORT_FNS.sort_by_any_melee_desc, on_change=self:callback('refresh_list', 'sort'), @@ -657,37 +710,26 @@ function SquadAssignmentOverlay:init() on_change=self:callback('refresh_list', 'sort_mental_stability'), }, widgets.CycleHotkeyLabel{ - view_id='sort_solo_combat_potential', - frame={t=4, l=18, w=22}, - options={ - {label='solo combat potential', value=sort_noop}, - {label='solo combat potential'..CH_DN, value=sort_by_solo_combat_potential_desc, pen=COLOR_GREEN}, - {label='solo combat potential'..CH_UP, value=sort_by_solo_combat_potential_asc, pen=COLOR_YELLOW}, - }, - option_gap=0, - on_change=self:callback('refresh_list', 'sort_solo_combat_potential'), - }, - widgets.CycleHotkeyLabel{ - view_id='sort_group_combat_potential', - frame={t=4, l=41, w=23}, + view_id='sort_melee_combat_potential', + frame={t=4, l=20, w=16}, options={ - {label='group combat potential', value=sort_noop}, - {label='group combat potential'..CH_DN, value=sort_by_group_combat_potential_desc, pen=COLOR_GREEN}, - {label='group combat potential'..CH_UP, value=sort_by_group_combat_potential_asc, pen=COLOR_YELLOW}, + {label='melee potential', value=sort_noop}, + {label='melee potential'..CH_DN, value=sort_by_melee_combat_potential_desc, pen=COLOR_GREEN}, + {label='melee potential'..CH_UP, value=sort_by_melee_combat_potential_asc, pen=COLOR_YELLOW}, }, option_gap=0, - on_change=self:callback('refresh_list', 'sort_group_combat_potential'), + on_change=self:callback('refresh_list', 'sort_melee_combat_potential'), }, widgets.CycleHotkeyLabel{ - view_id='sort_ranged_potential', - frame={t=4, l=65, w=17}, + view_id='sort_ranged_combat_potential', + frame={t=4, l=39, w=17}, options={ {label='ranged potential', value=sort_noop}, - {label='ranged potential'..CH_DN, value=sort_by_ranged_potential_desc, pen=COLOR_GREEN}, - {label='ranged potential'..CH_UP, value=sort_by_ranged_potential_asc, pen=COLOR_YELLOW}, + {label='ranged potential'..CH_DN, value=sort_by_ranged_combat_potential_desc, pen=COLOR_GREEN}, + {label='ranged potential'..CH_UP, value=sort_by_ranged_combat_potential_asc, pen=COLOR_YELLOW}, }, option_gap=0, - on_change=self:callback('refresh_list', 'sort_ranged_potential'), + on_change=self:callback('refresh_list', 'sort_ranged_combat_potential'), }, } }, @@ -767,9 +809,8 @@ local SORT_WIDGET_NAMES = { 'sort_spear', 'sort_crossbow', 'sort_mental_stability', - 'sort_solo_combat_potential', - 'sort_group_combat_potential', - 'sort_ranged_potential', + 'sort_melee_combat_potential', + 'sort_ranged_combat_potential', } function SquadAssignmentOverlay:refresh_list(sort_widget, sort_fn) From 2c97ab9c8db9cfe75560c73ab4d958f2e399784e Mon Sep 17 00:00:00 2001 From: Mikhail Date: Wed, 30 Aug 2023 17:16:59 +0300 Subject: [PATCH 10/11] Tidying up melee skill and ranged skill funcitons in sort.lua. --- plugins/lua/sort.lua | 93 +++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index 02431bd7f..09740f7c3 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -16,12 +16,10 @@ local MELEE_WEAPON_SKILLS = { df.job_skill.MACE, df.job_skill.HAMMER, df.job_skill.SPEAR, - -- df.job_skill.MELEE_COMBAT, --Fighter } local RANGED_WEAPON_SKILLS = { df.job_skill.CROSSBOW, - -- df.job_skill.RANGED_COMBAT, } local LEADERSHIP_SKILLS = { @@ -125,9 +123,7 @@ local function get_max_skill(unit_id, list) return max end -local function melee_skill_effectiveness(unit_id, skill_list) - local unit = df.unit.find(unit_id) - +local function melee_skill_effectiveness(unit, skill_list) -- Physical attributes local strength = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.STRENGTH) local agility = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.AGILITY) @@ -141,15 +137,15 @@ local function melee_skill_effectiveness(unit_id, skill_list) local kinesthetic_sense = dfhack.units.getMentalAttrValue(unit, df.mental_attribute_type.KINESTHETIC_SENSE) -- Skills - local melee_skill = get_max_skill(unit_id, skill_list) - if melee_skill then - melee_skill = melee_skill.rating - else - melee_skill = 0 + -- Finding the highest skill + skill_rating = 0 + for _, skill in ipairs(skill_list) do + melee_skill = dfhack.units.getNominalSkill(unit, skill, true) + skill_rating = math.max(skill_rating, melee_skill) end - local melee_combat = dfhack.units.getNominalSkill(unit, df.job_skill.MELEE_COMBAT, true) + local melee_combat_rating = dfhack.units.getNominalSkill(unit, df.job_skill.MELEE_COMBAT, true) - local rating = melee_skill * 27000 + melee_combat * 9000 + local rating = skill_rating * 27000 + melee_combat_rating * 9000 + strength * 180 + body_size_base * 100 + kinesthetic_sense * 50 + endurance * 50 + agility * 30 + toughness * 20 + willpower * 20 + spatial_sense * 20 return rating @@ -158,30 +154,33 @@ end local function make_sort_by_melee_skill_effectiveness_desc(list) return function(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end - if unit_id_1 == -1 then return -1 end - if unit_id_2 == -1 then return 1 end - local rating1 = melee_skill_effectiveness(unit_id_1, list) - local rating2 = melee_skill_effectiveness(unit_id_2, list) + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local rating1 = melee_skill_effectiveness(unit1, list) + local rating2 = melee_skill_effectiveness(unit2, list) if rating1 == rating2 then return sort_by_name_desc(unit_id_1, unit_id_2) end - if rating1 ~= rating2 then return utils.compare(rating2, rating1) end + return utils.compare(rating2, rating1) end end local function make_sort_by_melee_skill_effectiveness_asc(list) return function(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end - if unit_id_1 == -1 then return -1 end - if unit_id_2 == -1 then return 1 end - local rating1 = melee_skill_effectiveness(unit_id_1, list) - local rating2 = melee_skill_effectiveness(unit_id_2, list) + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local rating1 = melee_skill_effectiveness(unit1, list) + local rating2 = melee_skill_effectiveness(unit2, list) if rating1 == rating2 then return sort_by_name_desc(unit_id_1, unit_id_2) end - if rating1 ~= rating2 then return utils.compare(rating1, rating2) end + return utils.compare(rating1, rating2) end end -local function ranged_skill_effectiveness(unit_id, skill_list) - local unit = df.unit.find(unit_id) - +-- FUnction could easily be adapted to different weapon types. +local function ranged_skill_effectiveness(unit, skill_list) -- Physical attributes local agility = dfhack.units.getPhysicalAttrValue(unit, df.physical_attribute_type.AGILITY) @@ -191,15 +190,15 @@ local function ranged_skill_effectiveness(unit_id, skill_list) local focus = dfhack.units.getMentalAttrValue(unit, df.mental_attribute_type.FOCUS) -- Skills - local ranged_skill = get_max_skill(unit_id, skill_list) - if ranged_skill then - ranged_skill = ranged_skill.rating - else - ranged_skill = 0 + -- Finding the highest skill + skill_rating = 0 + for _, skill in ipairs(skill_list) do + ranged_skill = dfhack.units.getNominalSkill(unit, skill, true) + skill_rating = math.max(skill_rating, ranged_skill) end local ranged_combat = dfhack.units.getNominalSkill(unit, df.job_skill.RANGED_COMBAT, true) - local rating = ranged_skill * 24000 + ranged_combat * 8000 + local rating = skill_rating * 24000 + ranged_combat * 8000 + agility * 15 + spatial_sense * 15 + kinesthetic_sense * 6 + focus * 6 return rating end @@ -207,24 +206,28 @@ end local function make_sort_by_ranged_skill_effectiveness_desc(list) return function(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end - if unit_id_1 == -1 then return -1 end - if unit_id_2 == -1 then return 1 end - local rating1 = ranged_skill_effectiveness(unit_id_1, list) - local rating2 = ranged_skill_effectiveness(unit_id_2, list) + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local rating1 = ranged_skill_effectiveness(unit1, list) + local rating2 = ranged_skill_effectiveness(unit2, list) if rating1 == rating2 then return sort_by_name_desc(unit_id_1, unit_id_2) end - if rating1 ~= rating2 then return utils.compare(rating2, rating1) end + return utils.compare(rating2, rating1) end end local function make_sort_by_ranged_skill_effectiveness_asc(list) return function(unit_id_1, unit_id_2) if unit_id_1 == unit_id_2 then return 0 end - if unit_id_1 == -1 then return -1 end - if unit_id_2 == -1 then return 1 end - local rating1 = ranged_skill_effectiveness(unit_id_1, list) - local rating2 = ranged_skill_effectiveness(unit_id_2, list) + local unit1 = df.unit.find(unit_id_1) + local unit2 = df.unit.find(unit_id_2) + if not unit1 then return -1 end + if not unit2 then return 1 end + local rating1 = ranged_skill_effectiveness(unit1, list) + local rating2 = ranged_skill_effectiveness(unit2, list) if rating1 == rating2 then return sort_by_name_desc(unit_id_1, unit_id_2) end - if rating1 ~= rating2 then return utils.compare(rating1, rating2) end + return utils.compare(rating1, rating2) end end @@ -308,7 +311,7 @@ local function make_sort_by_skill_asc(sort_skill) end end --- Statistical rating that is bigger for dwarves that are mentally stable +-- Statistical rating that is higher for dwarves that are mentally stable local function mental_stability(unit) local ALTRUISM = unit.status.current_soul.personality.traits.ALTRUISM local ANXIETY_PROPENSITY = unit.status.current_soul.personality.traits.ANXIETY_PROPENSITY @@ -379,7 +382,7 @@ local function sort_by_mental_stability_asc(unit_id_1, unit_id_2) return utils.compare(rating1, rating2) end --- Statistical rating that is bigger for more potent dwarves in long run melee military training +-- Statistical rating that is higher for more potent dwarves in long run melee military training -- Rating considers fighting melee opponents -- Wounds are not considered! local function melee_combat_potential(unit) @@ -429,7 +432,7 @@ local function sort_by_melee_combat_potential_asc(unit_id_1, unit_id_2) return utils.compare(rating1, rating2) end --- Statistical rating that is bigger for more potent dwarves in long run ranged military training +-- Statistical rating that is higher for more potent dwarves in long run ranged military training -- Wounds are not considered! local function ranged_combat_potential(unit) -- Physical attributes @@ -506,7 +509,7 @@ SquadAssignmentOverlay.ATTRS{ default_pos={x=-33, y=40}, default_enabled=true, viewscreens='dwarfmode/UnitSelector/SQUAD_FILL_POSITION', - frame={w=65, h=9}, + frame={w=75, h=9}, frame_style=gui.FRAME_PANEL, frame_background=gui.CLEAR_PEN, } From 4a788e79c3d8355e52a1a2ab4e499394307c9ffe Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 30 Aug 2023 23:26:03 -0700 Subject: [PATCH 11/11] Apply suggestions from code review --- plugins/lua/sort.lua | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index 09740f7c3..0f6f73558 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -81,6 +81,9 @@ local function sort_by_stress_desc(unit_id_1, unit_id_2) if not unit2 then return 1 end local happiness1 = unit1.status.current_soul.personality.stress local happiness2 = unit2.status.current_soul.personality.stress + if happiness1 == happiness2 then + return sort_by_name_desc(unit_id_1, unit_id_2) + end return utils.compare(happiness2, happiness1) end @@ -92,6 +95,9 @@ local function sort_by_stress_asc(unit_id_1, unit_id_2) if not unit2 then return 1 end local happiness1 = unit1.status.current_soul.personality.stress local happiness2 = unit2.status.current_soul.personality.stress + if happiness1 == happiness2 then + return sort_by_name_desc(unit_id_1, unit_id_2) + end return utils.compare(happiness1, happiness2) end @@ -138,16 +144,16 @@ local function melee_skill_effectiveness(unit, skill_list) -- Skills -- Finding the highest skill - skill_rating = 0 + local skill_rating = 0 for _, skill in ipairs(skill_list) do - melee_skill = dfhack.units.getNominalSkill(unit, skill, true) + local melee_skill = dfhack.units.getNominalSkill(unit, skill, true) skill_rating = math.max(skill_rating, melee_skill) end local melee_combat_rating = dfhack.units.getNominalSkill(unit, df.job_skill.MELEE_COMBAT, true) local rating = skill_rating * 27000 + melee_combat_rating * 9000 - + strength * 180 + body_size_base * 100 + kinesthetic_sense * 50 + endurance * 50 - + agility * 30 + toughness * 20 + willpower * 20 + spatial_sense * 20 + + strength * 180 + body_size_base * 100 + kinesthetic_sense * 50 + endurance * 50 + + agility * 30 + toughness * 20 + willpower * 20 + spatial_sense * 20 return rating end @@ -191,9 +197,9 @@ local function ranged_skill_effectiveness(unit, skill_list) -- Skills -- Finding the highest skill - skill_rating = 0 + local skill_rating = 0 for _, skill in ipairs(skill_list) do - ranged_skill = dfhack.units.getNominalSkill(unit, skill, true) + local ranged_skill = dfhack.units.getNominalSkill(unit, skill, true) skill_rating = math.max(skill_rating, ranged_skill) end local ranged_combat = dfhack.units.getNominalSkill(unit, df.job_skill.RANGED_COMBAT, true) @@ -400,7 +406,7 @@ local function melee_combat_potential(unit) -- melee combat potential rating local rating = strength * 264 + endurance * 84 + body_size_base * 77 + kinesthetic_sense * 74 - + agility * 33 + willpower * 31 + spatial_sense * 27 + toughness * 25 + + agility * 33 + willpower * 31 + spatial_sense * 27 + toughness * 25 return rating end