Here are a few "peace of mind" scripts that I made to manage tasks in dwarf fortress.
Communal: Have everyone drink, eat, or take a nap at the same time. Useful if everyone is in the tavern, next to the favored drinks / meals...
-- Do things as a community
local utils=require('utils')
validArgs = utils.invert({
'help',
'nap',
'meal',
'drink'
})
local args = utils.processArgs({...}, validArgs)
local help = false
local nap = false
local meal = false
local drink = false
-- Handle help requests
if args.help then
print(helpme)
return
end
if ( args.nap ) then
nap = true
end
if ( args.meal ) then
meal = true
end
if ( args.drink ) then
drink = true
end
local helpme = [===[
communal.lua
=========
This script makes the citizens in your fort do things as a community.
arguments:
-help
Print this help message
-nap
Siesta!
-meal
It's time to eat!
-drink
It's time for a drink!
]===]
for k,unit in ipairs(df.global.world.units.active) do
if ( dfhack.units.isCitizen(unit) ) then
if nap then
unit.counters2.sleepiness_timer = 53000
end
if meal then
unit.counters2.hunger_timer = 45000
end
if drink then
unit.counters2.thirst_timer = 22000
end
end
end
NeedWatch: All dwarves have a need to frequently craft objects. This script enables a crafting labor when that need gets to be distracting and disables it when it's fulfilled.
-- Enable crafting labors based on needs
-- NeedWatch 0.1
-- Fortress mode script called from DFHack
-- LNP: [04412r02-x64]
local utils=require('utils')
validArgs = utils.invert({
'help',
'verbose'
})
local args = utils.processArgs({...}, validArgs)
local verbose = false
local helpme = [===[
needwatch.lua
=========
This script enables crafting on citizens where the crafting need is < -1000 (lvl * focus) and disables it where it is > -1000.
Future versions may be smarter.
arguments:
-help
print this help message
-verbose
prints debug data to the dfhack console. Default is false
Examples:
Set it to run daily
repeat -name Needs -time 600 -timeUnits ticks -command [ "needwatch" ]
]===]
-- Handle help requests
if args.help then
print(helpme)
return
end
if ( args.verbose ) then
verbose = true
end
function count_this(to_be_counted)
local count = -1
local var1 = ""
while var1 ~= nil do
count = count + 1
var1 = (to_be_counted[count])
end
count=count-1
return count
end
function GetUnitNeeds(unit)
-- Get the unit's needs
local i
local tmpUnitNeedsTable={}
local count_max = count_this(df.need_type)
-- We need to add all the possible needs to a table
for i=0, count_max do
-- Expected table structure is {id, need name, 0 }
table.insert(tmpUnitNeedsTable,{i,df.need_type[i],0})
end
for i=0, #unit.status.current_soul.personality.needs-1 do
tbl_index = unit.status.current_soul.personality.needs[i].id + 1
needs_name = tmpUnitNeedsTable[tbl_index][2]
needs_lvl = unit.status.current_soul.personality.needs[i].need_level
needs_focus = unit.status.current_soul.personality.needs[i].focus_level
needs_threshold = needs_focus
-- This allows us to just name needs. Comparison will break if spelling is ever corrected
if(needs_name == "CraftObject" and needs_threshold <= -1200 ) then
if verbose then print("CraftObject Value: ".. needs_threshold .. ": Enabling CraftWatch") end
-- unit.status.labors.BONE_CARVE = true
unit.status.labors.CUT_GEM = true
-- unit.status.labors.METAL_CRAFT = true
-- unit.status.labors.FORGE_WEAPON = true
end
if(needs_name == "CraftObject" and needs_threshold > -1000 ) then
if verbose then print("CraftObject Value: ".. needs_threshold .. ": Disabling CraftWatch") end
-- unit.status.labors.BONE_CARVE = false
unit.status.labors.CUT_GEM = false
-- unit.status.labors.METAL_CRAFT = false
-- unit.status.labors.FORGE_WEAPON = false
end
end
end
-- Main script begins
for k,u in ipairs(df.global.world.units.active) do
if ( dfhack.units.isCitizen(u) ) then
if verbose then print("Checking: ", dfhack.TranslateName(dfhack.units.getVisibleName(u))) end
GetUnitNeeds(u)
end
end
JobToggle: I noticed that using an alert to force dwarves into the tavern interrupted jobs and created tons of job cancellation spam. This script allows you to save the current labor settings for all citizens, clear the labor settings so citizens can naturally get free time to satisfy needs, and then reload those saved labor profiles at some later point. Uses persist-table, so saved labor data is persistent across play sessions.
-- Job_Toggle: Save and reload labors
-- This script saves and reloads the labors of citizens
-- Tested for FortMode in 44.12
local utils=require('utils')
local persistTable = require 'persist-table'
validArgs = utils.invert({
'help',
'verbose',
'clearState',
'save',
'load',
'nolabor'
})
local args = utils.processArgs({...}, validArgs)
local helpme = [===[
job_toggle.lua
=========
This script saves and reloads the labors of citizens. This could be useful when you want to save or reload complicated labor setups
arguments:
-help
print this help message
-verbose
Print extra log messages
-clearState
Sometimes you just need to start over. Removes all saved labors
-save
Save the current labor layout
-load
Load the saved labor layout. OVERWRITES EXISTING LABORS
-nolabor
Remove ALL labors from all citizens
TODO:
Save and reload different profiles?
Specific units?
Examples
job_toggle -verbose -save
job_toggle -verbose -load
]===]
-- Handle help requests
if args.help then
print(helpme)
return
end
if ( args.verbose ) then
verbose = true
end
-- Main program begins
-- Check for the persistent table. Create if doesn't exist
if not safe_index(persistTable.GlobalTable.whisk,'labors') then
if verbose then print("No labor table found, creating") end
persistTable.GlobalTable.whisk = {}
persistTable.GlobalTable.whisk.labors = {}
labors = persistTable.GlobalTable.whisk.labors
else
-- Try to load data from existing save
if verbose then print("A labor table was found, reusing") end
labors = persistTable.GlobalTable.whisk.labors
end
if ( args.clearState ) then
persistTable.GlobalTable.whisk.labors = nil
print("Persistent table cleared")
end
-- Save: Loop through the citizens' labors, creating a table of labors for each
if ( args.save ) then
local i = 0
for k,u in ipairs(df.global.world.units.active) do
if ( dfhack.units.isCitizen(u) ) then
if verbose then print("Saving", dfhack.TranslateName(dfhack.units.getVisibleName(u))) end
thisUnit = u.id
if verbose then print("Designation: " .. thisUnit) end
labors[thisUnit] = {}
for key,v in pairs(u.status.labors) do
job = tostring(key)
-- Convert the boolean true/false to strings
local enabled = "F"
if v then
enabled = "T"
end
-- Write the data
labors[thisUnit][job] = enabled
if verbose then print(job .. ":" .. labors[thisUnit][job] ) end
end
i = i + 1
end
end
print("Saved labors for " .. i .. " units")
end
-- Load: Loop through the citizens, overwriting labors
if ( args.load ) then
local i = 0
for _,unitChild in ipairs(labors._children) do
if verbose then print(unitChild) end
-- Find the unit record
local unit = df.unit.find(unitChild)
for k,thisJob in ipairs(labors[unitChild]._children) do
local job = thisJob
if verbose then print(job .. ":" .. labors[unitChild][thisJob] ) end
local setJob = false
if ( labors[unitChild][thisJob] == "T" ) then
setJob = true
end
-- Replay stored labors back into labor list
-- TODO: What happens to dead or missing units?
-- Handle labors with no names
if string.find(job, "^%d") then
job = tonumber(job)
unit.status.labors[job] = setJob
else
unit.status.labors[job] = setJob
end
end
i = i + 1
end
print("Labors reloaded for " .. i .. " units")
end
-- No Labor: Sets all labors to false
if ( args.nolabor ) then
local i = 0
for k,u in ipairs(df.global.world.units.active) do
if ( dfhack.units.isCitizen(u) ) then
for key,v in pairs(u.status.labors) do
u.status.labors[key] = false
end
end
end
end
DFCron: Allows you to schedule scripts using dwarf fortress dates. Like the *nix cron, only far less capable. Somewhat useful if you want to schedule scripts (like those above) for specific days. Requires you to create a dfcron.txt file, see the "-example" output for an example.
-- Schedule scripts using DF dates
-- This script allows scripts to be scheduled more easily. It is intended to be
-- called by the repeat script on a daily basis. It reads scheduled jobs from the dfcron.txt file.
-- Inspired by the following thread:
-- http://www.bay12forums.com/smf/index.php?topic=174676.0
-- Tested for FortMode in 44.12
local utils=require('utils')
validArgs = utils.invert({
'help',
'verbose',
'show',
'example'
})
local args = utils.processArgs({...}, validArgs)
local verbose = false
local show = false
local example = false
local helpme = [===[
dfcron.lua
=========
This script allows scripts to be scheduled and run on a specific date. The schedule is read from the dfcron.txt file.
The dfcron.txt file should be in the Dwarf Fortress directory, where the gamelog.txt is. This script should be run daily using repeat.
arguments:
-help
print this help message
-verbose
Print extra log messages
-show
ONLY prints the scheduled jobs from dfcron.txt
-example
Print an example dfcron.txt file. Save this into an empty dfcron.txt file.
Examples
dfcron -show
repeat -name Cron -time 1 -timeUnits days -command [ "dfcron" ]
]===]
local myexample = [===[
# Day/Month/Year|"Script"
# Blank lines and lines starting with "#" are not read.
# This script will run once on the 1st day of Granite in the year 250.
# 1/1/250|script -with parameters
# Run the position command every day
*/*/*|position
]===]
--[[
DF Calendar Months
01: Granite
02: Slate
03: Felsite
04: Hematite
05: Malachite
06: Galena
07: Limestone
08: Sandstone
09: Timber
10: Moonstone
11: Opal
12: Obsidian
Each month has 28 days
--]]
-- Handle help requests
if args.help then
print(helpme)
return
end
if args.example then
print(myexample)
return
end
if ( args.verbose ) then
verbose = true
else
verbose = false
end
if ( args.show ) then
show = true
end
function GetDFDate()
-- Handle the formatting of the Dwarf Fortress date
-- Thanks to Kurik Amudnil for the date stuff
-- http://www.bay12forums.com/smf/index.php?PHPSESSID=669fc6cc7664043c4b34992a301abb0c&topic=91166.msg4247785#msg4247785
-- Would it be useful to return a part of the DF date?
-- local absTick = 1200*28*12*df.global.cur_year + df.global.cur_year_tick
local dfYear = df.global.cur_year
local dfMonth = math.floor((df.global.cur_year_tick / 33600) + 1)
local dfDay = math.floor((df.global.cur_year_tick % 33600)/1200)+1
if(dfMonth < 10) then
dfMonth = "0"..dfMonth
end
if(dfDay < 10) then
dfDay = "0"..dfDay
end
dfDateString = dfYear .. "-" .. dfMonth .. "-" .. dfDay
return dfDateString
end
function dayCheck(dfDay, thisDay)
-- There are 28 days
-- This function should test if the "day" value extracted from the cron entry looks like
-- A single number: 1
-- An asterisk: * (every day)
-- TODO: Day validation
-- TODO: comma separated values (1,6)
-- TODO: ranges (1-3)
-- TODO: Divisors (*/X), where "Date modulus X == 0" returns true
-- TODO: Combinations: 1-10,15
if (tostring(dfDay) == thisDay or thisDay == "*") then
return true
else
return false
end
end
function monthCheck(dfMonth, thisMonth)
-- There are 12 months
-- This function should test if the "month" value extracted from the cron entry looks like
-- A single number: 1
-- An asterisk: * (every month)
if (tostring(dfMonth) == thisMonth or thisMonth == "*") then
return true
else
return false
end
end
function yearCheck(dfYear, thisYear)
-- This function should test if the "year" value extracted from the cron entry looks like
-- A single number: 1
-- An asterisk: * (every year)
if (tostring(dfYear) == thisYear or thisYear == "*") then
return true
else
return false
end
end
-- Main program begins
-- Thanks to Kurik Amudnil for the date stuff
-- http://www.bay12forums.com/smf/index.php?PHPSESSID=669fc6cc7664043c4b34992a301abb0c&topic=91166.msg4247785#msg4247785
-- Get the current day, month, and year
local dfYear = df.global.cur_year
local dfMonth = math.floor((df.global.cur_year_tick / 33600) + 1)
local dfDay = math.floor((df.global.cur_year_tick % 33600)/1200)+1
local fullDate = GetDFDate()
if verbose then print("Today is: Day " .. dfDay .. " of Month " .. dfMonth .. " of Year " .. dfYear) end
-- Load the active cron from file (or persist?)
file = io.open("dfcron.txt", "r")
-- Set the default to file
io.input(file)
local lines = {}
-- read the lines in table 'lines'
for line in io.lines() do
if (line:find("^[#%s]") ~= nil or string.len(line) == 0) then
-- Ignore lines that start with #, a space, or are blank
--if verbose then print(line) end
else
-- TODO: Better Validate lines before processing
_, _, thisDay, thisMonth, thisYear, thisCommand = string.find(line, "^([%d%*]+)/([%d%*]+)/([%d%*]+)|(.*)$")
if show then print(line) end
local passDay = false
local passMonth = false
local passYear = false
-- Check the day
passDay = dayCheck(dfDay, thisDay)
-- Check the month
passMonth = monthCheck(dfMonth, thisMonth)
-- Check the year
passYear = yearCheck(dfYear, thisYear)
-- If everything matches the script's date pattern, run the script
if (not show and passDay and passMonth and passYear) then
if verbose then print("Now Running command: " .. thisCommand) end
dfhack.run_command(thisCommand)
end
end
end