Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
A variant of Kurik Amudnil's hire-guard.lua that forces the unit to become a civ creature rather than checking.
-- Takes slaves. Hooks reactions that start with LUA_HOOK_TAKE_SLAVE. Copied very much from hire-guard.lua:
sample reaction
[NAME:Take someone ]
product field must exist, using quantity 0 because it
doesn'tneed to actually produce anything.
product dimension determines sight radius in a circle with shadow cast visibility (default 10).
product probability + effective skill level determines chance to convert
each valid in range target, limit 1. If the first found target fails,
the second found target has the same chance etc. If all fail, reagents
are consumed as normal (respects PRESERVE_REAGENTS if present)
if no valid targets in range are found, forces PRESERVE_REAGENTS
success: "{guard name} has been taken as a slave!" in bright green
no targets: "{Worker Name}, {Worker Profession} cancels {reaction name}: No visible targets in range." in yellow
failed: "{Reaction name} failed." in dark red
local eventful = require 'plugins.eventful'
local mo = require 'makeown'
local fov = require 'fov'
local utils = require 'utils'
-- (removed indents since I am not using them)
function wrap(str, limit)--, indent, indent1)
--indent = indent or ""
--indent1 = indent1 or indent
local limit = limit or 72
local here = 1 ---#indent1
return str:gsub("(%s+)()(%S+)()", --indent1..str:gsub(
function(sp, st, word, fi)
if fi-here > limit then
here = st -- - #indent
return "\n"..word --..indent..word
function isSeen(view,p)
return p.z == view.z and view[p.y][p.x] > 0
function findSlaveCaste(unitRaw)
for k,caste in ipairs(unitRaw.caste) do
if string.find(caste.caste_id,"SLAVE") then return k end
return nil
function cbHireGuard(reaction,unit,input_items,input_reagents,output_items,call_native)
local probability = reaction.products[0].probability + dfhack.units.getEffectiveSkill(unit,reaction.skill)
local radius = reaction.products[0].product_dimension
if radius < 1 then radius = 10 end
local view = fov.get_fov(radius,unit.pos)
local i,v,u, found, success
--find applicable unit
v =
for i=#v-1,0,-1 do -- search list in reverse, guards/invaders are probably nearer the end of the list
u = v[i]
if u.flags1.forest and not dfhack.units.isDead(u) and not dfhack.units.isDwarf(u) and
not u.flags1.caged and isSeen(view,u.pos) and dfhack.units.isSane(u) then
--found one, check success
found = true
if math.random(100) < probability then
u.enemy.normal_race =
u.enemy.normal_caste = findSlaveCaste( or 0 --0 is default/FEMALE, usually
success = true
-- override presearve reagents to consume? or let the reaction def control it?
if not found then -- no valid targets
local lines = utils.split_string( wrap( string.format("%s, %s cancels %s: No visible targets in range.",
dfhack.TranslateName(dfhack.units.getVisibleName(unit)), dfhack.units.getProfessionName(unit), ) , NEWLINE )
for _,v in ipairs(lines) do
dfhack.gui.showAnnouncement( v, COLOR_YELLOW)
for _,v in ipairs(input_reagents or {}) do
v.flags.PRESERVE_REAGENT = true
--unit.job.current_job.flags.suspend = true
elseif success then
dfhack.gui.showAnnouncement( dfhack.TranslateName(" has been taken as a slave!" , COLOR_GREEN, true)--color[,is_bright]
--if no successful targets, force reagents consumed? (same probability chance to recover reagent? for each item/stack?)
--currently let the reaction's presearve reagents determine result here.
dfhack.gui.showAnnouncement(" failed.", COLOR_RED)
call_native.value = false
function string.starts(String,Start)
return string.sub(String,1,string.len(Start))==Start
dfhack.onStateChange.loadTakeSlave = function(code)
local registered_reactions
if code==SC_MAP_LOADED then
--registered_reactions = {}
for i,reaction in ipairs( do
-- register each applicable reaction (to avoid doing string check
-- for every lua hook reaction (not just ours), this way uses identity check
if string.starts(reaction.code,'LUA_HOOK_TAKE_SLAVE') then
-- register reaction.code
-- save reaction.code
registered_reactions = true
--if #registered_reactions > 0 then print('HireGuard: Loaded') end
if registered_reactions then print('HireGuard: Loaded') end
elseif code==SC_MAP_UNLOADED then
--[[ doesn't seem to be working, and probably not needed
registered_reactions = registered_reactions or {}
if #registered_reactions > 0 then print('HireGuard: Unloaded') end
for i,reaction in ipairs(registered_reactions) do
-- un register each registered reaction (to prevent persistance between
-- differing worlds (probably irrelavant, but doesn't hurt)
-- un register reaction.code
registered_reactions = nil -- clear registered_reactions
-- if dfhack.init has already been run, force it to think SC_WORLD_LOADED to that reactions get refreshed
if dfhack.isMapLoaded() then dfhack.onStateChange.loadTakeSlave(SC_MAP_LOADED) end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.