Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Exterminate #1187

Merged
merged 11 commits into from
Jun 22, 2024
2 changes: 2 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions docs/exterminate.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Options
on the map.
``-f``, ``--include-friendly``
Specifies the tool should also kill units friendly to the player.
``-l``, ``--limit <num>``
Set the maximum number of units to exterminate.

Methods
-------
Expand All @@ -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
-----------------
Expand Down
59 changes: 40 additions & 19 deletions exterminate.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does isOwnGroup behave in adventure mode?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesn't. isOwnGroup only checks plotinfo which is irrelevant to adventure mode

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does that mean it always returns false, or that it returns garbage data?

dfhack.units.isVisiting(unit) or
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -156,13 +170,15 @@ local options, args = {
method = killMethod.INSTANT,
only_visible = false,
include_friendly = false,
limit = -1,
}, {...}

local positionals = argparse.processArgsGetopt(args, {
{'h', 'help', handler = function() options.help = true end},
{'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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down