diff --git a/changelog.txt b/changelog.txt index d21bd28bcd..acac0224cd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -73,6 +73,8 @@ Template for new versions: - `gui/launcher`: refresh default tag filter when mortal mode is toggled in `gui/control-panel` so changes to which tools autocomplete take effect immediately - `gui/civ-alert`: you can now register multiple burrows as civilian alert safe spaces - `exterminate`: add ``all`` target for convenient scorched earth tactics +- `exterminate`: add ``--limit`` option to limit number of exterminated creatures +- `exterminate`: add ``knockout`` and ``traumatize`` method for a non-lethal incapacitation - `caravan`: add shortcut to the trade request screen for selecting item types by value (e.g. so you can quickly select expensive gems or cheap leather) - `gui/notify`: notification panel extended to apply to adventure mode - `gui/control-panel`: highlight prefrences that have been changed from the defaults diff --git a/docs/exterminate.rst b/docs/exterminate.rst index 7c91144b31..5fe271db72 100644 --- a/docs/exterminate.rst +++ b/docs/exterminate.rst @@ -48,6 +48,8 @@ Options on the map. ``-f``, ``--include-friendly`` Specifies the tool should also kill units friendly to the player. +``-l``, ``--limit `` + Set the maximum number of units to exterminate. Methods ------- @@ -64,6 +66,8 @@ Methods :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). Technical details ----------------- diff --git a/exterminate.lua b/exterminate.lua index 127608b5f9..2ad479c4a3 100644 --- a/exterminate.lua +++ b/exterminate.lua @@ -25,6 +25,16 @@ local function isUnitFriendly(unit) if dfhack.units.isDanger(unit) 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 + end + return dfhack.units.isOwnCiv(unit) or dfhack.units.isOwnGroup(unit) or dfhack.units.isVisiting(unit) or @@ -39,6 +49,8 @@ killMethod = { DROWN = 3, VAPORIZE = 4, DISINTEGRATE = 5, + KNOCKOUT = 6, + TRAUMATIZE = 7, } -- removes the unit from existence, leaving no corpse if the unit hasn't died @@ -59,6 +71,18 @@ local function butcherUnit(unit) unit.flags2.slaughter = true end +-- Knocks a unit out for 30k ticks or the target value +local function knockoutUnit(unit, target_value) + target_value = target_value or 30000 + unit.counters.unconscious = target_value +end + +-- Traumatizes the unit, forcing them to stare off into space. Cuts down on pathfinding +local function traumatizeUnit(unit) + unit.mood = df.mood_type.Traumatized +end + + local function drownUnit(unit, liquid_type) previousPositions = previousPositions or {} previousPositions[unit.id] = copyall(unit.pos) @@ -78,24 +102,10 @@ local function drownUnit(unit, liquid_type) createLiquid() end -local function destroyItem(item) - item.flags.garbage_collect = true - item.flags.forbid = true - item.flags.hidden = true -end - -local function destroyContainedItems(container) - for _, item in ipairs(dfhack.items.getContainedItems(container)) do - destroyContainedItems(item) - destroyItem(item) - end -end - local function destroyInventory(unit) - for _, inv_item in ipairs(unit.inventory) do - local item = inv_item.item - destroyContainedItems(item) - destroyItem(item) + for index = #unit.inventory-1, 0, -1 do + local item = unit.inventory[index].item + dfhack.items.remove(item) end end @@ -111,6 +121,10 @@ function killUnit(unit, method) elseif method == killMethod.DISINTEGRATE then vaporizeUnit(unit) destroyInventory(unit) + elseif method == killMethod.KNOCKOUT then + knockoutUnit(unit) + elseif method == killMethod.TRAUMATIZE then + traumatizeUnit(unit) else destroyUnit(unit) end @@ -156,6 +170,7 @@ local options, args = { method = killMethod.INSTANT, only_visible = false, include_friendly = false, + limit = -1, }, {...} local positionals = argparse.processArgsGetopt(args, { @@ -163,6 +178,7 @@ local positionals = argparse.processArgsGetopt(args, { {'m', 'method', handler = function(arg) options.method = killMethod[arg:upper()] end, hasArg = true}, {'o', 'only-visible', handler = function() options.only_visible = true end}, {'f', 'include-friendly', handler = function() options.include_friendly = true end}, + {'l', 'limit', handler = function(arg) options.limit = argparse.positiveInt(arg, 'limit') end, hasArg = true}, }) if not dfhack.isMapLoaded() then @@ -217,7 +233,9 @@ elseif positionals[1]:split(':')[1] == "all" then local 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 @@ -251,7 +269,7 @@ else elseif map_races[selected_race_under] then selected_race = selected_race_under else - qerror("No creatures of this race on the map.") + qerror("No creatures of this race on the map (" .. selected_race .. ").") end end @@ -269,6 +287,9 @@ else target = selected_race 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