From 26b81cfbbd8c9ac6625bb62ed821a94a834681a7 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Wed, 30 Aug 2023 09:39:13 +0300 Subject: [PATCH] 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)