From ee81c7396abf14e417bcfa6f83a81602f38d3594 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 9 Aug 2024 21:33:47 -0700 Subject: [PATCH 1/2] use new unit module property functions add some annotations for units in gui/sitemap clean up exterminate --- agitation-rebalance.lua | 8 +-- changelog.txt | 2 + docs/exterminate.rst | 30 +++++----- emigration.lua | 17 +++--- exterminate.lua | 94 ++++++++++--------------------- gui/sitemap.lua | 23 +++++++- internal/notify/notifications.lua | 31 ++++------ 7 files changed, 90 insertions(+), 115 deletions(-) diff --git a/agitation-rebalance.lua b/agitation-rebalance.lua index e6ed365af8..e5a9ad7fdc 100644 --- a/agitation-rebalance.lua +++ b/agitation-rebalance.lua @@ -106,10 +106,6 @@ local function persist_state() dfhack.persistent.saveSiteData(GLOBAL_KEY, state) end -local function is_agitated(unit) - return unit and unit.flags4.agitated_wilderness_creature -end - local world = df.global.world local map_features = world.features.map_features local plotinfo = df.global.plotinfo @@ -237,7 +233,7 @@ end local function get_agitated_units() local agitators = {} for _, unit in ipairs(world.units.active) do - if is_unkilled(unit) and is_agitated(unit) then + if is_unkilled(unit) and dfhack.units.isAgitated(unit) then table.insert(agitators, unit) end end @@ -250,7 +246,7 @@ local function check_new_unit(unit_id) if new_unit_min_frame_counter >= world.frame_counter then return end local unit = df.unit.find(unit_id) if not unit or not is_unkilled(unit) then return end - if state.features.surface and is_agitated(unit) then + if state.features.surface and dfhack.units.isAgitated(unit) then on_surface_attack() return end diff --git a/changelog.txt b/changelog.txt index a385f6b610..059011337e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -35,6 +35,8 @@ Template for new versions: - `timestream`: ensure child growth events (e.g. becoming an adult) are not skipped over ## Misc Improvements +- `gui/sitemap`: show whether a unit is friendly, hostile, or wildlife +- `gui/sitemap`: show whether a unit is caged ## Removed diff --git a/docs/exterminate.rst b/docs/exterminate.rst index 5fe271db72..664019aec2 100644 --- a/docs/exterminate.rst +++ b/docs/exterminate.rst @@ -5,16 +5,15 @@ exterminate :summary: Kill things. :tags: fort armok units -Kills any unit, or all undead, or all units of a given race. You can target any -unit on a revealed tile of the map, including hidden ambushers, but caged or -chained creatures cannot be killed with this tool. +Kills any individual unit, or all undead, or all units of a given race. Caged +and chained creatures are ignored. Usage ----- :: - exterminate + exterminate [list] exterminate this [] exterminate undead [] exterminate all[:] [] @@ -25,10 +24,10 @@ Race and caste names are case insensitive. Examples -------- -``exterminate this`` - Kill the selected unit. ``exterminate`` List the targets on your map. +``exterminate this`` + Kill the selected unit. ``exterminate BIRD_RAVEN:MALE`` Kill the ravens flying around the map (but only the male ones). ``exterminate goblin --method magma --only-visible`` @@ -65,18 +64,21 @@ Methods :drown: Drown the unit in water. :magma: Boil the unit in magma (not recommended for magma-safe creatures). :butcher: Will mark the units for butchering instead of killing them. This is - more useful for pets than armed enemies. -:knockout: Will put units into an unconscious state for 30k ticks (about a month). -:traumatize: Traumatizes all units, forcing them to stare off into space (catatonic state). + useful for pets and not useful for armed enemies. +:knockout: Will put units into an unconscious state for 30k ticks (about a + month in fort mode). +:traumatize: Traumatizes units, forcing them to stare off into space (catatonic + state). Technical details ----------------- -This tool kills by setting a unit's ``blood_count`` to 0, which means -immediate death at the next game tick. For creatures where this is not enough, -such as vampires, it also sets ``animal.vanish_countdown``, allowing the unit -to vanish in a puff of smoke if the blood loss doesn't kill them. +For the ``instant`` method, this tool kills by setting a unit's ``blood_count`` +to 0, which means immediate death at the next game tick. For creatures where +this is not enough, such as vampires, it also sets ``animal.vanish_countdown``, +allowing the unit to vanish in a puff of smoke if the blood loss doesn't kill +them. If the method of choice involves liquids, the tile is filled with a liquid level of 7 every tick. If the target unit moves, the liquid moves along with -it, leaving the vacated tiles clean. +it, leaving the vacated tiles clean (though possibly scorched). diff --git a/emigration.lua b/emigration.lua index 28a1bdee4e..3721535a3d 100644 --- a/emigration.lua +++ b/emigration.lua @@ -153,16 +153,13 @@ function canLeave(unit) return false end - return dfhack.units.isCitizen(unit) and - dfhack.units.isActive(unit) and - not dfhack.units.isOpposedToLife(unit) and - not unit.flags1.merchant and - not unit.flags1.diplomat and - not unit.flags1.chained and - dfhack.units.getNoblePositions(unit) == nil and - unit.military.squad_id == -1 and - not dfhack.units.isBaby(unit) and - not dfhack.units.isChild(unit) + return dfhack.units.isActive(unit) and + dfhack.units.isCitizen(unit) and + not dfhack.units.getNoblePositions(unit) and + not unit.flags1.chained and + unit.military.squad_id == -1 and + not dfhack.units.isBaby(unit) and + not dfhack.units.isChild(unit) end function checkForDeserters(method,civ_id) diff --git a/exterminate.lua b/exterminate.lua index 2ad479c4a3..06b764eb98 100644 --- a/exterminate.lua +++ b/exterminate.lua @@ -14,32 +14,24 @@ local function spawnLiquid(position, liquid_level, liquid_type, update_liquids) map_block.flags.update_liquid_twice = update_liquids end -local function checkUnit(unit) - return (unit.body.blood_count ~= 0 or unit.body.blood_max == 0) and - not unit.flags1.inactive and - not unit.flags1.caged and - not unit.flags1.chained -end - -local function isUnitFriendly(unit) - if dfhack.units.isDanger(unit) then +local function checkUnit(opts, unit) + if not dfhack.units.isActive(unit) or + (unit.body.blood_max ~= 0 and unit.body.blood_count == 0) or + unit.flags1.caged or + unit.flags1.chained + then return false end - local adv = dfhack.world.getAdventurer() - if adv then - if adv == unit or - unit.relationship_ids.GroupLeader == adv.id or - unit.relationship_ids.PetOwner == adv.id - then - return true - end + if opts.only_visible and not dfhack.units.isVisible(unit) then + return false end - - return dfhack.units.isOwnCiv(unit) or - dfhack.units.isOwnGroup(unit) or - dfhack.units.isVisiting(unit) or - dfhack.units.isTame(unit) or - dfhack.units.isDomesticated(unit) + if not opts.include_friendly and not dfhack.units.isDanger(unit) and not dfhack.units.isWildlife(unit) then + return false + end + if opts.selected_caste and opts.selected_caste ~= df.creature_raw.find(unit.race).caste[unit.caste].caste_id then + return false + end + return true end killMethod = { @@ -138,26 +130,17 @@ local function getRaceCastes(race_id) return unit_castes end -local function getMapRaces(only_visible, include_friendly) +local function getMapRaces(opts) local map_races = {} for _, unit in pairs(df.global.world.units.active) do - if only_visible and not dfhack.units.isVisible(unit) then - goto skipunit - end - if not include_friendly and isUnitFriendly(unit) then - goto skipunit - end - if dfhack.units.isActive(unit) and checkUnit(unit) then - local unit_race_name = dfhack.units.isUndead(unit) and "UNDEAD" or df.creature_raw.find(unit.race).creature_id - - local race = ensure_key(map_races, unit_race_name) - race.id = unit.race - race.name = unit_race_name - race.count = (race.count or 0) + 1 - end - :: skipunit :: + if not checkUnit(opts, unit) then goto continue end + local unit_race_name = dfhack.units.isUndead(unit) and "UNDEAD" or df.creature_raw.find(unit.race).creature_id + local race = ensure_key(map_races, unit_race_name) + race.id = unit.race + race.name = unit_race_name + race.count = (race.count or 0) + 1 + ::continue:: end - return map_races end @@ -200,9 +183,9 @@ if positionals[1] == "this" then return end -local map_races = getMapRaces(options.only_visible, options.include_friendly) +local map_races = getMapRaces(options) -if not positionals[1] then +if not positionals[1] or positionals[1] == 'list' then local sorted_races = {} for race, value in pairs(map_races) do table.insert(sorted_races, { name = race, count = value.count }) @@ -224,28 +207,19 @@ if race_name:lower() == 'undead' then qerror("No undead found on the map.") end for _, unit in pairs(df.global.world.units.active) do - if dfhack.units.isUndead(unit) and checkUnit(unit) then + if dfhack.units.isUndead(unit) and checkUnit(options, unit) then killUnit(unit, options.method) count = count + 1 end end elseif positionals[1]:split(':')[1] == "all" then - local selected_caste = positionals[1]:split(':')[2] + options.selected_caste = positionals[1]:split(':')[2] for _, unit in ipairs(df.global.world.units.active) do if options.limit > 0 and count >= options.limit then break end - if not checkUnit(unit) then - goto skipunit - end - if options.only_visible and not dfhack.units.isVisible(unit) then - goto skipunit - end - if not options.include_friendly and isUnitFriendly(unit) then - goto skipunit - end - if selected_caste and selected_caste ~= df.creature_raw.find(unit.race).caste[unit.caste].caste_id then + if not checkUnit(options, unit) then goto skipunit end @@ -285,21 +259,13 @@ else end target = selected_race + options.selected_caste = selected_caste for _, unit in pairs(df.global.world.units.active) do if options.limit > 0 and count >= options.limit then break end - if not checkUnit(unit) then - goto skipunit - end - if options.only_visible and not dfhack.units.isVisible(unit) then - goto skipunit - end - if not options.include_friendly and isUnitFriendly(unit) then - goto skipunit - end - if selected_caste and selected_caste ~= df.creature_raw.find(unit.race).caste[unit.caste].caste_id then + if not checkUnit(options, unit) then goto skipunit end diff --git a/gui/sitemap.lua b/gui/sitemap.lua index 9bbf9eb774..5dd34b48e3 100644 --- a/gui/sitemap.lua +++ b/gui/sitemap.lua @@ -99,6 +99,27 @@ local function zoom_to_next_zone(_, choice) data.next_idx = data.next_idx % #data.zones + 1 end +local function get_unit_disposition_and_pen(unit) + local prefix = unit.flags1.caged and 'caged ' or '' + if dfhack.units.isDanger(unit) then + return prefix..'hostile', COLOR_LIGHTRED + end + if not dfhack.units.isFortControlled(unit) and dfhack.units.isWildlife(unit) then + return prefix..'wildlife', COLOR_GREEN + end + return prefix..'friendly', COLOR_LIGHTGREEN +end + +local function get_unit_choice_text(unit) + local disposition, disposition_pen = get_unit_disposition_and_pen(unit) + return { + dfhack.units.getReadableName(unit), + ' (', + {text=disposition, pen=disposition_pen}, + ')', + } +end + local function get_unit_choices() local is_fort = dfhack.world.isFortressMode() local choices = {} @@ -110,7 +131,7 @@ local function get_unit_choices() goto continue end table.insert(choices, { - text=dfhack.units.getReadableName(unit), + text=get_unit_choice_text(unit), data={ unit_id=unit.id, }, diff --git a/internal/notify/notifications.lua b/internal/notify/notifications.lua index b06f139d90..f3ce18c391 100644 --- a/internal/notify/notifications.lua +++ b/internal/notify/notifications.lua @@ -46,7 +46,7 @@ local function for_agitated_creature(fn, reverse) dfhack.units.isActive(unit) and not unit.flags1.caged and not unit.flags1.chained and - unit.flags4.agitated_wilderness_creature + dfhack.units.isAgitated(unit) end, fn, reverse) end @@ -61,13 +61,6 @@ local function for_invader(fn, reverse) end, fn, reverse) end -local function is_likely_hostile(unit) - return dfhack.units.isCrazed(unit) or - dfhack.units.isOpposedToLife(unit) or - dfhack.units.isSemiMegabeast(unit) or - dfhack.units.isGreatDanger(unit) -end - local function for_hostile(fn, reverse) for_iter(units.active, function(unit) return not dfhack.units.isDead(unit) and @@ -77,8 +70,8 @@ local function for_hostile(fn, reverse) not dfhack.units.isInvader(unit) and not dfhack.units.isFortControlled(unit) and not dfhack.units.isHidden(unit) and - not unit.flags4.agitated_wilderness_creature and - is_likely_hostile(unit) + not dfhack.units.isAgitated(unit) and + dfhack.units.isDanger(unit) end, fn, reverse) end @@ -119,14 +112,14 @@ local function for_nuisance(fn, reverse) for_iter(units.active, function(unit) return not dfhack.units.isDead(unit) and dfhack.units.isActive(unit) and + (is_stealer(unit) or dfhack.units.isMischievous(unit)) and not unit.flags1.caged and not unit.flags1.chained and not dfhack.units.isHidden(unit) and not dfhack.units.isFortControlled(unit) and not dfhack.units.isInvader(unit) and - not unit.flags4.agitated_wilderness_creature and - not is_likely_hostile(unit) and - (is_stealer(unit) or dfhack.units.isMischievous(unit)) + not dfhack.units.isAgitated(unit) and + not dfhack.units.isDanger(unit) end, fn, reverse) end @@ -134,19 +127,17 @@ local function for_wildlife(fn, reverse) for_iter(units.active, function(unit) return not dfhack.units.isDead(unit) and dfhack.units.isActive(unit) and + dfhack.units.isWildlife(unit) and not unit.flags1.caged and not unit.flags1.chained and not dfhack.units.isHidden(unit) and not dfhack.units.isFortControlled(unit) and - not dfhack.units.isInvader(unit) and - not unit.flags4.agitated_wilderness_creature and - not is_likely_hostile(unit) and + not dfhack.units.isDanger(unit) and not is_stealer(unit) and not dfhack.units.isMischievous(unit) and not dfhack.units.isMerchant(unit) and not dfhack.units.isForest(unit) and - not dfhack.units.isVisitor(unit) and - unit.animal.population.population_idx >= 0 + not dfhack.units.isVisitor(unit) end, fn, reverse) end @@ -155,13 +146,13 @@ local function for_wildlife_adv(fn, reverse) for_iter(units.active, function(unit) return not dfhack.units.isDead(unit) and dfhack.units.isActive(unit) and + dfhack.units.isWildlife(unit) and not unit.flags1.caged and not unit.flags1.chained and not dfhack.units.isHidden(unit) and unit.relationship_ids.GroupLeader ~= adv_id and unit.relationship_ids.PetOwner ~= adv_id and - is_adv_unhidden(unit) and - unit.animal.population.population_idx >= 0 + is_adv_unhidden(unit) end, fn, reverse) end From c8bd23fe067debd616cecb14ce2cf642b4d748de Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 9 Aug 2024 21:42:59 -0700 Subject: [PATCH 2/2] remove unneeded checks --- internal/notify/notifications.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/notify/notifications.lua b/internal/notify/notifications.lua index f3ce18c391..cd3341af62 100644 --- a/internal/notify/notifications.lua +++ b/internal/notify/notifications.lua @@ -131,12 +131,9 @@ local function for_wildlife(fn, reverse) not unit.flags1.caged and not unit.flags1.chained and not dfhack.units.isHidden(unit) and - not dfhack.units.isFortControlled(unit) and not dfhack.units.isDanger(unit) and not is_stealer(unit) and not dfhack.units.isMischievous(unit) and - not dfhack.units.isMerchant(unit) and - not dfhack.units.isForest(unit) and not dfhack.units.isVisitor(unit) end, fn, reverse) end