adjust colorization of ratings

move mental stability from a sort to a filter
develop
Myk Taylor 2023-09-08 16:44:59 -07:00
parent d0f08dcc0d
commit 5f32042f1a
No known key found for this signature in database
1 changed files with 146 additions and 30 deletions

@ -9,14 +9,15 @@ local widgets = require('gui.widgets')
local CH_UP = string.char(30)
local CH_DN = string.char(31)
local function get_rating(val, max, med, low)
val = math.min(max, val)
local percent = (val * 100) // max
local color = COLOR_GREEN
if percent < (low or 50) then color = COLOR_RED
elseif percent < (med or 75) then color = COLOR_YELLOW
end
return percent, color
local function get_rating(val, baseline, range, highest, high, med, low)
val = val - (baseline or 0)
range = range or 100
local percentile = (math.min(range, val) * 100) // range
if percentile < (low or 25) then return percentile, COLOR_RED end
if percentile < (med or 50) then return percentile, COLOR_LIGHTRED end
if percentile < (high or 75) then return percentile, COLOR_YELLOW end
if percentile < (highest or 90) then return percentile, COLOR_GREEN end
return percentile, COLOR_LIGHTGREEN
end
local function sort_noop(a, b)
@ -91,7 +92,7 @@ local function get_stress(unit)
end
local function get_stress_rating(unit)
return get_rating(-get_stress(unit) + 100000, 200000, 50, 25)
return get_rating(dfhack.units.getStressCategory(unit), 0, 100, 4, 3, 2, 1)
end
local function sort_by_stress_desc(unit_id_1, unit_id_2)
@ -135,7 +136,7 @@ end
local function get_skill_rating(skill, unit)
local uskill = get_skill(skill, unit)
if not uskill then return nil end
return get_rating(uskill.rating, 100, 5, 0)
return get_rating(uskill.rating, 0, 100, 10, 5, 1, 0)
end
local MELEE_WEAPON_SKILLS = {
@ -175,7 +176,7 @@ local function melee_skill_effectiveness(unit)
end
local function get_melee_skill_effectiveness_rating(unit)
return get_rating(melee_skill_effectiveness(unit), 2000000)
return get_rating(melee_skill_effectiveness(unit), 350000, 2350000, 78, 64, 49, 35)
end
local function make_sort_by_melee_skill_effectiveness_desc()
@ -235,7 +236,7 @@ local function ranged_skill_effectiveness(unit)
end
local function get_ranged_skill_effectiveness_rating(unit)
return get_rating(ranged_skill_effectiveness(unit), 500000)
return get_rating(ranged_skill_effectiveness(unit), 0, 500000, 90, 62, 44, 27)
end
local function make_sort_by_ranged_skill_effectiveness_desc(list)
@ -347,10 +348,6 @@ local function get_mental_stability(unit)
return rating
end
local function get_mental_stability_rating(unit)
return get_rating(get_mental_stability(unit), 100, 10, 0)
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)
@ -404,7 +401,7 @@ local function get_melee_combat_potential(unit)
end
local function get_melee_combat_potential_rating(unit)
return get_rating(get_melee_combat_potential(unit), 2000000)
return get_rating(get_melee_combat_potential(unit), 300000, 2600000, 81, 64, 46, 29)
end
local function sort_by_melee_combat_potential_desc(unit_id_1, unit_id_2)
@ -447,12 +444,12 @@ local function get_ranged_combat_potential(unit)
local kinesthetic_sense = unit.status.current_soul.mental_attrs.KINESTHETIC_SENSE.max_value
-- ranged combat potential formula
local rating = agility * 5 + kinesthetic_sense * 5 + spatial_sense * 2 + focus * 2
local rating = agility * 5 + kinesthetic_sense * 2 + spatial_sense * 5 + focus * 2
return rating
end
local function get_ranged_combat_potential_rating(unit)
return get_rating(get_ranged_combat_potential(unit), 40000)
return get_rating(get_ranged_combat_potential(unit), 0, 70000, 73, 57, 41, 25)
end
local function sort_by_ranged_combat_potential_desc(unit_id_1, unit_id_2)
@ -483,6 +480,54 @@ local function sort_by_ranged_combat_potential_asc(unit_id_1, unit_id_2)
return utils.compare(rating1, rating2)
end
local function get_need(unit)
if not unit or not unit.status.current_soul then return end
for _, need in ipairs(unit.status.current_soul.personality.needs) do
if need.id == df.need_type.MartialTraining and need.focus_level < 0 then
return -need.focus_level
end
end
end
local function get_need_rating(unit)
local focus_level = get_need(unit)
if not focus_level then return end
focus_level = math.min(focus_level, 100000)
return get_rating(100000 - focus_level, 0, 100000, 100, 99, 90, 0)
end
local function sort_by_need_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 = get_need(unit1)
local rating2 = get_need(unit2)
if rating1 == rating2 then
return sort_by_stress_desc(unit_id_1, unit_id_2)
end
if not rating2 then return -1 end
if not rating1 then return 1 end
return utils.compare(rating2, rating1)
end
local function sort_by_need_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 = get_need(unit1)
local rating2 = get_need(unit2)
if rating1 == rating2 then
return sort_by_stress_asc(unit_id_1, unit_id_2)
end
if not rating2 then return 1 end
if not rating1 then return -1 end
return utils.compare(rating1, rating2)
end
local sort_by_any_melee_desc=make_sort_by_melee_skill_effectiveness_desc()
local sort_by_any_melee_asc=make_sort_by_melee_skill_effectiveness_asc()
local sort_by_any_ranged_desc=make_sort_by_ranged_skill_effectiveness_desc()
@ -512,7 +557,7 @@ local SORT_LIBRARY = {
{label='tactics skill', desc_fn=sort_by_tactics_desc, asc_fn=sort_by_tactics_asc, rating_fn=curry(get_skill_rating, df.job_skill.MILITARY_TACTICS)},
{label='migrant wave', desc_fn=sort_by_migrant_wave_desc, asc_fn=sort_by_migrant_wave_asc, rating_fn=get_migrant_wave_rating},
{label='stress level', desc_fn=sort_by_stress_desc, asc_fn=sort_by_stress_asc, rating_fn=get_stress_rating},
{label='mental stability', desc_fn=sort_by_mental_stability_desc, asc_fn=sort_by_mental_stability_asc, rating_fn=get_mental_stability_rating},
{label='need for training', desc_fn=sort_by_need_desc, asc_fn=sort_by_need_asc, rating_fn=get_need_rating},
{label='axe skill', desc_fn=sort_by_axe_desc, asc_fn=sort_by_axe_asc, rating_fn=curry(get_skill_rating, df.job_skill.AXE)},
{label='sword skill', desc_fn=sort_by_sword_desc, asc_fn=sort_by_sword_asc, rating_fn=curry(get_skill_rating, df.job_skill.SWORD)},
{label='mace skill', desc_fn=sort_by_mace_desc, asc_fn=sort_by_mace_asc, rating_fn=curry(get_skill_rating, df.job_skill.MACE)},
@ -538,7 +583,7 @@ SquadAssignmentOverlay.ATTRS{
default_pos={x=18, y=5},
default_enabled=true,
viewscreens='dwarfmode/UnitSelector/SQUAD_FILL_POSITION',
frame={w=38, h=25},
frame={w=38, h=31},
frame_style=gui.FRAME_PANEL,
frame_background=gui.CLEAR_PEN,
autoarrange_subviews=true,
@ -669,15 +714,15 @@ function SquadAssignmentOverlay:init()
on_change=self:callback('refresh_list', 'sort_stress'),
},
widgets.CycleHotkeyLabel{
view_id='sort_mental_stability',
frame={t=6, r=0, w=17},
view_id='sort_need',
frame={t=6, r=0, w=18},
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='need for training', value=sort_noop},
{label='need for training'..CH_DN, value=sort_by_need_desc, pen=COLOR_GREEN},
{label='need for training'..CH_UP, value=sort_by_need_asc, pen=COLOR_YELLOW},
},
option_gap=0,
on_change=self:callback('refresh_list', 'sort_mental_stability'),
on_change=self:callback('refresh_list', 'sort_need'),
},
widgets.CycleHotkeyLabel{
view_id='sort_axe',
@ -808,6 +853,45 @@ function SquadAssignmentOverlay:init()
initial_option='include',
on_change=function() self:refresh_list() end,
},
widgets.CycleHotkeyLabel{
view_id='infant',
frame={l=0},
key='CUSTOM_SHIFT_M',
label='Mothers carrying infants:',
options={
{label='Include', value='include', pen=COLOR_GREEN},
{label='Only', value='only', pen=COLOR_YELLOW},
{label='Exclude', value='exclude', pen=COLOR_RED},
},
initial_option='include',
on_change=function() self:refresh_list() end,
},
widgets.CycleHotkeyLabel{
view_id='unstable',
frame={l=0},
key='CUSTOM_SHIFT_U',
label='Easily stressed units:',
options={
{label='Include', value='include', pen=COLOR_GREEN},
{label='Only', value='only', pen=COLOR_YELLOW},
{label='Exclude', value='exclude', pen=COLOR_RED},
},
initial_option='include',
on_change=function() self:refresh_list() end,
},
widgets.CycleHotkeyLabel{
view_id='maimed',
frame={l=0},
key='CUSTOM_SHIFT_I',
label='Critically injured:',
options={
{label='Include', value='include', pen=COLOR_GREEN},
{label='Only', value='only', pen=COLOR_YELLOW},
{label='Exclude', value='exclude', pen=COLOR_RED},
},
initial_option='include',
on_change=function() self:refresh_list() end,
},
}
end
@ -846,6 +930,23 @@ local function is_nobility(unit)
return false
end
local function has_infant(unit)
-- TODO
return false
end
local function is_unstable(unit)
-- stddev percentiles are 61, 48, 35, 23
-- let's go with one stddev below the mean (35) as the cutoff
local _, color = get_rating(get_mental_stability(unit), -40, 80, 35, 0, 0, 0)
return color ~= COLOR_LIGHTGREEN
end
local function is_maimed(unit)
-- TODO
return false
end
local function filter_matches(unit_id, filter)
if unit_id == -1 then return true end
local unit = df.unit.find(unit_id)
@ -856,6 +957,12 @@ local function filter_matches(unit_id, filter)
if filter.officials == 'exclude' and is_elected_or_appointed_official(unit) then return false end
if filter.nobles == 'only' and not is_nobility(unit) then return false end
if filter.nobles == 'exclude' and is_nobility(unit) then return false end
if filter.infant == 'only' and not has_infant(unit) then return false end
if filter.infant == 'exclude' and has_infant(unit) then return false end
if filter.unstable == 'only' and not is_unstable(unit) then return false end
if filter.unstable == 'exclude' and is_unstable(unit) then return false end
if filter.maimed == 'only' and not is_maimed(unit) then return false end
if filter.maimed == 'exclude' and is_maimed(unit) then return false end
if #filter.search == 0 then return true end
local search_key = dfhack.TranslateName(dfhack.units.getVisibleName(unit))
return normalize_search_key(search_key):find(dfhack.toSearchNormalized(filter.search))
@ -865,14 +972,20 @@ local function is_noop_filter(filter)
return #filter.search == 0 and
filter.military == 'include' and
filter.officials == 'include' and
filter.nobles == 'include'
filter.nobles == 'include' and
filter.infant == 'include' and
filter.unstable == 'include' and
filter.maimed == 'include'
end
local function is_filter_equal(a, b)
return a.search == b.search and
a.military == b.military and
a.officials == b.officials and
a.nobles == b.nobles
a.nobles == b.nobles and
a.infant == b.infant and
a.unstable == b.unstable and
a.maimed == b.maimed
end
local unit_selector = df.global.game.main_interface.unit_selector
@ -944,7 +1057,7 @@ local SORT_WIDGET_NAMES = {
'sort_tactics',
'sort_migrant_wave',
'sort_stress',
'sort_mental_stability',
'sort_need',
'sort_axe',
'sort_sword',
'sort_mace',
@ -970,6 +1083,9 @@ function SquadAssignmentOverlay:refresh_list(sort_widget, sort_fn)
military=self.subviews.military:getOptionValue(),
officials=self.subviews.officials:getOptionValue(),
nobles=self.subviews.nobles:getOptionValue(),
infant=self.subviews.infant:getOptionValue(),
unstable=self.subviews.unstable:getOptionValue(),
maimed=self.subviews.maimed:getOptionValue(),
}
filter_vector(filter, self.prev_filter or {})
self.prev_filter = filter