Remove scripts/ folder

develop
lethosor 2016-06-14 19:40:48 -04:00
parent 450fcdba31
commit 6258870e2c
172 changed files with 0 additions and 28178 deletions

18
.gitmodules vendored

@ -10,21 +10,3 @@
[submodule "depends/clsocket"]
path = depends/clsocket
url = git://github.com/DFHack/clsocket.git
[submodule "scripts/3rdparty/lethosor"]
path = scripts/3rdparty/lethosor
url = git://github.com/DFHack/lethosor-scripts
[submodule "scripts/3rdparty/roses"]
path = scripts/3rdparty/roses
url = git://github.com/DFHack/roses-scripts.git
[submodule "scripts/3rdparty/maxthyme"]
path = scripts/3rdparty/maxthyme
url = git://github.com/DFHack/maxthyme-scripts.git
[submodule "scripts/3rdparty/dscorbett"]
path = scripts/3rdparty/dscorbett
url = git://github.com/DFHack/dscorbett-scripts.git
[submodule "scripts/3rdparty/kane-t"]
path = scripts/3rdparty/kane-t
url = git://github.com/DFHack/kane-t-scripts.git
[submodule "scripts/3rdparty/maienm"]
path = scripts/3rdparty/maienm
url = git://github.com/DFHack/maienm-scripts.git

@ -1 +0,0 @@
Subproject commit 4353c10401ced7aec89f002947d1252f30237789

@ -1 +0,0 @@
Subproject commit 0a75d5ff69916cf9b3739f4b20d36ab4cfdcf824

@ -1 +0,0 @@
Subproject commit 704aed4447f27ae802dae6994479ebc9c46568cc

@ -1 +0,0 @@
Subproject commit 45c78449e71d1ba263044fb00108509088ad0026

@ -1 +0,0 @@
Subproject commit b337e931b8b7a167ee5ce1ac6b5c3155c291f260

@ -1 +0,0 @@
Subproject commit 4b6e772654df6805b66f77900a4618bbf9b54dab

@ -1,14 +0,0 @@
include(Scripts.cmake)
DFHACK_3RDPARTY_SCRIPT_REPO(dscorbett)
DFHACK_3RDPARTY_SCRIPT_REPO(kane-t)
DFHACK_3RDPARTY_SCRIPT_REPO(lethosor)
DFHACK_3RDPARTY_SCRIPT_REPO(maienm)
DFHACK_3RDPARTY_SCRIPT_REPO(maxthyme)
# DFHACK_3RDPARTY_SCRIPT_REPO(roses)
install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts
DESTINATION ${DFHACK_DATA_DESTINATION}
FILES_MATCHING PATTERN "*.lua"
PATTERN "*.rb"
PATTERN "3rdparty" EXCLUDE
)

@ -1,19 +0,0 @@
include(../plugins/Plugins.cmake)
MACRO(DFHACK_SCRIPTS)
PARSE_ARGUMENTS(SCRIPT
"SUBDIRECTORY"
"SOME_OPT"
${ARGN}
)
CAR(SCRIPT_SUBDIRECTORY ${SCRIPT_SUBDIRECTORY})
install(FILES ${SCRIPT_DEFAULT_ARGS}
DESTINATION ${DFHACK_DATA_DESTINATION}/scripts/${SCRIPT_SUBDIRECTORY})
ENDMACRO()
MACRO(DFHACK_3RDPARTY_SCRIPT_REPO repo_path)
if(NOT EXISTS ${dfhack_SOURCE_DIR}/scripts/3rdparty/${repo_path}/CMakeLists.txt)
MESSAGE(SEND_ERROR "Script submodule scripts/3rdparty/${repo_path} does not exist - run `git submodule update --init`.")
endif()
add_subdirectory(3rdparty/${repo_path})
ENDMACRO()

@ -1,2 +0,0 @@
Basic scripts are not stored in any subdirectory, and can be invoked directly.
They are generally useful tools for any player.

@ -1,104 +0,0 @@
# View or set cavern adaptation levels
# based on removebadthoughts.rb
=begin
adaptation
==========
View or set level of cavern adaptation for the selected unit or the whole fort.
Usage: ``adaptation <show|set> <him|all> [value]``. The ``value`` must be
between 0 and 800,000 inclusive.
=end
# Color constants, values mapped to color_value enum in include/ColorText.h
COLOR_GREEN = 2
COLOR_RED = 4
COLOR_YELLOW = 14
COLOR_WHITE = 15
def usage(s)
if nil != s
puts(s)
end
puts "Usage: adaptation <show|set> <him|all> [value]"
throw :script_finished
end
mode = $script_args[0] || 'help'
who = $script_args[1]
value = $script_args[2]
if 'help' == mode
usage(nil)
elsif 'show' != mode && 'set' != mode
usage("Invalid mode '#{mode}': must be either 'show' or 'set'")
end
if nil == who
usage("Target not specified")
elsif 'him' != who && 'all' != who
usage("Invalid target '#{who}'")
end
if 'set' == mode
if nil == value
usage("Value not specified")
elsif !/[[:digit:]]/.match(value)
usage("Invalid value '#{value}'")
end
if 0 > value.to_i || 800000 < value.to_i
usage("Value must be between 0 and 800000")
end
value = value.to_i
end
num_set = 0
set_adaptation_value = lambda { |u,v|
next if !df.unit_iscitizen(u)
next if u.flags1.dead
u.status.misc_traits.each { |t|
if t.id == :CaveAdapt
# TBD: expose the color_ostream console and color values of
# t.value based on adaptation level
if mode == 'show'
if df.respond_to?(:print_color)
print "Unit #{u.id} (#{u.name}) has an adaptation of "
case t.value
when 0..399999
#df.print_color(COLOR_GREEN, "#{t.value}\n")
print "#{t.value}\n"
when 400000..599999
df.print_color(COLOR_YELLOW, "#{t.value}\n")
else
df.print_color(COLOR_RED, "#{t.value}\n")
end
else
puts "Unit #{u.id} (#{u.name}) has an adaptation of #{t.value}"
end
elsif mode == 'set'
puts "Unit #{u.id} (#{u.name}) changed from #{t.value} to #{v}"
t.value = v
num_set += 1
end
end
}
}
case who
when 'him'
if u = df.unit_find
set_adaptation_value[u,value]
else
puts 'Please select a dwarf ingame'
end
when 'all'
df.unit_citizens.each { |uu|
set_adaptation_value[uu,value]
}
end
if 'set' == mode
puts "#{num_set} unit#{'s' if num_set != 1} updated."
end

@ -1,93 +0,0 @@
-- Adds emotions to creatures.
--@ module = true
--[[=begin
add-thought
===========
Adds a thought or emotion to the selected unit. Can be used by other scripts,
or the gui invoked by running ``add-thought gui`` with a unit selected.
=end]]
local utils=require('utils')
function addEmotionToUnit(unit,thought,emotion,severity,strength,subthought)
local emotions=unit.status.current_soul.personality.emotions
if not (type(emotion)=='number') then emotion=df.emotion_type[emotion] end
if not (type(thought)=='number') then thought=df.unit_thought_type[thought] end
emotions:insert('#',{new=df.unit_personality.T_emotions,
type=emotion,
unk2=1,
strength=strength,
thought=thought,
subthought=subthought,
severity=severity,
flags=0,
unk7=0,
year=df.global.cur_year,
year_tick=df.global.cur_year_tick
})
local divider=df.emotion_type.attrs[emotion].divider
if divider~=0 then
unit.status.current_soul.personality.stress_level=unit.status.current_soul.personality.stress_level+math.ceil(severity/df.emotion_type.attrs[emotion].divider)
end
end
validArgs = validArgs or utils.invert({
'unit',
'thought',
'emotion',
'severity',
'strength',
'subthought',
'gui'
})
function tablify(iterableObject)
t={}
for k,v in ipairs(iterableObject) do
t[k] = v~=nil and v or 'nil'
end
return t
end
if moduleMode then
return
end
local args = utils.processArgs({...}, validArgs)
local unit = args.unit and df.unit.find(args.unit) or dfhack.gui.getSelectedUnit(true)
if not unit then qerror('A unit must be specified or selected.') end
if args.gui then
local script=require('gui.script')
script.start(function()
local tok,thought=script.showListPrompt('emotions','Which thought?',COLOR_WHITE,tablify(df.unit_thought_type),10,true)
if tok then
local eok,emotion=script.showListPrompt('emotions','Which emotion?',COLOR_WHITE,tablify(df.emotion_type),10,true)
if eok then
local sok,severity=script.showInputPrompt('emotions','At what severity?',COLOR_WHITE,'0')
if sok then
local stok,strength=script.showInputPrompt('emotions','At what strength?',COLOR_WHITE,'0')
if stok then
addEmotionToUnit(unit,thought,emotion,severity,strength,0)
end
end
end
end
end)
else
local thought = args.thought or 180
local emotion = args.emotion or -1
local severity = args.severity or 0
local subthought = args.subthought or 0
local strength = args.strength or 0
addEmotionToUnit(unit,thought,emotion,severity,strength,subthought)
end

@ -1,202 +0,0 @@
-- Adjust all attributes of all dwarves to an ideal
-- by vjek
--[[=begin
armoks-blessing
===============
Runs the equivalent of `rejuvenate`, `elevate-physical`, `elevate-mental`, and
`brainwash` on all dwarves currently on the map. This is an extreme change,
which sets every stat to an ideal - legendary skills, great traits, and
easy-to-satisfy preferences.
Without arguments, all attributes, age & personalities are adjusted.
Arguments allow for skills to be adjusted as well.
=end]]
function rejuvenate(unit)
if unit==nil then
print ("No unit available! Aborting with extreme prejudice.")
return
end
local current_year=df.global.cur_year
local newbirthyear=current_year - 20
if unit.relations.birth_year < newbirthyear then
unit.relations.birth_year=newbirthyear
end
if unit.relations.old_year < current_year+100 then
unit.relations.old_year=current_year+100
end
end
-- ---------------------------------------------------------------------------
function brainwash_unit(unit)
if unit==nil then
print ("No unit available! Aborting with extreme prejudice.")
return
end
local profile ={75,25,25,75,25,25,25,99,25,25,25,50,75,50,25,75,75,50,75,75,25,75,75,50,75,25,50,25,75,75,75,25,75,75,25,75,25,25,75,75,25,75,75,75,25,75,75,25,25,50}
local i
for i=1, #profile do
unit.status.current_soul.personality.traits[i-1]=profile[i]
end
end
-- ---------------------------------------------------------------------------
function elevate_attributes(unit)
if unit==nil then
print ("No unit available! Aborting with extreme prejudice.")
return
end
local ok,f,t,k = pcall(pairs,unit.status.current_soul.mental_attrs)
if ok then
for k,v in f,t,k do
v.value=v.max_value
end
end
local ok,f,t,k = pcall(pairs,unit.body.physical_attrs)
if ok then
for k,v in f,t,k do
v.value=v.max_value
end
end
end
-- ---------------------------------------------------------------------------
-- this function will return the number of elements, starting at zero.
-- useful for counting things where #foo doesn't work
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 make_legendary(skillname,unit)
local skillnamenoun,skillnum
if unit==nil then
print ("No unit available! Aborting with extreme prejudice.")
return
end
if (df.job_skill[skillname]) then
skillnamenoun = df.job_skill.attrs[df.job_skill[skillname]].caption_noun
else
print ("The skill name provided is not in the list.")
return
end
if skillnamenoun ~= nil then
utils = require 'utils'
skillnum = df.job_skill[skillname]
utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = skillnum, rating = 20 }, 'id')
print (unit.name.first_name.." is now a Legendary "..skillnamenoun)
else
print ("Empty skill name noun, bailing out!")
return
end
end
-- ---------------------------------------------------------------------------
function BreathOfArmok(unit)
if unit==nil then
print ("No unit available! Aborting with extreme prejudice.")
return
end
local i
local count_max = count_this(df.job_skill)
utils = require 'utils'
for i=0, count_max do
utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = i, rating = 20 }, 'id')
end
print ("The breath of Armok has engulfed "..unit.name.first_name)
end
-- ---------------------------------------------------------------------------
function LegendaryByClass(skilltype,v)
unit=v
if unit==nil then
print ("No unit available! Aborting with extreme prejudice.")
return
end
utils = require 'utils'
local i
local skillclass
local count_max = count_this(df.job_skill)
for i=0, count_max do
skillclass = df.job_skill_class[df.job_skill.attrs[i].type]
if skilltype == skillclass then
print ("Skill "..df.job_skill.attrs[i].caption.." is type: "..skillclass.." and is now Legendary for "..unit.name.first_name)
utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = i, rating = 20 }, 'id')
end
end
end
-- ---------------------------------------------------------------------------
function PrintSkillList()
local count_max = count_this(df.job_skill)
local i
for i=0, count_max do
print("'"..df.job_skill.attrs[i].caption.."' "..df.job_skill[i].." Type: "..df.job_skill_class[df.job_skill.attrs[i].type])
end
print ("Provide the UPPER CASE argument, for example: PROCESSPLANTS rather than Threshing")
end
-- ---------------------------------------------------------------------------
function PrintSkillClassList()
local i
local count_max = count_this(df.job_skill_class)
for i=0, count_max do
print(df.job_skill_class[i])
end
print ("Provide one of these arguments, and all skills of that type will be made Legendary")
print ("For example: Medical will make all medical skills legendary")
end
-- ---------------------------------------------------------------------------
function adjust_all_dwarves(skillname)
for _,v in ipairs(df.global.world.units.all) do
if v.race == df.global.ui.race_id then
print("Adjusting "..dfhack.TranslateName(dfhack.units.getVisibleName(v)))
brainwash_unit(v)
elevate_attributes(v)
rejuvenate(v)
if skillname then
if skillname=="Normal" or skillname=="Medical" or skillname=="Personal" or skillname=="Social" or skillname=="Cultural" or skillname=="MilitaryWeapon" or skillname=="MilitaryAttack" or skillname=="MilitaryDefense" or skillname=="MilitaryMisc" then
LegendaryByClass(skillname,v)
elseif skillname=="all" then
BreathOfArmok(v)
else
make_legendary(skillname,v)
end
end
end
end
end
-- ---------------------------------------------------------------------------
-- main script operation starts here
-- ---------------------------------------------------------------------------
local opt = ...
local skillname
if opt then
if opt=="list" then
PrintSkillList()
return
end
if opt=="classes" then
PrintSkillClassList()
return
end
skillname = opt
else
print ("No skillname supplied, no skills will be adjusted. Pass argument 'list' to see a skill list, 'classes' to show skill classes, or use 'all' if you want all skills legendary.")
end
adjust_all_dwarves(skillname)

@ -1,192 +0,0 @@
# Select crops to plant based on current stocks
=begin
autofarm
========
Automatically handle crop selection in farm plots based on current plant stocks.
Selects a crop for planting if current stock is below a threshold.
Selected crops are dispatched on all farmplots.
Usage::
autofarm start
autofarm default 30
autofarm threshold 150 helmet_plump tail_pig
=end
class AutoFarm
def initialize
@thresholds = Hash.new(50)
@lastcounts = Hash.new(0)
end
def setthreshold(id, v)
list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id }
if tok = df.match_rawname(id, list)
@thresholds[tok] = v.to_i
else
puts "No plant with id #{id}, try one of " +
list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.sort.join(' ')
end
end
def setdefault(v)
@thresholds.default = v.to_i
end
def is_plantable(plant)
has_seed = plant.flags[:SEED]
season = df.cur_season
harvest = df.cur_season_tick + plant.growdur * 10
will_finish = harvest < 10080
can_plant = has_seed && plant.flags[season]
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
can_plant
end
def find_plantable_plants
plantable = {}
counts = Hash.new(0)
df.world.items.other[:SEEDS].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact)
counts[i.mat_index] += i.stack_size
end
}
counts.keys.each { |i|
if df.ui.tasks.discovered_plants[i]
plant = df.world.raws.plants.all[i]
if is_plantable(plant)
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
end
end
}
return plantable
end
def set_farms(plants, farms)
return if farms.length == 0
if plants.length == 0
plants = [-1]
end
season = df.cur_season
farms.each_with_index { |f, idx|
f.plant_id[season] = plants[idx % plants.length]
}
end
def process
plantable = find_plantable_plants
@lastcounts = Hash.new(0)
df.world.items.other[:PLANT].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact && plantable.has_key?(i.mat_index))
id = df.world.raws.plants.all[i.mat_index].id
@lastcounts[id] += i.stack_size
end
}
return unless @running
plants_s = []
plants_u = []
plantable.each_key { |k|
plant = df.world.raws.plants.all[k]
if (@lastcounts[plant.id] < @thresholds[plant.id])
plants_s.push(k) if plantable[k] == :Surface
plants_u.push(k) if plantable[k] == :Underground
end
}
farms_s = []
farms_u = []
df.world.buildings.other[:FARM_PLOT].each { |f|
if (f.flags.exists)
underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean
farms_s.push(f) unless underground
farms_u.push(f) if underground
end
}
set_farms(plants_s, farms_s)
set_farms(plants_u, farms_u)
end
def start
return if @running
@onupdate = df.onupdate_register('autofarm', 1200) { process }
@running = true
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
stat = @running ? "Running." : "Stopped."
@lastcounts.each { |k,v|
stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}"
}
@thresholds.each { |k,v|
stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k)
}
stat << "\nDefault: #{@thresholds.default}"
stat
end
end
$AutoFarm ||= AutoFarm.new
case $script_args[0]
when 'start', 'enable'
$AutoFarm.start
puts $AutoFarm.status
when 'end', 'stop', 'disable'
$AutoFarm.stop
puts 'Stopped.'
when 'default'
$AutoFarm.setdefault($script_args[1])
when 'threshold'
t = $script_args[1]
$script_args[2..-1].each {|i|
$AutoFarm.setthreshold(i, t)
}
when 'delete'
$AutoFarm.stop
$AutoFarm = nil
when 'help', '?'
puts <<EOS
Automatically handle crop selection in farm plots based on current plant stocks.
Selects a crop for planting if current stock is below a threshold.
Selected crops are dispatched on all farmplots.
Usage:
autofarm start
autofarm default 30
autofarm threshold 150 helmet_plump tail_pig
EOS
else
$AutoFarm.process
puts $AutoFarm.status
end

@ -1,79 +0,0 @@
-- Run an autolabor command for skill-affected labors.
--[[=begin
autolabor-artisans
==================
Runs an `autolabor` command, for all labors where skill level
influences output quality. Examples::
autolabor-artisans 0 2 3
autolabor-artisans disable
=end]]
local artisan_labors = {
"CARPENTER",
"DETAIL",
"MASON",
"ARCHITECT",
"ANIMALTRAIN",
"LEATHER",
"WEAVER",
"CLOTHESMAKER",
"COOK",
"FORGE_WEAPON",
"FORGE_ARMOR",
"FORGE_FURNITURE",
"METAL_CRAFT",
"CUT_GEM",
"ENCRUST_GEM",
"WOOD_CRAFT",
"STONE_CRAFT",
"BONE_CARVE",
"GLASSMAKER",
"SIEGECRAFT",
"BOWYER",
"MECHANIC",
"DYER",
"POTTERY",
"WAX_WORKING",
}
local args = {...}
function make_cmd(labor)
local cmd = string.format("autolabor %s", labor)
for i, arg in ipairs(args) do
cmd = cmd .. " " .. arg
end
return cmd
end
function run()
if #args == 0 or args[1] == "help" then
print('Applies an autolabor command to all labors with quality-based output.')
print('')
print('Examples:')
print(' autolabor-artisans 0 2 3')
print(' autolabor-artisans disable')
return false
end
dfhack.run_command("autolabor enable")
-- Test with one to make sure the arguments are valid.
local cmd = make_cmd(artisan_labors[1])
local output, status = dfhack.run_command_silent(cmd)
if status ~= CR_OK then
qerror("Invalid arguments.", status)
return false
end
for i, labor in ipairs(artisan_labors) do
dfhack.run_command(make_cmd(labor))
end
return true
end
run()

@ -1,49 +0,0 @@
# un-suspend construction jobs, on a recurring basis
=begin
autounsuspend
=============
Automatically unsuspend jobs in workshops, on a recurring basis.
See `unsuspend` for one-off use, or `resume` ``all``.
=end
class AutoUnsuspend
attr_accessor :running
def process
count = 0
df.world.job_list.each { |job|
if job.job_type == :ConstructBuilding and job.flags.suspend and df.map_tile_at(job).designation.flow_size <= 1
job.flags.suspend = false
count += 1
end
}
if count > 0
puts "Unsuspended #{count} job(s)."
df.process_jobs = true
end
end
def start
@running = true
@onupdate = df.onupdate_register('autounsuspend', 5) { process if @running }
end
def stop
@running = false
df.onupdate_unregister(@onupdate)
end
end
case $script_args[0]
when 'start'
$AutoUnsuspend ||= AutoUnsuspend.new
$AutoUnsuspend.start
when 'end', 'stop'
$AutoUnsuspend.stop
else
puts $AutoUnsuspend && $AutoUnsuspend.running ? 'Running.' : 'Stopped.'
end

@ -1,132 +0,0 @@
# convenient way to ban cooking categories of food
=begin
ban-cooking
===========
A more convenient way to ban cooking various categories of foods than the
kitchen interface. Usage: ``ban-cooking <type>``. Valid types are ``booze``,
``honey``, ``tallow``, ``oil``, ``seeds`` (non-tree plants with seeds),
``brew``, ``mill``, ``thread``, and ``milk``.
=end
already_banned = {}
kitchen = df.ui.kitchen
kitchen.item_types.length.times { |i|
already_banned[[kitchen.mat_types[i], kitchen.mat_indices[i], kitchen.item_types[i], kitchen.item_subtypes[i]]] = kitchen.exc_types[i] & 1
}
ban_cooking = lambda { |mat_type, mat_index, type|
subtype = -1
key = [mat_type, mat_index, type, subtype]
if already_banned[key]
next if already_banned[key] == 1
index = kitchen.mat_types.zip(kitchen.mat_indices, kitchen.item_types, kitchen.item_subtypes)
kitchen.exc_types[index] |= 1
already_banned[key] = 1
next
end
df.ui.kitchen.mat_types << mat_type
df.ui.kitchen.mat_indices << mat_index
df.ui.kitchen.item_types << type
df.ui.kitchen.item_subtypes << subtype
df.ui.kitchen.exc_types << 1
already_banned[key] = 1
}
$script_args.each do |arg|
case arg
when 'booze'
df.world.raws.plants.all.each_with_index do |p, i|
p.material.each_with_index do |m, j|
if m.flags[:ALCOHOL]
ban_cooking[j + DFHack::MaterialInfo::PLANT_BASE, i, :DRINK]
end
end
end
df.world.raws.creatures.all.each_with_index do |c, i|
c.material.each_with_index do |m, j|
if m.flags[:ALCOHOL]
ban_cooking[j + DFHack::MaterialInfo::CREATURE_BASE, i, :DRINK]
end
end
end
when 'honey'
# hard-coded in the raws of the mead reaction
honey = df.decode_mat('CREATURE:HONEY_BEE:HONEY')
ban_cooking[honey.mat_type, honey.mat_index, :LIQUID_MISC]
when 'tallow'
df.world.raws.creatures.all.each_with_index do |c, i|
c.material.each_with_index do |m, j|
if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('SOAP_MAT')
ban_cooking[j + DFHack::MaterialInfo::CREATURE_BASE, i, :GLOB]
end
end
end
when 'oil'
df.world.raws.plants.all.each_with_index do |p, i|
p.material.each_with_index do |m, j|
if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('SOAP_MAT')
ban_cooking[j + DFHack::MaterialInfo::PLANT_BASE, i, :LIQUID_MISC]
end
end
end
when 'seeds'
df.world.raws.plants.all.each do |p|
m = df.decode_mat(p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat).material
ban_cooking[p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat, :PLANT] if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('SEED_MAT')
if not p.flags[:TREE]
p.growths.each do |g|
m = df.decode_mat(g).material
ban_cooking[g.mat_type, g.mat_index, :PLANT_GROWTH] if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('SEED_MAT')
end
end
end
when 'brew'
df.world.raws.plants.all.each do |p|
m = df.decode_mat(p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat).material
ban_cooking[p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat, :PLANT] if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('DRINK_MAT')
p.growths.each do |g|
m = df.decode_mat(g).material
ban_cooking[g.mat_type, g.mat_index, :PLANT_GROWTH] if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('DRINK_MAT')
end
end
when 'mill'
df.world.raws.plants.all.each do |p|
ban_cooking[p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat, :PLANT] if m.flags[:MILL]
end
when 'thread'
df.world.raws.plants.all.each do |p|
ban_cooking[p.material_defs.type_basic_mat, p.material_defs.idx_basic_mat, :PLANT] if m.flags[:THREAD]
end
when 'milk'
df.world.raws.creatures.all.each_with_index do |c, i|
c.material.each_with_index do |m, j|
if m.reaction_product and m.reaction_product.id and m.reaction_product.id.include?('CHEESE_MAT')
ban_cooking[j + DFHack::MaterialInfo::CREATURE_BASE, i, :LIQUID_MISC]
end
end
end
else
puts "ban-cooking booze - bans cooking of drinks"
puts "ban-cooking honey - bans cooking of honey bee honey"
puts "ban-cooking tallow - bans cooking of tallow"
puts "ban-cooking oil - bans cooking of oil"
puts "ban-cooking seeds - bans cooking of plants that have seeds (tree seeds don't count)"
puts "ban-cooking brew - bans cooking of plants that can be brewed into alcohol"
puts "ban-cooking mill - bans cooking of plants that can be milled into powder"
puts "ban-cooking thread - bans cooking of plants that can be turned into thread"
puts "ban-cooking milk - bans cooking of creature liquids that can be turned into cheese"
end
end

@ -1,51 +0,0 @@
-- Apply or remove binary patches at runtime.
--[[=begin
binpatch
========
Implements functions for in-memory binpatches. See `binpatches`.
=end]]
local bp = require('binpatch')
function run_command(cmd,name)
local pfix = name..': '
local patch, err = bp.load_dif_file(name)
if not patch then
dfhack.printerr(pfix..err)
return
end
if cmd == 'check' then
local status, addr = patch:status()
if status == 'conflict' then
dfhack.printerr(string.format('%sconflict at address %x', pfix, addr))
else
print(pfix..'patch is '..status)
end
elseif cmd == 'apply' then
local ok, msg = patch:apply()
if ok then
print(pfix..msg)
else
dfhack.printerr(pfix..msg)
end
elseif cmd == 'remove' then
local ok, msg = patch:remove()
if ok then
print(pfix..msg)
else
dfhack.printerr(pfix..msg)
end
else
qerror('Invalid command: '..cmd)
end
end
local cmd,name = ...
if not cmd or not name then
qerror('Usage: binpatch check/apply/remove <patchname>')
end
run_command(cmd, name)

@ -1,70 +0,0 @@
-- Brainwash a dwarf, modifying their personality
-- usage is: target a unit in DF, and execute this script in dfhack
-- by vjek
--[[=begin
brainwash
=========
Modify the personality traits of the selected dwarf to match an 'ideal'
personality - as stable and reliable as possible. This makes dwarves very
stable, preventing tantrums even after months of misery.
=end]]
function brainwash_unit(profile)
local i,unit_name
unit=dfhack.gui.getSelectedUnit()
if unit==nil then
print ("No unit under cursor! Aborting with extreme prejudice.")
return
end
unit_name=dfhack.TranslateName(dfhack.units.getVisibleName(unit))
print("Previous personality values for "..unit_name)
printall(unit.status.current_soul.personality.traits)
--now set new personality
for i=1, #profile do
unit.status.current_soul.personality.traits[i-1]=profile[i]
end
print("New personality values for "..unit_name)
printall(unit.status.current_soul.personality.traits)
print(unit_name.." has been brainwashed, praise Armok!")
end
-- main script starts here
-- profiles are listed here and passed to the brainwash function
--
local baseline={50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50}
local ideal={75,25,25,75,25,25,25,99,25,25,25,50,75,50,25,75,75,50,75,75,25,75,75,50,75,25,50,25,75,75,75,25,75,75,25,75,25,25,75,75,25,75,75,75,25,75,75,25,25,50}
local stepford={99,1,1,99,1,1,1,99,1,1,1,1,50,50,1,99,99,50,50,50,1,1,99,50,50,50,50,50,50,99,50,1,1,99,1,99,1,1,99,99,1,99,99,99,1,50,50,1,1,1}
local wrecked={1,99,99,1,99,99,99,1,99,99,99,1,1,99,99,1,1,1,1,1,99,1,1,99,1,99,99,99,1,1,1,99,1,1,99,1,99,99,1,1,99,1,1,1,99,1,1,99,99,99}
local opt = ...
if opt then
if opt=="ideal" then
brainwash_unit(ideal)
return
end
if opt=="baseline" then
brainwash_unit(baseline)
return
end
if opt=="stepford" then
brainwash_unit(stepford)
return
end
if opt=="wrecked" then
brainwash_unit(wrecked)
return
end
else
print ("Invalid or missing personality argument.\nValid choices are ideal , baseline , stepford, and wrecked.")
print ("ideal will create a reliable dwarf with generally positive personality traits.")
print ("baseline will reset all personality traits to a default / the average.")
print ("stepford amplifies all good qualities to an excessive degree.")
print ("wrecked amplifies all bad qualities to an excessive degree.")
end

@ -1,27 +0,0 @@
-- allows burial in unowned coffins
-- by Putnam https://gist.github.com/Putnam3145/e7031588f4d9b24b9dda
--[[=begin
burial
======
Sets all unowned coffins to allow burial. ``burial -pets`` also allows burial
of pets.
=end]]
local utils=require('utils')
validArgs = validArgs or utils.invert({
'pets'
})
local args = utils.processArgs({...}, validArgs)
for k,v in ipairs(df.global.world.buildings.other.COFFIN) do
if v.owner_id==-1 then
v.burial_mode.allow_burial=true
if not args.pets then
v.burial_mode.no_pets=true
end
end
end

@ -1,97 +0,0 @@
-- Make cats just /multiply/.
--[[=begin
catsplosion
===========
Makes cats (and other animals) just *multiply*. It is not a good idea to run this
more than once or twice.
Usage:
:catsplosion: Make all cats pregnant
:catsplosion list: List IDs of all animals on the map
:catsplosion ID ...: Make animals with given ID(s) pregnant
Animals will give birth within two in-game hours (100 ticks or fewer).
=end]]
world = df.global.world
if not dfhack.isWorldLoaded() then
qerror('World not loaded.')
end
args = {...}
list_only = false
creatures = {}
if #args > 0 then
for _, arg in pairs(args) do
if arg == 'list' then
list_only = true
else
creatures[arg:upper()] = true
end
end
else
creatures.CAT = true
end
total = 0
total_changed = 0
total_created = 0
males = {}
females = {}
for _, unit in pairs(world.units.all) do
local id = world.raws.creatures.all[unit.race].creature_id
males[id] = males[id] or {}
females[id] = females[id] or {}
table.insert((dfhack.units.isFemale(unit) and females or males)[id], unit)
end
if list_only then
print("Type Male # Female #")
-- sort IDs alphabetically
local ids = {}
for id in pairs(males) do
table.insert(ids, id)
end
table.sort(ids)
for _, id in pairs(ids) do
print(("%22s %6d %8d"):format(id, #males[id], #females[id]))
end
return
end
for id in pairs(creatures) do
local females = females[id] or {}
total = total + #females
for _, female in pairs(females) do
if female.relations.pregnancy_timer ~= 0 then
female.relations.pregnancy_timer = math.random(1, 100)
total_changed = total_changed + 1
elseif not female.relations.pregnancy_genes then
local preg = df.unit_genes:new()
preg.appearance:assign(female.appearance.genes.appearance)
preg.colors:assign(female.appearance.genes.colors)
female.relations.pregnancy_genes = preg
female.relations.pregnancy_timer = math.random(1, 100)
female.relations.pregnancy_caste = 1
total_created = total_created + 1
end
end
end
if total_changed ~= 0 then
print(("%d pregnancies accelerated."):format(total_changed))
end
if total_created ~= 0 then
print(("%d pregnancies created."):format(total_created))
end
if total == 0 then
qerror("No creatures matched.")
end
print(("Total creatures checked: %d"):format(total))

@ -1,82 +0,0 @@
-- List, create, or change wild colonies (eg honey bees)
-- By PeridexisErrant and Warmist
local help = [[=begin
colonies
========
List vermin colonies, place honey bees, or convert all vermin
to honey bees. Usage:
:colonies: List all vermin colonies on the map.
:colonies place: Place a honey bee colony under the cursor.
:colonies convert: Convert all existing colonies to honey bees.
The ``place`` and ``convert`` subcommands by default create or
convert to honey bees, as this is the most commonly useful.
However both accept an optional flag to use a different vermin
type, for example ``colonies place ANT`` creates an ant colony
and ``colonies convert TERMITE`` ends your beekeeping industry.
=end]]
function findVermin(target_verm)
for k,v in pairs(df.global.world.raws.creatures.all) do
if v.creature_id == target_verm then
return k
end
end
qerror("No vermin found with name: "..target_verm)
end
function list_colonies()
for idx, col in pairs(df.global.world.vermin.colonies) do
race = df.global.world.raws.creatures.all[col.race].creature_id
print(race..' at '..col.pos.x..', '..col.pos.y..', '..col.pos.z)
end
end
function convert_vermin_to(target_verm)
local vermin_id = findVermin(target_verm)
local changed = 0
for _, verm in pairs(df.global.world.vermin.colonies) do
verm.race = vermin_id
verm.caste = -1 -- check for queen bee?
verm.amount = 18826
verm.visible = true
changed = changed + 1
end
print('Converted '..changed..' colonies to '..target_verm)
end
function place_vermin(target_verm)
local pos = copyall(df.global.cursor)
if pos.x == -30000 then
qerror("Cursor must be pointing somewhere")
end
local verm = df.vermin:new()
verm.race = findVermin(target_verm)
verm.flags.is_colony = true
verm.caste = -1 -- check for queen bee?
verm.amount = 18826
verm.visible = true
verm.pos:assign(pos)
df.global.world.vermin.colonies:insert("#", verm)
df.global.world.vermin.all:insert("#", verm)
end
local args = {...}
local target_verm = args[2] or "HONEY_BEE"
if args[1] == 'help' or args[1] == '?' then
print(help)
elseif args[1] == 'convert' then
convert_vermin_to(target_verm)
elseif args[1] == 'place' then
place_vermin(target_verm)
else
if #df.global.world.vermin.colonies < 1 then
dfhack.printerr('There are no colonies on the map.')
end
list_colonies()
end

@ -1,207 +0,0 @@
# create first necessity items under cursor
=begin
create-items
============
Spawn items under the cursor, to get your fortress started.
The first argument gives the item category, the second gives the material,
and the optionnal third gives the number of items to create (defaults to 20).
Currently supported item categories: ``boulder``, ``bar``, ``plant``, ``log``,
``web``.
Instead of material, using ``list`` makes the script list eligible materials.
The ``web`` item category will create an uncollected cobweb on the floor.
Note that the script does not enforce anything, and will let you create
boulders of toad blood and stuff like that.
However the ``list`` mode will only show 'normal' materials.
Examples::
create-items boulders COAL_BITUMINOUS 12
create-items plant tail_pig
create-items log list
create-items web CREATURE:SPIDER_CAVE_GIANT:SILK
create-items bar CREATURE:CAT:SOAP
create-items bar adamantine
=end
category = $script_args[0] || 'help'
mat_raw = $script_args[1] || 'list'
count = $script_args[2]
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help'
if category == 'help'
puts <<EOS
Create first necessity items under the cursor.
Usage:
create-items [category] [raws token] [number]
Item categories:
bars, boulders, plants, logs, webs, anvils
Raw token:
Either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
(the missing part is autocompleted depending on the item category)
Use 'list' to show all possibilities
Exemples:
create-items boulders hematite 30
create-items bars CREATURE_MAT:CAT:SOAP 10
create-items web cave_giant
create-items plants list
EOS
throw :script_finished
elsif mat_raw == 'list'
# allowed with no cursor
elsif df.cursor.x == -30000
puts "Please place the game cursor somewhere"
throw :script_finished
elsif !(maptile = df.map_tile_at(df.cursor))
puts "Error: unallocated map block !"
throw :script_finished
elsif !maptile.shape_passablehigh
puts "Error: impassible tile !"
throw :script_finished
end
def match_list(tok, list)
if tok != 'list'
tok = df.match_rawname(tok, list)
if not tok
puts "Invalid raws token, use one in:"
tok = 'list'
end
end
if tok == 'list'
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
throw :script_finished
end
tok
end
case category
when 'bars'
# create metal bar, eg createbar INORGANIC:IRON
cls = DFHack::ItemBarst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
customize = lambda { |item|
item.dimension = 150
item.subtype = -1
}
when 'boulders'
cls = DFHack::ItemBoulderst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_STONE]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
when 'plants'
cls = DFHack::ItemPlantst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
puts mat_raw
end
when 'logs'
cls = DFHack::ItemWoodst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'WOOD' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
puts mat_raw
end
when 'webs'
cls = DFHack::ItemThreadst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.creatures.all.find_all { |cre|
cre.material.find { |mat| mat.id == 'SILK' }
}.map { |cre| cre.creature_id }
mat_raw = match_list(mat_raw, list)
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
puts mat_raw
end
count ||= 1
customize = lambda { |item|
item.flags.spider_web = true
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
}
when 'anvils'
cls = DFHack::ItemAnvilst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
count ||= 1
end
mat = df.decode_mat mat_raw
count ||= 20
count.to_i.times {
item = cls.cpp_new
item.id = df.item_next_id
item.stack_size = 1
item.mat_type = mat.mat_type
item.mat_index = mat.mat_index
customize[item] if customize
df.item_next_id += 1
item.categorize(true)
df.world.items.all << item
item.pos = df.cursor
item.flags.on_ground = true
df.map_tile_at.mapblock.items << item.id
df.map_tile_at.occupancy.item = true
}
# move game view, so that the ui menu updates
if df.cursor.z > 5
df.curview.feed_keys(:CURSOR_DOWN_Z)
df.curview.feed_keys(:CURSOR_UP_Z)
else
df.curview.feed_keys(:CURSOR_UP_Z)
df.curview.feed_keys(:CURSOR_DOWN_Z)
end

@ -1,75 +0,0 @@
# show death cause of a creature
=begin
deathcause
==========
Select a body part ingame, or a unit from the :kbd:`u` unit list, and this
script will display the cause of death of the creature.
=end
def display_death_event(e)
str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}"
str << " (cause: #{e.death_cause.to_s.downcase}),"
str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1
str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON
str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.shooter_item_subtype].name}" if e.weapon.shooter_item_type == :WEAPON
puts str.chomp(',') + '.'
end
def display_death_unit(u)
death_info = u.counters.death_tg
killer = death_info.killer_tg if death_info
str = "The #{u.race_tg.name[0]}"
str << " #{u.name}" if u.name.has_name
str << " died"
str << " in year #{death_info.event_year}" if death_info
str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
puts str.chomp(',') + '.'
end
item = df.item_find(:selected)
unit = df.unit_find(:selected)
if !item or !item.kind_of?(DFHack::ItemBodyComponent)
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
end
if item and item.kind_of?(DFHack::ItemBodyComponent)
hf = item.hist_figure_id
elsif unit
hf = unit.hist_figure_id
end
if not hf
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
elsif hf == -1
if unit ||= item.unit_tg
display_death_unit(unit)
else
puts "Not a historical figure, cannot death find info"
end
else
histfig = df.world.history.figures.binsearch(hf)
unit = histfig ? df.unit_find(histfig.unit_id) : nil
if unit and not unit.flags1.dead and not unit.flags3.ghostly
puts "#{unit.name} is not dead yet !"
else
events = df.world.history.events
(0...events.length).reverse_each { |i|
e = events[i]
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
display_death_event(e)
break
end
}
end
end

@ -1,81 +0,0 @@
# Increase the rate at which clothes wear out
=begin
deteriorateclothes
==================
Somewhere between a "mod" and a "fps booster", with a small impact on
vanilla gameplay. All of those slightly worn wool shoes that dwarves
scatter all over the place will deteriorate at a greatly increased rate,
and eventually just crumble into nothing. As warm and fuzzy as a dining
room full of used socks makes your dwarves feel, your FPS does not like it.
Usage: ``deteriorateclothes (start|stop)``
=end
class DeteriorateClothes
def initialize
end
def process
return false unless @running
items = [df.world.items.other[:GLOVES],
df.world.items.other[:ARMOR],
df.world.items.other[:SHOES],
df.world.items.other[:PANTS],
df.world.items.other[:HELM]]
items.each { |type|
type.each { |i|
if (i.subtype.armorlevel == 0 and i.flags.on_ground == true and i.wear > 0)
i.wear_timer *= i.wear + 0.5
if (i.wear > 2)
i.flags.garbage_collect = true
end
end
}
}
end
def start
@onupdate = df.onupdate_register('deteriorateclothes', 1200, 1200) { process }
@running = true
puts "Deterioration of old clothes commencing..."
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
@running ? 'Running.' : 'Stopped.'
end
end
case $script_args[0]
when 'start'
if ($DeteriorateClothes)
$DeteriorateClothes.stop
end
$DeteriorateClothes = DeteriorateClothes.new
$DeteriorateClothes.start
when 'end', 'stop'
$DeteriorateClothes.stop
else
if $DeteriorateClothes
puts $DeteriorateClothes.status
else
puts 'Not loaded.'
end
end

@ -1,106 +0,0 @@
# Make corpse parts decay and vanish over time
=begin
deterioratecorpses
==================
Somewhere between a "mod" and a "fps booster", with a small impact on
vanilla gameplay.
In long running forts, especially evil biomes, you end up with a lot
of toes, teeth, fingers, and limbs scattered all over the place.
Various corpses from various sieges, stray kitten corpses, probably
some heads. Basically, your map will look like a giant pile of
assorted body parts, all of which individually eat up a small part
of your FPS, which collectively eat up quite a bit.
In addition, this script also targets various butchery byproducts.
Enjoying your thriving animal industry? Your FPS does not. Those
thousands of skulls, bones, hooves, and wool eat up precious FPS
that could be used to kill goblins and elves. Whose corpses will
also get destroyed by the script to kill more goblins and elves.
This script causes all of those to rot away into nothing after
several months.
Usage: ``deterioratecorpses (start|stop)``
=end
class DeteriorateCorpses
def initialize
end
def process
return false unless @running
df.world.items.other[:ANY_CORPSE].each { |i|
if (i.flags.dead_dwarf == false)
i.wear_timer += 1
if (i.wear_timer > 24 + rand(8))
i.wear_timer = 0
i.wear += 1
end
if (i.wear > 3)
i.flags.garbage_collect = true
end
end
}
df.world.items.other[:REMAINS].each { |i|
if (i.flags.dead_dwarf == false)
i.wear_timer += 1
if (i.wear_timer > 6)
i.wear_timer = 0
i.wear += 1
end
if (i.wear > 3)
i.flags.garbage_collect = true
end
end
}
end
def start
@onupdate = df.onupdate_register('deterioratecorpses', 1200, 1200) { process }
@running = true
puts "Deterioration of body parts commencing..."
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
@running ? 'Running.' : 'Stopped.'
end
end
case $script_args[0]
when 'start'
if ($DeteriorateCorpses)
$DeteriorateCorpses.stop
end
$DeteriorateCorpses = DeteriorateCorpses.new
$DeteriorateCorpses.start
when 'end', 'stop'
$DeteriorateCorpses.stop
else
if $DeteriorateCorpses
puts $DeteriorateCorpses.status
else
puts 'Not loaded.'
end
end

@ -1,96 +0,0 @@
# Food and plants decay, and vanish after a few months
=begin
deterioratefood
===============
Somewhere between a "mod" and a "fps booster", with a small impact on
vanilla gameplay.
With this script running, all food and plants wear out and disappear
after several months. Barrels and stockpiles will keep them from
rotting, but it won't keep them from decaying. No more sitting on a
hundred years worth of food. No more keeping barrels of pig tails
sitting around until you decide to use them. Either use it, eat it,
or lose it. Seeds, are excluded from this, if you aren't planning on
using your pig tails, hold onto the seeds for a rainy day.
This script is...pretty far reaching. However, almost all long
running forts I've had end up sitting on thousands and thousands of
food items. Several thousand cooked meals, three thousand plump
helmets, just as many fish and meat. It gets pretty absurd. And your
FPS doesn't like it.
Usage: ``deterioratefood (start|stop)``
=end
class DeteriorateFood
def initialize
end
def process
return false unless @running
items = [df.world.items.other[:FISH],
df.world.items.other[:FISH_RAW],
df.world.items.other[:EGG],
df.world.items.other[:CHEESE],
df.world.items.other[:PLANT],
df.world.items.other[:PLANT_GROWTH],
df.world.items.other[:FOOD]]
items.each { |type|
type.each { |i|
i.wear_timer += 1
if (i.wear_timer > 24 + rand(8))
i.wear_timer = 0
i.wear += 1
end
if (i.wear > 3)
i.flags.garbage_collect = true
end
}
}
end
def start
@onupdate = df.onupdate_register('deterioratefood', 1200, 1200) { process }
@running = true
puts "Deterioration of food commencing..."
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
@running ? 'Running.' : 'Stopped.'
end
end
case $script_args[0]
when 'start'
if ($DeteriorateFood)
$DeteriorateFood.stop
end
$DeteriorateFood = DeteriorateFood.new
$DeteriorateFood.start
when 'end', 'stop'
$DeteriorateFood.stop
else
if $DeteriorateFood
puts $DeteriorateFood.status
else
puts 'Not loaded.'
end
end

@ -1,6 +0,0 @@
``devel/*`` scripts are intended for developer use, but many may
be of interest to anyone investigating odd phenomema or just messing
around. They are documented to encourage such inquiry.
Some can PERMANENTLY DAMAGE YOUR SAVE if misused, so please be careful.
The warnings are real; if in doubt make backups before running the command.

@ -1,15 +0,0 @@
-- Changes the first name of all units to "Bob"
--author expwnent
--
--[[=begin
devel/all-bob
=============
Changes the first name of all units to "Bob".
Useful for testing `modtools/interaction-trigger` events.
=end]]
for _,v in ipairs(df.global.world.units.all) do
v.name.first_name = "Bob"
end

@ -1,59 +0,0 @@
-- basic check for release readiness
--[[=begin
devel/check-release
===================
Basic checks for release readiness
=end]]
ok = true
function err(s)
dfhack.printerr(s)
ok = false
end
function warn(s)
dfhack.color(COLOR_YELLOW)
dfhack.print(s .. '\n')
dfhack.color(nil)
end
dfhack_ver = dfhack.getDFHackVersion()
git_desc = dfhack.getGitDescription()
git_commit = dfhack.getGitCommit()
if not dfhack.isRelease() then
err('This build is not tagged as a release')
print[[
This is probably due to missing git tags.
Try running `git fetch origin --tags` in the DFHack source tree.
]]
end
expected_git_desc = ('%s-0-g%s'):format(dfhack_ver, git_commit:sub(1, 7))
if git_desc:sub(1, #expected_git_desc) ~= expected_git_desc then
err(([[Bad git description:
Expected %s, got %s]]):format(expected_git_desc, git_desc))
print[[
Ensure that the DFHack source tree is up-to-date (`git pull`) and CMake is
installing DFHack to this DF folder.
]]
end
if not dfhack.gitXmlMatch() then
err('library/xml submodule commit does not match tracked commit\n' ..
('Expected %s, got %s'):format(
dfhack.getGitXmlCommit():sub(1, 7),
dfhack.getGitXmlExpectedCommit():sub(1, 7)
))
print('Try running `git submodule update` in the DFHack source tree.')
end
if dfhack.isPrerelease() then
warn('This build is marked as a prerelease.')
print('If this is not intentional, be sure your DFHack tree is up-to-date\n' ..
'(`git pull`) and try again.')
end
if not ok then
err('This build is not release-ready!')
else
print('Release checks succeeded')
end

@ -1,26 +0,0 @@
-- Clear script environment
--[[=begin
devel/clear-script-env
======================
Clears the environment of the specified lua script(s).
=end]]
args = {...}
if #args < 1 then qerror("script name(s) required") end
for _, name in pairs(args) do
local file = dfhack.findScript(name)
if file then
local script = dfhack.internal.scripts[file]
if script then
local env = script.env
while next(env) do
env[next(env)] = nil
end
else
dfhack.printerr("Script not loaded: " .. name)
end
else
dfhack.printerr("Can't find script: " .. name)
end
end

@ -1,50 +0,0 @@
-- Lists and/or compares two tiletype material groups.
--[[=begin
devel/cmptiles
==============
Lists and/or compares two tiletype material groups.
Usage: ``devel/cmptiles material1 [material2]``
=end]]
local nmat1,nmat2=...
local mat1 = df.tiletype_material[nmat1]
local mat2 = df.tiletype_material[nmat2]
local tmat1 = {}
local tmat2 = {}
local attrs = df.tiletype.attrs
for i=df.tiletype._first_item,df.tiletype._last_item do
local shape = df.tiletype_shape[attrs[i].shape] or ''
local variant = df.tiletype_variant[attrs[i].variant] or ''
local special = df.tiletype_special[attrs[i].special] or ''
local direction = attrs[i].direction or ''
local code = shape..':'..variant..':'..special..':'..direction
if attrs[i].material == mat1 then
tmat1[code] = true
end
if attrs[i].material == mat2 then
tmat2[code] = true
end
end
local function list_diff(n, t1, t2)
local lst = {}
for k,v in pairs(t1) do
if not t2[k] then
lst[#lst+1] = k
end
end
table.sort(lst)
for k,v in ipairs(lst) do
print(n, v)
end
end
list_diff(nmat1,tmat1,tmat2)
list_diff(nmat2,tmat2,tmat1)

@ -1,515 +0,0 @@
-- Exports an ini file for Dwarf Therapist.
--[[=begin
devel/export-dt-ini
===================
Exports an ini file containing memory addresses for Dwarf Therapist.
=end]]
local utils = require 'utils'
local ms = require 'memscan'
-- Utility functions
local globals = df.global
local global_addr = dfhack.internal.getAddress
local os_type = dfhack.getOSType()
local rdelta = dfhack.internal.getRebaseDelta()
local lines = {}
local complete = true
local function header(name)
table.insert(lines, '')
table.insert(lines, '['..name..']')
end
local function value(name,addr)
local line
if not addr then
complete = false
line = name..'=0x0'
elseif addr < 0x10000 then
line = string.format('%s=0x%04x',name,addr)
else
line = string.format('%s=0x%08x',name,addr)
end
table.insert(lines, line)
end
local function address(name,base,field,...)
local addr
if base == globals then
addr = global_addr(field)
if addr and select('#',...) > 0 then
_,addr = df.sizeof(ms.field_ref(base,field,...))
end
elseif base._kind == 'class-type' then
-- field_offset crashes with classes due to vtable problems,
-- so we have to create a real temporary object here.
local obj = df.new(base)
if obj then
local _,a1 = df.sizeof(obj)
local _,a2 = df.sizeof(ms.field_ref(obj,field,...))
addr = a2-a1
obj:delete()
end
else
addr = ms.field_offset(base,field,...)
end
value(name, addr)
end
-- List of actual values
header('addresses')
address('cur_year_tick',globals,'cur_year_tick')
address('current_year',globals,'cur_year')
address('dwarf_civ_index',globals,'ui','civ_id')
address('dwarf_race_index',globals,'ui','race_id')
address('fortress_entity',globals,'ui','main','fortress_entity')
address('historical_entities_vector',globals,'world','entities','all')
address('creature_vector',globals,'world','units','all')
address('active_creature_vector',globals,'world','units','active')
address('weapons_vector',globals,'world','items','other','WEAPON')
address('shields_vector',globals,'world','items','other', 'SHIELD')
address('quivers_vector',globals,'world','items','other', 'QUIVER')
address('crutches_vector',globals,'world','items','other', 'CRUTCH')
address('backpacks_vector',globals,'world','items','other', 'BACKPACK')
address('ammo_vector',globals,'world','items','other', 'AMMO')
address('flasks_vector',globals,'world','items','other', 'FLASK')
address('pants_vector',globals,'world','items','other', 'PANTS')
address('armor_vector',globals,'world','items','other', 'ARMOR')
address('shoes_vector',globals,'world','items','other', 'SHOES')
address('helms_vector',globals,'world','items','other', 'HELM')
address('gloves_vector',globals,'world','items','other', 'GLOVES')
address('artifacts_vector',globals,'world','artifacts','all')
address('squad_vector',globals,'world','squads','all')
address('activities_vector',globals,'world','activities','all')
address('fake_identities_vector',globals,'world','identities','all')
address('poetic_forms_vector',globals,'world','poetic_forms','all')
address('musical_forms_vector',globals,'world','musical_forms','all')
address('dance_forms_vector',globals,'world','dance_forms','all')
address('occupations_vector',globals,'world','occupations','all')
address('world_data',globals,'world','world_data')
address('material_templates_vector',globals,'world','raws','material_templates')
address('inorganics_vector',globals,'world','raws','inorganics')
address('plants_vector',globals,'world','raws','plants','all')
address('races_vector',globals,'world','raws','creatures','all')
address('itemdef_weapons_vector',globals,'world','raws','itemdefs','weapons')
address('itemdef_trap_vector',globals,'world','raws','itemdefs','trapcomps')
address('itemdef_toy_vector',globals,'world','raws','itemdefs','toys')
address('itemdef_tool_vector',globals,'world','raws','itemdefs','tools')
address('itemdef_instrument_vector',globals,'world','raws','itemdefs','instruments')
address('itemdef_armor_vector',globals,'world','raws','itemdefs','armor')
address('itemdef_ammo_vector',globals,'world','raws','itemdefs','ammo')
address('itemdef_siegeammo_vector',globals,'world','raws','itemdefs','siege_ammo')
address('itemdef_glove_vector',globals,'world','raws','itemdefs','gloves')
address('itemdef_shoe_vector',globals,'world','raws','itemdefs','shoes')
address('itemdef_shield_vector',globals,'world','raws','itemdefs','shields')
address('itemdef_helm_vector',globals,'world','raws','itemdefs','helms')
address('itemdef_pant_vector',globals,'world','raws','itemdefs','pants')
address('itemdef_food_vector',globals,'world','raws','itemdefs','food')
address('language_vector',globals,'world','raws','language','words')
address('translation_vector',globals,'world','raws','language','translations')
address('colors_vector',globals,'world','raws','language','colors')
address('shapes_vector',globals,'world','raws','language','shapes')
address('reactions_vector',globals,'world','raws','reactions')
address('base_materials',globals,'world','raws','mat_table','builtin')
address('all_syndromes_vector',globals,'world','raws','syndromes','all')
address('events_vector',globals,'world','history','events')
address('historical_figures_vector',globals,'world','history','figures')
address('world_site_type',df.world_site,'type')
address('active_sites_vector',df.world_data,'active_site')
header('offsets')
address('word_table',df.language_translation,'words')
value('string_buffer_offset', 0x0000)
header('word_offsets')
address('base',df.language_word,'word')
address('noun_singular',df.language_word,'forms','Noun')
address('noun_plural',df.language_word,'forms','NounPlural')
address('adjective',df.language_word,'forms','Adjective')
address('verb',df.language_word,'forms','Verb')
address('present_simple_verb',df.language_word,'forms','Verb3rdPerson')
address('past_simple_verb',df.language_word,'forms','VerbPast')
address('past_participle_verb',df.language_word,'forms','VerbPassive')
address('present_participle_verb',df.language_word,'forms','VerbGerund')
address('words',df.language_name,'words')
address('word_type',df.language_name,'parts_of_speech')
address('language_id',df.language_name,'language')
header('general_ref_offsets')
--WARNING below value should be: "general_ref::vtable","1","0x8","0x4","vmethod","getType","general_ref_type",""
value('ref_type',0x8)
address('artifact_id',df.general_ref_artifact,'artifact_id')
address('item_id',df.general_ref_item,'item_id')
header('race_offsets')
address('name_singular',df.creature_raw,'name',0)
address('name_plural',df.creature_raw,'name',1)
address('adjective',df.creature_raw,'name',2)
address('baby_name_singular',df.creature_raw,'general_baby_name',0)
address('baby_name_plural',df.creature_raw,'general_baby_name',1)
address('child_name_singular',df.creature_raw,'general_child_name',0)
address('child_name_plural',df.creature_raw,'general_child_name',1)
address('pref_string_vector',df.creature_raw,'prefstring')
address('castes_vector',df.creature_raw,'caste')
address('pop_ratio_vector',df.creature_raw,'pop_ratio')
address('materials_vector',df.creature_raw,'material')
address('flags',df.creature_raw,'flags')
address('tissues_vector',df.creature_raw,'tissue')
header('caste_offsets')
address('caste_name',df.caste_raw,'caste_name')
address('caste_descr',df.caste_raw,'description')
address('caste_trait_ranges',df.caste_raw,'personality','a')
address('caste_phys_att_ranges',df.caste_raw,'attributes','phys_att_range')
address('baby_age',df.caste_raw,'misc','baby_age')
address('child_age',df.caste_raw,'misc','child_age')
address('adult_size',df.caste_raw,'misc','adult_size')
address('flags',df.caste_raw,'flags')
address('body_info',df.caste_raw,'body_info')
address('skill_rates',df.caste_raw,'skill_rates')
address('caste_att_rates',df.caste_raw,'attributes','phys_att_rates')
address('caste_att_caps',df.caste_raw,'attributes','phys_att_cap_perc')
address('shearable_tissues_vector',df.caste_raw,'shearable_tissue_layer')
address('extracts',df.caste_raw,'extracts','extract_matidx')
header('hist_entity_offsets')
address('histfigs',df.historical_entity,'histfig_ids')
address('beliefs',df.historical_entity,'resources','values')
address('squads',df.historical_entity,'squads')
address('positions',df.historical_entity,'positions','own')
address('assignments',df.historical_entity,'positions','assignments')
address('assign_hist_id',df.entity_position_assignment,'histfig')
address('assign_position_id',df.entity_position_assignment,'position_id')
address('position_id',df.entity_position,'id')
address('position_name',df.entity_position,'name')
address('position_female_name',df.entity_position,'name_female')
address('position_male_name',df.entity_position,'name_male')
header('hist_figure_offsets')
address('hist_race',df.historical_figure,'race')
address('hist_name',df.historical_figure,'name')
address('id',df.historical_figure,'id')
address('hist_fig_info',df.historical_figure,'info')
address('reputation',df.historical_figure_info,'reputation')
address('current_ident',df.historical_figure_info.T_reputation,'cur_identity')
address('fake_name',df.identity,'name')
address('fake_birth_year',df.identity,'birth_year')
address('fake_birth_time',df.identity,'birth_second')
address('kills',df.historical_figure_info,'kills')
address('killed_race_vector',df.historical_kills,'killed_race')
address('killed_undead_vector',df.historical_kills,'killed_undead')
address('killed_counts_vector',df.historical_kills,'killed_count')
header('hist_event_offsets')
address('event_year',df.history_event,'year')
address('id',df.history_event,'id')
address('killed_hist_id',df.history_event_hist_figure_diedst,'victim_hf')
header('item_offsets')
if os_type == 'darwin' then
value('item_type',0x4)
else
value('item_type',0x1)
end
address('item_def',df.item_ammost,'subtype') --currently same for all
address('id',df.item,'id')
address('general_refs',df.item,'general_refs')
address('stack_size',df.item_actual,'stack_size')
address('wear',df.item_actual,'wear')
address('mat_type',df.item_crafted,'mat_type')
address('mat_index',df.item_crafted,'mat_index')
address('maker_race',df.item_crafted,'maker_race')
address('quality',df.item_crafted,'quality')
header('item_subtype_offsets')
address('sub_type',df.itemdef,'subtype')
address('name',df.itemdef_armorst,'name')
address('name_plural',df.itemdef_armorst,'name_plural')
address('adjective',df.itemdef_armorst,'name_preplural')
header('item_filter_offsets')
address('item_subtype',df.item_filter_spec,'item_subtype')
address('mat_class',df.item_filter_spec,'material_class')
address('mat_type',df.item_filter_spec,'mattype')
address('mat_index',df.item_filter_spec,'matindex')
header('weapon_subtype_offsets')
address('single_size',df.itemdef_weaponst,'two_handed')
address('multi_size',df.itemdef_weaponst,'minimum_size')
address('ammo',df.itemdef_weaponst,'ranged_ammo')
address('melee_skill',df.itemdef_weaponst,'skill_melee')
address('ranged_skill',df.itemdef_weaponst,'skill_ranged')
header('armor_subtype_offsets')
address('layer',df.armor_properties,'layer')
address('mat_name',df.itemdef_armorst,'material_placeholder')
address('other_armor_level',df.itemdef_helmst,'armorlevel')
address('armor_adjective',df.itemdef_armorst,'adjective')
address('armor_level',df.itemdef_armorst,'armorlevel')
address('chest_armor_properties',df.itemdef_armorst,'props')
address('pants_armor_properties',df.itemdef_pantsst,'props')
address('other_armor_properties',df.itemdef_helmst,'props')
header('material_offsets')
address('solid_name',df.material_common,'state_name','Solid')
address('liquid_name',df.material_common,'state_name','Liquid')
address('gas_name',df.material_common,'state_name','Gas')
address('powder_name',df.material_common,'state_name','Powder')
address('paste_name',df.material_common,'state_name','Paste')
address('pressed_name',df.material_common,'state_name','Pressed')
address('flags',df.material_common,'flags')
address('inorganic_materials_vector',df.inorganic_raw,'material')
address('inorganic_flags',df.inorganic_raw,'flags')
header('plant_offsets')
address('name',df.plant_raw,'name')
address('name_plural',df.plant_raw,'name_plural')
address('name_leaf_plural',df.plant_raw,'leaves_plural')
address('name_seed_plural',df.plant_raw,'seed_plural')
address('materials_vector',df.plant_raw,'material')
address('flags',df.plant_raw,'flags')
header('descriptor_offsets')
address('color_name',df.descriptor_color,'name')
address('shape_name_plural',df.descriptor_shape,'name_plural')
header('health_offsets')
address('parent_id',df.body_part_raw,'con_part_id')
address('body_part_flags',df.body_part_raw,'flags')
address('layers_vector',df.body_part_raw,'layers')
address('number',df.body_part_raw,'number')
address('names_vector',df.body_part_raw,'name_singular')
address('names_plural_vector',df.body_part_raw,'name_plural')
address('layer_tissue',df.body_part_layer_raw,'tissue_id')
address('layer_global_id',df.body_part_layer_raw,'layer_id')
address('tissue_name',df.tissue_template,'tissue_name_singular')
address('tissue_flags',df.tissue_template,'flags')
header('dwarf_offsets')
address('first_name',df.unit,'name','first_name')
address('nick_name',df.unit,'name','nickname')
address('last_name',df.unit,'name','words')
address('custom_profession',df.unit,'custom_profession')
address('profession',df.unit,'profession')
address('race',df.unit,'race')
address('flags1',df.unit,'flags1')
address('flags2',df.unit,'flags2')
address('flags3',df.unit,'flags3')
address('meeting',df.unit,'meeting')
address('caste',df.unit,'caste')
address('sex',df.unit,'sex')
address('id',df.unit,'id')
address('animal_type',df.unit,'training_level')
address('civ',df.unit,'civ_id')
address('specific_refs',df.unit,'specific_refs')
address('squad_id',df.unit,'military','squad_id')
address('squad_position',df.unit,'military','squad_position')
address('recheck_equipment',df.unit,'military','pickup_flags')
address('mood',df.unit,'mood')
address('birth_year',df.unit,'relations','birth_year')
address('birth_time',df.unit,'relations','birth_time')
address('pet_owner_id',df.unit,'relations','pet_owner_id')
address('current_job',df.unit,'job','current_job')
address('physical_attrs',df.unit,'body','physical_attrs')
address('body_size',df.unit,'appearance','body_modifiers')
address('size_info',df.unit,'body','size_info')
address('curse',df.unit,'curse','name')
address('curse_add_flags1',df.unit,'curse','add_tags1')
address('turn_count',df.unit,'curse','time_on_site')
address('souls',df.unit,'status','souls')
address('states',df.unit,'status','misc_traits')
address('labors',df.unit,'status','labors')
address('hist_id',df.unit,'hist_figure_id')
address('artifact_name',df.unit,'status','artifact_name')
address('active_syndrome_vector',df.unit,'syndromes','active')
address('syn_sick_flag',df.unit_syndrome,'flags')
address('unit_health_info',df.unit,'health')
address('temp_mood',df.unit,'counters','soldier_mood')
address('counters1',df.unit,'counters','winded')
address('counters2',df.unit, 'counters','pain')
address('counters3',df.unit, 'counters2','paralysis')
address('limb_counters',df.unit,'status2','limbs_stand_max')
address('blood',df.unit,'body','blood_max')
address('body_component_info',df.unit,'body','components')
address('layer_status_vector',df.body_component_info,'layer_status')
address('wounds_vector',df.unit,'body','wounds')
address('mood_skill',df.unit,'job','mood_skill')
address('used_items_vector',df.unit,'used_items')
address('affection_level',df.unit_item_use,'affection_level')
address('inventory',df.unit,'inventory')
address('inventory_item_mode',df.unit_inventory_item,'mode')
address('inventory_item_bodypart',df.unit_inventory_item,'body_part_id')
header('syndrome_offsets')
address('cie_effects',df.syndrome,'ce')
address('cie_end',df.creature_interaction_effect,'end')
address('cie_first_perc',df.creature_interaction_effect_phys_att_changest,'phys_att_perc') --same for mental
address('cie_phys',df.creature_interaction_effect_phys_att_changest,'phys_att_add')
address('cie_ment',df.creature_interaction_effect_ment_att_changest,'ment_att_add')
address('syn_classes_vector',df.syndrome,'syn_class')
address('trans_race_id',df.creature_interaction_effect_body_transformationst,'race')
header('unit_wound_offsets')
address('parts',df.unit_wound,'parts')
address('id',df.unit_wound.T_parts,'body_part_id')
address('layer',df.unit_wound.T_parts,'layer_idx')
address('general_flags',df.unit_wound,'flags')
address('flags1',df.unit_wound.T_parts,'flags1')
address('flags2',df.unit_wound.T_parts,'flags2')
address('effects_vector',df.unit_wound.T_parts,'effect_type')
address('bleeding',df.unit_wound.T_parts,'bleeding')
address('pain',df.unit_wound.T_parts,'pain')
address('cur_pen',df.unit_wound.T_parts,'cur_penetration_perc')
address('max_pen',df.unit_wound.T_parts,'max_penetration_perc')
header('soul_details')
address('name',df.unit_soul,'name')
address('orientation',df.unit_soul,'orientation_flags')
address('mental_attrs',df.unit_soul,'mental_attrs')
address('skills',df.unit_soul,'skills')
address('preferences',df.unit_soul,'preferences')
address('personality',df.unit_soul,'personality')
address('beliefs',df.unit_personality,'values')
address('emotions',df.unit_personality,'emotions')
address('goals',df.unit_personality,'dreams')
address('goal_realized',df.unit_personality.T_dreams,'unk8')
address('traits',df.unit_personality,'traits')
address('stress_level',df.unit_personality,'stress_level')
header('emotion_offsets')
address('emotion_type',df.unit_personality.T_emotions,'type')
address('strength',df.unit_personality.T_emotions,'strength')
address('thought_id',df.unit_personality.T_emotions,'thought')
address('sub_id',df.unit_personality.T_emotions,'subthought')
address('level',df.unit_personality.T_emotions,'severity')
address('year',df.unit_personality.T_emotions,'year')
address('year_tick',df.unit_personality.T_emotions,'year_tick')
header('job_details')
address('id',df.job,'job_type')
address('mat_type',df.job,'mat_type')
address('mat_index',df.job,'mat_index')
address('mat_category',df.job,'material_category')
value('on_break_flag',df.misc_trait_type.OnBreak)
address('sub_job_id',df.job,'reaction_name')
address('reaction',df.reaction,'name')
address('reaction_skill',df.reaction,'skill')
header('squad_offsets')
address('id',df.squad,'id')
address('name',df.squad,'name')
address('alias',df.squad,'alias')
address('members',df.squad,'positions')
address('orders',df.squad,'orders')
address('schedules',df.squad,'schedule')
if os_type ~= 'windows' then --squad_schedule_entry size
value('sched_size',0x20)
else
value('sched_size',0x40)
end
address('sched_orders',df.squad_schedule_entry,'orders')
address('sched_assign',df.squad_schedule_entry,'order_assignments')
address('alert',df.squad,'cur_alert_idx')
address('carry_food',df.squad,'carry_food')
address('carry_water',df.squad,'carry_water')
address('ammunition',df.squad,'ammunition')
address('ammunition_qty',df.squad_ammo_spec,'amount')
address('quiver',df.squad_position,'quiver')
address('backpack',df.squad_position,'backpack')
address('flask',df.squad_position,'flask')
address('armor_vector',df.squad_position,'uniform','body')
address('helm_vector',df.squad_position,'uniform','head')
address('pants_vector',df.squad_position,'uniform','pants')
address('gloves_vector',df.squad_position,'uniform','gloves')
address('shoes_vector',df.squad_position,'uniform','shoes')
address('shield_vector',df.squad_position,'uniform','shield')
address('weapon_vector',df.squad_position,'uniform','weapon')
address('uniform_item_filter',df.squad_uniform_spec,'item_filter')
address('uniform_indiv_choice',df.squad_uniform_spec,'indiv_choice')
header('activity_offsets')
address('activity_type',df.activity_entry,'type')
address('events',df.activity_entry,'events')
address('participants',df.activity_event_combat_trainingst,'participants')
address('sq_lead',df.activity_event_skill_demonstrationst,'hist_figure_id')
address('sq_skill',df.activity_event_skill_demonstrationst,'skill')
address('sq_train_rounds',df.activity_event_skill_demonstrationst,'train_rounds')
address('pray_deity',df.activity_event_prayerst,'histfig_id')
address('pray_sphere',df.activity_event_prayerst,'topic')
address('knowledge_category',df.activity_event_ponder_topicst,'knowledge_category')
address('knowledge_flag',df.activity_event_ponder_topicst,'knowledge_flag')
address('perf_type',df.activity_event_performancest,'type')
address('perf_participants',df.activity_event_performancest,'participant_actions')
address('perf_histfig',df.activity_event_performancest.T_participant_actions,'histfig_id')
-- Final creation of the file
local out = io.open('therapist.ini', 'w')
out:write('[info]\n')
if dfhack.getOSType() == 'windows' and dfhack.internal.getPE then
out:write(('checksum=0x%x\n'):format(dfhack.internal.getPE()))
elseif dfhack.getOSType() ~= 'windows' and dfhack.internal.getMD5 then
out:write(('checksum=0x%s\n'):format(dfhack.internal.getMD5():sub(1, 8)))
else
out:write('checksum=<<fillme>>\n')
end
out:write('version_name='..dfhack.getDFVersion()..'\n')
out:write('complete='..(complete and 'true' or 'false')..'\n')
for i,v in ipairs(lines) do
out:write(v..'\n')
end
out:write[[
[valid_flags_2]
size=0
[invalid_flags_1]
size=9
1\name=a skeleton
1\value=0x00002000
2\name=a merchant
2\value=0x00000040
3\name=outpost liason or diplomat
3\value=0x00000800
4\name=an invader or hostile
4\value=0x00020000
5\name=an invader or hostile
5\value=0x00080000
6\name=resident, invader or ambusher
6\value=0x00600000
7\name=part of a merchant caravan
7\value=0x00000080
8\name="Dead, Jim."
8\value=0x00000002
9\name=marauder
9\value=0x00000010
[invalid_flags_2]
size=5
1\name="killed, Jim."
1\value=0x00000080
2\name=from the Underworld. SPOOKY!
2\value=0x00040000
3\name=resident
3\value=0x00080000
4\name=uninvited visitor
4\value=0x00400000
5\name=visitor
5\value=0x00800000
[invalid_flags_3]
size=1
1\name=a ghost
1\value=0x00001000
]]
out:close()

File diff suppressed because it is too large Load Diff

@ -1,197 +0,0 @@
-- Inject new raw definitions into the world
--[[=begin
devel/inject-raws
=================
WARNING: THIS SCRIPT CAN PERMANENLY DAMAGE YOUR SAVE.
This script attempts to inject new raw objects into your
world. If the injected references do not match the actual
edited raws, your save will refuse to load, or load but crash.
This script can handle reaction, item and building definitions.
The savegame contains a list of the relevant definition tokens in
the right order, but all details are read from raws every time.
This allows just adding stub definitions, and simply saving and
reloading the game.
This is useful enough for modders and some users to justify the danger.
Usage example::
devel/inject-raws trapcomp ITEM_TRAPCOMP_STEAM_PISTON workshop STEAM_ENGINE MAGMA_STEAM_ENGINE reaction STOKE_BOILER
=end]]
local utils = require 'utils'
local raws = df.global.world.raws
print[[
WARNING: THIS SCRIPT CAN PERMANENLY DAMAGE YOUR SAVE.
This script attempts to inject new raw objects into your
world. If the injected references do not match the actual
edited raws, your save will refuse to load, or load but crash.
]]
if not utils.prompt_yes_no('Did you make a backup?') then
qerror('Not backed up.')
end
df.global.pause_state = true
local changed = false
function inject_reaction(name)
for _,v in ipairs(raws.reactions) do
if v.code == name then
print('Reaction '..name..' already exists.')
return
end
end
print('Injecting reaction '..name)
changed = true
raws.reactions:insert('#', {
new = true,
code = name,
name = 'Dummy reaction '..name,
index = #raws.reactions,
})
end
local building_types = {
workshop = { df.building_def_workshopst, raws.buildings.workshops },
furnace = { df.building_def_furnacest, raws.buildings.furnaces },
}
function inject_building(btype, name)
for _,v in ipairs(raws.buildings.all) do
if v.code == name then
print('Building '..name..' already exists.')
return
end
end
print('Injecting building '..name)
changed = true
local typeinfo = building_types[btype]
local id = raws.buildings.next_id
raws.buildings.next_id = id+1
raws.buildings.all:insert('#', {
new = typeinfo[1],
code = name,
name = 'Dummy '..btype..' '..name,
id = id,
})
typeinfo[2]:insert('#', raws.buildings.all[#raws.buildings.all-1])
end
local itemdefs = raws.itemdefs
local item_types = {
weapon = { df.itemdef_weaponst, itemdefs.weapons, 'weapon_type' },
trainweapon = { df.itemdef_weaponst, itemdefs.weapons, 'training_weapon_type' },
pick = { df.itemdef_weaponst, itemdefs.weapons, 'digger_type' },
trapcomp = { df.itemdef_trapcompst, itemdefs.trapcomps, 'trapcomp_type' },
toy = { df.itemdef_toyst, itemdefs.toys, 'toy_type' },
tool = { df.itemdef_toolst, itemdefs.tools, 'tool_type' },
instrument = { df.itemdef_instrumentst, itemdefs.instruments, 'instrument_type' },
armor = { df.itemdef_armorst, itemdefs.armor, 'armor_type' },
ammo = { df.itemdef_ammost, itemdefs.ammo, 'ammo_type' },
siegeammo = { df.itemdef_siegeammost, itemdefs.siege_ammo, 'siegeammo_type' },
gloves = { df.itemdef_glovest, itemdefs.gloves, 'gloves_type' },
shoes = { df.itemdef_shoest, itemdefs.shoes, 'shoes_type' },
shield = { df.itemdef_shieldst, itemdefs.shields, 'shield_type' },
helm = { df.itemdef_helmst, itemdefs.helms, 'helm_type' },
pants = { df.itemdef_pantsst, itemdefs.pants, 'pants_type' },
food = { df.itemdef_foodst, itemdefs.food },
}
function add_to_civ(entity, bvec, id)
for _,v in ipairs(entity.resources[bvec]) do
if v == id then
return
end
end
entity.resources[bvec]:insert('#', id)
end
function add_to_dwarf_civs(btype, id)
local typeinfo = item_types[btype]
if not typeinfo[3] then
print('Not adding to civs.')
end
for _,entity in ipairs(df.global.world.entities.all) do
if entity.race == df.global.ui.race_id then
add_to_civ(entity, typeinfo[3], id)
end
end
end
function inject_item(btype, name)
for _,v in ipairs(itemdefs.all) do
if v.id == name then
print('Itemdef '..name..' already exists.')
return
end
end
print('Injecting item '..name)
changed = true
local typeinfo = item_types[btype]
local vec = typeinfo[2]
local id = #vec
vec:insert('#', {
new = typeinfo[1],
id = name,
subtype = id,
name = name,
name_plural = name,
})
itemdefs.all:insert('#', vec[id])
add_to_dwarf_civs(btype, id)
end
local args = {...}
local mode = nil
local ops = {}
for _,kv in ipairs(args) do
if mode and string.match(kv, '^[%u_]+$') then
table.insert(ops, curry(mode, kv))
elseif kv == 'reaction' then
mode = inject_reaction
elseif building_types[kv] then
mode = curry(inject_building, kv)
elseif item_types[kv] then
mode = curry(inject_item, kv)
else
qerror('Invalid option: '..kv)
end
end
if #ops > 0 then
print('')
for _,v in ipairs(ops) do
v()
end
end
if changed then
print('\nNow without unpausing save and reload the game to re-read raws.')
else
print('\nNo changes made.')
end

@ -1,110 +0,0 @@
-- Read from the screen and display info about the tiles
--[[=begin
devel/inspect-screen
====================
Read the tiles from the screen and display info about them.
=end]]
local utils = require 'utils'
local gui = require 'gui'
InspectScreen = defclass(InspectScreen, gui.Screen)
function InspectScreen:init(args)
local w,h = dfhack.screen.getWindowSize()
self.cursor_x = math.floor(w/2)
self.cursor_y = math.floor(h/2)
end
function InspectScreen:computeFrame(parent_rect)
local sw, sh = parent_rect.width, parent_rect.height
self.cursor_x = math.max(0, math.min(self.cursor_x, sw-1))
self.cursor_y = math.max(0, math.min(self.cursor_y, sh-1))
local frame = { w = 14, r = 1, h = 10, t = 1 }
if self.cursor_x > sw/2 then
frame = { w = 14, l = 1, h = 10, t = 1 }
end
return gui.compute_frame_body(sw, sh, frame, 1, 0, false)
end
function InspectScreen:onRenderFrame(dc, rect)
self:renderParent()
self.cursor_pen = dfhack.screen.readTile(self.cursor_x, self.cursor_y)
if gui.blink_visible(100) then
dfhack.screen.paintTile({ch='X',fg=COLOR_LIGHTGREEN}, self.cursor_x, self.cursor_y)
end
dc:fill(rect, {ch=' ',fg=COLOR_WHITE,bg=COLOR_CYAN})
end
local FG_PEN = {fg=COLOR_WHITE,bg=COLOR_BLACK,tile_color=true}
local BG_PEN = {fg=COLOR_BLACK,bg=COLOR_WHITE,tile_color=true}
local TXT_PEN = {fg=COLOR_WHITE}
function InspectScreen:onRenderBody(dc)
dc:pen(COLOR_WHITE, COLOR_CYAN)
if self.cursor_pen then
local info = self.cursor_pen
dc:string('CH: '):char(info.ch, FG_PEN):char(info.ch, BG_PEN):string(' '):string(''..info.ch,TXT_PEN):newline()
local fgcolor = info.fg
local fgstr = info.fg
if info.bold then
fgcolor = (fgcolor+8)%16
fgstr = fgstr..'+8'
end
dc:string('FG: '):string('NN',{fg=fgcolor}):string(' '):string(''..fgstr,TXT_PEN)
dc:seek(dc.width-1):char(info.ch,{fg=info.fg,bold=info.bold}):newline()
dc:string('BG: '):string('NN',{fg=info.bg}):string(' '):string(''..info.bg,TXT_PEN)
dc:seek(dc.width-1):char(info.ch,{fg=COLOR_BLACK,bg=info.bg}):newline()
local bstring = 'false'
if info.bold then bstring = 'true' end
dc:string('Bold: '..bstring):newline():newline()
if info.tile and gui.USE_GRAPHICS then
dc:string('TL: '):tile(' ', info.tile, FG_PEN):tile(' ', info.tile, BG_PEN):string(' '..info.tile):newline()
if info.tile_color then
dc:string('Color: true')
elseif info.tile_fg then
dc:string('FG: '):string('NN',{fg=info.tile_fg}):string(' '):string(''..info.tile_fg,TXT_PEN):newline()
dc:string('BG: '):string('NN',{fg=info.tile_bg}):string(' '):string(''..info.tile_bg,TXT_PEN):newline()
end
end
else
dc:string('Invalid', COLOR_LIGHTRED)
end
end
local MOVEMENT_KEYS = {
CURSOR_UP = { 0, -1, 0 }, CURSOR_DOWN = { 0, 1, 0 },
CURSOR_LEFT = { -1, 0, 0 }, CURSOR_RIGHT = { 1, 0, 0 },
CURSOR_UPLEFT = { -1, -1, 0 }, CURSOR_UPRIGHT = { 1, -1, 0 },
CURSOR_DOWNLEFT = { -1, 1, 0 }, CURSOR_DOWNRIGHT = { 1, 1, 0 },
CURSOR_UP_FAST = { 0, -1, 0, true }, CURSOR_DOWN_FAST = { 0, 1, 0, true },
CURSOR_LEFT_FAST = { -1, 0, 0, true }, CURSOR_RIGHT_FAST = { 1, 0, 0, true },
CURSOR_UPLEFT_FAST = { -1, -1, 0, true }, CURSOR_UPRIGHT_FAST = { 1, -1, 0, true },
CURSOR_DOWNLEFT_FAST = { -1, 1, 0, true }, CURSOR_DOWNRIGHT_FAST = { 1, 1, 0, true },
}
function InspectScreen:onInput(keys)
if keys.LEAVESCREEN then
self:dismiss()
else
for k,v in pairs(MOVEMENT_KEYS) do
if keys[k] then
local delta = 1
if v[4] then
delta = 10
end
self.cursor_x = self.cursor_x + delta*v[1]
self.cursor_y = self.cursor_y + delta*v[2]
self:updateLayout()
return
end
end
end
end
InspectScreen{}:show()

@ -1,388 +0,0 @@
-- an experimental lighting engine
--[[=begin
devel/light
===========
An experimental lighting engine for DF, using the `rendermax` plugin.
Call ``devel/light static`` to not recalculate lighting when in game.
Press :kbd:`~` to recalculate lighting. Press :kbd:`\`` to exit.
=end]]
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local render = require 'plugins.rendermax'
local levelDim=0.05
local tile_attrs = df.tiletype.attrs
local args={...}
function setCell(x,y,cell)
cell=cell or {}
cell.fm=cell.fm or {r=1,g=1,b=1}
cell.bm=cell.bm or {r=1,g=1,b=1}
cell.fo=cell.fo or {r=0,g=0,b=0}
cell.bo=cell.bo or {r=0,g=0,b=0}
render.setCell(x,y,cell)
end
function getCursorPos()
local g_cursor=df.global.cursor
if g_cursor.x ~= -30000 then
return copyall(g_cursor)
end
end
function falloff(color,sqDist,maxdist)
local v1=1/(sqDist/maxdist+1)
local v2=v1-1/(1+maxdist*maxdist)
local v=v2/(1-1/(1+maxdist*maxdist))
return {r=v*color.r,g=v*color.g,b=v*color.b}
end
function blend(c1,c2)
return {r=math.max(c1.r,c2.r),
g=math.max(c1.g,c2.g),
b=math.max(c1.b,c2.b)}
end
LightOverlay=defclass(LightOverlay,guidm.DwarfOverlay)
LightOverlay.ATTRS {
lightMap={},
dynamic=true,
dirty=false,
}
function LightOverlay:init(args)
self.tick=df.global.cur_year_tick_advmode
end
function lightPassable(shape)
if shape==df.tiletype_shape.WALL or
shape==df.tiletype_shape.BROOK_BED or
shape==df.tiletype_shape.TREE then
return false
else
return true
end
end
function circle(xm, ym,r,plot)
local x = -r
local y = 0
local err = 2-2*r -- /* II. Quadrant */
repeat
plot(xm-x, ym+y);--/* I. Quadrant */
plot(xm-y, ym-x);--/* II. Quadrant */
plot(xm+x, ym-y);--/* III. Quadrant */
plot(xm+y, ym+x);--/* IV. Quadrant */
r = err;
if (r <= y) then
y=y+1
err =err+y*2+1; --/* e_xy+e_y < 0 */
end
if (r > x or err > y) then
x=x+1
err =err+x*2+1; --/* e_xy+e_x > 0 or no 2nd y-step */
end
until (x >= 0);
end
function line(x0, y0, x1, y1,plot)
local dx = math.abs(x1-x0)
local dy = math.abs(y1-y0)
local sx,sy
if x0 < x1 then sx = 1 else sx = -1 end
if y0 < y1 then sy = 1 else sy = -1 end
local err = dx-dy
while true do
if not plot(x0,y0) then
return
end
if x0 == x1 and y0 == y1 then
break
end
local e2 = 2*err
if e2 > -dy then
err = err - dy
x0 = x0 + sx
end
if x0 == x1 and y0 == y1 then
if not plot(x0,y0) then
return
end
break
end
if e2 < dx then
err = err + dx
y0 = y0 + sy
end
end
end
function LightOverlay:calculateFovs()
self.fovs=self.fovs or {}
self.precalc=self.precalc or {}
for k,v in ipairs(self.fovs) do
self:calculateFov(v.pos,v.radius,v.color)
end
end
function LightOverlay:calculateFov(pos,radius,color)
local vp=self:getViewport()
local map = self.df_layout.map
local ray=function(tx,ty)
local power=copyall(color)
local lx=pos.x
local ly=pos.y
local setTile=function(x,y)
if x>0 and y>0 and x<=map.width and y<=map.height then
local dtsq=(lx-x)*(lx-x)+(ly-y)*(ly-y)
local dt=math.sqrt(dtsq)
local tile=x+y*map.width
if self.precalc[tile] then
local tcol=blend(self.precalc[tile],power)
if tcol.r==self.precalc[tile].r and tcol.g==self.precalc[tile].g and self.precalc[tile].b==self.precalc[tile].b
and dtsq>0 then
return false
end
end
local ocol=self.lightMap[tile] or {r=0,g=0,b=0}
local ncol=blend(power,ocol)
self.lightMap[tile]=ncol
local v=self.ocupancy[tile]
if dtsq>0 then
power.r=power.r*(v.r^dt)
power.g=power.g*(v.g^dt)
power.b=power.b*(v.b^dt)
end
lx=x
ly=y
local pwsq=power.r*power.r+power.g*power.g+power.b*power.b
return pwsq>levelDim*levelDim
end
return false
end
line(pos.x,pos.y,tx,ty,setTile)
end
circle(pos.x,pos.y,radius,ray)
end
function LightOverlay:placeLightFov(pos,radius,color)
local map = self.df_layout.map
local tile=pos.x+pos.y*map.width
local ocol=self.precalc[tile] or {r=0,g=0,b=0}
local ncol=blend(color,ocol)
self.precalc[tile]=ncol
local ocol=self.lightMap[tile] or {r=0,g=0,b=0}
local ncol=blend(color,ocol)
self.lightMap[tile]=ncol
table.insert(self.fovs,{pos=pos,radius=radius,color=color})
end
function LightOverlay:placeLightFov2(pos,radius,color,f,rays)
f=f or falloff
local raycount=rays or 25
local vp=self:getViewport()
local map = self.df_layout.map
local off=math.random(0,math.pi)
local done={}
for d=0,math.pi*2,math.pi*2/raycount do
local dx,dy
dx=math.cos(d+off)
dy=math.sin(d+off)
local cx=0
local cy=0
for dt=0,radius,0.01 do
if math.abs(math.floor(dt*dx)-cx)>0 or math.abs(math.floor(dt*dy)-cy)> 0 then
local x=cx+pos.x
local y=cy+pos.y
if x>0 and y>0 and x<=map.width and y<=map.height and not done[tile] then
local tile=x+y*map.width
done[tile]=true
local ncol=f(color,dt*dt,radius)
local ocol=self.lightMap[tile] or {r=0,g=0,b=0}
ncol=blend(ncol,ocol)
self.lightMap[tile]=ncol
if --(ncol.r==ocol.r and ncol.g==ocol.g and ncol.b==ocol.b) or
not self.ocupancy[tile] then
break
end
end
cx=math.floor(dt*dx)
cy=math.floor(dt*dy)
end
end
end
end
function LightOverlay:placeLight(pos,radius,color,f)
f=f or falloff
local vp=self:getViewport()
local map = self.df_layout.map
for i=-radius,radius do
for j=-radius,radius do
local x=pos.x+i+1
local y=pos.y+j+1
if x>0 and y>0 and x<=map.width and y<=map.height then
local tile=x+y*map.width
local ncol=f(color,(i*i+j*j),radius)
local ocol=self.lightMap[tile] or {r=0,g=0,b=0}
self.lightMap[tile]=blend(ncol,ocol)
end
end
end
end
function LightOverlay:calculateLightLava()
local vp=self:getViewport()
local map = self.df_layout.map
for i=map.x1,map.x2 do
for j=map.y1,map.y2 do
local pos={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z}
local pos2={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z-1}
local t1=dfhack.maps.getTileFlags(pos)
local tt=dfhack.maps.getTileType(pos)
if tt then
local shape=tile_attrs[tt].shape
local t2=dfhack.maps.getTileFlags(pos2)
if (t1 and t1.liquid_type and t1.flow_size>0) or
(shape==df.tiletype_shape.EMPTY and t2 and t2.liquid_type and t2.flow_size>0) then
--self:placeLight({x=i,y=j},5,{r=0.8,g=0.2,b=0.2})
self:placeLightFov({x=i,y=j},5,{r=0.8,g=0.2,b=0.2},nil)
end
end
end
end
end
function LightOverlay:calculateLightSun()
local vp=self:getViewport()
local map = self.df_layout.map
for i=map.x1,map.x2+1 do
for j=map.y1,map.y2+1 do
local pos={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z}
local t1=dfhack.maps.getTileFlags(pos)
if (t1 and t1.outside ) then
self:placeLightFov({x=i,y=j},15,{r=1,g=1,b=1},nil)
end
end
end
end
function LightOverlay:calculateLightCursor()
local c=getCursorPos()
if c then
local vp=self:getViewport()
local pos=vp:tileToScreen(c)
--self:placeLight(pos,11,{r=0.96,g=0.84,b=0.03})
self:placeLightFov({x=pos.x+1,y=pos.y+1},11,{r=0.96,g=0.84,b=0.03})
end
end
function LightOverlay:buildOcupancy()
self.ocupancy={}
local vp=self:getViewport()
local map = self.df_layout.map
for i=map.x1,map.x2+1 do
for j=map.y1,map.y2+1 do
local pos={x=i+vp.x1-1,y=j+vp.y1-1,z=vp.z}
local tile=i+j*map.width
local tt=dfhack.maps.getTileType(pos)
local t1=dfhack.maps.getTileFlags(pos)
if tt then
local shape=tile_attrs[tt].shape
if not lightPassable(shape) then
self.ocupancy[tile]={r=0,g=0,b=0}
else
if t1 and not t1.liquid_type and t1.flow_size>2 then
self.ocupancy[tile]={r=0.5,g=0.5,b=0.7}
else
self.ocupancy[tile]={r=0.8,g=0.8,b=0.8}
end
end
end
end
end
end
function LightOverlay:changed()
if self.dirty or self.tick~=df.global.cur_year_tick_advmode then
self.dirty=false
self.tick=df.global.cur_year_tick_advmode
return true
end
return false
end
function LightOverlay:makeLightMap()
if not self:changed() then
return
end
self.fovs={}
self.precalc={}
self.lightMap={}
self:buildOcupancy()
self:calculateLightCursor()
self:calculateLightLava()
self:calculateLightSun()
self:calculateFovs()
end
function LightOverlay:onIdle()
self._native.parent:logic()
end
function LightOverlay:render(dc)
if self.dynamic then
self:makeLightMap()
end
self:renderParent()
local vp=self:getViewport()
local map = self.df_layout.map
self.lightMap=self.lightMap or {}
render.lockGrids()
render.invalidate({x=map.x1,y=map.y1,w=map.width,h=map.height})
render.resetGrids()
for i=map.x1,map.x2 do
for j=map.y1,map.y2 do
local v=self.lightMap[i+j*map.width]
if v then
setCell(i,j,{fm=v,bm=v})
else
local dimRgb={r=levelDim,g=levelDim,b=levelDim}
setCell(i,j,{fm=dimRgb,bm=dimRgb})
end
end
end
render.unlockGrids()
end
function LightOverlay:onDismiss()
render.lockGrids()
render.resetGrids()
render.invalidate()
render.unlockGrids()
end
function LightOverlay:onInput(keys)
if keys.STRING_A096 then
self:dismiss()
else
self:sendInputToParent(keys)
if keys.CHANGETAB then
self:updateLayout()
end
if keys.STRING_A126 and not self.dynamic then
self:makeLightMap()
end
self.dirty=true
end
end
if not render.isEnabled() then
qerror("Lua rendermode not enabled!")
end
local dyn=true
if #args>0 and args[1]=="static" then dyn=false end
local lview = LightOverlay{ dynamic=dyn}
lview:show()

@ -1,78 +0,0 @@
-- List input items for the building being built.
--[[=begin
devel/list-filters
==================
List input items for the building currently being built.
This is where the filters in lua/dfhack/buildings.lua come from.
=end]]
local dumper = require 'dumper'
local utils = require 'utils'
local buildings = require 'dfhack.buildings'
local function name_enum(tgt,name,ename,enum)
if tgt[name] ~= nil then
tgt[name] = ename..'.'..enum[tgt[name]]
end
end
local lookup = {}
local items = df.global.world.items
for i=df.job_item_vector_id._first_item,df.job_item_vector_id._last_item do
local id = df.job_item_vector_id.attrs[i].other
local ptr
if id == df.items_other_id.ANY then
ptr = items.all
elseif id == df.items_other_id.BAD then
ptr = items.bad
else
ptr = items.other[id]
end
if ptr then
local _,addr = df.sizeof(ptr)
lookup[addr] = 'df.job_item_vector_id.'..df.job_item_vector_id[i]
end
end
local function clone_filter(src,quantity)
local tgt = utils.clone_with_default(src, buildings.input_filter_defaults, true)
if quantity ~= 1 then
tgt.quantity = quantity
end
name_enum(tgt, 'item_type', 'df.item_type', df.item_type)
name_enum(tgt, 'has_tool_use', 'df.tool_uses', df.tool_uses)
local ptr = src.item_vector
if ptr and ptr ~= df.global.world.items.other[0] then
local _,addr = df.sizeof(ptr)
tgt.vector_id = lookup[addr]
end
return tgt
end
local function dump(name)
local out = {}
for i,v in ipairs(df.global.ui_build_selector.requirements) do
out[#out+1] = clone_filter(v.filter, v.count_required)
end
local fmt = dumper.DataDumper(out,name,false,1,4)
fmt = string.gsub(fmt, '"(df%.[^"]+)"','%1')
fmt = string.gsub(fmt, '%s+$', '')
print(fmt)
end
local itype = df.global.ui_build_selector.building_type
local stype = df.global.ui_build_selector.building_subtype
if itype == df.building_type.Workshop then
dump(' [df.workshop_type.'..df.workshop_type[stype]..'] = ')
elseif itype == df.building_type.Furnace then
dump(' [df.furnace_type.'..df.furnace_type[stype]..'] = ')
elseif itype == df.building_type.Trap then
dump(' [df.trap_type.'..df.trap_type[stype]..'] = ')
else
dump(' [df.building_type.'..df.building_type[itype]..'] = ')
end

@ -1,21 +0,0 @@
-- Prints memory ranges of the process.
--[[=begin
devel/lsmem
===========
Prints memory ranges of the process.
=end]]
for _,v in ipairs(dfhack.internal.getMemRanges()) do
local access = { '-', '-', '-', 'p' }
if v.read then access[1] = 'r' end
if v.write then access[2] = 'w' end
if v.execute then access[3] = 'x' end
if not v.valid then
access[4] = '?'
elseif v.shared then
access[4] = 's'
end
print(string.format('%08x-%08x %s %s', v.start_addr, v.end_addr, table.concat(access), v.name))
end

@ -1,14 +0,0 @@
-- Example of a lua script.
--[[=begin
devel/lua-example
=================
An example lua script, which reports the number of times it has
been called. Useful for testing environment persistence.
=end]]
run_count = (run_count or 0) + 1
print('Arguments: ',...)
print('Command called '..run_count..' times.')

@ -1,492 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
my ($version, $timestamp, $hash);
open FH, 'version.lisp' or die "Cannot open version";
while (<FH>) {
if (/df-version-str.*\"(.*)\"/) {
$version = $1;
} elsif (/windows-timestamp.*#x([0-9a-f]+)/) {
$timestamp = $1;
} elsif (/linux-hash.*\"(.*)\"/) {
$hash = $1;
}
}
close FH;
sub load_csv(\%$) {
my ($rhash, $fname) = @_;
open FH, $fname or die "Cannot open $fname";
while (<FH>) {
next unless /^\"([^\"]*)\",\"(\d+)\",\"(?:0x([0-9a-fA-F]+))?\",\"[^\"]*\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\"/;
my ($top, $level, $addr, $type, $name, $target) = ($1,$2,$3,$4,$5,$6);
next if defined $rhash->{$top}{$name};
$rhash->{$top}{$name} = ($type eq 'enum-item' ? $target : hex $addr);
}
close FH;
}
our $complete;
sub lookup_addr(\%$$;$) {
my ($rhash, $top, $name, $bias) = @_;
my $val = $rhash->{$top}{$name};
unless (defined $val) {
$complete = 0;
return 0;
}
return $val + ($bias||0);
}
our @lines;
sub emit_header($) {
my ($name) = @_;
push @lines, '' if @lines;
push @lines, "[$name]";
}
sub emit_addr($\%$$;$) {
my ($name, $rhash, $top, $var, $bias) = @_;
my $val = $rhash->{$top}{$var};
if (defined $val) {
$val += ($bias||0);
if ($val < 0x10000) {
push @lines, sprintf('%s=0x%04x', $name, $val);
} else {
push @lines, sprintf('%s=0x%08x', $name, $val);
}
} else {
$complete = 0;
push @lines, "$name=0x0";
}
}
sub generate_dt_ini($$$$) {
my ($subdir, $version, $checksum, $ssize) = @_;
my %globals;
load_csv %globals, "$subdir/globals.csv";
my %all;
load_csv %all, "$subdir/all.csv";
local $complete = 1;
local @lines;
emit_header 'addresses';
emit_addr 'translation_vector',%globals,'world','world.raws.language.translations';
emit_addr 'language_vector',%globals,'world','world.raws.language.words';
emit_addr 'creature_vector',%globals,'world','world.units.all';
emit_addr 'active_creature_vector',%globals,'world','world.units.active';
emit_addr 'dwarf_race_index',%globals,'ui','ui.race_id';
emit_addr 'squad_vector',%globals,'world','world.squads.all';
emit_addr 'current_year',%globals,'cur_year','cur_year';
emit_addr 'cur_year_tick',%globals,'cur_year_tick','cur_year_tick';
emit_addr 'dwarf_civ_index',%globals,'ui','ui.civ_id';
emit_addr 'races_vector',%globals,'world','world.raws.creatures.all';
emit_addr 'reactions_vector',%globals,'world','world.raws.reactions';
emit_addr 'events_vector',%globals,'world','world.history.events';
emit_addr 'historical_figures_vector',%globals,'world','world.history.figures';
emit_addr 'fake_identities_vector',%globals,'world','world.identities.all';
emit_addr 'fortress_entity',%globals,'ui','ui.main.fortress_entity';
emit_addr 'historical_entities_vector',%globals,'world','world.entities.all';
emit_addr 'itemdef_weapons_vector',%globals,'world','world.raws.itemdefs.weapons';
emit_addr 'itemdef_trap_vector',%globals,'world','world.raws.itemdefs.trapcomps';
emit_addr 'itemdef_toy_vector',%globals,'world','world.raws.itemdefs.toys';
emit_addr 'itemdef_tool_vector',%globals,'world','world.raws.itemdefs.tools';
emit_addr 'itemdef_instrument_vector',%globals,'world','world.raws.itemdefs.instruments';
emit_addr 'itemdef_armor_vector',%globals,'world','world.raws.itemdefs.armor';
emit_addr 'itemdef_ammo_vector',%globals,'world','world.raws.itemdefs.ammo';
emit_addr 'itemdef_siegeammo_vector',%globals,'world','world.raws.itemdefs.siege_ammo';
emit_addr 'itemdef_glove_vector',%globals,'world','world.raws.itemdefs.gloves';
emit_addr 'itemdef_shoe_vector',%globals,'world','world.raws.itemdefs.shoes';
emit_addr 'itemdef_shield_vector',%globals,'world','world.raws.itemdefs.shields';
emit_addr 'itemdef_helm_vector',%globals,'world','world.raws.itemdefs.helms';
emit_addr 'itemdef_pant_vector',%globals,'world','world.raws.itemdefs.pants';
emit_addr 'itemdef_food_vector',%globals,'world','world.raws.itemdefs.food';
emit_addr 'colors_vector',%globals,'world','world.raws.language.colors';
emit_addr 'shapes_vector',%globals,'world','world.raws.language.shapes';
emit_addr 'base_materials',%globals,'world','world.raws.mat_table.builtin';
emit_addr 'inorganics_vector',%globals,'world','world.raws.inorganics';
emit_addr 'plants_vector',%globals,'world','world.raws.plants.all';
emit_addr 'material_templates_vector',%globals,'world','world.raws.material_templates';
emit_addr 'all_syndromes_vector',%globals,'world','world.raws.syndromes.all';
emit_addr 'world_data',%globals,'world','world.world_data';
emit_addr 'active_sites_vector',%all,'world_data','active_site';
emit_addr 'world_site_type',%all,'world_site','type';
emit_addr 'weapons_vector',%globals,'world','world.items.other[WEAPON]';
emit_addr 'shields_vector',%globals,'world','world.items.other[SHIELD]';
emit_addr 'quivers_vector',%globals,'world','world.items.other[QUIVER]';
emit_addr 'crutches_vector',%globals,'world','world.items.other[CRUTCH]';
emit_addr 'backpacks_vector',%globals,'world','world.items.other[BACKPACK]';
emit_addr 'ammo_vector',%globals,'world','world.items.other[AMMO]';
emit_addr 'flasks_vector',%globals,'world','world.items.other[FLASK]';
emit_addr 'pants_vector',%globals,'world','world.items.other[PANTS]';
emit_addr 'armor_vector',%globals,'world','world.items.other[ARMOR]';
emit_addr 'shoes_vector',%globals,'world','world.items.other[SHOES]';
emit_addr 'helms_vector',%globals,'world','world.items.other[HELM]';
emit_addr 'gloves_vector',%globals,'world','world.items.other[GLOVES]';
emit_addr 'artifacts_vector',%globals,'world','world.artifacts.all';
emit_header 'offsets';
emit_addr 'word_table',%all,'language_translation','words';
push @lines, 'string_buffer_offset=0x0000';
emit_header 'word_offsets';
emit_addr 'base',%all,'language_word','word';
emit_addr 'noun_singular',%all,'language_word','forms[Noun]';
emit_addr 'noun_plural',%all,'language_word','forms[NounPlural]';
emit_addr 'adjective',%all,'language_word','forms[Adjective]';
emit_addr 'verb',%all,'language_word','forms[Verb]';
emit_addr 'present_simple_verb',%all,'language_word','forms[Verb3rdPerson]';
emit_addr 'past_simple_verb',%all,'language_word','forms[VerbPast]';
emit_addr 'past_participle_verb',%all,'language_word','forms[VerbPassive]';
emit_addr 'present_participle_verb',%all,'language_word','forms[VerbGerund]';
emit_addr 'words',%all,'language_name','words';
emit_addr 'word_type',%all,'language_name','parts_of_speech';
emit_addr 'language_id',%all,'language_name','language';
emit_header 'general_ref_offsets';
emit_addr 'ref_type',%all,'general_ref::vtable','getType';
emit_addr 'artifact_id',%all,'general_ref_artifact','artifact_id';
emit_addr 'item_id',%all,'general_ref_item','item_id';
emit_header 'race_offsets';
emit_addr 'name_singular',%all,'creature_raw','name';
emit_addr 'name_plural',%all,'creature_raw','name',$ssize;
emit_addr 'adjective',%all,'creature_raw','name',$ssize*2;
emit_addr 'baby_name_singular',%all,'creature_raw','general_baby_name';
emit_addr 'baby_name_plural',%all,'creature_raw','general_baby_name',$ssize;
emit_addr 'child_name_singular',%all,'creature_raw','general_child_name';
emit_addr 'child_name_plural',%all,'creature_raw','general_child_name',$ssize;
emit_addr 'pref_string_vector',%all,'creature_raw','prefstring';
emit_addr 'castes_vector',%all,'creature_raw','caste';
emit_addr 'pop_ratio_vector',%all,'creature_raw','pop_ratio';
emit_addr 'materials_vector',%all,'creature_raw','material';
emit_addr 'flags',%all,'creature_raw','flags';
emit_addr 'tissues_vector',%all,'creature_raw','tissue';
emit_header 'caste_offsets';
emit_addr 'caste_name',%all,'caste_raw','caste_name';
emit_addr 'caste_descr',%all,'caste_raw','description';
emit_addr 'caste_trait_ranges',%all,'caste_raw','personality.a';
emit_addr 'caste_phys_att_ranges',%all,'caste_raw','attributes.phys_att_range';
emit_addr 'baby_age',%all,'caste_raw','misc.baby_age';
emit_addr 'child_age',%all,'caste_raw','misc.child_age';
emit_addr 'adult_size',%all,'caste_raw','misc.adult_size';
emit_addr 'flags',%all,'caste_raw','flags';
emit_addr 'body_info',%all,'caste_raw','body_info';
emit_addr 'skill_rates',%all,'caste_raw','skill_rates';
emit_addr 'caste_att_rates',%all,'caste_raw','attributes.phys_att_rates';
emit_addr 'caste_att_caps',%all,'caste_raw','attributes.phys_att_cap_perc';
emit_addr 'shearable_tissues_vector',%all,'caste_raw','shearable_tissue_layer';
emit_addr 'extracts',%all,'caste_raw','extracts.extract_matidx';
emit_header 'hist_entity_offsets';
emit_addr 'beliefs',%all,'historical_entity','resources.values';
emit_addr 'squads',%all,'historical_entity','squads';
emit_addr 'positions',%all,'historical_entity','positions.own';
emit_addr 'assignments',%all,'historical_entity','positions.assignments';
emit_addr 'assign_hist_id',%all,'entity_position_assignment','histfig';
emit_addr 'assign_position_id',%all,'entity_position_assignment','position_id';
emit_addr 'position_id',%all,'entity_position','id';
emit_addr 'position_name',%all,'entity_position','name';
emit_addr 'position_female_name',%all,'entity_position','name_female';
emit_addr 'position_male_name',%all,'entity_position','name_male';
emit_header 'hist_figure_offsets';
emit_addr 'hist_race',%all,'historical_figure','race';
emit_addr 'hist_name',%all,'historical_figure','name';
emit_addr 'id',%all,'historical_figure','id';
emit_addr 'hist_fig_info',%all,'historical_figure','info';
emit_addr 'reputation',%all,'historical_figure_info','reputation';
emit_addr 'current_ident',%all,'historical_figure_info::anon13','cur_identity';
emit_addr 'fake_name',%all,'identity','name';
emit_addr 'fake_birth_year',%all,'identity','birth_year';
emit_addr 'fake_birth_time',%all,'identity','birth_second';
emit_addr 'kills',%all,'historical_figure_info','kills';
emit_addr 'killed_race_vector',%all,'historical_kills','killed_race';
emit_addr 'killed_undead_vector',%all,'historical_kills','killed_undead';
emit_addr 'killed_counts_vector',%all,'historical_kills','killed_count';
emit_header 'hist_event_offsets';
emit_addr 'event_year',%all,'history_event','year';
emit_addr 'id',%all,'history_event','id';
emit_addr 'killed_hist_id',%all,'history_event_hist_figure_diedst','victim_hf';
emit_header 'item_offsets';
if ($subdir eq 'osx') {
push @lines, 'item_type=0x0004';
} else {
push @lines, 'item_type=0x0001';
}
emit_addr 'item_def',%all,'item_ammost','subtype'; #currently same for all
emit_addr 'id',%all,'item','id';
emit_addr 'general_refs',%all,'item','general_refs';
emit_addr 'stack_size',%all,'item_actual','stack_size';
emit_addr 'wear',%all,'item_actual','wear';
emit_addr 'mat_type',%all,'item_crafted','mat_type';
emit_addr 'mat_index',%all,'item_crafted','mat_index';
emit_addr 'quality',%all,'item_crafted','quality';
emit_header 'item_subtype_offsets';
emit_addr 'sub_type',%all,'itemdef','subtype';
emit_addr 'name',%all,'itemdef_armorst','name';
emit_addr 'name_plural',%all,'itemdef_armorst','name_plural';
emit_addr 'adjective',%all,'itemdef_armorst','name_preplural';
emit_header 'item_filter_offsets';
emit_addr 'item_subtype',%all,'item_filter_spec','item_subtype';
emit_addr 'mat_class',%all,'item_filter_spec','material_class';
emit_addr 'mat_type',%all,'item_filter_spec','mattype';
emit_addr 'mat_index',%all,'item_filter_spec','matindex';
emit_header 'weapon_subtype_offsets';
emit_addr 'single_size',%all,'itemdef_weaponst','two_handed';
emit_addr 'multi_size',%all,'itemdef_weaponst','minimum_size';
emit_addr 'ammo',%all,'itemdef_weaponst','ranged_ammo';
emit_addr 'melee_skill',%all,'itemdef_weaponst','skill_melee';
emit_addr 'ranged_skill',%all,'itemdef_weaponst','skill_ranged';
emit_header 'armor_subtype_offsets';
emit_addr 'layer',%all,'armor_properties','layer';
emit_addr 'mat_name',%all,'itemdef_armorst','material_placeholder';
emit_addr 'other_armor_level',%all,'itemdef_helmst','armorlevel';
emit_addr 'armor_adjective',%all,'itemdef_armorst','adjective';
emit_addr 'armor_level',%all,'itemdef_armorst','armorlevel';
emit_addr 'chest_armor_properties',%all,'itemdef_armorst','props';
emit_addr 'pants_armor_properties',%all,'itemdef_pantsst','props';
emit_addr 'other_armor_properties',%all,'itemdef_helmst','props';
emit_header 'material_offsets';
emit_addr 'solid_name',%all,'material_common','state_name[Solid]';
emit_addr 'liquid_name',%all,'material_common','state_name[Liquid]';
emit_addr 'gas_name',%all,'material_common','state_name[Gas]';
emit_addr 'powder_name',%all,'material_common','state_name[Powder]';
emit_addr 'paste_name',%all,'material_common','state_name[Paste]';
emit_addr 'pressed_name',%all,'material_common','state_name[Pressed]';
emit_addr 'flags',%all,'material_common','flags';
emit_addr 'inorganic_materials_vector',%all,'inorganic_raw','material';
emit_addr 'inorganic_flags',%all,'inorganic_raw','flags';
emit_header 'plant_offsets';
emit_addr 'name',%all,'plant_raw','name';
emit_addr 'name_plural',%all,'plant_raw','name_plural';
emit_addr 'name_leaf_plural',%all,'plant_raw','leaves_plural';
emit_addr 'name_seed_plural',%all,'plant_raw','seed_plural';
emit_addr 'materials_vector',%all,'plant_raw','material';
emit_addr 'flags',%all,'plant_raw','flags';
emit_header 'descriptor_offsets';
emit_addr 'color_name',%all,'descriptor_color','name';
emit_addr 'shape_name_plural',%all,'descriptor_shape','name_plural';
emit_header 'health_offsets';
emit_addr 'parent_id',%all,'body_part_raw','con_part_id';
emit_addr 'layers_vector',%all,'body_part_raw','layers';
emit_addr 'number',%all,'body_part_raw','number';
emit_addr 'names_vector',%all,'body_part_raw','name_singular';
emit_addr 'names_plural_vector',%all,'body_part_raw','name_plural';
emit_addr 'layer_tissue',%all,'body_part_layer_raw','tissue_id';
emit_addr 'layer_global_id',%all,'body_part_layer_raw','layer_id';
emit_addr 'tissue_name',%all,'tissue_template','tissue_name_singular';
emit_addr 'tissue_flags',%all,'tissue_template','flags';
emit_header 'dwarf_offsets';
emit_addr 'first_name',%all,'unit','name',lookup_addr(%all,'language_name','first_name');
emit_addr 'nick_name',%all,'unit','name',lookup_addr(%all,'language_name','nickname');
emit_addr 'last_name',%all,'unit','name',lookup_addr(%all,'language_name','words');
emit_addr 'custom_profession',%all,'unit','custom_profession';
emit_addr 'profession',%all,'unit','profession';
emit_addr 'race',%all,'unit','race';
emit_addr 'flags1',%all,'unit','flags1';
emit_addr 'flags2',%all,'unit','flags2';
emit_addr 'flags3',%all,'unit','flags3';
emit_addr 'caste',%all,'unit','caste';
emit_addr 'sex',%all,'unit','sex';
emit_addr 'id',%all,'unit','id';
emit_addr 'animal_type',%all,'unit','training_level';
emit_addr 'civ',%all,'unit','civ_id';
emit_addr 'specific_refs',%all,'unit','specific_refs';
emit_addr 'squad_id',%all,'unit','military.squad_id';
emit_addr 'squad_position',%all,'unit','military.squad_position';
emit_addr 'recheck_equipment',%all,'unit','military.pickup_flags';
emit_addr 'mood',%all,'unit','mood';
emit_addr 'birth_year',%all,'unit','relations.birth_year';
emit_addr 'birth_time',%all,'unit','relations.birth_time';
emit_addr 'pet_owner_id',%all,'unit','relations.pet_owner_id';
emit_addr 'current_job',%all,'unit','job.current_job';
emit_addr 'physical_attrs',%all,'unit','body.physical_attrs';
emit_addr 'body_size',%all,'unit','appearance.body_modifiers';
emit_addr 'size_info',%all,'unit','body.size_info';
emit_addr 'curse',%all,'unit','curse.name';
emit_addr 'curse_add_flags1',%all,'unit','curse.add_tags1';
emit_addr 'turn_count',%all,'unit','curse.time_on_site';
emit_addr 'souls',%all,'unit','status.souls';
emit_addr 'states',%all,'unit','status.misc_traits';
emit_addr 'labors',%all,'unit','status.labors';
emit_addr 'hist_id',%all,'unit','hist_figure_id';
emit_addr 'artifact_name',%all,'unit','status.artifact_name';
emit_addr 'active_syndrome_vector',%all,'unit','syndromes.active';
emit_addr 'syn_sick_flag',%all,'unit_syndrome','flags.is_sick';
emit_addr 'unit_health_info',%all,'unit','health';
emit_addr 'temp_mood',%all,'unit','counters.soldier_mood';
emit_addr 'counters1',%all,'unit','counters.winded';
emit_addr 'counters2',%all,'unit','counters.pain';
emit_addr 'counters3',%all,'unit','counters2.paralysis';
emit_addr 'limb_counters',%all,'unit','status2.limbs_stand_max';
emit_addr 'blood',%all,'unit','body.blood_max';
emit_addr 'body_component_info',%all,'unit','body.components';
emit_addr 'layer_status_vector',%all,'body_component_info','layer_status';
emit_addr 'wounds_vector',%all,'unit','body.wounds';
emit_addr 'mood_skill',%all,'unit','job.mood_skill';
emit_addr 'used_items_vector',%all,'unit','used_items';
emit_addr 'affection_level',%all,'unit_item_use','affection_level';
emit_addr 'inventory',%all,'unit','inventory';
emit_addr 'inventory_item_mode',%all,'unit_inventory_item','mode';
emit_addr 'inventory_item_bodypart',%all,'unit_inventory_item','body_part_id';
emit_header 'syndrome_offsets';
emit_addr 'cie_effects',%all,'syndrome','ce';
emit_addr 'cie_end',%all,'creature_interaction_effect','end';
emit_addr 'cie_first_perc',%all,'creature_interaction_effect_phys_att_changest','phys_att_perc'; #same for mental
emit_addr 'cie_phys',%all,'creature_interaction_effect_phys_att_changest','phys_att_add';
emit_addr 'cie_ment',%all,'creature_interaction_effect_ment_att_changest','ment_att_add';
emit_addr 'syn_classes_vector',%all,'syndrome','syn_class';
emit_addr 'trans_race_id',%all,'creature_interaction_effect_body_transformationst','race';
emit_header 'unit_wound_offsets';
emit_addr 'parts',%all,'unit_wound','parts';
emit_addr 'id',%all,'unit_wound::anon2','body_part_id';
emit_addr 'layer',%all,'unit_wound::anon2','layer_idx';
emit_addr 'general_flags',%all,'unit_wound','flags';
emit_addr 'flags1',%all,'unit_wound::anon2','flags1';
emit_addr 'flags2',%all,'unit_wound::anon2','flags2';
emit_addr 'effects_vector',%all,'unit_wound::anon2','effect_type';
emit_addr 'bleeding',%all,'unit_wound::anon2','bleeding';
emit_addr 'pain',%all,'unit_wound::anon2','pain';
emit_addr 'cur_pen',%all,'unit_wound::anon2','cur_penetration_perc';
emit_addr 'max_pen',%all,'unit_wound::anon2','max_penetration_perc';
emit_header 'soul_details';
emit_addr 'name',%all,'unit_soul','name';
emit_addr 'orientation',%all,'unit_soul','orientation_flags';
emit_addr 'mental_attrs',%all,'unit_soul','mental_attrs';
emit_addr 'skills',%all,'unit_soul','skills';
emit_addr 'preferences',%all,'unit_soul','preferences';
emit_addr 'personality',%all,'unit_soul','personality';
emit_addr 'beliefs',%all,'unit_personality','values';
emit_addr 'emotions',%all,'unit_personality','emotions';
emit_addr 'goals',%all,'unit_personality','dreams';
emit_addr 'goal_realized',%all,'unit_personality::anon5','unk8';
emit_addr 'traits',%all,'unit_personality','traits';
emit_addr 'stress_level',%all,'unit_personality','stress_level';
emit_header 'emotion_offsets';
emit_addr 'emotion_type',%all,'unit_personality::anon4','type';
emit_addr 'strength',%all,'unit_personality::anon4','strength';
emit_addr 'thought_id',%all,'unit_personality::anon4','thought';
emit_addr 'sub_id',%all,'unit_personality::anon4','subthought';
emit_addr 'level',%all,'unit_personality::anon4','severity';
emit_addr 'year',%all,'unit_personality::anon4','year';
emit_addr 'year_tick',%all,'unit_personality::anon4','year_tick';
emit_header 'job_details';
emit_addr 'id',%all,'job','job_type';
emit_addr 'mat_type',%all,'job','mat_type';
emit_addr 'mat_index',%all,'job','mat_index';
emit_addr 'mat_category',%all,'job','material_category';
emit_addr 'on_break_flag',%all,'misc_trait_type','OnBreak';
emit_addr 'sub_job_id',%all,'job','reaction_name';
emit_addr 'reaction',%all,'reaction','name';
emit_addr 'reaction_skill',%all,'reaction','skill';
emit_header 'squad_offsets';
emit_addr 'id',%all,'squad','id';
emit_addr 'name',%all,'squad','name';
emit_addr 'alias',%all,'squad','alias';
emit_addr 'members',%all,'squad','positions';
emit_addr 'carry_food',%all,'squad','carry_food';
emit_addr 'carry_water',%all,'squad','carry_water';
emit_addr 'ammunition',%all,'squad','ammunition';
emit_addr 'quiver',%all,'squad_position','quiver';
emit_addr 'backpack',%all,'squad_position','backpack';
emit_addr 'flask',%all,'squad_position','flask';
emit_addr 'armor_vector',%all,'squad_position','uniform[body]';
emit_addr 'helm_vector',%all,'squad_position','uniform[head]';
emit_addr 'pants_vector',%all,'squad_position','uniform[pants]';
emit_addr 'gloves_vector',%all,'squad_position','uniform[gloves]';
emit_addr 'shoes_vector',%all,'squad_position','uniform[shoes]';
emit_addr 'shield_vector',%all,'squad_position','uniform[shield]';
emit_addr 'weapon_vector',%all,'squad_position','uniform[weapon]';
emit_addr 'uniform_item_filter',%all,'squad_uniform_spec','item_filter';
emit_addr 'uniform_indiv_choice',%all,'squad_uniform_spec','indiv_choice';
my $body_str = join("\n",@lines);
my $complete_str = ($complete ? 'true' : 'false');
open OUT, ">$subdir/therapist.ini" or die "Cannot open output file";
print OUT <<__END__;
[info]
checksum=0x$checksum
version_name=$version
complete=$complete_str
$body_str
[valid_flags_2]
size=0
[invalid_flags_1]
size=10
1\\name=a zombie
1\\value=0x00001000
2\\name=a skeleton
2\\value=0x00002000
3\\name=a merchant
3\\value=0x00000040
4\\name=outpost liason or diplomat
4\\value=0x00000800
5\\name=an invader or hostile
5\\value=0x00020000
6\\name=an invader or hostile
6\\value=0x00080000
7\\name=resident, invader or ambusher
7\\value=0x00600000
8\\name=part of a merchant caravan
8\\value=0x00000080
9\\name="Dead, Jim."
9\\value=0x00000002
10\\name=marauder
10\\value=0x00000010
[invalid_flags_2]
size=5
1\\name="killed, Jim."
1\\value=0x00000080
2\\name=from the Underworld. SPOOKY!
2\\value=0x00040000
3\\name=resident
3\\value=0x00080000
4\\name=uninvited visitor
4\\value=0x00400000
5\\name=visitor
5\\value=0x00800000
[invalid_flags_3]
size=1
1\\name=a ghost
1\\value=0x00001000
__END__
close OUT;
}
generate_dt_ini 'linux', $version, substr($hash,0,8), 4;
generate_dt_ini 'windows', $version.' (graphics)', $timestamp, 0x1C;
generate_dt_ini 'osx', $version, substr($hash,0,8), 4;

@ -1,22 +0,0 @@
-- Delete ALL items not held by units, buildings or jobs
--[[=begin
devel/nuke-items
================
Deletes ALL items not held by units, buildings or jobs.
Intended solely for lag investigation.
=end]]
local count = 0
for _,v in ipairs(df.global.world.items.all) do
if not (v.flags.in_building or v.flags.construction or v.flags.in_job
or dfhack.items.getGeneralRef(v,df.general_ref_type.UNIT_HOLDER)) then
count = count + 1
v.flags.forbid = true
v.flags.garbage_collect = true
end
end
print('Deletion requested: '..count)

@ -1,10 +0,0 @@
-- For killing bugged out gui script screens.
--[[=begin
devel/pop-screen
================
For killing bugged out gui script screens.
=end]]
dfhack.screen.dismiss(dfhack.gui.getCurViewscreen())

@ -1,98 +0,0 @@
-- Prepare the current save for devel/find-offsets
--[[=begin
devel/prepare-save
==================
WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS.
This script prepares the current savegame to be used
with `devel/find-offsets`. It CHANGES THE GAME STATE
to predefined values, and initiates an immediate
`quicksave`, thus PERMANENTLY MODIFYING the save.
=end]]
local utils = require 'utils'
df.global.pause_state = true
print[[
WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS.
This script prepares the current savegame to be used
with devel/find-offsets. It CHANGES THE GAME STATE
to predefined values, and initiates an immediate
quicksave, thus PERMANENTLY MODIFYING the save.
]]
if not utils.prompt_yes_no('Proceed?') then
return
end
--[[print('Placing anchor...')
do
local wp = df.global.ui.waypoints
for _,pt in ipairs(wp.points) do
if pt.name == 'dfhack_anchor' then
print('Already placed.')
goto found
end
end
local x,y,z = pos2xyz(df.global.cursor)
if not x then
error("Place cursor at your preferred anchor point.")
end
local id = wp.next_point_id
wp.next_point_id = id + 1
wp.points:insert('#',{
new = true, id = id, name = 'dfhack_anchor',
comment=(x..','..y..','..z),
tile = string.byte('!'), fg_color = COLOR_LIGHTRED, bg_color = COLOR_BLUE,
pos = xyz2pos(x,y,z)
})
::found::
end]]
print('Nicknaming units...')
for i,unit in ipairs(df.global.world.units.active) do
dfhack.units.setNickname(unit, i..':'..unit.id)
end
print('Setting weather...')
local wbytes = {
2, 1, 0, 2, 0,
1, 2, 1, 0, 0,
2, 0, 2, 1, 2,
1, 2, 0, 1, 1,
2, 0, 1, 0, 2
}
for i=0,4 do
for j = 0,4 do
df.global.current_weather[i][j] = (wbytes[i*5+j+1] or 2)
end
end
local yearstr = df.global.cur_year..','..df.global.cur_year_tick
print('Cur year and tick: '..yearstr)
dfhack.persistent.save{
key='prepare-save/cur_year',
value=yearstr,
ints={df.global.cur_year, df.global.cur_year_tick}
}
-- Save
dfhack.run_script('quicksave')

@ -1,15 +0,0 @@
--print-args.lua
--author expwnent
--[[=begin
devel/print-args
================
Prints all the arguments you supply to the script on their own line.
Useful for debugging other scripts.
=end]]
local args = {...}
for _,arg in ipairs(args) do
print(arg)
end

@ -1,17 +0,0 @@
--print-args2.lua
--author expwnent
--[[=begin
devel/print-args2
=================
Prints all the arguments you supply to the script on their own line
with quotes around them.
=end]]
local args = {...}
print("print-args")
for _,arg in ipairs(args) do
print("'"..arg.."'")
end

@ -1,154 +0,0 @@
-- Display DF version information about the current save
--@module = true
--[[=begin
devel/save-version
==================
Display DF version information about the current save
=end]]
local function dummy() return nil end
function has_field(tbl, field)
return (pcall(function() assert(tbl[field] ~= nil) end))
end
function class_has_field(cls, field)
local obj = cls:new()
local ret = has_field(obj, field)
obj:delete()
return ret
end
versions = {
-- skipped v0.21-v0.28
[1287] = "0.31.01",
[1288] = "0.31.02",
[1289] = "0.31.03",
[1292] = "0.31.04",
[1295] = "0.31.05",
[1297] = "0.31.06",
[1300] = "0.31.08",
[1304] = "0.31.09",
[1305] = "0.31.10",
[1310] = "0.31.11",
[1311] = "0.31.12",
[1323] = "0.31.13",
[1325] = "0.31.14",
[1326] = "0.31.15",
[1327] = "0.31.16",
[1340] = "0.31.17",
[1341] = "0.31.18",
[1351] = "0.31.19",
[1353] = "0.31.20",
[1354] = "0.31.21",
[1359] = "0.31.22",
[1360] = "0.31.23",
[1361] = "0.31.24",
[1362] = "0.31.25",
[1372] = "0.34.01",
[1374] = "0.34.02",
[1376] = "0.34.03",
[1377] = "0.34.04",
[1378] = "0.34.05",
[1382] = "0.34.06",
[1383] = "0.34.07",
[1400] = "0.34.08",
[1402] = "0.34.09",
[1403] = "0.34.10",
[1404] = "0.34.11",
[1441] = "0.40.01",
[1442] = "0.40.02",
[1443] = "0.40.03",
[1444] = "0.40.04",
[1445] = "0.40.05",
[1446] = "0.40.06",
[1448] = "0.40.07",
[1449] = "0.40.08",
[1451] = "0.40.09",
[1452] = "0.40.10",
[1456] = "0.40.11",
[1459] = "0.40.12",
[1462] = "0.40.13",
[1469] = "0.40.14",
[1470] = "0.40.15",
[1471] = "0.40.16",
[1472] = "0.40.17",
[1473] = "0.40.18",
[1474] = "0.40.19",
[1477] = "0.40.20",
[1478] = "0.40.21",
[1479] = "0.40.22",
[1480] = "0.40.23",
[1481] = "0.40.24",
[1531] = "0.42.01",
[1532] = "0.42.02",
[1533] = "0.42.03",
[1534] = "0.42.04",
[1537] = "0.42.05",
[1542] = "0.42.06",
[1551] = "0.43.01",
[1552] = "0.43.02",
}
min_version = math.huge
max_version = -math.huge
for k in pairs(versions) do
min_version = math.min(min_version, k)
max_version = math.max(max_version, k)
end
if class_has_field(df.world.T_cur_savegame, 'save_version') then
function get_save_version()
return df.global.world.cur_savegame.save_version
end
elseif class_has_field(df.world.T_pathfinder, 'anon_2') then
function get_save_version()
return df.global.world.pathfinder.anon_2
end
else
get_save_version = dummy
end
if class_has_field(df.world, 'original_save_version') then
function get_original_save_version()
return df.global.world.original_save_version
end
else
get_original_save_version = dummy
end
function describe(version)
if version == 0 then
return 'no world loaded'
elseif versions[version] then
return versions[version]
elseif version < min_version then
return 'unknown old version before ' .. describe(min_version) .. ': ' .. tostring(version)
elseif version > max_version then
return 'unknown new version after ' .. describe(max_version) .. ': ' .. tostring(version)
else
return 'unknown version: ' .. tostring(version)
end
end
function dump(desc, func)
local ret = tonumber(func())
if ret then
print(desc .. ': ' .. describe(ret))
else
dfhack.printerr('could not find ' .. desc .. ' (DFHack version too old)')
end
end
if not moduleMode then
if not dfhack.isWorldLoaded() then qerror('no world loaded') end
dump('original DF version', get_original_save_version)
dump('most recent DF version', get_save_version)
end

@ -1,16 +0,0 @@
# list selected item's indices in world.item.other[]
=begin
devel/scanitemother
===================
List indices in ``world.item.other[]`` where current selected item appears.
=end
tg = df.item_find
raise 'select an item' if not tg
o = df.world.items.other
# discard ANY/BAD
o._indexenum::ENUM.sort.transpose[1][1..-2].each { |k|
puts k if o[k].find { |i| i == tg }
}

@ -1,35 +0,0 @@
# Allow arena creature spawn after a mode change
df.world.arena_spawn.race.clear
df.world.arena_spawn.caste.clear
df.world.raws.creatures.all.length.times { |r_idx|
df.world.raws.creatures.all[r_idx].caste.length.times { |c_idx|
df.world.arena_spawn.race << r_idx
df.world.arena_spawn.caste << c_idx
}
}
df.world.arena_spawn.creature_cnt[df.world.arena_spawn.race.length-1] = 0
puts <<EOS
=begin
devel/spawn-unit-helper
=======================
Setup stuff to allow arena creature spawn after a mode change.
With Arena spawn data initialized:
- enter the :kbd:`k` menu and change mode using
``rb_eval df.gametype = :DWARF_ARENA``
- spawn creatures (:kbd:`c` ingame)
- revert to game mode using ``rb_eval df.gametype = #{df.gametype.inspect}``
- To convert spawned creatures to livestock, select each one with
the :kbd:`v` menu, and enter ``rb_eval df.unit_find.civ_id = df.ui.civ_id``
=end
EOS

@ -1,193 +0,0 @@
-- Generates an image using perlin noise
--[[=begin
devel/test-perlin
=================
Generates an image using multiple octaves of perlin noise.
=end]]
local args = {...}
local rng = dfhack.random.new(3)
if #args < 3 then
qerror('Usage: devel/test-perlin <fname.pgm> <density> <expr> [-xy <xyscale>] [-z <zscale>]')
end
local fname = table.remove(args,1)
local goal = tonumber(table.remove(args,1)) or qerror('Invalid density')
local expr = table.remove(args,1) or qerror('No expression')
local zscale = 2
local xyscale = 1
for i = 1,#args,2 do
if args[i] == '-xy' then
xyscale = tonumber(args[i+1]) or qerror('Invalid xyscale')
end
if args[i] == '-z' then
zscale = tonumber(args[i+1]) or qerror('Invalid zscale')
end
end
local fn_env = copyall(math)
fn_env.rng = rng
fn_env.apow = function(x,y) return math.pow(math.abs(x),y) end
fn_env.spow = function(x,y) return x*math.pow(math.abs(x),y-1) end
-- Noise functions are referenced from expressions
-- as variables of form like "x3a", where:
-- 1) x is one of x/y/z/w independent functions in each octave
-- 2) 3 is the octave number; 0 corresponds to the whole range
-- 3) a is the subtype
-- Independent noise functions: support 4
local ids = { 'x', 'y', 'z', 'w' }
-- Subtype: provides an offset to the coordinates
local subs = {
[''] = { 0, 0, 0 },
a = { 0.5, 0, 0 },
b = { 0, 0.5, 0 },
c = { 0.5, 0.5, 0 },
d = { 0, 0, 0.5 },
e = { 0.5, 0, 0.5 },
f = { 0, 0.5, 0.5 },
g = { 0.5, 0.5, 0.5 },
}
function mkdelta(v)
if v == 0 then
return ''
else
return '+'..v
end
end
function mkexpr(expr)
-- Collect referenced variables
local max_octave = -1
local vars = {}
for var,id,octave,subtype in string.gmatch(expr,'%f[%w](([xyzw])(%d+)(%a*))%f[^%w]') do
if not vars[var] then
octave = tonumber(octave)
if octave > max_octave then
max_octave = octave
end
local sub = subs[subtype] or qerror('Invalid subtype: '..subtype)
vars[var] = { id = id, octave = octave, subtype = subtype, sub = sub }
end
end
if max_octave < 0 then
qerror('No noise function references in expression.')
end
-- Allocate the noise functions
local code = ''
for i = 0,max_octave do
for j,id in ipairs(ids) do
code = code .. 'local _fn_'..i..'_'..id..' = rng:perlin(3)\n';
end
end
-- Evaluate variables
code = code .. 'return function(x,y,z)\n'
for var,info in pairs(vars) do
local fn = '_fn_'..info.octave..'_'..info.id
local mul = math.pow(2,info.octave)
mul = math.min(48*4, mul)
code = code .. ' local '..var
.. ' = _fn_'..info.octave..'_'..info.id
.. '(x*'..mul..mkdelta(info.sub[1])
.. ',y*'..mul..mkdelta(info.sub[2])
.. ',z*'..mul..mkdelta(info.sub[3])
.. ')\n'
end
-- Complete and compile the function
code = code .. ' return ('..expr..')\nend\n'
local f,err = load(code, '=(expr)', 't', fn_env)
if not f then
qerror(err)
end
return f()
end
local field_fn = mkexpr(expr)
function render(thresh,file)
local area = 0
local line, arr = '', {}
for zy = 0,1 do
for y = 0,48*4-1 do
line = ''
for zx = 0,1 do
for x = 0,48*4-1 do
local tx = (0.5+x)/(48*4/xyscale)
local ty = (0.5+y)/(48*4/xyscale)
local tz = 0.3+(zx+zy*2)/(48*4/zscale)
local v1 = field_fn(tx, ty, tz)
local v = -1
if v1 > thresh then
v = v1;
area = area + 1
end
if file then
local c = math.max(0, math.min(255, v * 127 + 128))
arr[2*x+1] = c
arr[2*x+2] = c
end
end
if file then
line = line..string.char(table.unpack(arr))
end
end
if file then
file:write(line,line)
end
end
end
return area/4/(48*4)/(48*4)
end
function search(fn,min,max,goal,eps)
local center
for i = 1,32 do
center = (max+min)/2
local cval = fn(center)
print('At '..center..': '..cval)
if math.abs(cval-goal) < eps then
break
end
if cval > goal then
min = center
else
max = center
end
end
return center
end
local thresh = search(render, -2, 2, goal, math.min(0.001,goal/20))
local file,err = io.open(fname, 'wb')
if not file then
print('error: ',err)
return
end
file:write('P5\n')
file:write('# '..goal..' '..expr..' '..xyscale..' '..zscale..'\n')
file:write('768 768\n255\n')
local area = render(thresh, file)
file:close()
print('Area fraction: '..area)

@ -1,10 +0,0 @@
# unforbid all items
=begin
devel/unforbidall
=================
Unforbid all items.
=end
df.world.items.all.each { |i| i.flags.forbid = false }

@ -1,225 +0,0 @@
-- Show the internal path a unit is currently following.
--[[=begin
devel/unit-path
===============
Show the internal path a unit is currently following.
=end]]
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local dlg = require 'gui.dialogs'
local tile_attrs = df.tiletype.attrs
UnitPathUI = defclass(UnitPathUI, guidm.MenuOverlay)
UnitPathUI.focus_path = 'unit-path'
UnitPathUI.ATTRS {
unit = DEFAULT_NIL,
has_path = false,
has_goal = false,
}
function UnitPathUI:init()
self.saved_mode = df.global.ui.main.mode
if self.unit then
self.has_path = #self.unit.path.path.x > 0
self.has_goal = self.unit.path.dest.x >= 0
end
end
function UnitPathUI:onShow()
-- with cursor, but without those ugly lines from native hauling mode
df.global.ui.main.mode = df.ui_sidebar_mode.Stockpiles
end
function UnitPathUI:onDestroy()
self:moveCursorTo(copyall(self.unit.pos))
df.global.ui.main.mode = self.saved_mode
end
local function getTileType(cursor)
local block = dfhack.maps.getTileBlock(cursor)
if block then
return block.tiletype[cursor.x%16][cursor.y%16]
else
return 0
end
end
local function getTileWalkable(cursor)
local block = dfhack.maps.getTileBlock(cursor)
if block then
return block.walkable[cursor.x%16][cursor.y%16]
else
return 0
end
end
local function paintMapTile(dc, vp, cursor, pos, ...)
if not same_xyz(cursor, pos) then
local stile = vp:tileToScreen(pos)
if stile.z == 0 then
dc:seek(stile.x,stile.y):char(...)
end
end
end
local function get_path_point(gpath,i)
return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i])
end
function UnitPathUI:renderPath(dc,vp,cursor)
local gpath = self.unit.path.path
local startp = self.unit.pos
local endp = self.unit.path.dest
local visible = gui.blink_visible(500)
if visible then
paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN)
end
local ok = nil
local pcnt = #gpath.x
if pcnt > 0 then
ok = true
for i = 0,pcnt-1 do
local pt = get_path_point(gpath, i)
if i == 0 and not same_xyz(startp,pt) then
ok = false
end
if i == pcnt-1 and not same_xyz(endp,pt) then
ok = false
end
--[[local tile = getTileType(pt)
if not isTrackTile(tile) then
ok = false
end]]
if visible then
local char = '+'
if i < pcnt-1 then
local npt = get_path_point(gpath, i+1)
if npt.z == pt.z+1 then
char = 30
elseif npt.z == pt.z-1 then
char = 31
elseif npt.x == pt.x+1 then
char = 26
elseif npt.x == pt.x-1 then
char = 27
elseif npt.y == pt.y+1 then
char = 25
elseif npt.y == pt.y-1 then
char = 24
end
end
local color = COLOR_LIGHTGREEN
if getTileWalkable(pt) == 0 then color = COLOR_LIGHTRED end
paintMapTile(dc, vp, cursor, pt, char, color)
end
end
end
if gui.blink_visible(120) then
paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN)
end
return ok
end
function UnitPathUI:onRenderBody(dc)
dc:clear():seek(1,1):pen(COLOR_WHITE):string("Unit Path")
local prof = dfhack.units.getProfessionName(self.unit)
local name = dfhack.units.getVisibleName(self.unit)
dc:seek(2,3):pen(COLOR_BLUE):string(prof)
if name and name.has_name then
dc:seek(2,4):pen(COLOR_BLUE):string(dfhack.TranslateName(name))
end
local cursor = guidm.getCursorPos()
local vp = self:getViewport()
local mdc = gui.Painter.new(self.df_layout.map)
if not self.has_path then
if gui.blink_visible(120) then
paintMapTile(mdc, vp, cursor, self.unit.pos, 15, COLOR_LIGHTRED, COLOR_RED)
end
dc:seek(1,6):pen(COLOR_RED):string('Not following path')
else
self:renderPath(mdc,vp,cursor)
dc:seek(1,6):pen(COLOR_GREEN):string(df.unit_path_goal[self.unit.path.goal] or '?')
end
dc:newline():pen(COLOR_GREY)
dc:newline(2):string('Speed: '..dfhack.units.computeMovementSpeed(self.unit))
dc:newline(2):string('Slowdown: '..dfhack.units.computeSlowdownFactor(self.unit))
dc:newline():newline(1)
local has_station = self.unit.idle_area_type >= 0
if has_station then
if gui.blink_visible(250) then
paintMapTile(mdc, vp, cursor, self.unit.idle_area, 21, COLOR_LIGHTCYAN)
end
dc:pen(COLOR_GREEN):string(df.unit_station_type[self.unit.idle_area_type])
dc:newline():newline(2):pen(COLOR_GREY):string('Threshold: '..self.unit.idle_area_threshold)
else
dc:pen(COLOR_RED):string('No station'):newline():newline(2)
end
dc:newline():newline(1):string('At cursor:')
dc:newline():newline(2)
local tile = getTileType(cursor)
dc:string(df.tiletype[tile],COLOR_CYAN)
dc:newline():newline(1):pen(COLOR_WHITE)
dc:key('CUSTOM_Z'):string(": Zoom unit, ")
dc:key('CUSTOM_G'):string(": Zoom goal",COLOR_GREY,nil,self.has_goal)
dc:newline(1)
dc:key('CUSTOM_N'):string(": Zoom station",COLOR_GREY,nil,has_station)
dc:newline():newline(1)
dc:key('LEAVESCREEN'):string(": Back")
end
function UnitPathUI:onInput(keys)
if keys.CUSTOM_Z then
self:moveCursorTo(copyall(self.unit.pos))
elseif keys.CUSTOM_G then
if self.has_goal then
self:moveCursorTo(copyall(self.unit.path.dest))
end
elseif keys.CUSTOM_N then
if self.unit.idle_area_type > 0 then
self:moveCursorTo(copyall(self.unit.idle_area))
end
elseif keys.LEAVESCREEN then
self:dismiss()
elseif self:propagateMoveKeys(keys) then
return
end
end
function UnitPathUI:onGetSelectedUnit()
return self.unit
end
local unit = dfhack.gui.getSelectedUnit(true)
if not unit or not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/ViewUnits/Some/') then
qerror("This script requires the main dwarfmode view in 'v' mode with a unit selected")
end
UnitPathUI{ unit = unit }:show()

@ -1,83 +0,0 @@
-- Logs minecart coordinates and speeds to console.
--[[=begin
devel/watch-minecarts
=====================
Logs minecart coordinates and speeds to console.
Usage: ``devel/watch-minecarts start|stop``
=end]]
last_stats = last_stats or {}
function compare_one(vehicle)
local last = last_stats[vehicle.id]
local item = df.item.find(vehicle.item_id)
local ipos = item.pos
local new = {
ipos.x*100000 + vehicle.offset_x, vehicle.speed_x,
ipos.y*100000 + vehicle.offset_y, vehicle.speed_y,
ipos.z*100000 + vehicle.offset_z, vehicle.speed_z
}
if (last == nil) or item.flags.on_ground then
local delta = { vehicle.id }
local show = (last == nil)
for i=1,6 do
local rv = 0
if last then
rv = last[i]
end
delta[i*2] = new[i]/100000
local dv = new[i] - rv
delta[i*2+1] = dv/100000
if dv ~= 0 then
show = true
end
end
if show then
print(table.unpack(delta))
end
end
last_stats[vehicle.id] = new
end
function compare_all()
local seen = {}
for _,v in ipairs(df.global.world.vehicles.all) do
seen[v.id] = true
compare_one(v)
end
for k,v in pairs(last_stats) do
if not seen[k] then
print(k,'DEAD')
end
end
end
function start_timer()
if not dfhack.timeout_active(timeout_id) then
timeout_id = dfhack.timeout(1, 'ticks', function()
compare_all()
start_timer()
end);
if not timeout_id then
dfhack.printerr('Could not start timer in watch-minecarts')
end
end
end
compare_all()
local cmd = ...
if cmd == 'start' then
start_timer()
elseif cmd == 'stop' then
dfhack.timeout_active(timeout_id, nil)
end

@ -1,94 +0,0 @@
# designate an area based on a '.csv' plan
=begin
digfort
=======
A script to designate an area for digging according to a plan in csv format.
This script, inspired from quickfort, can designate an area for digging.
Your plan should be stored in a .csv file like this::
# this is a comment
d;d;u;d;d;skip this tile;d
d;d;d;i
Available tile shapes are named after the 'dig' menu shortcuts:
``d`` for dig, ``u`` for upstairs, ``j`` downstairs, ``i`` updown,
``h`` channel, ``r`` upward ramp, ``x`` remove designation.
Unrecognized characters are ignored (eg the 'skip this tile' in the sample).
Empty lines and data after a ``#`` are ignored as comments.
To skip a row in your design, use a single ``;``.
One comment in the file may contain the phrase ``start(3,5)``. It is interpreted
as an offset for the pattern: instead of starting at the cursor, it will start
3 tiles left and 5 tiles up from the cursor.
The script takes the plan filename, starting from the root df folder (where
``Dwarf Fortress.exe`` is found).
=end
fname = $script_args[0].to_s
if not $script_args[0] then
puts " Usage: digfort <plan filename>"
throw :script_finished
end
if not fname[-4..-1] == ".csv" then
puts " The plan file must be in .csv format."
throw :script_finished
end
if not File.file?(fname) then
puts " The specified file does not exist."
throw :script_finished
end
planfile = File.read(fname)
if df.cursor.x == -30000
puts "place the game cursor to the top-left corner of the design and retry"
throw :script_finished
end
offset = [0, 0]
tiles = []
planfile.each_line { |l|
if l =~ /#.*start\s*\(\s*(-?\d+)\s*[,;]\s*(-?\d+)/
raise "Error: multiple start() comments" if offset != [0, 0]
offset = [$1.to_i, $2.to_i]
end
l = l.chomp.sub(/#.*/, '')
next if l == ''
tiles << l.split(/[;,]/).map { |t|
t = t.strip
(t[0] == ?") ? t[1..-2] : t
}
}
x = df.cursor.x - offset[0]
y = df.cursor.y - offset[1]
z = df.cursor.z
tiles.each { |line|
next if line.empty? or line == ['']
line.each { |tile|
t = df.map_tile_at(x, y, z)
s = t.shape_basic
case tile
when 'd'; t.dig(:Default) if s == :Wall
when 'u'; t.dig(:UpStair) if s == :Wall
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
when 'i'; t.dig(:UpDownStair) if s == :Wall
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
when 'r'; t.dig(:Ramp) if s == :Wall
when 'x'; t.dig(:No)
end
x += 1
}
x = df.cursor.x - offset[0]
y += 1
}
puts ' done'

@ -1,40 +0,0 @@
-- Remove all aquifers from the map
--[[=begin
drain-aquifer
=============
Remove all 'aquifer' tag from the map blocks. Irreversible.
=end]]
local function drain()
local layers = {}
local layer_count = 0
local tile_count = 0
for k, block in ipairs(df.global.world.map.map_blocks) do
if block.flags.has_aquifer then
block.flags.has_aquifer = false
block.flags.check_aquifer = false
for x, row in ipairs(block.designation) do
for y, tile in ipairs(row) do
if tile.water_table then
tile.water_table = false
tile_count = tile_count + 1
end
end
end
if not layers[block.map_pos.z] then
layers[block.map_pos.z] = true
layer_count = layer_count + 1
end
end
end
print("Cleared "..tile_count.." aquifer tile"..((tile_count ~= 1) and "s" or "")..
" in "..layer_count.." layer"..((layer_count ~= 1) and "s" or "")..".")
end
drain(...)

@ -1,52 +0,0 @@
-- Elevate all the mental attributes of a unit
-- by vjek
--[[=begin
elevate-mental
==============
Set all mental attributes of the selected dwarf to 2600, which is very high.
Numbers between 0 and 5000 can be passed as an argument: ``elevate-mental 100``
for example would make the dwarf very stupid indeed.
=end]]
function ElevateMentalAttributes(value)
unit=dfhack.gui.getSelectedUnit()
if unit==nil then
print ("No unit under cursor! Aborting with extreme prejudice.")
return
end
--print name of dwarf
print("Adjusting "..dfhack.TranslateName(dfhack.units.getVisibleName(unit)))
--walk through available attributes, adjust current to max
local ok,f,t,k = pcall(pairs,unit.status.current_soul.mental_attrs)
if ok then
for k,v in f,t,k do
if value ~= nil then
print("Adjusting current value for "..tostring(k).." of "..v.value.." to the value of "..value)
v.value=value
else
print("Adjusting current value for "..tostring(k).." of "..v.value.." to max value of "..v.max_value)
v.value=v.max_value
--below will reset values back to "normal"
--v.value=v.max_value/2
end
end
end
end
--script execution starts here
local opt = ...
opt = tonumber(opt)
if opt ~= nil then
if opt >=0 and opt <=5000 then
ElevateMentalAttributes(opt)
end
if opt <0 or opt >5000 then
print("Invalid Range or argument. This script accepts either no argument, in which case it will increase the attribute to the max_value for the unit, or an argument between 0 and 5000, which will set all attributes to that value.")
end
end
if opt == nil then
ElevateMentalAttributes()
end

@ -1,51 +0,0 @@
-- Elevate all the physical attributes of a unit
-- by vjek
--[[=begin
elevate-physical
================
As for elevate-mental, but for physical traits. High is good for soldiers,
while having an ineffective hammerer can be useful too...
=end]]
function ElevatePhysicalAttributes(value)
unit=dfhack.gui.getSelectedUnit()
if unit==nil then
print ("No unit under cursor! Aborting with extreme prejudice.")
return
end
--print name of dwarf
print("Adjusting "..dfhack.TranslateName(dfhack.units.getVisibleName(unit)))
--walk through available attributes, adjust current to max
local ok,f,t,k = pcall(pairs,unit.body.physical_attrs)
if ok then
for k,v in f,t,k do
if value ~= nil then
print("Adjusting current value for "..tostring(k).." of "..v.value.." to the value of "..value)
v.value=value
else
print("Adjusting current value for "..tostring(k).." of "..v.value.." to max value of "..v.max_value)
v.value=v.max_value
--below will reset values back to "normal"
--v.value=v.max_value/2
end
end
end
end
--script execution starts here
local opt = ...
opt = tonumber(opt)
if opt ~= nil then
if opt >=0 and opt <=5000 then
ElevatePhysicalAttributes(opt)
end
if opt <0 or opt >5000 then
print("Invalid Range or argument. This script accepts either no argument, in which case it will increase the attribute to the max_value for the unit, or an argument between 0 and 5000, which will set all attributes to that value.")
end
end
if opt == nil then
ElevatePhysicalAttributes()
end

@ -1,132 +0,0 @@
--Allow stressed dwarves to emigrate from the fortress
-- For 34.11 by IndigoFenix; update and cleanup by PeridexisErrant
-- old version: http://dffd.bay12games.com/file.php?id=8404
--[[=begin
emigration
==========
Allows dwarves to emigrate from the fortress when stressed,
in proportion to how badly stressed they are and adjusted
for who they would have to leave with - a dwarven merchant
being more attractive than leaving alone (or with an elf).
The check is made monthly.
A happy dwarf (ie with negative stress) will never emigrate.
Usage: ``emigration enable|disable``
=end]]
local args = {...}
if args[1] == "enable" then
enabled = true
elseif args[1] == "disable" then
enabled = false
end
function desireToStay(unit,method,civ_id)
-- on a percentage scale
local value = 100 - unit.status.current_soul.personality.stress_level / 5000
if method == 'merchant' or method == 'diplomat' then
if civ_id ~= unit.civ_id then value = value*2 end end
if method == 'wild' then
value = value*5 end
return value
end
function desert(u,method,civ)
u.relations.following = nil
local line = dfhack.TranslateName(dfhack.units.getVisibleName(u)) .. " has "
if method == 'merchant' then
line = line.."joined the merchants"
u.flags1.merchant = true
u.civ_id = civ
elseif method == 'diplomat' then
line = line.."followed the diplomat"
u.flags1.diplomat = true
u.civ_id = civ
else
line = line.."abandoned the settlement in search of a better life."
u.civ_id = -1
u.flags1.forest = true
u.animal.leave_countdown = 2
end
print(line)
dfhack.gui.showAnnouncement(line, COLOR_WHITE)
end
function canLeave(unit)
for _, skill in pairs(unit.status.current_soul.skills) do
if skill.rating > 14 then return false end
end
if unit.flags1.caged
or unit.race ~= df.global.ui.race_id
or unit.civ_id ~= df.global.ui.civ_id
or dfhack.units.isDead(unit)
or dfhack.units.isOpposedToLife(unit)
or unit.flags1.merchant
or unit.flags1.diplomat
or unit.flags1.chained
or dfhack.units.getNoblePositions(unit) ~= nil
or unit.military.squad_id ~= -1
or dfhack.units.isCitizen(unit)
or dfhack.units.isSane(unit)
or unit.profession ~= 103
or not dfhack.units.isDead(unit)
then return false end
return true
end
function checkForDeserters(method,civ_id)
local allUnits = df.global.world.units.active
for i=#allUnits-1,0,-1 do -- search list in reverse
local u = allUnits[i]
if canLeave(u) and math.random(100) < desireToStay(u,method,civ_id) then
desert(u,method,civ_id)
end
end
end
function checkmigrationnow()
local merchant_civ_ids = {}
local diplomat_civ_ids = {}
local allUnits = df.global.world.units.active
for i=0, #allUnits-1 do
local unit = allUnits[i]
if dfhack.units.isSane(unit)
and not dfhack.units.isDead(unit)
and not dfhack.units.isOpposedToLife(unit)
and not unit.flags1.tame
then
if unit.flags1.merchant then table.insert(merchant_civ_ids, unit.civ_id) end
if unit.flags1.diplomat then table.insert(diplomat_civ_ids, unit.civ_id) end
end
end
for _, civ_id in pairs(merchant_civ_ids) do checkForDeserters('merchant', civ_id) end
for _, civ_id in pairs(diplomat_civ_ids) do checkForDeserters('diplomat', civ_id) end
checkForDeserters('wild', -1)
end
local function event_loop()
if enabled then
checkmigrationnow()
dfhack.timeout(1, 'months', event_loop)
end
end
dfhack.onStateChange.loadEmigration = function(code)
if code==SC_MAP_LOADED then
if enabled then
print("Emigration enabled.")
event_loop()
else
print("Emigration disabled.")
end
end
end
if dfhack.isMapLoaded() then
dfhack.onStateChange.loadEmigration(SC_MAP_LOADED)
end

@ -1,836 +0,0 @@
-- Export everything from legends mode
--[[=begin
exportlegends
=============
Controls legends mode to export data - especially useful to set-and-forget large
worlds, or when you want a map of every site when there are several hundred.
The 'info' option exports more data than is possible in vanilla, to a
:file:`region-date-legends_plus.xml` file developed to extend
:forums:`World Viewer <128932>` and other legends utilities.
Options:
:info: Exports the world/gen info, the legends XML, and a custom XML with more information
:custom: Exports a custom XML with more information
:sites: Exports all available site maps
:maps: Exports all seventeen detailed maps
:all: Equivalent to calling all of the above, in that order
=end]]
gui = require 'gui'
local args = {...}
local vs = dfhack.gui.getCurViewscreen()
local i = 1
local MAPS = {
"Standard biome+site map",
"Elevations including lake and ocean floors",
"Elevations respecting water level",
"Biome",
"Hydrosphere",
"Temperature",
"Rainfall",
"Drainage",
"Savagery",
"Volcanism",
"Current vegetation",
"Evil",
"Salinity",
"Structures/fields/roads/etc.",
"Trade",
"Nobility and Holdings",
"Diplomacy",
}
function getItemSubTypeName(itemType, subType)
if (dfhack.items.getSubtypeCount(itemType)) <= 0 then
return tostring(-1)
end
local subtypename = dfhack.items.getSubtypeDef(itemType, subType)
if (subtypename == nil) then
return tostring(-1)
else
return tostring(subtypename.name):lower()
end
end
function findEntity(id)
for k,v in ipairs(df.global.world.entities.all) do
if (v.id == id) then
return v
end
end
return nil
end
function table.contains(table, element)
for _, value in pairs(table) do
if value == element then
return true
end
end
return false
end
function table.containskey(table, key)
for value, _ in pairs(table) do
if value == key then
return true
end
end
return false
end
-- wrapper that returns "unknown N" for df.enum_type[BAD_VALUE],
-- instead of returning nil or causing an error
df_enums = {}
setmetatable(df_enums, {
__index = function(self, enum)
if not df[enum] or df[enum]._kind ~= 'enum-type' then
error('invalid enum: ' .. enum)
end
local t = {}
setmetatable(t, {
__index = function(self, k)
return df[enum][k] or 'unknown ' .. k
end
})
return t
end,
__newindex = function() error('read-only') end
})
--create an extra legends xml with extra data, by Mason11987 for World Viewer
function export_more_legends_xml()
local month = dfhack.world.ReadCurrentMonth() + 1 --days and months are 1-indexed
local day = dfhack.world.ReadCurrentDay()
local year_str = string.format('%0'..math.max(5, string.len(''..df.global.cur_year))..'d', df.global.cur_year)
local date_str = year_str..string.format('-%02d-%02d', month, day)
local filename = df.global.world.cur_savegame.save_dir.."-"..date_str.."-legends_plus.xml"
local file = io.open(filename, 'w')
if not file then qerror("could not open file: " .. filename) end
file:write("<?xml version=\"1.0\" encoding='UTF-8'?>\n")
file:write("<df_world>\n")
file:write("<name>"..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name)).."</name>\n")
file:write("<altname>"..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name,1)).."</altname>\n")
file:write("<landmasses>\n")
for landmassK, landmassV in ipairs(df.global.world.world_data.landmasses) do
file:write("\t<landmass>\n")
file:write("\t\t<id>"..landmassV.index.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(landmassV.name,1)).."</name>\n")
file:write("\t\t<coord_1>"..landmassV.min_x..","..landmassV.min_y.."</coord_1>\n")
file:write("\t\t<coord_2>"..landmassV.max_x..","..landmassV.max_y.."</coord_2>\n")
file:write("\t</landmass>\n")
end
file:write("</landmasses>\n")
file:write("<mountain_peaks>\n")
for mountainK, mountainV in ipairs(df.global.world.world_data.mountain_peaks) do
file:write("\t<mountain_peak>\n")
file:write("\t\t<id>"..mountainK.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(mountainV.name,1)).."</name>\n")
file:write("\t\t<coords>"..mountainV.pos.x..","..mountainV.pos.y.."</coords>\n")
file:write("\t\t<height>"..mountainV.height.."</height>\n")
file:write("\t</mountain_peak>\n")
end
file:write("</mountain_peaks>\n")
file:write("<regions>\n")
for regionK, regionV in ipairs(df.global.world.world_data.regions) do
file:write("\t<region>\n")
file:write("\t\t<id>"..regionV.index.."</id>\n")
file:write("\t\t<coords>")
for xK, xVal in ipairs(regionV.region_coords.x) do
file:write(xVal..","..regionV.region_coords.y[xK].."|")
end
file:write("</coords>\n")
file:write("\t</region>\n")
end
file:write("</regions>\n")
file:write("<underground_regions>\n")
for regionK, regionV in ipairs(df.global.world.world_data.underground_regions) do
file:write("\t<underground_region>\n")
file:write("\t\t<id>"..regionV.index.."</id>\n")
file:write("\t\t<coords>")
for xK, xVal in ipairs(regionV.region_coords.x) do
file:write(xVal..","..regionV.region_coords.y[xK].."|")
end
file:write("</coords>\n")
file:write("\t</underground_region>\n")
end
file:write("</underground_regions>\n")
file:write("<sites>\n")
for siteK, siteV in ipairs(df.global.world.world_data.sites) do
file:write("\t<site>\n")
for k,v in pairs(siteV) do
if (k == "id" or k == "civ_id" or k == "cur_owner_id") then
file:write("\t\t<"..k..">"..tostring(v).."</"..k..">\n")
elseif (k == "buildings") then
if (#siteV.buildings > 0) then
file:write("\t\t<structures>\n")
for buildingK, buildingV in ipairs(siteV.buildings) do
file:write("\t\t\t<structure>\n")
file:write("\t\t\t\t<id>"..buildingV.id.."</id>\n")
file:write("\t\t\t\t<type>"..df_enums.abstract_building_type[buildingV:getType()]:lower().."</type>\n")
if (df_enums.abstract_building_type[buildingV:getType()]:lower() ~= "underworld_spire" or table.containskey(buildingV,"name")) then
file:write("\t\t\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(buildingV.name, 1)).."</name>\n")
file:write("\t\t\t\t<name2>"..dfhack.df2utf(dfhack.TranslateName(buildingV.name)).."</name2>\n")
end
if (buildingV:getType() == df.abstract_building_type.TEMPLE) then
file:write("\t\t\t\t<deity>"..buildingV.deity.."</deity>\n")
file:write("\t\t\t\t<religion>"..buildingV.religion.."</religion>\n")
end
if (buildingV:getType() == df.abstract_building_type.DUNGEON) then
file:write("\t\t\t\t<dungeon_type>"..buildingV.dungeon_type.."</dungeon_type>\n")
end
for inhabitabntK,inhabitabntV in pairs(buildingV.inhabitants) do
file:write("\t\t\t\t<inhabitant>"..inhabitabntV.anon_2.."</inhabitant>\n")
end
file:write("\t\t\t</structure>\n")
end
file:write("\t\t</structures>\n")
end
end
end
file:write("\t</site>\n")
end
file:write("</sites>\n")
file:write("<world_constructions>\n")
for wcK, wcV in ipairs(df.global.world.world_data.constructions.list) do
file:write("\t<world_construction>\n")
file:write("\t\t<id>"..wcV.id.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(wcV.name,1)).."</name>\n")
file:write("\t\t<type>"..(df_enums.world_construction_type[wcV:getType()]):lower().."</type>\n")
file:write("\t\t<coords>")
for xK, xVal in ipairs(wcV.square_pos.x) do
file:write(xVal..","..wcV.square_pos.y[xK].."|")
end
file:write("</coords>\n")
file:write("\t</world_construction>\n")
end
file:write("</world_constructions>\n")
file:write("<artifacts>\n")
for artifactK, artifactV in ipairs(df.global.world.artifacts.all) do
file:write("\t<artifact>\n")
file:write("\t\t<id>"..artifactV.id.."</id>\n")
if (artifactV.item:getType() ~= -1) then
file:write("\t\t<item_type>"..tostring(df_enums.item_type[artifactV.item:getType()]):lower().."</item_type>\n")
if (artifactV.item:getSubtype() ~= -1) then
file:write("\t\t<item_subtype>"..artifactV.item.subtype.name.."</item_subtype>\n")
end
for improvementK,impovementV in pairs(artifactV.item.improvements) do
if impovementV:getType() == df.improvement_type.WRITING then
for writingk,writingV in pairs(impovementV["itemimprovement_writingst.anon_1"]) do
file:write("\t\t<writing>"..writingV.."</writing>\n")
end
elseif impovementV:getType() == df.improvement_type.PAGES then
file:write("\t\t<page_count>"..impovementV.count.."</page_count>\n")
for writingk,writingV in pairs(impovementV.contents) do
file:write("\t\t<writing>"..writingV.."</writing>\n")
end
end
end
end
if (table.containskey(artifactV.item,"description")) then
file:write("\t\t<item_description>"..dfhack.df2utf(artifactV.item.description:lower()).."</item_description>\n")
end
if artifactV.item:getMaterial() ~= -1 then
file:write("\t\t<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(artifactV.item:getMaterial(), artifactV.item:getMaterialIndex())).."</mat>\n")
end
file:write("\t</artifact>\n")
end
file:write("</artifacts>\n")
file:write("<historical_figures>\n")
for hfK, hfV in ipairs(df.global.world.history.figures) do
file:write("\t<historical_figure>\n")
file:write("\t\t<id>"..hfV.id.."</id>\n")
file:write("\t\t<sex>"..hfV.sex.."</sex>\n")
if hfV.race >= 0 then file:write("\t\t<race>"..df.global.world.raws.creatures.all[hfV.race].name[0].."</race>\n") end
file:write("\t</historical_figure>\n")
end
file:write("</historical_figures>\n")
file:write("<entity_populations>\n")
for entityPopK, entityPopV in ipairs(df.global.world.entity_populations) do
file:write("\t<entity_population>\n")
file:write("\t\t<id>"..entityPopV.id.."</id>\n")
for raceK, raceV in ipairs(entityPopV.races) do
local raceName = (df.global.world.raws.creatures.all[raceV].creature_id):lower()
file:write("\t\t<race>"..raceName..":"..entityPopV.counts[raceK].."</race>\n")
end
file:write("\t\t<civ_id>"..entityPopV.civ_id.."</civ_id>\n")
file:write("\t</entity_population>\n")
end
file:write("</entity_populations>\n")
file:write("<entities>\n")
for entityK, entityV in ipairs(df.global.world.entities.all) do
file:write("\t<entity>\n")
file:write("\t\t<id>"..entityV.id.."</id>\n")
if entityV.race >= 0 then
file:write("\t\t<race>"..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."</race>\n")
end
file:write("\t\t<type>"..(df_enums.historical_entity_type[entityV.type]):lower().."</type>\n")
if entityV.type == df.historical_entity_type.Religion then -- Get worshipped figure
if (entityV.unknown1b ~= nil and entityV.unknown1b.worship ~= nil) then
for k,v in pairs(entityV.unknown1b.worship) do
file:write("\t\t<worship_id>"..v.."</worship_id>\n")
end
end
end
for id, link in pairs(entityV.entity_links) do
file:write("\t\t<entity_link>\n")
for k, v in pairs(link) do
if (k == "type") then
file:write("\t\t\t<"..k..">"..tostring(df_enums.entity_entity_link_type[v]).."</"..k..">\n")
else
file:write("\t\t\t<"..k..">"..v.."</"..k..">\n")
end
end
file:write("\t\t</entity_link>\n")
end
for positionK,positionV in pairs(entityV.positions.own) do
file:write("\t\t<entity_position>\n")
file:write("\t\t\t<id>"..positionV.id.."</id>\n")
if positionV.name[0] ~= "" then file:write("\t\t\t<name>"..positionV.name[0].."</name>\n") end
if positionV.name_male[0] ~= "" then file:write("\t\t\t<name_male>"..positionV.name_male[0].."</name_male>\n") end
if positionV.name_female[0] ~= "" then file:write("\t\t\t<name_female>"..positionV.name_female[0].."</name_female>\n") end
if positionV.spouse[0] ~= "" then file:write("\t\t\t<spouse>"..positionV.spouse[0].."</spouse>\n") end
if positionV.spouse_male[0] ~= "" then file:write("\t\t\t<spouse_male>"..positionV.spouse_male[0].."</spouse_male>\n") end
if positionV.spouse_female[0] ~= "" then file:write("\t\t\t<spouse_female>"..positionV.spouse_female[0].."</spouse_female>\n") end
file:write("\t\t</entity_position>\n")
end
for assignmentK,assignmentV in pairs(entityV.positions.assignments) do
file:write("\t\t<entity_position_assignment>\n")
for k, v in pairs(assignmentV) do
if (k == "id" or k == "histfig" or k == "position_id" or k == "squad_id") then
file:write("\t\t\t<"..k..">"..v.."</"..k..">\n")
end
end
file:write("\t\t</entity_position_assignment>\n")
end
for idx,id in pairs(entityV.histfig_ids) do
file:write("\t\t<histfig_id>"..id.."</histfig_id>\n")
end
for id, link in ipairs(entityV.children) do
file:write("\t\t<child>"..link.."</child>\n")
end
file:write("\t\t<claims>")
for xK, xVal in ipairs(entityV.claims.unk2.x) do
file:write(xVal..","..entityV.claims.unk2.y[xK].."|")
end
file:write("\t\t</claims>\n")
if (table.containskey(entityV,"occasion_info") and entityV.occasion_info ~= nil) then
for occasionK, occasionV in pairs(entityV.occasion_info.occasions) do
file:write("\t\t<occasion>\n")
file:write("\t\t\t<id>"..occasionV.id.."</id>\n")
file:write("\t\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(occasionV.name,1)).."</name>\n")
file:write("\t\t\t<event>"..occasionV.event.."</event>\n")
for scheduleK, scheduleV in pairs(occasionV.schedule) do
file:write("\t\t\t<schedule>\n")
file:write("\t\t\t\t<id>"..scheduleK.."</id>\n")
file:write("\t\t\t\t<type>"..df_enums.occasion_schedule_type[scheduleV.type]:lower().."</type>\n")
if(scheduleV.type == df.occasion_schedule_type.THROWING_COMPETITION) then
file:write("\t\t\t\t<item_type>"..df_enums.item_type[scheduleV.reference]:lower().."</item_type>\n")
file:write("\t\t\t\t<item_subtype>"..getItemSubTypeName(scheduleV.reference,scheduleV.reference2).."</item_subtype>\n")
else
file:write("\t\t\t\t<reference>"..scheduleV.reference.."</reference>\n")
file:write("\t\t\t\t<reference2>"..scheduleV.reference2.."</reference2>\n")
end
for featureK, featureV in pairs(scheduleV.features) do
file:write("\t\t\t\t<feature>\n")
if(df_enums.occasion_schedule_feature[featureV.feature] ~= nil) then
file:write("\t\t\t\t\t<type>"..df_enums.occasion_schedule_feature[featureV.feature]:lower().."</type>\n")
else
file:write("\t\t\t\t\t<type>"..featureV.feature.."</type>\n")
end
file:write("\t\t\t\t\t<reference>"..featureV.reference.."</reference>\n")
file:write("\t\t\t\t</feature>\n")
end
file:write("\t\t\t</schedule>\n")
end
file:write("\t\t</occasion>\n")
end
end
file:write("\t</entity>\n")
end
file:write("</entities>\n")
file:write("<poetic_forms>\n")
for formK, formV in ipairs(df.global.world.poetic_forms.all) do
file:write("\t<poetic_form>\n")
file:write("\t\t<id>"..formV.id.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(formV.name,1)).."</name>\n")
file:write("\t</poetic_form>\n")
end
file:write("</poetic_forms>\n")
file:write("<musical_forms>\n")
for formK, formV in ipairs(df.global.world.musical_forms.all) do
file:write("\t<musical_form>\n")
file:write("\t\t<id>"..formV.id.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(formV.name,1)).."</name>\n")
file:write("\t</musical_form>\n")
end
file:write("</musical_forms>\n")
file:write("<dance_forms>\n")
for formK, formV in ipairs(df.global.world.dance_forms.all) do
file:write("\t<dance_form>\n")
file:write("\t\t<id>"..formV.id.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(formV.name,1)).."</name>\n")
file:write("\t</dance_form>\n")
end
file:write("</dance_forms>\n")
file:write("<written_contents>\n")
for wcK, wcV in ipairs(df.global.world.written_contents.all) do
file:write("\t<written_content>\n")
file:write("\t\t<id>"..wcV.id.."</id>\n")
file:write("\t\t<title>"..wcV.title.."</title>\n")
file:write("\t\t<page_start>"..wcV.page_start.."</page_start>\n")
file:write("\t\t<page_end>"..wcV.page_end.."</page_end>\n")
for refK, refV in pairs(wcV.refs) do
file:write("\t\t<reference>\n")
file:write("\t\t\t<type>"..df_enums.general_ref_type[refV:getType()].."</type>\n")
if refV:getType() == df.general_ref_type.ARTIFACT then file:write("\t\t\t<id>"..refV.artifact_id.."</id>\n") -- artifact
elseif refV:getType() == df.general_ref_type.ENTITY then file:write("\t\t\t<id>"..refV.entity_id.."</id>\n") -- entity
elseif refV:getType() == df.general_ref_type.HISTORICAL_EVENT then file:write("\t\t\t<id>"..refV.event_id.."</id>\n") -- event
elseif refV:getType() == df.general_ref_type.SITE then file:write("\t\t\t<id>"..refV.site_id.."</id>\n") -- site
elseif refV:getType() == df.general_ref_type.SUBREGION then file:write("\t\t\t<id>"..refV.region_id.."</id>\n") -- region
elseif refV:getType() == df.general_ref_type.HISTORICAL_FIGURE then file:write("\t\t\t<id>"..refV.hist_figure_id.."</id>\n") -- hist figure
elseif refV:getType() == df.general_ref_type.WRITTEN_CONTENT then file:write("\t\t\t<id>"..refV.anon_1.."</id>\n")
elseif refV:getType() == df.general_ref_type.POETIC_FORM then file:write("\t\t\t<id>"..refV.poetic_form_id.."</id>\n") -- poetic form
elseif refV:getType() == df.general_ref_type.MUSICAL_FORM then file:write("\t\t\t<id>"..refV.musical_form_id.."</id>\n") -- musical form
elseif refV:getType() == df.general_ref_type.DANCE_FORM then file:write("\t\t\t<id>"..refV.dance_form_id.."</id>\n") -- dance form
elseif refV:getType() == df.general_ref_type.INTERACTION then -- TODO INTERACTION
elseif refV:getType() == df.general_ref_type.KNOWLEDGE_SCHOLAR_FLAG then -- TODO KNOWLEDGE_SCHOLAR_FLAG
elseif refV:getType() == df.general_ref_type.VALUE_LEVEL then -- TODO VALUE_LEVEL
elseif refV:getType() == df.general_ref_type.LANGUAGE then -- TODO LANGUAGE
else
print("unknown reference",refV:getType(),df_enums.general_ref_type[refV:getType()])
--for k,v in pairs(refV) do print(k,v) end
end
file:write("\t\t</reference>\n")
end
file:write("\t\t<type>"..(df_enums.written_content_type[wcV.type] or wcV.type).."</type>\n")
for styleK, styleV in pairs(wcV.styles) do
file:write("\t\t<style>"..(df_enums.written_content_style[styleV] or styleV).."</style>\n")
end
file:write("\t\t<author>"..wcV.author.."</author>\n")
file:write("\t</written_content>\n")
end
file:write("</written_contents>\n")
file:write("<historical_events>\n")
for ID, event in ipairs(df.global.world.history.events) do
if event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK
or event:getType() == df.history_event_type.ADD_HF_SITE_LINK
or event:getType() == df.history_event_type.ADD_HF_HF_LINK
or event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK
or event:getType() == df.history_event_type.TOPICAGREEMENT_CONCLUDED
or event:getType() == df.history_event_type.TOPICAGREEMENT_REJECTED
or event:getType() == df.history_event_type.TOPICAGREEMENT_MADE
or event:getType() == df.history_event_type.BODY_ABUSED
or event:getType() == df.history_event_type.CHANGE_CREATURE_TYPE
or event:getType() == df.history_event_type.CHANGE_HF_JOB
or event:getType() == df.history_event_type.CHANGE_HF_STATE
or event:getType() == df.history_event_type.CREATED_BUILDING
or event:getType() == df.history_event_type.CREATURE_DEVOURED
or event:getType() == df.history_event_type.HF_DOES_INTERACTION
or event:getType() == df.history_event_type.HF_LEARNS_SECRET
or event:getType() == df.history_event_type.HIST_FIGURE_NEW_PET
or event:getType() == df.history_event_type.HIST_FIGURE_REACH_SUMMIT
or event:getType() == df.history_event_type.ITEM_STOLEN
or event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK
or event:getType() == df.history_event_type.REMOVE_HF_SITE_LINK
or event:getType() == df.history_event_type.REPLACED_BUILDING
or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_DESIGN
or event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM
or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT
or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM
or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT
or event:getType() == df.history_event_type.MASTERPIECE_CREATED_FOOD
or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ENGRAVING
or event:getType() == df.history_event_type.MASTERPIECE_LOST
or event:getType() == df.history_event_type.ENTITY_ACTION
or event:getType() == df.history_event_type.HF_ACT_ON_BUILDING
or event:getType() == df.history_event_type.ARTIFACT_CREATED
or event:getType() == df.history_event_type.ASSUME_IDENTITY
or event:getType() == df.history_event_type.CREATE_ENTITY_POSITION
or event:getType() == df.history_event_type.DIPLOMAT_LOST
or event:getType() == df.history_event_type.MERCHANT
or event:getType() == df.history_event_type.WAR_PEACE_ACCEPTED
or event:getType() == df.history_event_type.WAR_PEACE_REJECTED
or event:getType() == df.history_event_type.HIST_FIGURE_WOUNDED
or event:getType() == df.history_event_type.HIST_FIGURE_DIED
then
file:write("\t<historical_event>\n")
file:write("\t\t<id>"..event.id.."</id>\n")
file:write("\t\t<type>"..tostring(df_enums.history_event_type[event:getType()]):lower().."</type>\n")
for k,v in pairs(event) do
if k == "year" or k == "seconds" or k == "flags" or k == "id"
or (k == "region" and event:getType() ~= df.history_event_type.HF_DOES_INTERACTION)
or k == "region_pos" or k == "layer" or k == "feature_layer" or k == "subregion"
or k == "anon_1" or k == "anon_2" or k == "flags2" or k == "unk1" then
elseif event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK and k == "link_type" then
file:write("\t\t<"..k..">"..df_enums.histfig_entity_link_type[v]:lower().."</"..k..">\n")
elseif event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK and k == "position_id" then
local entity = findEntity(event.civ)
if (entity ~= nil and event.civ > -1 and v > -1) then
for entitypositionsK, entityPositionsV in ipairs(entity.positions.own) do
if entityPositionsV.id == v then
file:write("\t\t<position>"..tostring(entityPositionsV.name[0]):lower().."</position>\n")
break
end
end
else
file:write("\t\t<position>-1</position>\n")
end
elseif event:getType() == df.history_event_type.CREATE_ENTITY_POSITION and k == "position" then
local entity = findEntity(event.site_civ)
if (entity ~= nil and v > -1) then
for entitypositionsK, entityPositionsV in ipairs(entity.positions.own) do
if entityPositionsV.id == v then
file:write("\t\t<position>"..tostring(entityPositionsV.name[0]):lower().."</position>\n")
break
end
end
else
file:write("\t\t<position>-1</position>\n")
end
elseif event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK and k == "link_type" then
file:write("\t\t<"..k..">"..df_enums.histfig_entity_link_type[v]:lower().."</"..k..">\n")
elseif event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK and k == "position_id" then
local entity = findEntity(event.civ)
if (entity ~= nil and event.civ > -1 and v > -1) then
for entitypositionsK, entityPositionsV in ipairs(entity.positions.own) do
if entityPositionsV.id == v then
file:write("\t\t<position>"..tostring(entityPositionsV.name[0]):lower().."</position>\n")
break
end
end
else
file:write("\t\t<position>-1</position>\n")
end
elseif event:getType() == df.history_event_type.ADD_HF_HF_LINK and k == "type" then
file:write("\t\t<link_type>"..df_enums.histfig_hf_link_type[v]:lower().."</link_type>\n")
elseif event:getType() == df.history_event_type.ADD_HF_SITE_LINK and k == "type" then
file:write("\t\t<link_type>"..df_enums.histfig_site_link_type[v]:lower().."</link_type>\n")
elseif event:getType() == df.history_event_type.REMOVE_HF_SITE_LINK and k == "type" then
file:write("\t\t<link_type>"..df_enums.histfig_site_link_type[v]:lower().."</link_type>\n")
elseif (event:getType() == df.history_event_type.ITEM_STOLEN or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM
) and k == "item_type" then
file:write("\t\t<item_type>"..df_enums.item_type[v]:lower().."</item_type>\n")
elseif (event:getType() == df.history_event_type.ITEM_STOLEN or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM
) and k == "item_subtype" then
--if event.item_type > -1 and v > -1 then
file:write("\t\t<"..k..">"..getItemSubTypeName(event.item_type,v).."</"..k..">\n")
--end
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_FOOD and k == "item_subtype" then
--if event.item_type > -1 and v > -1 then
file:write("\t\t<item_type>food</item_type>\n")
file:write("\t\t<"..k..">"..getItemSubTypeName(df.item_type.FOOD,v).."</"..k..">\n")
--end
elseif event:getType() == df.history_event_type.ITEM_STOLEN and k == "mattype" then
if (v > -1) then
if (dfhack.matinfo.decode(event.mattype, event.matindex) == nil) then
file:write("\t\t<mattype>"..event.mattype.."</mattype>\n")
file:write("\t\t<matindex>"..event.matindex.."</matindex>\n")
else
file:write("\t\t<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mattype, event.matindex)).."</mat>\n")
end
end
elseif (event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_FOOD or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM
) and k == "mat_type" then
if (v > -1) then
if (dfhack.matinfo.decode(event.mat_type, event.mat_index) == nil) then
file:write("\t\t<mat_type>"..event.mat_type.."</mat_type>\n")
file:write("\t\t<mat_index>"..event.mat_index.."</mat_index>\n")
else
file:write("\t\t<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mat_type, event.mat_index)).."</mat>\n")
end
end
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "imp_mat_type" then
if (v > -1) then
if (dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index) == nil) then
file:write("\t\t<imp_mat_type>"..event.imp_mat_type.."</imp_mat_type>\n")
file:write("\t\t<imp_mat_index>"..event.imp_mat_index.."</imp_mat_index>\n")
else
file:write("\t\t<imp_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index)).."</imp_mat>\n")
end
end
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_DYE_ITEM and k == "dye_mat_type" then
if (v > -1) then
if (dfhack.matinfo.decode(event.dye_mat_type, event.dye_mat_index) == nil) then
file:write("\t\t<dye_mat_type>"..event.dye_mat_type.."</dye_mat_type>\n")
file:write("\t\t<dye_mat_index>"..event.dye_mat_index.."</dye_mat_index>\n")
else
file:write("\t\t<dye_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.dye_mat_type, event.dye_mat_index)).."</dye_mat>\n")
end
end
elseif event:getType() == df.history_event_type.ITEM_STOLEN and k == "matindex" then
--skip
elseif event:getType() == df.history_event_type.ITEM_STOLEN and k == "item" and v == -1 then
--skip
elseif (event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT
) and k == "mat_index" then
--skip
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "imp_mat_index" then
--skip
elseif (event:getType() == df.history_event_type.WAR_PEACE_ACCEPTED or
event:getType() == df.history_event_type.WAR_PEACE_REJECTED or
event:getType() == df.history_event_type.TOPICAGREEMENT_CONCLUDED or
event:getType() == df.history_event_type.TOPICAGREEMENT_REJECTED or
event:getType() == df.history_event_type.TOPICAGREEMENT_MADE
) and k == "topic" then
file:write("\t\t<topic>"..tostring(df_enums.meeting_topic[v]):lower().."</topic>\n")
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "improvement_type" then
file:write("\t\t<improvement_type>"..df_enums.improvement_type[v]:lower().."</improvement_type>\n")
elseif ((event:getType() == df.history_event_type.HIST_FIGURE_REACH_SUMMIT and k == "group")
or (event:getType() == df.history_event_type.HIST_FIGURE_NEW_PET and k == "group")
or (event:getType() == df.history_event_type.BODY_ABUSED and k == "bodies")) then
for detailK,detailV in pairs(v) do
file:write("\t\t<"..k..">"..detailV.."</"..k..">\n")
end
elseif event:getType() == df.history_event_type.HIST_FIGURE_NEW_PET and k == "pets" then
for detailK,detailV in pairs(v) do
file:write("\t\t<"..k..">"..df.global.world.raws.creatures.all[detailV].name[0].."</"..k..">\n")
end
elseif event:getType() == df.history_event_type.BODY_ABUSED and (k == "props") then
file:write("\t\t<props_item_type>"..tostring(df_enums.item_type[event.props.item.item_type]):lower().."</props_item_type>\n")
file:write("\t\t<props_item_subtype>"..getItemSubTypeName(event.props.item.item_type,event.props.item.item_subtype).."</props_item_subtype>\n")
if (event.props.item.mat_type > -1) then
if (dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index) == nil) then
file:write("\t\t<props_item_mat_type>"..event.props.item.mat_type.."</props_item_mat_type>\n")
file:write("\t\t<props_item_mat_index>"..event.props.item.mat_index.."</props_item_mat_index>\n")
else
file:write("\t\t<props_item_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index)).."</props_item_mat>\n")
end
end
--file:write("\t\t<"..k.."_item_mat_type>"..tostring(event.props.item.mat_type).."</"..k.."_item_mat_index>\n")
--file:write("\t\t<"..k.."_item_mat_index>"..tostring(event.props.item.mat_index).."</"..k.."_item_mat_index>\n")
file:write("\t\t<"..k.."_pile_type>"..tostring(event.props.pile_type).."</"..k.."_pile_type>\n")
elseif event:getType() == df.history_event_type.ASSUME_IDENTITY and k == "identity" then
if (table.contains(df.global.world.identities.all,v)) then
if (df.global.world.identities.all[v].histfig_id == -1) then
local thisIdentity = df.global.world.identities.all[v]
file:write("\t\t<identity_name>"..thisIdentity.name.first_name.."</identity_name>\n")
file:write("\t\t<identity_race>"..(df.global.world.raws.creatures.all[thisIdentity.race].creature_id):lower().."</identity_race>\n")
file:write("\t\t<identity_caste>"..(df.global.world.raws.creatures.all[thisIdentity.race].caste[thisIdentity.caste].caste_id):lower().."</identity_caste>\n")
else
file:write("\t\t<identity_hf>"..df.global.world.identities.all[v].histfig_id.."</identity_hf>\n")
end
end
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT and k == "building_type" then
file:write("\t\t<building_type>"..df_enums.building_type[v]:lower().."</building_type>\n")
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT and k == "building_subtype" then
if (df_enums.building_type[event.building_type]:lower() == "furnace") then
file:write("\t\t<building_subtype>"..df_enums.furnace_type[v]:lower().."</building_subtype>\n")
elseif v > -1 then
file:write("\t\t<building_subtype>"..tostring(v).."</building_subtype>\n")
end
elseif k == "race" then
if v > -1 then
file:write("\t\t<race>"..df.global.world.raws.creatures.all[v].name[0].."</race>\n")
end
elseif k == "caste" then
if v > -1 then
file:write("\t\t<caste>"..(df.global.world.raws.creatures.all[event.race].caste[v].caste_id):lower().."</caste>\n")
end
elseif k == "interaction" and event:getType() == df.history_event_type.HF_DOES_INTERACTION then
file:write("\t\t<interaction_action>"..df.global.world.raws.interactions[v].str[3].value.."</interaction_action>\n")
file:write("\t\t<interaction_string>"..df.global.world.raws.interactions[v].str[4].value.."</interaction_string>\n")
elseif k == "interaction" and event:getType() == df.history_event_type.HF_LEARNS_SECRET then
file:write("\t\t<secret_text>"..df.global.world.raws.interactions[v].str[2].value.."</secret_text>\n")
elseif event:getType() == df.history_event_type.HIST_FIGURE_DIED and k == "weapon" then
for detailK,detailV in pairs(v) do
if (detailK == "item") then
if detailV > -1 then
file:write("\t\t<"..detailK..">"..detailV.."</"..detailK..">\n")
local thisItem = df.item.find(detailV)
if (thisItem ~= nil) then
if (thisItem.flags.artifact == true) then
for refk,refv in pairs(thisItem.general_refs) do
if (refv:getType() == df.general_ref_type.IS_ARTIFACT) then
file:write("\t\t<artifact_id>"..refv.artifact_id.."</artifact_id>\n")
break
end
end
end
end
end
elseif (detailK == "item_type") then
if event.weapon.item > -1 then
file:write("\t\t<"..detailK..">"..tostring(df_enums.item_type[detailV]):lower().."</"..detailK..">\n")
end
elseif (detailK == "item_subtype") then
if event.weapon.item > -1 and detailV > -1 then
file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.item_type,detailV).."</"..detailK..">\n")
end
elseif (detailK == "mattype") then
if (detailV > -1) then
file:write("\t\t<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.mattype, event.weapon.matindex)).."</mat>\n")
end
elseif (detailK == "matindex") then
elseif (detailK == "shooter_item") then
if detailV > -1 then
file:write("\t\t<"..detailK..">"..detailV.."</"..detailK..">\n")
local thisItem = df.item.find(detailV)
if thisItem ~= nil then
if (thisItem.flags.artifact == true) then
for refk,refv in pairs(thisItem.general_refs) do
if (refv:getType() == df.general_ref_type.IS_ARTIFACT) then
file:write("\t\t<shooter_artifact_id>"..refv.artifact_id.."</shooter_artifact_id>\n")
break
end
end
end
end
end
elseif (detailK == "shooter_item_type") then
if event.weapon.shooter_item > -1 then
file:write("\t\t<"..detailK..">"..tostring(df_enums.item_type[detailV]):lower().."</"..detailK..">\n")
end
elseif (detailK == "shooter_item_subtype") then
if event.weapon.shooter_item > -1 and detailV > -1 then
file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.shooter_item_type,detailV).."</"..detailK..">\n")
end
elseif (detailK == "shooter_mattype") then
if (detailV > -1) then
file:write("\t\t<shooter_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.shooter_mattype, event.weapon.shooter_matindex)).."</shooter_mat>\n")
end
elseif (detailK == "shooter_matindex") then
--skip
elseif detailK == "slayer_race" or detailK == "slayer_caste" then
--skip
else
file:write("\t\t<"..detailK..">"..detailV.."</"..detailK..">\n")
end
end
elseif event:getType() == df.history_event_type.HIST_FIGURE_DIED and k == "death_cause" then
file:write("\t\t<"..k..">"..df_enums.death_type[v]:lower().."</"..k..">\n")
elseif event:getType() == df.history_event_type.CHANGE_HF_JOB and (k == "new_job" or k == "old_job") then
file:write("\t\t<"..k..">"..df_enums.profession[v]:lower().."</"..k..">\n")
elseif event:getType() == df.history_event_type.CHANGE_CREATURE_TYPE and (k == "old_race" or k == "new_race") and v >= 0 then
file:write("\t\t<"..k..">"..df.global.world.raws.creatures.all[v].name[0].."</"..k..">\n")
else
file:write("\t\t<"..k..">"..tostring(v).."</"..k..">\n")
end
end
file:write("\t</historical_event>\n")
end
end
file:write("</historical_events>\n")
file:write("<historical_event_collections>\n")
file:write("</historical_event_collections>\n")
file:write("<historical_eras>\n")
file:write("</historical_eras>\n")
file:write("</df_world>\n")
file:close()
end
-- export information and XML ('p, x')
function export_legends_info()
print(' Exporting: World map/gen info')
gui.simulateInput(vs, 'LEGENDS_EXPORT_MAP')
print(' Exporting: Legends xml')
gui.simulateInput(vs, 'LEGENDS_EXPORT_XML')
print(" Exporting: Extra legends_plus xml")
export_more_legends_xml()
end
--- presses 'd' for detailed maps
function wait_for_legends_vs()
local vs = dfhack.gui.getCurViewscreen()
if i <= #MAPS then
if df.viewscreen_legendsst:is_instance(vs.parent) then
vs = vs.parent
end
if df.viewscreen_legendsst:is_instance(vs) then
gui.simulateInput(vs, 'LEGENDS_EXPORT_DETAILED_MAP')
dfhack.timeout(10,'frames',wait_for_export_maps_vs)
else
dfhack.timeout(10,'frames',wait_for_legends_vs)
end
end
end
-- selects detailed map and export it
function wait_for_export_maps_vs()
local vs = dfhack.gui.getCurViewscreen()
if dfhack.gui.getCurFocus() == "export_graphical_map" then
vs.sel_idx = i-1
print(' Exporting: '..MAPS[i]..' map')
gui.simulateInput(vs, 'SELECT')
i = i + 1
dfhack.timeout(10,'frames',wait_for_legends_vs)
else
dfhack.timeout(10,'frames',wait_for_export_maps_vs)
end
end
-- export site maps
function export_site_maps()
local vs = dfhack.gui.getCurViewscreen()
if ((dfhack.gui.getCurFocus() ~= "legends" ) and (not table.contains(vs, "main_cursor"))) then -- Using open-legends
vs = vs.parent
end
print(' Exporting: All possible site maps')
vs.main_cursor = 1
gui.simulateInput(vs, 'SELECT')
for i=1, #vs.sites do
gui.simulateInput(vs, 'LEGENDS_EXPORT_MAP')
gui.simulateInput(vs, 'STANDARDSCROLL_DOWN')
end
gui.simulateInput(vs, 'LEAVESCREEN')
end
-- main()
if dfhack.gui.getCurFocus() == "legends" or dfhack.gui.getCurFocus() == "dfhack/lua/legends" then
-- either native legends mode, or using the open-legends.lua script
if args[1] == "all" then
export_legends_info()
export_site_maps()
wait_for_legends_vs()
elseif args[1] == "info" then
export_legends_info()
elseif args[1] == "custom" then
export_more_legends_xml()
elseif args[1] == "maps" then
wait_for_legends_vs()
elseif args[1] == "sites" then
export_site_maps()
else dfhack.printerr('Valid arguments are "all", "info", "maps" or "sites"')
end
elseif args[1] == "maps" and
dfhack.gui.getCurFocus() == "export_graphical_map" then
wait_for_export_maps_vs()
else
dfhack.printerr('Exportlegends must be run from the main legends view')
end

@ -1,192 +0,0 @@
# exterminate creatures
=begin
exterminate
===========
Kills any unit of a given race.
With no argument, lists the available races and count eligible targets.
With the special argument ``him``, targets only the selected creature.
With the special argument ``undead``, targets all undeads on the map,
regardless of their race.
When specifying a race, a caste can be specified to further restrict the
targeting. To do that, append and colon and the caste name after the race.
Any non-dead non-caged unit of the specified race gets its ``blood_count``
set to 0, which means immediate death at the next game tick. For creatures
such as vampires, it also sets animal.vanish_countdown to 2.
An alternate mode is selected by adding a 2nd argument to the command,
``magma``. In this case, a column of 7/7 magma is generated on top of the
targets until they die (Warning: do not call on magma-safe creatures. Also,
using this mode on birds is not recommended.) The final alternate mode
is ``butcher``, which marks them for butchering but does not kill.
Will target any unit on a revealed tile of the map, including ambushers,
but ignore caged/chained creatures.
Ex::
exterminate gob
exterminate gob:male
To kill a single creature, select the unit with the 'v' cursor and::
exterminate him
To purify all elves on the map with fire (may have side-effects)::
exterminate elve magma
=end
race = $script_args[0]
# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death
# if it is 'butcher' mark all units for butchering (wont work with hostiles)
kill_by = $script_args[1]
case kill_by
when 'magma'
slain = 'burning'
when 'slaughter', 'butcher'
slain = 'marked for butcher'
when nil
slain = 'slain'
else
race = 'help'
end
checkunit = lambda { |u|
(u.body.blood_count != 0 or u.body.blood_max == 0) and
not u.flags1.dead and
not u.flags1.caged and not u.flags1.chained and
#not u.flags1.hidden_in_ambush and
not df.map_designation_at(u).hidden
}
slayit = lambda { |u|
case kill_by
when 'magma'
# it's getting hot around here
# !!WARNING!! do not call on a magma-safe creature
ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) {
if u.flags1.dead
df.onupdate_unregister(ouh)
else
x, y, z = u.pos.x, u.pos.y, u.pos.z
z += 1 while tile = df.map_tile_at(x, y, z+1) and
tile.shape_passableflow and tile.shape_passablelow
df.map_tile_at(x, y, z).spawn_magma(7)
end
}
when 'butcher', 'slaughter'
# mark for slaughter at butcher's shop
u.flags2.slaughter = true
else
# just make them drop dead
u.body.blood_count = 0
# some races dont mind having no blood, ensure they are still taken care of.
u.animal.vanish_countdown = 2
end
}
all_races = Hash.new(0)
df.world.units.active.map { |u|
if checkunit[u]
if (u.enemy.undead or
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
u.curse.rem_tags1.OPPOSED_TO_LIFE))
all_races['Undead'] += 1
else
all_races[u.race_tg.creature_id] += 1
end
end
}
case race
when nil
all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" }
when 'help', '?'
puts <<EOS
Kills all creatures of a given race.
With no argument, lists possible targets with their head count.
With the special argument 'him' or 'her', kill only the currently selected creature.
With the special argument 'undead', kill all undead creatures/thralls.
The targets will bleed out on the next game tick, or if they are immune to that, will vanish in a puff of smoke.
The special final argument 'magma' will make magma rain on the targets instead.
The special final argument 'butcher' will mark the targets for butchering instead.
Ex: exterminate gob
exterminate gob:male
exterminate elve magma
exterminate him
exterminate pig butcher
EOS
when 'him', 'her', 'it', 'that'
if him = df.unit_find
case him.race_tg.caste[him.caste].gender
when 0; puts 'its a she !' if race != 'her'
when 1; puts 'its a he !' if race != 'him'
else; puts 'its an it !' if race != 'it' and race != 'that'
end
slayit[him]
else
puts "Select a target ingame"
end
when /^undead/i
count = 0
df.world.units.active.each { |u|
if (u.enemy.undead or
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
u.curse.rem_tags1.OPPOSED_TO_LIFE)) and
checkunit[u]
slayit[u]
count += 1
end
}
puts "#{slain} #{count} undeads"
else
if race.index(':')
race, caste = race.split(':')
end
raw_race = df.match_rawname(race, all_races.keys)
if not raw_race
puts "Invalid race, use one of #{all_races.keys.sort.join(' ')}"
throw :script_finished
end
race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race }
if caste
all_castes = df.world.raws.creatures.all[race_nr].caste.map { |c| c.caste_id }
raw_caste = df.match_rawname(caste, all_castes)
if not raw_caste
puts "Invalid caste, use one of #{all_castes.sort.join(' ')}"
throw :script_finished
end
caste_nr = all_castes.index(raw_caste)
end
count = 0
df.world.units.active.each { |u|
if u.race == race_nr and checkunit[u]
next if caste_nr and u.caste != caste_nr
slayit[u]
count += 1
end
}
puts "#{slain} #{count} #{raw_caste} #{raw_race}"
end

@ -1,75 +0,0 @@
-- List or manage map features & enable magma furnaces
local help = [[=begin
feature
=======
Enables management of map features.
* Discovering a magma feature (magma pool, volcano, magma sea, or curious
underground structure) permits magma workshops and furnaces to be built.
* Discovering a cavern layer causes plants (trees, shrubs, and grass) from
that cavern to grow within your fortress.
Options:
:list: Lists all map features in your current embark by index.
:magma: Enable magma furnaces (discovers a random magma feature).
:show X: Marks the selected map feature as discovered.
:hide X: Marks the selected map feature as undiscovered.
=end]]
local map_features = df.global.world.features.map_features
function toggle_feature(idx, discovered)
idx = tonumber(idx)
if idx < 0 or idx >= #map_features then
qerror('Invalid feature ID')
end
map_features[tonumber(idx)].flags.Discovered = discovered
end
function list_features()
local name = df.new('string')
for idx, feat in ipairs(map_features) do
local tags = ''
for _, t in pairs({'water', 'magma', 'subterranean', 'chasm', 'layer'}) do
if feat['is' .. t:sub(1, 1):upper() .. t:sub(2)](feat) then
tags = tags .. (' [%s]'):format(t)
end
end
feat:getName(name)
print(('Feature #%i is %s: "%s", type %s%s'):format(
idx,
feat.flags.Discovered and 'shown' or 'hidden',
name.value,
df.feature_type[feat:getType()],
tags
))
end
df.delete(name)
end
function enable_magma_funaces()
for idx, feat in ipairs(map_features) do
if tostring(feat):find('magma') ~= nil then
toggle_feature(idx, true)
print('Enabled magma furnaces.')
return
end
end
dfhack.printerr('Could not find a magma-bearing feature.')
end
local args = {...}
if args[1] == 'list' then
list_features()
elseif args[1] == 'magma' then
enable_magma_funaces()
elseif args[1] == 'show' then
toggle_feature(args[2], true)
elseif args[1] == 'hide' then
toggle_feature(args[2], false)
else
print((help:gsub('=[a-z]+', '')))
end

@ -1,122 +0,0 @@
-- makes creatures [in]fertile, by modifying orientation
-- usage: fix-ster [fert|ster] [all|animals|only:<creature>]
-- original author: Tacomagic
-- minor fixes by PeridexisErrant, Lethosor
--@ module = true
--[[=begin
fix-ster
========
Utilizes the orientation tag to either fix infertile creatures or inflict
infertility on creatures that you do not want to breed. Usage::
fix-ster [fert|ster] [all|animals|only:<creature>]
``fert`` or ``ster`` is a required argument; whether to make the target fertile
or sterile. Optional arguments specify the target: no argument for the
selected unit, ``all`` for all units on the map, ``animals`` for all non-dwarf
creatures, or ``only:<creature>`` to only process matching creatures.
=end]]
function changeorient(unit, ori)
--Sets the fertility flag based on gender.
if not unit.status.current_soul then
return
end
if unit.sex == 0 then
unit.status.current_soul.orientation_flags.marry_male=ori
else
unit.status.current_soul.orientation_flags.marry_female=ori
end
end
function changelots(creatures, ori, silent)
local v, unit
local c = 0
--loops through indexes in creatures table and changes orientation flags
for _, v in ipairs(creatures) do
unit = df.global.world.units.active[v]
changeorient(unit,ori)
c = c + 1
end
if not silent then
print("Changed " .. c .. " creatures.")
end
end
function process_args(args)
local n, v, ori, crename, crenum
local creatures = {}
--Checks for any arguments at all.
if args == nil or #args == 0 then
print("No arguments. Usage is: fixster <fert|ster> [all|animals|only:<creature>]")
return
end
for i, a in pairs(args) do
args[i] = tostring(a):lower()
end
if args[1]:sub(1, 1) == "s" then -- sterile
ori = false
elseif args[1]:sub(1, 1) == "f" then -- fertile
ori = true
else
qerror("Unrecognised first argument: " .. args[1] .. ". Aborting.")
end
--Checks for the existence of the second argument. If it's missing, uses selected unit (if any)
if args[2] == nil then
unit = dfhack.gui.getSelectedUnit()
if not unit then return end
changeorient(unit, ori)
print('Changed selected creature.')
--ALL arg processing
elseif args[2] == "all" then
--Create table of all current unit indexes
for n,v in ipairs(df.global.world.units.active) do
table.insert(creatures,n)
end
changelots(creatures,ori)
--ANIMALS arg processing
elseif args[2] == "animals" then
--Create a table of all creature indexes except dwarves on the current map
for n,v in ipairs(df.global.world.units.active) do
if v.race ~= df.global.ui.race_id then
table.insert(creatures,n)
end
end
changelots(creatures,ori)
-- ONLY:<creature> arg processing
elseif args[2]:sub(1,4) == "only" then
crename = args[2]:sub(6):upper()
--Search raws for creature
for k,v in pairs(df.global.world.raws.creatures.all) do
if v.creature_id == crename then
crenum = k
end
end
--If no match, abort
if crenum == nil then
qerror("Creature not found. Check spelling.")
end
--create a table of all the matching creature indexes on the map for processing
for n,v in ipairs(df.global.world.units.active) do
if v.race == crenum then
table.insert(creatures,n)
end
end
changelots(creatures,ori)
else
qerror("Unrecognised optional argument. Aborting")
end
end
if not moduleMode then
local args = table.pack(...)
process_args(args)
end

@ -1 +0,0 @@
``fix/*`` scripts fix various bugs and issues, some of them obscure.

@ -1,62 +0,0 @@
-- Stop traders bringing blood, ichor, or goo
--author Urist Da Vinci; edited by expwnent, scamtank
--[[=begin
fix/blood-del
=============
Makes it so that future caravans won't bring barrels full of blood, ichor, or goo.
=end]]
local my_entity=df.historical_entity.find(df.global.ui.civ_id)
local sText=" "
local k=0
local v=1
for x,y in pairs(df.global.world.entities.all) do
my_entity=y
k=0
while k < #my_entity.resources.misc_mat.extracts.mat_index do
v=my_entity.resources.misc_mat.extracts.mat_type[k]
sText=dfhack.matinfo.decode(v,my_entity.resources.misc_mat.extracts.mat_index[k])
if (sText==nil) then
--LIQUID barrels
my_entity.resources.misc_mat.extracts.mat_type:erase(k)
my_entity.resources.misc_mat.extracts.mat_index:erase(k)
k=k-1
else
if(sText.material.id=="BLOOD") then
my_entity.resources.misc_mat.extracts.mat_type:erase(k)
my_entity.resources.misc_mat.extracts.mat_index:erase(k)
k=k-1
end
if(sText.material.id=="ICHOR") then
my_entity.resources.misc_mat.extracts.mat_type:erase(k)
my_entity.resources.misc_mat.extracts.mat_index:erase(k)
k=k-1
end
if(sText.material.id=="GOO") then
my_entity.resources.misc_mat.extracts.mat_type:erase(k)
my_entity.resources.misc_mat.extracts.mat_index:erase(k)
k=k-1
end
if(sText.material.id=="SWEAT") then
my_entity.resources.misc_mat.extracts.mat_type:erase(k)
my_entity.resources.misc_mat.extracts.mat_index:erase(k)
k=k-1
end
if(sText.material.id=="TEARS") then
my_entity.resources.misc_mat.extracts.mat_type:erase(k)
my_entity.resources.misc_mat.extracts.mat_index:erase(k)
k=k-1
end
--VENOM
--POISON
--FLUID
--MILK
--EXTRACT
end
k=k+1
end
end

@ -1,45 +0,0 @@
-- Lets constructions reconsider the build location.
-- Partial work-around for http://www.bay12games.com/dwarves/mantisbt/view.php?id=5991
--[[=begin
fix/build-location
==================
Fixes construction jobs that are stuck trying to build a wall while standing
on the same exact tile (:bug:`5991`), designates the tile restricted traffic to
hopefully avoid jamming it again, and unsuspends them.
=end]]
local utils = require('utils')
local count = 0
for link,job in utils.listpairs(df.global.world.job_list) do
local job = link.item
local place = dfhack.job.getHolder(job)
if job.job_type == df.job_type.ConstructBuilding
and place and place:isImpassableAtCreation()
and job.item_category[0]
then
local cpos = utils.getBuildingCenter(place)
if same_xyz(cpos, job.pos) then
-- Reset the flag
job.item_category[0] = false
job.flags.suspend = false
-- Mark the tile restricted traffic
local dsgn,occ = dfhack.maps.getTileFlags(cpos)
dsgn.traffic = df.tile_traffic.Restricted
count = count + 1
end
end
end
print('Found and unstuck '..count..' construct building jobs.')
if count > 0 then
df.global.process_jobs = true
end

@ -1,37 +0,0 @@
-- Remove uninteresting dead units from the unit list.
--[[=begin
fix/dead-units
==============
Removes uninteresting dead units from the unit list. Doesn't seem to give any
noticeable performance gain, but migrants normally stop if the unit list grows
to around 3000 units, and this script reduces it back.
=end]]
local units = df.global.world.units.active
local dwarf_race = df.global.ui.race_id
local dwarf_civ = df.global.ui.civ_id
local count = 0
for i=#units-1,0,-1 do
local unit = units[i]
local flags1 = unit.flags1
local flags2 = unit.flags2
if flags1.dead and unit.race ~= dwarf_race then
local remove = false
if flags2.slaughter then
remove = true
elseif not unit.name.has_name then
remove = true
elseif unit.civ_id ~= dwarf_civ and
not (flags1.merchant or flags1.diplomat) then
remove = true
end
if remove then
count = count + 1
units:erase(i)
end
end
end
print('Units removed from active: '..count)

@ -1,106 +0,0 @@
-- Add Elven diplomats to negotiate tree caps
--[[=begin
fix/diplomats
=============
Adds a Diplomat position to all Elven civilizations, allowing them to negotiate
tree cutting quotas - and you to violate them and start wars.
This was vanilla behaviour until ``0.31.12``, in which the "bug" was "fixed".
=end]]
function update_pos(ent)
local pos = df.entity_position:new()
ent.positions.own:insert('#', pos)
pos.code = "DIPLOMAT"
pos.id = ent.positions.next_position_id + 1
ent.positions.next_position_id = ent.positions.next_position_id + 1
pos.flags.DO_NOT_CULL = true
pos.flags.MENIAL_WORK_EXEMPTION = true
pos.flags.SLEEP_PRETENSION = true
pos.flags.PUNISHMENT_EXEMPTION = true
pos.flags.ACCOUNT_EXEMPT = true
pos.flags.DUTY_BOUND = true
pos.flags.COLOR = true
pos.flags.HAS_RESPONSIBILITIES = true
pos.flags.IS_DIPLOMAT = true
pos.flags.IS_LEADER = true
-- not sure what these flags do, but the game sets them for a valid diplomat
pos.flags.unk_12 = true
pos.flags.unk_1a = true
pos.flags.unk_1b = true
pos.name[0] = "Diplomat"
pos.name[1] = "Diplomats"
pos.precedence = 70
pos.color[0] = 7
pos.color[1] = 0
pos.color[2] = 1
return pos
end
local checked = 0
local fixed = 0
for _,ent in pairs(df.global.world.entities.all) do
if ent.type == df.historical_entity_type.Civilization and ent.entity_raw.flags.TREE_CAP_DIPLOMACY then
checked = checked + 1
update = true
local found_position
-- see if we need to add a new position or modify an existing one
for _,pos in pairs(ent.positions.own) do
if pos.responsibilities.MAKE_INTRODUCTIONS and
pos.responsibilities.MAKE_PEACE_AGREEMENTS and
pos.responsibilities.MAKE_TOPIC_AGREEMENTS then
-- a diplomat position exists with the proper responsibilities - skip to the end
update = false
found_position=pos
break
end
-- Diplomat position already exists, but has the wrong options - modify it instead of creating a new one
if pos.code == "DIPLOMAT" then
found_position=pos
break
end
end
if update then
-- either there's no diplomat, or there is one and it's got the wrong responsibilities
if not found_position then
found_position = add_guild_rep( ent )
end
-- assign responsibilities
found_position.responsibilities.MAKE_INTRODUCTIONS = true
found_position.responsibilities.MAKE_PEACE_AGREEMENTS = true
found_position.responsibilities.MAKE_TOPIC_AGREEMENTS = true
end
-- make sure the diplomat position, whether we created it or not, is set up for proper assignment
local assign = true
for _,p in pairs(ent.positions.assignments) do
if p.position_id == found_position.id then -- it is - nothing more to do here
assign = false
break
end
end
if assign then -- it isn't - set it up
local asn = df.entity_position_assignment:new()
ent.positions.assignments:insert('#', asn);
asn.id = ent.positions.next_assignment_id
ent.positions.next_assignment_id = asn.id + 1
asn.position_id = found_position.id
asn.flags:resize(math.max(32, #asn.flags)) -- make room for 32 flags
asn.flags[0] = true -- and set the first one
end
if update or assign then
fixed = fixed + 1
end
end
end
print("Enabled tree cap diplomacy for "..fixed.." of "..checked.." civilizations.")

@ -1,28 +0,0 @@
-- Removes water from buckets (for lye-making).
--[[=begin
fix/dry-buckets
===============
Removes water from all buckets in your fortress, allowing them
to be used for making lye. Skips buckets in buildings (eg a well),
being carried, or currently used by a job.
=end]]
local emptied = 0
local water_type = dfhack.matinfo.find('WATER').type
for _,item in ipairs(df.global.world.items.all) do
container = dfhack.items.getContainer(item)
if container ~= nil
and container:getType() == df.item_type.BUCKET
and not (container.flags.in_job or container.flags.in_building)
and item:getMaterial() == water_type
and item:getType() == df.item_type.LIQUID_MISC
and not (item.flags.in_job or item.flags.in_building) then
dfhack.items.remove(item)
emptied = emptied + 1
end
end
print('Emptied '..emptied..' buckets.')

@ -1,33 +0,0 @@
-- Makes fat dwarves non-fat.
--
-- See for more info:
-- http://www.bay12games.com/dwarves/mantisbt/view.php?id=5971
--[[=begin
fix/fat-dwarves
===============
Avoids 5-10% FPS loss due to constant recalculation of insulation for dwarves at
maximum fatness, by reducing the cap from 1,000,000 to 999,999.
Recalculation is triggered in steps of 250 units, and very fat dwarves
constantly bounce off the maximum value while eating.
=end]]
local num_fat = 0
local num_lagging = 0
for _,v in ipairs(df.global.world.units.all) do
local fat = v.counters2.stored_fat
if fat > 850000 then
v.counters2.stored_fat = 500000
if v.race == df.global.ui.race_id then
print(fat,dfhack.TranslateName(dfhack.units.getVisibleName(v)))
num_fat = num_fat + 1
if fat > 999990 then
num_lagging = num_lagging + 1
end
end
end
end
print("Fat dwarves cured: "..num_fat)
print("Lag sources: "..num_lagging)

@ -1,43 +0,0 @@
-- feeding-timers.lua
-- original author: tejón
-- rewritten by expwnent
-- see repeat.lua for how to run this every so often automatically
--[[=begin
fix/feeding-timers
==================
Reset the GiveWater and GiveFood timers of all units as appropriate.
=end]]
local args = {...}
if args[1] ~= nil then
print("fix/feeding-timers usage")
print(" fix/feeding-timers")
print(" reset the feeding timers of all units as appropriate")
print(" fix/feeding-timers help")
print(" print this help message")
print(" repeat -time [n] [years/months/ticks/days/etc] -command fix/feeding-timers")
print(" run this script every n time units")
print(" repeat -cancel fix/feeding-timers")
print(" stop automatically running this script")
return
end
local fixcount = 0
for _,unit in ipairs(df.global.world.units.all) do
if dfhack.units.isCitizen(unit) and not (unit.flags1.dead) then
for _,v in pairs(unit.status.misc_traits) do
local didfix = 0
if v.id == 0 then -- I think this should have additional conditions...
v.value = 0 -- GiveWater cooldown set to zero
didfix = 1
end
if v.id == 1 then -- I think this should have additional conditions...
v.value = 0 -- GiveFood cooldown set to zero
didfix = 1
end
fixcount = fixcount + didfix
end
end
end
print("Fixed feeding timers for " .. fixcount .. " citizens.")

@ -1,131 +0,0 @@
-- Verify item occupancy and block item list integrity.
--[[=begin
fix/item-occupancy
==================
Diagnoses and fixes issues with nonexistant 'items occupying site', usually
caused by `autodump` bugs or other hacking mishaps. Checks that:
#. Item has ``flags.on_ground`` <=> it is in the correct block item list
#. A tile has items in block item list <=> it has ``occupancy.item``
#. The block item lists are sorted
=end]]
local utils = require 'utils'
function check_block_items(fix)
local cnt = 0
local icnt = 0
local found = {}
local found_somewhere = {}
local should_fix = false
local can_fix = true
for _,block in ipairs(df.global.world.map.map_blocks) do
local itable = {}
local bx,by,bz = pos2xyz(block.map_pos)
-- Scan the block item vector
local last_id = nil
local resort = false
for _,id in ipairs(block.items) do
local item = df.item.find(id)
local ix,iy,iz = pos2xyz(item.pos)
local dx,dy,dz = ix-bx,iy-by,iz-bz
-- Check sorted order
if last_id and last_id >= id then
print(bx,by,bz,last_id,id,'block items not sorted')
resort = true
else
last_id = id
end
-- Check valid coordinates and flags
if not item.flags.on_ground then
print(bx,by,bz,id,dx,dy,'in block & not on ground')
elseif dx < 0 or dx >= 16 or dy < 0 or dy >= 16 or dz ~= 0 then
found_somewhere[id] = true
print(bx,by,bz,id,dx,dy,dz,'invalid pos')
can_fix = false
else
found[id] = true
itable[dx + dy*16] = true;
-- Check missing occupancy
if not block.occupancy[dx][dy].item then
print(bx,by,bz,dx,dy,'item & not occupied')
if fix then
block.occupancy[dx][dy].item = true
else
should_fix = true
end
end
end
end
-- Sort the vector if needed
if resort then
if fix then
utils.sort_vector(block.items)
else
should_fix = true
end
end
icnt = icnt + #block.items
-- Scan occupancy for spurious marks
for x=0,15 do
local ocx = block.occupancy[x]
for y=0,15 do
if ocx[y].item and not itable[x + y*16] then
print(bx,by,bz,x,y,'occupied & no item')
if fix then
ocx[y].item = false
else
should_fix = true
end
end
end
end
cnt = cnt + 256
end
-- Check if any items are missing from blocks
for _,item in ipairs(df.global.world.items.all) do
if item.flags.on_ground and not found[item.id] then
can_fix = false
if not found_somewhere[item.id] then
print(id,item.pos.x,item.pos.y,item.pos.z,'on ground & not in block')
end
end
end
-- Report
print(cnt.." tiles and "..icnt.." items checked.")
if should_fix and can_fix then
print("Use 'fix/item-occupancy --fix' to fix the listed problems.")
elseif should_fix then
print("The problems are too severe to be fixed by this script.")
end
end
local opt = ...
local fix = false
if opt then
if opt == '--fix' then
fix = true
else
qerror('Invalid option: '..opt)
end
end
print("Checking item occupancy - this will take a few seconds.")
check_block_items(fix)

@ -1,70 +0,0 @@
# Cancels a 'loyalty cascade' when citizens are killed
=begin
fix/loyaltycascade
==================
Aborts loyalty cascades by fixing units whose own civ is the enemy.
=end
def fixunit(unit)
return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id
links = unit.hist_figure_tg.entity_links
fixed = false
# check if the unit is a civ renegade
if i1 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
l.entity_id == df.ui.civ_id
} and i2 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
l.entity_id == df.ui.civ_id
}
fixed = true
i1, i2 = i2, i1 if i1 > i2
links.delete_at i2
links.delete_at i1
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.civ_id, :link_strength => 100)
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.civ_tg.name} again"
end
# check if the unit is a group renegade
if i1 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
l.entity_id == df.ui.group_id
} and i2 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
l.entity_id == df.ui.group_id
}
fixed = true
i1, i2 = i2, i1 if i1 > i2
links.delete_at i2
links.delete_at i1
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.group_id, :link_strength => 100)
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.group_tg.name} again"
end
# fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed)
if fixed and unit.enemy.enemy_status_slot != -1
i = unit.enemy.enemy_status_slot
unit.enemy.enemy_status_slot = -1
cache = df.world.enemy_status_cache
cache.slot_used[i] = false
cache.rel_map[i].map! { -1 }
cache.rel_map.each { |a| a[i] = -1 }
cache.next_slot = i if cache.next_slot > i
end
# return true if we actually fixed the unit
fixed
end
count = 0
df.unit_citizens.each { |u|
count += 1 if fixunit(u)
}
if count > 0
puts "loyalty cascade fixed (#{count} dwarves)"
else
puts "no loyalty cascade found"
end

@ -1,104 +0,0 @@
-- Allow humans to make trade agreements
--[[=begin
fix/merchants
=============
Adds the Guild Representative position to all Human civilizations,
allowing them to make trade agreements. This was the default behaviour in
``0.28.181.40d`` and earlier.
=end]]
function add_guild_rep(ent)
-- there was no guild rep - create it
local pos = df.entity_position:new()
ent.positions.own:insert('#', pos)
pos.code = "GUILD_REPRESENTATIVE"
pos.id = ent.positions.next_position_id + 1
ent.positions.next_position_id = ent.positions.next_position_id + 1
pos.flags.DO_NOT_CULL = true
pos.flags.MENIAL_WORK_EXEMPTION = true
pos.flags.SLEEP_PRETENSION = true
pos.flags.PUNISHMENT_EXEMPTION = true
pos.flags.ACCOUNT_EXEMPT = true
pos.flags.DUTY_BOUND = true
pos.flags.COLOR = true
pos.flags.HAS_RESPONSIBILITIES = true
pos.flags.IS_DIPLOMAT = true
pos.flags.IS_LEADER = true
-- not sure what these flags do, but the game sets them for a valid guild rep
pos.flags.unk_12 = true
pos.flags.unk_1a = true
pos.flags.unk_1b = true
pos.name[0] = "Guild Representative"
pos.name[1] = "Guild Representatives"
pos.precedence = 40
pos.color[0] = 7
pos.color[1] = 0
pos.color[2] = 1
return pos
end
local checked = 0
local fixed = 0
for _,ent in pairs(df.global.world.entities.all) do
if ent.type == df.historical_entity_type.Civilization and ent.entity_raw.flags.MERCHANT_NOBILITY then
checked = checked + 1
update = true
-- see if we need to add a new position or modify an existing one
local found_position
for _,pos in pairs(ent.positions.own) do
if pos.responsibilities.TRADE and pos.responsibilities.ESTABLISH_COLONY_TRADE_AGREEMENTS then
-- a guild rep exists with the proper responsibilities - skip to the end
update = false
found_position=pos
break
end
-- Guild Representative position already exists, but has the wrong options - modify it instead of creating a new one
if pos.code == "GUILD_REPRESENTATIVE" then
found_position=pos
break
end
end
if update then
-- either there's no guild rep, or there is one and it's got the wrong responsibilities
if not found_position then
found_position = add_guild_rep(ent)
end
-- assign responsibilities
found_position.responsibilities.ESTABLISH_COLONY_TRADE_AGREEMENTS = true
found_position.responsibilities.TRADE=true
end
-- make sure the guild rep position, whether we created it or not, is set up for proper assignment
local assign = true
for _,p in pairs(ent.positions.assignments) do
if p.position_id == found_position.id then -- it is - nothing more to do here
assign = false
break
end
end
if assign then
-- it isn't - set it up
local asn = df.entity_position_assignment:new()
ent.positions.assignments:insert('#', asn)
asn.id = ent.positions.next_assignment_id
ent.positions.next_assignment_id = asn.id + 1
asn.position_id = found_position.id
asn.flags:resize(math.max(32, #asn.flags)) -- make room for 32 flags
asn.flags[0] = true -- and set the first one
end
if update or assign then
fixed = fixed + 1
end
end
end
print("Added merchant nobility for "..fixed.." of "..checked.." civilizations.")

@ -1,47 +0,0 @@
-- Tells mountainhomes your pop. to avoid overshoot
--[[=begin
fix/population-cap
==================
Run this after every migrant wave to ensure your population cap is not exceeded.
The reason for population cap problems is that the population value it
is compared against comes from the last dwarven caravan that successfully
left for mountainhomes. This script instantly updates it.
Note that a migration wave can still overshoot the limit by 1-2 dwarves because
of the last migrant bringing his family. Likewise, king arrival ignores cap.
=end]]
local args = {...}
local ui = df.global.ui
local ui_stats = ui.tasks
local civ = df.historical_entity.find(ui.civ_id)
if not civ then
qerror('No active fortress.')
end
local civ_stats = civ.activity_stats
if not civ_stats then
if args[1] ~= 'force' then
qerror('No caravan report object; use "fix/population-cap force" to create one')
end
print('Creating an empty statistics structure...')
civ.activity_stats = {
new = true,
created_weapons = { resize = #ui_stats.created_weapons },
discovered_creature_foods = { resize = #ui_stats.discovered_creature_foods },
discovered_creatures = { resize = #ui_stats.discovered_creatures },
discovered_plant_foods = { resize = #ui_stats.discovered_plant_foods },
discovered_plants = { resize = #ui_stats.discovered_plants },
}
civ_stats = civ.activity_stats
end
-- Use max to keep at least some of the original caravan communication idea
civ_stats.population = math.max(civ_stats.population, ui_stats.population)
print('Home civ notified about current population.')

@ -1,71 +0,0 @@
-- Reset item temperature to the value of their tile.
--[[=begin
fix/stable-temp
===============
Instantly sets the temperature of all free-lying items to be in equilibrium with
the environment, which stops temperature updates until something changes.
To maintain this efficient state, use `tweak fast-heat <tweak>`.
=end]]
local args = {...}
local apply = (args[1] == 'apply')
local count = 0
local types = {}
local function update_temp(item,btemp)
if item.temperature.whole ~= btemp then
count = count + 1
local tid = item:getType()
types[tid] = (types[tid] or 0) + 1
end
if apply then
item.temperature.whole = btemp
item.temperature.fraction = 0
if item.contaminants then
for _,c in ipairs(item.contaminants) do
c.temperature.whole = btemp
c.temperature.fraction = 0
end
end
end
for _,sub in ipairs(dfhack.items.getContainedItems(item)) do
update_temp(sub,btemp)
end
if apply then
item:checkTemperatureDamage()
end
end
local last_frame = df.global.world.frame_counter-1
for _,item in ipairs(df.global.world.items.all) do
if item.flags.on_ground and df.item_actual:is_instance(item) and
item.temp_updated_frame == last_frame then
local pos = item.pos
local block = dfhack.maps.getTileBlock(pos)
if block then
update_temp(item, block.temperature_1[pos.x%16][pos.y%16])
end
end
end
if apply then
print('Items updated: '..count)
else
print("Use 'fix/stable-temp apply' to force-change temperature.")
print('Items not in equilibrium: '..count)
end
local tlist = {}
for k,_ in pairs(types) do tlist[#tlist+1] = k end
table.sort(tlist, function(a,b) return types[a] > types[b] end)
for _,k in ipairs(tlist) do
print(' '..df.item_type[k]..':', types[k])
end

@ -1,32 +0,0 @@
# fix doors that are frozen in 'open' state
# this may happen after people mess with the game by (incorrectly) teleporting units or items
# a door may stick open if the map occupancy flags are wrong
=begin
fix/stuckdoors
==============
Fix doors that are stuck open due to incorrect map occupancy flags, eg due to
incorrect use of `teleport`.
=end
count = 0
df.world.buildings.all.each { |bld|
# for all doors
next if bld._rtti_classname != :building_doorst
# check if it is open
next if bld.close_timer == 0
# check if occupancy is set
occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z)
if (occ.unit or occ.unit_grounded) and not
# check if an unit is present
df.world.units.active.find { |u| u.pos.x == bld.x1 and u.pos.y == bld.y1 and u.pos.z == bld.z }
count += 1
occ.unit = occ.unit_grounded = false
end
if occ.item and not df.world.items.all.find { |i| i.pos.x == bld.x1 and i.pos.y == bld.y1 and i.pos.z == bld.z }
count += 1
occ.item = false
end
}
puts "unstuck #{count} doors"

@ -1,50 +0,0 @@
--removes unhappy thoughts due to lack of clothing
--[[=begin
fixnaked
========
Removes all unhappy thoughts due to lack of clothing.
=end]]
function fixnaked()
local total_fixed = 0
local total_removed = 0
for fnUnitCount,fnUnit in ipairs(df.global.world.units.all) do
if fnUnit.race == df.global.ui.race_id then
local listEvents = fnUnit.status.recent_events
--for lkey,lvalue in pairs(listEvents) do
-- print(df.unit_thought_type[lvalue.type],lvalue.type,lvalue.age,lvalue.subtype,lvalue.severity)
--end
local found = 1
local fixed = 0
while found == 1 do
local events = fnUnit.status.recent_events
found = 0
for k,v in pairs(events) do
if v.type == df.unit_thought_type.Uncovered
or v.type == df.unit_thought_type.NoShirt
or v.type == df.unit_thought_type.NoShoes
or v.type == df.unit_thought_type.NoCloak
or v.type == df.unit_thought_type.OldClothing
or v.type == df.unit_thought_type.TatteredClothing
or v.type == df.unit_thought_type.RottedClothing then
events:erase(k)
found = 1
total_removed = total_removed + 1
fixed = 1
break
end
end
end
if fixed == 1 then
total_fixed = total_fixed + 1
print(total_fixed, total_removed, dfhack.TranslateName(dfhack.units.getVisibleName(fnUnit)))
end
end
end
print("Total Fixed: "..total_fixed)
end
fixnaked()

@ -1,133 +0,0 @@
-- Save a copy of a text screen for the DF forums
-- original author: Caldfir; edited by expwnent, Mchl
--[[=begin
forum-dwarves
=============
Saves a copy of a text screen, formatted in bbcode for posting to the Bay12 Forums.
Use ``forum-dwarves help`` for more information.
=end]]
local args = {...}
if args[1] == 'help' then
print([[
description:
This script will attempt to read the current df-screen, and if it is a
text-viewscreen (such as the dwarf 'thoughts' screen or an item
'description') then append a marked-up version of this text to the
target file. Previous entries in the file are not overwritten, so you
may use the 'forumdwarves' command multiple times to create a single
document containing the text from multiple screens (eg: text screens
from several dwarves, or text screens from multiple artifacts/items,
or some combination).
known screens:
The screens which have been tested and known to function properly with
this script are:
1: dwarf/unit 'thoughts' screen
2: item/art 'description' screen
3: individual 'historical item/figure' screens
There may be other screens to which the script applies. It should be
safe to attempt running the script with any screen active, with an
error message to inform you when the selected screen is not appropriate
for this script.
target file:
The target file's name is 'forumdwarves.txt'. A remider to this effect
will be displayed if the script is successful.
character encoding:
The text will likely be using system-default encoding, and as such
will likely NOT display special characters (eg:È,ı,Á) correctly. To
fix this, you need to modify the character set that you are reading
the document with. 'Notepad++' is a freely available program which
can do this using the following steps:
1: open the document in Notepad++
2: in the menu-bar, select
Encoding->Character Sets->Western European->OEM-US
3: copy the text normally to wherever you want to use it
]])
return
end
local utils = require 'utils'
local gui = require 'gui'
local dialog = require 'gui.dialogs'
local colors_css = {
[0] = 'black',
[1] = 'navy',
[2] = 'green',
[3] = 'teal',
[4] = 'maroon',
[5] = 'purple',
[6] = 'olive',
[7] = 'silver',
[8] = 'gray',
[9] = 'blue',
[10] = 'lime',
[11] = 'cyan',
[12] = 'red',
[13] = 'magenta',
[14] = 'yellow',
[15] = 'white'
}
local scrn = dfhack.gui.getCurViewscreen()
local flerb = dfhack.gui.getFocusString(scrn)
local function format_for_forum(strin)
local strout = strin
local newline_idx = string.find(strout, '[P]', 1, true)
while newline_idx ~= nil do
strout = string.sub(strout,1, newline_idx-1)..'\n'..string.sub(strout,newline_idx+3)
newline_idx = string.find(strout, '[P]', 1, true)
end
newline_idx = string.find(strout, '[B]', 1, true)
while newline_idx ~= nil do
strout = string.sub(strout,1, newline_idx-1)..'\n'..string.sub(strout,newline_idx+3)
newline_idx = string.find(strout, '[B]', 1, true)
end
newline_idx = string.find(strout, '[R]', 1, true)
while newline_idx ~= nil do
strout = string.sub(strout,1, newline_idx-1)..'\n'..string.sub(strout,newline_idx+3)
newline_idx = string.find(strout, '[R]', 1, true)
end
local color_idx = string.find(strout, '[C:', 1, true)
while color_idx ~= nil do
local colormatch = (string.byte(strout, color_idx+3)-48)+((string.byte(strout, color_idx+7)-48)*8)
strout = string.sub(strout,1, color_idx-1)..'[/color][color='..colors_css[colormatch]..']'..string.sub(strout,color_idx+9)
color_idx = string.find(strout, '[C:', 1, true)
end
return strout
end
if flerb == 'textviewer' then
print(scrn)
printall(scrn)
local lines = scrn.src_text
local line = ""
if lines ~= nil then
local log = io.open('forumdwarves.txt', 'a')
log:write("[color=silver]")
log:write(scrn.title)
for n,x in ipairs(lines) do
print(x)
printall(x)
print(x.value)
printall(x.value)
if (x ~= nil) and (x.value ~= nil) then
log:write(format_for_forum(x.value), ' ')
--log:write(x[0],'\n')
end
end
log:write("[/color]\n")
log:close()
end
print 'data prepared for forum in \"forumdwarves.txt\"'
else
print 'this is not a textview screen'
end

@ -1,153 +0,0 @@
-- Attempts to fully heal the selected unit
--author Kurik Amudnil, Urist DaVinci
--edited by expwnent
--[[=begin
full-heal
=========
Attempts to fully heal the selected unit. ``full-heal -r`` attempts to resurrect the unit.
=end]]
local utils=require('utils')
validArgs = validArgs or utils.invert({
'r',
'help',
'unit',
'keep_corpse'
})
local args = utils.processArgs({...}, validArgs)
if args.help then
print('full-heal: heal a unit completely from anything, optionally including death.')
print(' full-heal -unit [unitId]')
print(' heal the unit with the given id')
print(' full-heal -r -unit [unitId]')
print(' heal the unit with the given id and bring them back from death if they are dead')
print(' full-heal -r -keep_corpse -unit [unitId]')
print(' heal the unit with the given id and bring them back from death if they are dead, without removing their corpse')
print(' full-heal')
print(' heal the currently selected unit')
print(' full-heal -r')
print(' heal the currently selected unit and bring them back from death if they are dead')
print(' full-heal -help')
print(' print this help message')
return
end
if(args.unit) then
unit = df.unit.find(args.unit)
else
unit = dfhack.gui.getSelectedUnit()
end
if not unit then
qerror('Error: please select a unit or pass its id as an argument.')
end
if unit then
if args.r then
if unit.flags1.dead then
--print("Resurrecting...")
unit.flags2.slaughter = false
unit.flags3.scuttle = false
end
unit.flags1.dead = false
unit.flags2.killed = false
unit.flags3.ghostly = false
if not args.keep_corpse then
for _,corpse in ipairs(df.global.world.items.other.CORPSE) do
if corpse.unit_id==unit.id then
corpse.flags.garbage_collect=true
corpse.flags.forbid=true
corpse.flags.hidden=true
end
end
end
--unit.unk_100 = 3
end
--print("Erasing wounds...")
while #unit.body.wounds > 0 do
unit.body.wounds:erase(#unit.body.wounds-1)
end
unit.body.wound_next_id=1
--print("Refilling blood...")
unit.body.blood_count=unit.body.blood_max
--print("Resetting grasp/stand status...")
unit.status2.limbs_stand_count=unit.status2.limbs_stand_max
unit.status2.limbs_grasp_count=unit.status2.limbs_grasp_max
--print("Resetting status flags...")
unit.flags2.has_breaks=false
unit.flags2.gutted=false
unit.flags2.circulatory_spray=false
unit.flags2.vision_good=true
unit.flags2.vision_damaged=false
unit.flags2.vision_missing=false
unit.flags2.breathing_good=true
unit.flags2.breathing_problem=false
unit.flags2.calculated_nerves=false
unit.flags2.calculated_bodyparts=false
unit.flags2.calculated_insulation=false
unit.flags3.compute_health=true
--print("Resetting counters...")
unit.counters.winded=0
unit.counters.stunned=0
unit.counters.unconscious=0
unit.counters.webbed=0
unit.counters.pain=0
unit.counters.nausea=0
unit.counters.dizziness=0
unit.counters2.paralysis=0
unit.counters2.fever=0
unit.counters2.exhaustion=0
unit.counters2.hunger_timer=0
unit.counters2.thirst_timer=0
unit.counters2.sleepiness_timer=0
unit.counters2.vomit_timeout=0
--print("Resetting body part status...")
local v=unit.body.components
for i=0,#v.nonsolid_remaining - 1,1 do
v.nonsolid_remaining[i] = 100 -- percent remaining of fluid layers (Urist Da Vinci)
end
v=unit.body.components
for i=0,#v.layer_wound_area - 1,1 do
v.layer_status[i].whole = 0 -- severed, leaking layers (Urist Da Vinci)
v.layer_wound_area[i] = 0 -- wound contact areas (Urist Da Vinci)
v.layer_cut_fraction[i] = 0 -- 100*surface percentage of cuts/fractures on the body part layer (Urist Da Vinci)
v.layer_dent_fraction[i] = 0 -- 100*surface percentage of dents on the body part layer (Urist Da Vinci)
v.layer_effect_fraction[i] = 0 -- 100*surface percentage of "effects" on the body part layer (Urist Da Vinci)
end
v=unit.body.components.body_part_status
for i=0,#v-1,1 do
v[i].on_fire = false
v[i].missing = false
v[i].organ_loss = false
v[i].organ_damage = false
v[i].muscle_loss = false
v[i].muscle_damage = false
v[i].bone_loss = false
v[i].bone_damage = false
v[i].skin_damage = false
v[i].motor_nerve_severed = false
v[i].sensory_nerve_severed = false
end
if unit.job.current_job and unit.job.current_job.job_type == df.job_type.Rest then
--print("Wake from rest -> clean self...")
unit.job.current_job = df.job_type.CleanSelf
end
end

@ -1,208 +0,0 @@
-- Shows the sexual orientation of units
--[[=begin
gaydar
======
Shows the sexual orientation of units, useful for social engineering or checking
the viability of livestock breeding programs. Use ``gaydar -help`` for information
on available filters for orientation, citizenship, species, etc.
=end]]
local utils = require('utils')
validArgs = utils.invert({
'all',
'citizens',
'named',
'notStraight',
'gayOnly',
'biOnly',
'straightOnly',
'asexualOnly',
'help'
})
local args = utils.processArgs({...}, validArgs)
if args.help then
print(
[[gaydar.lua
arguments:
-help
print this help message
unit filters:
-all
shows orientation of every creature
-citizens
shows only orientation of citizens in fort mode
-named
shows orientation of all named units on map
orientation filters:
-notStraight
shows only creatures who are not strictly straight
-gayOnly
shows only creatures who are strictly gay
-biOnly
shows only creatures who can get into romances with
both sexes
-straightOnly
shows only creatures who are strictly straight.
-asexualOnly
shows only creatures who are strictly asexual.
No argument will show the orientation of the unit
under the cursor.
]])
return
end
function dfprint(s)
print(dfhack.df2console(s))
end
function getSexString(sex)
local sexStr
if sex==0 then
sexStr=string.char(12)
elseif sex==1 then
sexStr=string.char(11)
else
return ""
end
return string.char(40)..sexStr..string.char(41)
end
local function determineorientation(unit)
if unit.sex~=-1 and unit.status.current_soul then
local return_string=''
local orientation=unit.status.current_soul.orientation_flags
if orientation.indeterminate then
return ' indeterminate (probably adventurer)'
end
local male_interested,asexual=false,true
if orientation.romance_male then
return_string=return_string..' likes males'
male_interested=true
asexual=false
elseif orientation.marry_male then
return_string=return_string..' will marry males'
male_interested=true
asexual=false
end
if orientation.romance_female then
if male_interested then
return_string=return_string..' and likes females'
else
return_string=return_string..' likes females'
end
asexual=false
elseif orientation.marry_female then
if male_interested then
return_string=return_string..' and will marry females'
else
return_string=return_string..' will marry females'
end
asexual=false
end
if asexual then
return_string=' is asexual'
end
return return_string
else
return " is not biologically capable of sex"
end
end
local function nameOrSpeciesAndNumber(unit)
if unit.name.has_name then
return dfhack.TranslateName(dfhack.units.getVisibleName(unit))..' '..getSexString(unit.sex),true
else
return 'Unit #'..unit.id..' ('..df.creature_raw.find(unit.race).caste[unit.caste].caste_name[0]..' '..getSexString(unit.sex)..')',false
end
end
local orientations={}
if args.citizens then
for k,v in ipairs(df.global.world.units.active) do
if dfhack.units.isCitizen(v) then
table.insert(orientations,nameOrSpeciesAndNumber(v) .. determineorientation(v))
end
end
elseif args.all then
for k,v in ipairs(df.global.world.units.active) do
table.insert(orientations,nameOrSpeciesAndNumber(v)..determineorientation(v))
end
elseif args.named then
for k,v in ipairs(df.global.world.units.active) do
local name,ok=nameOrSpeciesAndNumber(v)
if ok then
table.insert(orientations,name..determineorientation(v))
end
end
else
local unit=dfhack.gui.getSelectedUnit(true)
local name,ok=nameOrSpeciesAndNumber(unit)
dfprint(name..determineorientation(unit))
return
end
function isNotStraight(v)
if v:find(string.char(12)) and v:find(' female') then return true end
if v:find(string.char(11)) and v:find(' male') then return true end
if v:find('asexual') then return true end
if v:find('indeterminate') then return true end
return false
end
function isGay(v)
if v:find('asexual') then return false end
if v:find(string.char(12)) and not v:find(' male') then return true end
if v:find(string.char(11)) and not v:find(' female') then return true end
return false
end
function isAsexual(v)
if v:find('asexual') or v:find('indeterminate') then return true else return false end
end
function isBi(v)
if v:find(' female') and v:find(' male') then return true else return false end
end
if args.notStraight then
local totalNotShown=0
for k,v in ipairs(orientations) do
if isNotStraight(v) then dfprint(v) else totalNotShown=totalNotShown+1 end
end
print('Total not shown: '..totalNotShown)
elseif args.gayOnly then
local totalNotShown=0
for k,v in ipairs(orientations) do
if isGay(v) then dfprint(v) else totalNotShown=totalNotShown+1 end
end
print('Total not shown: '..totalNotShown)
elseif args.asexualOnly then
local totalNotShown=0
for k,v in ipairs(orientations) do
if isAsexual(v) then dfprint(v) else totalNotShown=totalNotShown+1 end
end
print('Total not shown: '..totalNotShown)
elseif args.straightOnly then
local totalNotShown=0
for k,v in ipairs(orientations) do
if not isNotStraight(v) then dfprint(v) else totalNotShown=totalNotShown+1 end
end
print('Total not shown: '..totalNotShown)
elseif args.biOnly then
local totalNotShown=0
for k,v in ipairs(orientations) do
if isBi(v) then dfprint(v) else totalNotShown=totalNotShown+1 end
end
print('Total not shown: '..totalNotShown)
else
for k,v in ipairs(orientations) do
dfprint(v)
end
end

@ -1,64 +0,0 @@
# Instantly grow crops in farm plots
=begin
growcrops
=========
Instantly grow seeds inside farming plots.
With no argument, this command list the various seed types currently in
use in your farming plots. With a seed type, the script will grow 100 of
these seeds, ready to be harvested. Set the number with a 2nd argument.
For example, to grow 40 plump helmet spawn::
growcrops plump 40
=end
material = $script_args[0]
count_max = $script_args[1].to_i
count_max = 100 if count_max == 0
# cache information from the raws
@raws_plant_name ||= {}
@raws_plant_growdur ||= {}
if @raws_plant_name.empty?
df.world.raws.plants.all.each_with_index { |p, idx|
@raws_plant_name[idx] = p.id
@raws_plant_growdur[idx] = p.growdur
}
end
inventory = Hash.new(0)
df.world.items.other[:SEEDS].each { |seed|
next if not seed.flags.in_building
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
inventory[seed.mat_index] += 1
}
if !material or material == 'help' or material == 'list'
# show a list of available crop types
inventory.sort_by { |mat, c| c }.each { |mat, c|
name = df.world.raws.plants.all[mat].id
puts " #{name} #{c}"
}
else
mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] })
unless wantmat = @raws_plant_name.index(mat)
raise "invalid plant material #{material}"
end
count = 0
df.world.items.other[:SEEDS].each { |seed|
next if seed.mat_index != wantmat
next if not seed.flags.in_building
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
seed.grow_counter = @raws_plant_growdur[seed.mat_index]
count += 1
}
puts "Grown #{count} #{mat}"
end

@ -1,6 +0,0 @@
``gui/*`` scripts implement dialogs in the main game window.
In order to avoid user confusion, as a matter of policy all these tools
display the word "DFHack" on the screen somewhere while active.
When that is not appropriate because they merely add keybinding hints to
existing DF screens, they deliberately use red instead of green for the key.

File diff suppressed because it is too large Load Diff

@ -1,226 +0,0 @@
--Does something with items in adventure mode jobs
--[[=begin
gui/advfort_items
=================
Does something with items in adventure mode jobs.
=end]]
local _ENV = mkmodule('hack.scripts.gui.advfort_items')
local gui=require('gui')
local wid=require('gui.widgets')
local gscript=require('gui.script')
jobitemEditor=defclass(jobitemEditor,gui.FramedScreen)
jobitemEditor.ATTRS{
frame_style = gui.GREY_LINE_FRAME,
frame_inset = 1,
allow_add=false,
allow_remove=false,
allow_any_item=false,
job=DEFAULT_NIL,
job_items=DEFAULT_NIL,
items=DEFAULT_NIL,
on_okay=DEFAULT_NIL,
autofill=true,
}
function update_slot_text(slot)
local items=""
for i,v in ipairs(slot.items) do
items=items.." "..dfhack.items.getDescription(v,0)
if i~=#slot.items then
items=items..","
end
end
slot.text=string.format("%02d. Filled(%d/%d):%s",slot.id+1,slot.filled_amount,slot.job_item.quantity,items)
end
--items-> table => key-> id of job.job_items, value-> table => key (num), value => item(ref)
function jobitemEditor:init(args)
--self.job=args.job
if self.job==nil and self.job_items==nil then qerror("This screen must have job target or job_items list") end
if self.items==nil then qerror("This screen must have item list") end
self:addviews{
wid.Label{
view_id = 'label',
text = args.prompt,
text_pen = args.text_pen,
frame = { l = 0, t = 0 },
},
wid.List{
view_id = 'itemList',
frame = { l = 0, t = 2 ,b=2},
},
wid.Label{
frame = { b=1,l=1},
text ={{text= ": cancel",
key = "LEAVESCREEN",
on_activate= self:callback("dismiss")
},
{
gap=3,
text= ": accept",
key = "SEC_SELECT",
on_activate= self:callback("commit"),
enabled=self:callback("jobValid")
},
{
gap=3,
text= ": add",
key = "CUSTOM_A",
enabled=self:callback("can_add"),
on_activate= self:callback("add_item")
},
{
gap=3,
text= ": remove",
key = "CUSTOM_R",
enabled=self:callback("can_remove"),
on_activate= self:callback("remove_item")
},}
},
}
self.assigned={}
self:fill()
if self.autofill then
self:fill_slots()
end
end
function jobitemEditor:get_slot()
local idx,choice=self.subviews.itemList:getSelected()
return choice
end
function jobitemEditor:can_add()
local slot=self:get_slot()
return slot.filled_amount<slot.job_item.quantity
end
function jobitemEditor:can_remove()
local slot=self:get_slot()
return #slot.items>0
end
function jobitemEditor:get_item_filters( job_item )
local true_flags={}
for k,v in pairs(job_item.flags1) do
if v then
table.insert(true_flags,k)
end
end
for k,v in pairs(job_item.flags2) do
if v then
table.insert(true_flags,k)
end
end
for k,v in pairs(job_item.flags3) do
if v then
table.insert(true_flags,k)
end
end
return table.concat(true_flags,"\n")
end
function jobitemEditor:add_item()
local cur_slot=self:get_slot()
local choices={}
table.insert(choices,{text="<no item>"})
for k,v in pairs(cur_slot.choices) do
if not self.assigned[v.id] then
table.insert(choices,{text=dfhack.items.getDescription(v,0),item=v})
end
end
gscript.start(function ()
local _,_2,choice=gscript.showListPrompt("which item?", "Select item\nItem filters:\n"..self:get_item_filters(cur_slot.job_item), COLOR_WHITE, choices)
if choice ~= nil and choice.item~=nil then
self:add_item_to_slot(cur_slot,choice.item)
end
end
)
end
function jobitemEditor:fill_slots()
for i,v in ipairs(self.slots) do
while v.filled_amount<v.job_item.quantity do
local added=false
for _,it in ipairs(v.choices) do
if not self.assigned[it.id] then
self:add_item_to_slot(v,it)
added=true
break
end
end
if not added then
break
end
end
end
end
function jobitemEditor:add_item_to_slot(slot,item)
table.insert(slot.items,item)
slot.filled_amount=slot.filled_amount+item:getTotalDimension()
self.assigned[item.id]=true
update_slot_text(slot)
self.subviews.itemList:setChoices(self.slots)
end
function jobitemEditor:remove_item()
local slot=self:get_slot()
for k,v in pairs(slot.items) do
self.assigned[v.id]=nil
end
slot.items={}
slot.filled_amount=0
update_slot_text(slot)
self.subviews.itemList:setChoices(self.slots)
end
function jobitemEditor:fill()
self.slots={}
for k,v in pairs(self.items) do
local job_item
if self.job then
job_item=self.job.job_items[k]
else
job_item=self.job_items[k]
end
table.insert(self.slots,{job_item=job_item, id=k, items={},choices=v,filled_amount=0,slot_id=#self.slots})
update_slot_text(self.slots[#self.slots])
end
self.subviews.itemList:setChoices(self.slots)
end
function jobitemEditor:jobValid()
for k,v in pairs(self.slots) do
if v.filled_amount<v.job_item.quantity then
return false
end
end
return true
end
function jobitemEditor:commit()
if self.job then
for _,slot in pairs(self.slots) do
for _1,cur_item in pairs(slot.items) do
self.job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=slot.id})
end
end
end
self:dismiss()
if self.on_okay then self.on_okay(self.slots) end
end
function jobitemEditor:onDestroy()
if self.on_close then
self.on_close()
end
end
function showItemEditor(job,item_selections)
jobitemEditor{
job = job,
items = item_selections,
on_close = gscript.qresume(nil),
on_okay = gscript.mkresume(true)
--on_cancel=gscript.mkresume(false)
}:show()
return gscript.wait()
end
return _ENV

@ -1,209 +0,0 @@
-- Assign weapon racks to squads (needs binpatch)
--[[=begin
gui/assign-rack
===============
`This script requires a binpatch <binpatches/needs-patch>`, which has not
been available since DF 0.34.11
See :bug:`1445` for more info about the patches.
=end]]
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local widgets = require 'gui.widgets'
local dlg = require 'gui.dialogs'
local bp = require 'binpatch'
AssignRack = defclass(AssignRack, guidm.MenuOverlay)
AssignRack.focus_path = 'assign-rack'
AssignRack.ATTRS {
building = DEFAULT_NIL,
frame_inset = 1,
frame_background = COLOR_BLACK,
}
function list_squads(building,squad_table,squad_list)
local sqlist = building:getSquads()
if not sqlist then
return
end
for i,v in ipairs(sqlist) do
local obj = df.squad.find(v.squad_id)
if obj then
if not squad_table[v.squad_id] then
squad_table[v.squad_id] = { id = v.squad_id, obj = obj }
table.insert(squad_list, squad_table[v.squad_id])
end
-- Set specific use flags
for n,ok in pairs(v.mode) do
if ok then
squad_table[v.squad_id][n] = true
end
end
-- Check if any use is possible
local btype = building:getType()
if btype == df.building_type.Bed then
if v.mode.sleep then
squad_table[v.squad_id].any = true
end
elseif btype == df.building.Weaponrack then
if v.mode.train or v.mode.indiv_eq then
squad_table[v.squad_id].any = true
end
else
if v.mode.indiv_eq then
squad_table[v.squad_id].any = true
end
end
end
end
for i,v in ipairs(building.parents) do
list_squads(v, squad_table, squad_list)
end
end
function filter_invalid(list, id)
for i=#list-1,0,-1 do
local bld = df.building.find(list[i])
if not bld or bld:getSpecificSquad() ~= id then
list:erase(i)
end
end
end
function AssignRack:init(args)
self.squad_table = {}
self.squad_list = {}
list_squads(self.building, self.squad_table, self.squad_list)
table.sort(self.squad_list, function(a,b) return a.id < b.id end)
self.choices = {}
for i,v in ipairs(self.squad_list) do
if v.any and (v.train or v.indiv_eq) then
local name = v.obj.alias
if name == '' then
name = dfhack.TranslateName(v.obj.name, true)
end
filter_invalid(v.obj.rack_combat, v.id)
filter_invalid(v.obj.rack_training, v.id)
table.insert(self.choices, {
icon = self:callback('isSelected', v),
icon_pen = COLOR_LIGHTGREEN,
obj = v,
text = {
name, NEWLINE, ' ',
{ text = function()
return string.format('%d combat, %d training', #v.obj.rack_combat, #v.obj.rack_training)
end }
}
})
end
end
self:addviews{
widgets.Label{
frame = { l = 0, t = 0 },
text = {
'Assign Weapon Rack'
}
},
widgets.List{
view_id = 'list',
frame = { t = 2, b = 2 },
icon_width = 2, row_height = 2,
scroll_keys = widgets.SECONDSCROLL,
choices = self.choices,
on_submit = self:callback('onSubmit'),
},
widgets.Label{
frame = { l = 0, t = 2 },
text_pen = COLOR_LIGHTRED,
text = 'No appropriate barracks\n\nNote: weapon racks use the\nIndividual equipment flag',
visible = (#self.choices == 0),
},
widgets.Label{
frame = { l = 0, b = 0 },
text = {
{ key = 'LEAVESCREEN', text = ': Back',
on_activate = self:callback('dismiss') }
}
},
}
end
function AssignRack:isSelected(info)
if self.building.specific_squad == info.id then
return '\xfb'
else
return nil
end
end
function AssignRack:onSubmit(idx, choice)
local rid = self.building.id
local curid = self.building.specific_squad
local cur = df.squad.find(curid)
if cur then
utils.erase_sorted(cur.rack_combat, rid)
utils.erase_sorted(cur.rack_training, rid)
end
self.building.specific_squad = -1
df.global.ui.equipment.update.buildings = true
local new = df.squad.find(choice.obj.id)
if new and choice.obj.id ~= curid then
self.building.specific_squad = choice.obj.id
if choice.obj.indiv_eq then
utils.insert_sorted(new.rack_combat, rid)
end
if choice.obj.train then
utils.insert_sorted(new.rack_training, rid)
end
end
end
function AssignRack:onInput(keys)
if self:propagateMoveKeys(keys) then
if df.global.world.selected_building ~= self.building then
self:dismiss()
end
else
AssignRack.super.onInput(self, keys)
end
end
if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some/Weaponrack' then
qerror("This script requires a weapon rack selected in the 'q' mode")
end
AssignRack{ building = dfhack.gui.getSelectedBuilding() }:show()
if not already_patched then
local patch = bp.load_dif_file('weaponrack-unassign')
if patch and patch:isApplied() then
already_patched = true
end
end
if not already_patched then
dlg.showMessage(
'BUG ALERT',
{ 'This script requires applying the binary patch', NEWLINE,
'named weaponrack-unassign. Otherwise the game', NEWLINE,
'will lose your settings due to a bug.' },
COLOR_YELLOW
)
end

@ -1,662 +0,0 @@
-- A GUI front-end for the autobutcher plugin.
--[[=begin
gui/autobutcher
===============
An in-game interface for `autobutcher`.
=end]]
local gui = require 'gui'
local utils = require 'utils'
local widgets = require 'gui.widgets'
local dlg = require 'gui.dialogs'
local plugin = require 'plugins.zone'
WatchList = defclass(WatchList, gui.FramedScreen)
WatchList.ATTRS {
frame_title = 'Autobutcher Watchlist',
frame_inset = 0, -- cover full DF window
frame_background = COLOR_BLACK,
frame_style = gui.BOUNDARY_FRAME,
}
-- width of the race name column in the UI
local racewidth = 25
function nextAutowatchState()
if(plugin.autowatch_isEnabled()) then
return 'Stop '
end
return 'Start'
end
function nextAutobutcherState()
if(plugin.autobutcher_isEnabled()) then
return 'Stop '
end
return 'Start'
end
function getSleepTimer()
return plugin.autobutcher_getSleep()
end
function setSleepTimer(ticks)
plugin.autobutcher_setSleep(ticks)
end
function WatchList:init(args)
local colwidth = 7
self:addviews{
widgets.Panel{
frame = { l = 0, r = 0 },
frame_inset = 1,
subviews = {
widgets.Label{
frame = { l = 0, t = 0 },
text_pen = COLOR_CYAN,
text = {
{ text = 'Race', width = racewidth }, ' ',
{ text = 'female', width = colwidth }, ' ',
{ text = ' male', width = colwidth }, ' ',
{ text = 'Female', width = colwidth }, ' ',
{ text = ' Male', width = colwidth }, ' ',
{ text = 'watch? ' },
{ text = ' butchering' },
NEWLINE,
{ text = '', width = racewidth }, ' ',
{ text = ' kids', width = colwidth }, ' ',
{ text = ' kids', width = colwidth }, ' ',
{ text = 'adults', width = colwidth }, ' ',
{ text = 'adults', width = colwidth }, ' ',
{ text = ' ' },
{ text = ' ordered' },
}
},
widgets.List{
view_id = 'list',
frame = { t = 3, b = 5 },
not_found_label = 'Watchlist is empty.',
edit_pen = COLOR_LIGHTCYAN,
text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK },
cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN },
--on_select = self:callback('onSelectEntry'),
},
widgets.Label{
view_id = 'bottom_ui',
frame = { b = 0, h = 1 },
text = 'filled by updateBottom()'
}
}
},
}
self:initListChoices()
self:updateBottom()
end
-- change the viewmode for stock data displayed in left section of columns
local viewmodes = { 'total stock', 'protected stock', 'butcherable', 'butchering ordered' }
local viewmode = 1
function WatchList:onToggleView()
if viewmode < #viewmodes then
viewmode = viewmode + 1
else
viewmode = 1
end
self:initListChoices()
self:updateBottom()
end
-- update the bottom part of the UI (after sleep timer changed etc)
function WatchList:updateBottom()
self.subviews.bottom_ui:setText(
{
{ key = 'CUSTOM_SHIFT_V', text = ': View in colums shows: '..viewmodes[viewmode]..' / target max',
on_activate = self:callback('onToggleView') }, NEWLINE,
{ key = 'CUSTOM_F', text = ': f kids',
on_activate = self:callback('onEditFK') }, ', ',
{ key = 'CUSTOM_M', text = ': m kids',
on_activate = self:callback('onEditMK') }, ', ',
{ key = 'CUSTOM_SHIFT_F', text = ': f adults',
on_activate = self:callback('onEditFA') }, ', ',
{ key = 'CUSTOM_SHIFT_M', text = ': m adults',
on_activate = self:callback('onEditMA') }, '. ',
{ key = 'CUSTOM_W', text = ': Toggle watch',
on_activate = self:callback('onToggleWatching') }, '. ',
{ key = 'CUSTOM_X', text = ': Delete',
on_activate = self:callback('onDeleteEntry') }, '. ', NEWLINE,
--{ key = 'CUSTOM_A', text = ': Add race',
-- on_activate = self:callback('onAddRace') }, ', ',
{ key = 'CUSTOM_SHIFT_R', text = ': Set whole row',
on_activate = self:callback('onSetRow') }, '. ',
{ key = 'CUSTOM_B', text = ': Remove butcher orders',
on_activate = self:callback('onUnbutcherRace') }, '. ',
{ key = 'CUSTOM_SHIFT_B', text = ': Butcher race',
on_activate = self:callback('onButcherRace') }, '. ', NEWLINE,
{ key = 'CUSTOM_SHIFT_A', text = ': '..nextAutobutcherState()..' Autobutcher',
on_activate = self:callback('onToggleAutobutcher') }, '. ',
{ key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch',
on_activate = self:callback('onToggleAutowatch') }, '. ',
{ key = 'CUSTOM_SHIFT_S', text = ': Sleep ('..getSleepTimer()..' ticks)',
on_activate = self:callback('onEditSleepTimer') }, '. ',
})
end
function stringify(number)
-- cap displayed number to 3 digits
-- after population of 50 per race is reached pets stop breeding anyways
-- so probably this could safely be reduced to 99
local max = 999
if number > max then number = max end
return tostring(number)
end
function WatchList:initListChoices()
local choices = {}
-- first two rows are for "edit all races" and "edit new races"
local settings = plugin.autobutcher_getSettings()
local fk = stringify(settings.fk)
local fa = stringify(settings.fa)
local mk = stringify(settings.mk)
local ma = stringify(settings.ma)
local watched = ''
local colwidth = 7
table.insert (choices, {
text = {
{ text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = watched, width = 6, rjustify = true }
}
})
table.insert (choices, {
text = {
{ text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = watched, width = 6, rjustify = true }
}
})
local watchlist = plugin.autobutcher_getWatchList()
for i,entry in ipairs(watchlist) do
fk = stringify(entry.fk)
fa = stringify(entry.fa)
mk = stringify(entry.mk)
ma = stringify(entry.ma)
if viewmode == 1 then
fkc = stringify(entry.fk_total)
fac = stringify(entry.fa_total)
mkc = stringify(entry.mk_total)
mac = stringify(entry.ma_total)
end
if viewmode == 2 then
fkc = stringify(entry.fk_protected)
fac = stringify(entry.fa_protected)
mkc = stringify(entry.mk_protected)
mac = stringify(entry.ma_protected)
end
if viewmode == 3 then
fkc = stringify(entry.fk_butcherable)
fac = stringify(entry.fa_butcherable)
mkc = stringify(entry.mk_butcherable)
mac = stringify(entry.ma_butcherable)
end
if viewmode == 4 then
fkc = stringify(entry.fk_butcherflag)
fac = stringify(entry.fa_butcherflag)
mkc = stringify(entry.mk_butcherflag)
mac = stringify(entry.ma_butcherflag)
end
local butcher_ordered = entry.fk_butcherflag + entry.fa_butcherflag + entry.mk_butcherflag + entry.ma_butcherflag
local bo = ' '
if butcher_ordered > 0 then bo = stringify(butcher_ordered) end
local watched = 'no'
if entry.watched then watched = 'yes' end
local racestr = entry.name
-- highlight entries where the target quota can't be met because too many are protected
bad_pen = COLOR_LIGHTRED
good_pen = NONE -- this is stupid, but it works. sue me
fk_pen = good_pen
fa_pen = good_pen
mk_pen = good_pen
ma_pen = good_pen
if entry.fk_protected > entry.fk then fk_pen = bad_pen end
if entry.fa_protected > entry.fa then fa_pen = bad_pen end
if entry.mk_protected > entry.mk then mk_pen = bad_pen end
if entry.ma_protected > entry.ma then ma_pen = bad_pen end
table.insert (choices, {
text = {
{ text = racestr, width = racewidth, pad_char = ' ' }, --' ',
{ text = fkc, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = fk, width = 3, rjustify = false, pad_char = ' ', pen = fk_pen }, ' ',
{ text = mkc, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = mk, width = 3, rjustify = false, pad_char = ' ', pen = mk_pen }, ' ',
{ text = fac, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = fa, width = 3, rjustify = false, pad_char = ' ', pen = fa_pen }, ' ',
{ text = mac, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = ma, width = 3, rjustify = false, pad_char = ' ', pen = ma_pen }, ' ',
{ text = watched, width = 6, rjustify = true, pad_char = ' ' }, ' ',
{ text = bo, width = 8, rjustify = true, pad_char = ' ' }
},
obj = entry,
})
end
local list = self.subviews.list
list:setChoices(choices)
end
function WatchList:onInput(keys)
if keys.LEAVESCREEN then
self:dismiss()
else
WatchList.super.onInput(self, keys)
end
end
-- check the user input for target population values
function WatchList:checkUserInput(count, text)
if count == nil then
dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED)
return false
end
if count < 0 then
dlg.showMessage('Invalid Number', 'Negative numbers make no sense!', COLOR_LIGHTRED)
return false
end
return true
end
-- check the user input for sleep timer
function WatchList:checkUserInputSleep(count, text)
if count == nil then
dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED)
return false
end
if count < 1000 then
dlg.showMessage('Invalid Number',
'Minimum allowed timer value is 1000!'..NEWLINE..'Too low values could decrease performance'..NEWLINE..'and are not necessary!',
COLOR_LIGHTRED)
return false
end
return true
end
function WatchList:onEditFK()
local selidx,selobj = self.subviews.list:getSelected()
local settings = plugin.autobutcher_getSettings()
local fk = settings.fk
local mk = settings.mk
local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
if selidx > 2 then
local entry = selobj.obj
fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Race: '..race,
'Enter desired maximum of female kids:',
COLOR_WHITE,
' '..fk,
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
fk = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onEditMK()
local selidx,selobj = self.subviews.list:getSelected()
local settings = plugin.autobutcher_getSettings()
local fk = settings.fk
local mk = settings.mk
local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
if selidx > 2 then
local entry = selobj.obj
fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Race: '..race,
'Enter desired maximum of male kids:',
COLOR_WHITE,
' '..mk,
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
mk = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onEditFA()
local selidx,selobj = self.subviews.list:getSelected()
local settings = plugin.autobutcher_getSettings()
local fk = settings.fk
local mk = settings.mk
local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
if selidx > 2 then
local entry = selobj.obj
fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Race: '..race,
'Enter desired maximum of female adults:',
COLOR_WHITE,
' '..fa,
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
fa = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onEditMA()
local selidx,selobj = self.subviews.list:getSelected()
local settings = plugin.autobutcher_getSettings()
local fk = settings.fk
local mk = settings.mk
local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
if selidx > 2 then
local entry = selobj.obj
fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Race: '..race,
'Enter desired maximum of male adults:',
COLOR_WHITE,
' '..ma,
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
ma = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onEditSleepTimer()
local sleep = getSleepTimer()
dlg.showInputPrompt(
'Edit Sleep Timer',
'Enter new sleep timer in ticks:'..NEWLINE..'(1 ingame day equals 1200 ticks)',
COLOR_WHITE,
' '..sleep,
function(text)
local count = tonumber(text)
if self:checkUserInputSleep(count, text) then
sleep = count
setSleepTimer(sleep)
self:updateBottom()
end
end
)
end
function WatchList:onToggleWatching()
local selidx,selobj = self.subviews.list:getSelected()
if selidx > 2 then
local entry = selobj.obj
plugin.autobutcher_setWatchListRace(entry.id, entry.fk, entry.mk, entry.fa, entry.ma, not entry.watched)
end
self:initListChoices()
end
function WatchList:onDeleteEntry()
local selidx,selobj = self.subviews.list:getSelected()
if(selidx < 3 or selobj == nil) then
return
end
dlg.showYesNoPrompt(
'Delete from Watchlist',
'Really delete the selected entry?'..NEWLINE..'(you could just toggle watch instead)',
COLOR_YELLOW,
function()
plugin.autobutcher_removeFromWatchList(selobj.obj.id)
self:initListChoices()
end
)
end
function WatchList:onAddRace()
print('onAddRace - not implemented yet')
end
function WatchList:onUnbutcherRace()
local selidx,selobj = self.subviews.list:getSelected()
if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end
if selidx > 2 then
local entry = selobj.obj
local race = entry.name
plugin.autobutcher_unbutcherRace(entry.id)
self:initListChoices()
self:updateBottom()
end
end
function WatchList:onButcherRace()
local selidx,selobj = self.subviews.list:getSelected()
if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end
if selidx > 2 then
local entry = selobj.obj
local race = entry.name
plugin.autobutcher_butcherRace(entry.id)
self:initListChoices()
self:updateBottom()
end
end
-- set whole row (fk, mk, fa, ma) to one value
function WatchList:onSetRow()
local selidx,selobj = self.subviews.list:getSelected()
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
local watchindex = selidx - 3
if selidx > 2 then
local entry = selobj.obj
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Set whole row for '..race,
'Enter desired maximum for all subtypes:',
COLOR_WHITE,
' ',
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( count, count, count, count )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( count, count, count, count )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, count, count, count, count, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onToggleAutobutcher()
if(plugin.autobutcher_isEnabled()) then
plugin.autobutcher_setEnabled(false)
plugin.autobutcher_sortWatchList()
else
plugin.autobutcher_setEnabled(true)
end
self:initListChoices()
self:updateBottom()
end
function WatchList:onToggleAutowatch()
if(plugin.autowatch_isEnabled()) then
plugin.autowatch_setEnabled(false)
else
plugin.autowatch_setEnabled(true)
end
self:initListChoices()
self:updateBottom()
end
if not dfhack.isMapLoaded() then
qerror('Map is not loaded.')
end
if string.match(dfhack.gui.getCurFocus(), '^dfhack/lua') then
qerror("This script must not be called while other lua gui stuff is running.")
end
-- maybe this is too strict, there is not really a reason why it can only be called from the status screen
-- (other than the hotkey might overlap with other scripts)
if (not string.match(dfhack.gui.getCurFocus(), '^overallstatus') and not string.match(dfhack.gui.getCurFocus(), '^pet/List/Unit')) then
qerror("This script must either be called from the overall status screen or the animal list screen.")
end
local screen = WatchList{ }
screen:show()

@ -1,169 +0,0 @@
-- Rewrite individual choice weapons to specific types
--[[=begin
gui/choose-weapons
==================
Bind to a key (the example config uses :kbd:`Ctrl`:kbd:`W`), and activate in the Equip->View/Customize
page of the military screen.
Depending on the cursor location, it rewrites all 'individual choice weapon' entries
in the selected squad or position to use a specific weapon type matching the assigned
unit's top skill. If the cursor is in the rightmost list over a weapon entry, it rewrites
only that entry, and does it even if it is not 'individual choice'.
Rationale: individual choice seems to be unreliable when there is a weapon shortage,
and may lead to inappropriate weapons being selected.
=end]]
local utils = require 'utils'
local dlg = require 'gui.dialogs'
local defs = df.global.world.raws.itemdefs
local entity = df.global.ui.main.fortress_entity
local tasks = df.global.ui.tasks
local equipment = df.global.ui.equipment
function find_best_weapon(unit,mode)
local best = nil
local skill = nil
local skill_level = nil
local count = 0
local function try(id,iskill)
local slevel = dfhack.units.getNominalSkill(unit,iskill)
-- Choose most skill
if (skill ~= nil and slevel > skill_level)
or (skill == nil and slevel > 0) then
best,skill,skill_level,count = id,iskill,slevel,0
end
-- Then most produced within same skill
if skill == iskill then
local cnt = tasks.created_weapons[id]
if cnt > count then
best,count = id,cnt
end
end
end
for _,id in ipairs(entity.resources.weapon_type) do
local def = defs.weapons[id]
if def.skill_ranged >= 0 then
if mode == nil or mode == 'ranged' then
try(id, def.skill_ranged)
end
else
if mode == nil or mode == 'melee' then
try(id, def.skill_melee)
end
end
end
return best
end
function unassign_wrong_items(unit,position,spec,subtype)
for i=#spec.assigned-1,0,-1 do
local id = spec.assigned[i]
local item = df.item.find(id)
if item.subtype.subtype ~= subtype then
spec.assigned:erase(i)
-- TODO: somewhat unexplored area; maybe missing some steps
utils.erase_sorted(position.assigned_items,id)
if utils.erase_sorted(equipment.items_assigned.WEAPON,item,'id') then
utils.insert_sorted(equipment.items_unassigned.WEAPON,item,'id')
end
equipment.update.weapon = true
unit.military.pickup_flags.update = true
end
end
end
local count = 0
function adjust_uniform_spec(unit,position,spec,force)
if not unit then return end
local best
if spec.indiv_choice.melee then
best = find_best_weapon(unit, 'melee')
elseif spec.indiv_choice.ranged then
best = find_best_weapon(unit, 'ranged')
elseif spec.indiv_choice.any or force then
best = find_best_weapon(unit, nil)
end
if best then
count = count + 1
spec.item_filter.item_subtype = best
spec.indiv_choice.any = false
spec.indiv_choice.melee = false
spec.indiv_choice.ranged = false
unassign_wrong_items(unit, position, spec, best)
end
end
function adjust_position(unit,position,force)
if not unit then
local fig = df.historical_figure.find(position.occupant)
if not fig then return end
unit = df.unit.find(fig.unit_id)
end
for _,v in ipairs(position.uniform.weapon) do
adjust_uniform_spec(unit, position, v, force)
end
end
function adjust_squad(squad, force)
for _,pos in ipairs(squad.positions) do
adjust_position(nil, pos, force)
end
end
local args = {...}
local vs = dfhack.gui.getCurViewscreen()
local vstype = df.viewscreen_layer_militaryst
if not vstype:is_instance(vs) then
qerror('Call this from the military screen')
end
if vs.page == vstype.T_page.Equip
and vs.equip.mode == vstype.T_equip.T_mode.Customize then
local slist = vs.layer_objects[0]
local squad = vs.equip.squads[slist:getListCursor()]
if slist.active then
print('Adjusting squad.')
adjust_squad(squad)
else
local plist = vs.layer_objects[1]
local pidx = plist:getListCursor()
local pos = squad.positions[pidx]
local unit = vs.equip.units[pidx]
if plist.active then
print('Adjusting position.')
adjust_position(unit, pos)
elseif unit and vs.equip.edit_mode < 0 then
local wlist = vs.layer_objects[2]
local idx = wlist:getListCursor()
local cat = vs.equip.assigned.category[idx]
if wlist.active and cat == df.uniform_category.weapon then
print('Adjusting spec.')
adjust_uniform_spec(unit, pos, vs.equip.assigned.spec[idx], true)
end
end
end
else
qerror('Call this from the Equip page of military screen')
end
if count > 1 then
dlg.showMessage(
'Choose Weapons',
'Updated '..count..' uniform entries.', COLOR_GREEN
)
elseif count == 0 then
dlg.showMessage(
'Choose Weapons',
'Did not find any entries to update.', COLOR_YELLOW
)
end

@ -1,58 +0,0 @@
-- Clone a uniform template in the military screen
--[[=begin
gui/clone-uniform
=================
Bind to a key (the example config uses :kbd:`Ctrl`:kbd:`C`), and activate in the Uniforms
page of the military screen with the cursor in the leftmost list.
When invoked, the script duplicates the currently selected uniform template,
and selects the newly created copy.
=end]]
local utils = require 'utils'
local gui = require 'gui'
local entity = df.global.ui.main.fortress_entity
local args = {...}
local vs = dfhack.gui.getCurViewscreen()
local vstype = df.viewscreen_layer_militaryst
if not vstype:is_instance(vs) then
qerror('Call this from the military screen')
end
local slist = vs.layer_objects[0]
if vs.page == vstype.T_page.Uniforms
and slist.active and slist.num_entries > 0
and not vs.equip.in_name_uniform
then
local idx = slist.num_entries
if #vs.equip.uniforms ~= idx or #entity.uniforms ~= idx then
error('Uniform vector length mismatch')
end
local uniform = vs.equip.uniforms[slist:getListCursor()]
local ucopy = uniform:new()
ucopy.id = entity.next_uniform_id
ucopy.name = ucopy.name..'(Copy)'
for k,v in ipairs(ucopy.uniform_item_info) do
for k2,v2 in ipairs(v) do
v[k2] = v2:new()
end
end
entity.next_uniform_id = entity.next_uniform_id + 1
entity.uniforms:insert('#',ucopy)
vs.equip.uniforms:insert('#',ucopy)
slist.num_entries = idx+1
slist.cursor = idx-1
gui.simulateInput(vs, 'STANDARDSCROLL_DOWN')
else
qerror('Call this with a uniform selected on the Uniforms page of military screen')
end

@ -1,490 +0,0 @@
-- Issue orders to companions in Adventure mode
--[[=begin
gui/companion-order
===================
A script to issue orders for companions. Select companions with lower case chars, issue orders with upper
case. Must be in look or talk mode to issue command on tile.
.. image:: /docs/images/companion-order.png
* move - orders selected companions to move to location. If companions are following they will move no more than 3 tiles from you.
* equip - try to equip items on the ground.
* pick-up - try to take items into hand (also wield)
* unequip - remove and drop equipment
* unwield - drop held items
* wait - temporarily remove from party
* follow - rejoin the party after "wait"
* leave - remove from party (can be rejoined by talking)
=end]]
local gui = require 'gui'
local dlg = require 'gui.dialogs'
local args={...}
local is_cheat=(#args>0 and args[1]=="-c")
local cursor=xyz2pos(df.global.cursor.x,df.global.cursor.y,df.global.cursor.z)
local permited_equips={}
permited_equips[df.item_backpackst]="UPPERBODY"
permited_equips[df.item_quiverst]="UPPERBODY"
permited_equips[df.item_flaskst]="UPPERBODY"
permited_equips[df.item_armorst]="UPPERBODY"
permited_equips[df.item_shoesst]="STANCE"
permited_equips[df.item_glovesst]="GRASP"
permited_equips[df.item_helmst]="HEAD"
permited_equips[df.item_pantsst]="LOWERBODY"
function DoesHaveSubtype(item)
if df.item_backpackst:is_instance(item) or df.item_flaskst:is_instance(item) or df.item_quiverst:is_instance(item) then
return false
end
return true
end
function CheckCursor(p)
if p.x==-30000 then
dlg.showMessage(
'Companion orders',
'You must have a cursor on some tile!', COLOR_LIGHTRED
)
return false
end
return true
end
function getxyz() -- this will return pointers x,y and z coordinates.
local x=df.global.cursor.x
local y=df.global.cursor.y
local z=df.global.cursor.z
return x,y,z -- return the coords
end
function GetCaste(race_id,caste_id)
local race=df.creature_raw.find(race_id)
return race.caste[caste_id]
end
function EnumBodyEquipable(race_id,caste_id)
local caste=GetCaste(race_id,caste_id)
local bps=caste.body_info.body_parts
local ret={}
for k,v in pairs(bps) do
ret[k]={bp=v,layers={[0]={size=0,permit=0},[1]={size=0,permit=0},[2]={size=0,permit=0},[3]={size=0,permit=0} } }
end
return ret
end
function ReadCurrentEquiped(body_equip,unit)
for k,v in pairs(unit.inventory) do
if v.mode==2 then
local bpid=v.body_part_id
if DoesHaveSubtype(v.item) then
local sb=v.item.subtype.props
local trg=body_equip[bpid]
local trg_layer=trg.layers[sb.layer]
if trg_layer.permit==0 then
trg_layer.permit=sb.layer_permit
else
if trg_layer.permit>sb.layer_permit then
trg_layer.permit=sb.layer_permit
end
end
trg_layer.size=trg_layer.size+sb.layer_size
end
end
end
end
function LayeringPermits(body_part,item)
if not DoesHaveSubtype(item) then
return true
end
local sb=item.subtype.props
local trg_layer=body_part.layers[sb.layer]
if math.min(trg_layer.permit ,sb.layer_permit)<trg_layer.size+sb.layer_size then
return true
end
return false
end
function AddLayering(body_part,item)
if not DoesHaveSubtype(item) then
return
end
local sb=item.subtype.props
local trg_layer=body_part.layers[sb.layer]
trg_layer.permit=math.min(trg_layer.permit,sb.layer_permit)
trg_layer.size=trg_layer.size+sb.layer_size
end
function AddIfFits(body_equip,unit,item)
--TODO shaped items
local need_flag
for k,v in pairs(permited_equips) do
if k:is_instance(item) then
need_flag=v
break
end
end
if need_flag==nil then
return false
end
for k,bp in pairs(body_equip) do
local handedness_ok=true
if df.item_glovesst:is_instance(item) then
if bp.bp.flags["LEFT"]~=item.handedness[1] then
handedness_ok=false
end
end
if bp.bp.flags[need_flag] and LayeringPermits(bp,item) and handedness_ok then
if dfhack.items.moveToInventory(item,unit,2,k) then
AddLayering(bp,item)
return true
end
end
end
return false
end
function EnumGrasps(race_id,caste_id)
local caste=GetCaste(race_id,caste_id)
local bps=caste.body_info.body_parts
local ret={}
for k,v in pairs(bps) do
if v.flags.GRASP then
--table.insert(ret,{k,v})
ret[k]={v}
end
end
return ret
end
function EnumEmptyGrasps(unit)
local grasps=EnumGrasps(unit.race,unit.caste)
local ret={}
for k,v in pairs(unit.inventory) do
if grasps[v.body_part_id] and v.mode==1 then
grasps[v.body_part_id][2]=true
end
end
for k,v in pairs(grasps) do
if not v[2] then
table.insert(ret,k)
end
end
return ret
end
function GetBackpack(unit)
for k,v in pairs(unit.inventory) do
if df.item_backpackst:is_instance(v.item) then
return v.item
end
end
end
function AddBackpackItems(backpack,items)
if backpack then
local bitems=dfhack.items.getContainedItems(backpack)
for k,v in pairs(bitems) do
table.insert(items,v)
end
end
end
function GetItemsAtPos(pos)
local ret={}
for k,v in pairs(df.global.world.items.all) do
if v.flags.on_ground and v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then
table.insert(ret,v)
end
end
return ret
end
function isAnyOfEquipable(item)
for k,v in pairs(permited_equips) do
if k:is_instance(item) then
return true
end
end
return false
end
function FilterByEquipable(items)
local ret={}
for k,v in pairs(items) do
if isAnyOfEquipable(v) then
table.insert(ret,v)
end
end
return ret
end
function FilterBySize(items,race_id) --TODO add logic for compatible races
local ret={}
for k,v in pairs(items) do
if v.maker_race==race_id then
table.insert(ret,v)
end
end
return ret
end
--local companions=??
local orders={
{name="move",f=function (unit_list,pos)
if not CheckCursor(pos) then
return false
end
for k,v in pairs(unit_list) do
v.path.dest:assign(pos)
end
return true
end},
{name="equip",f=function (unit_list)
--search in inventory(hands/backpack/ground) and equip everything
--lot's of stuff to think: layering, which item goes where, which goes first, which body parts are missing, body sizes etc...
--dfhack.items.moveToInventory(item,unit,use_mode,body_part)
for k,unit in pairs(unit_list) do
local items=GetItemsAtPos(unit.pos)
--todo make a table join function or sth... submit it to the lua list!
AddBackpackItems(GetBackpack(unit),items)
items=FilterByEquipable(items)
FilterBySize(items,unit.race)
local body_parts=EnumBodyEquipable(unit.race,unit.caste)
ReadCurrentEquiped(body_parts,unit)
for it_num,item in pairs(items) do
AddIfFits(body_parts,unit,item)
end
end
end},
{name="pick-up",f=function (unit_list)
--pick everything up (first into hands (if empty) then backpack (if have and has space?))
for k,v in pairs(unit_list) do
local items=GetItemsAtPos(v.pos)
local grasps=EnumEmptyGrasps(v)
-- TODO sort with weapon/shield on top of list!
--add to grasps, then add to backpack (sanely? i.e. weapons/shields into hands then stuff)
--or add to backpack if have, only then check grasps (faster equiping)
while #grasps >0 and #items>0 do
if(dfhack.items.moveToInventory(items[#items],v,1,grasps[#grasps])) then
table.remove(grasps)
end
table.remove(items)
end
local backpack=GetBackpack(v)
if backpack then
while #items>0 do
dfhack.items.moveToContainer(items[#items],backpack)
table.remove(items)
end
end
end
return true
end},
{name="unequip",f=function (unit_list)
--remove and drop all the stuff (todo maybe a gui too?)
for k,v in pairs(unit_list) do
while #v.inventory ~=0 do
dfhack.items.moveToGround(v.inventory[0].item,v.pos)
end
end
return true
end},
{name="unwield",f=function (unit_list)
for k,v in pairs(unit_list) do
local wep_count=0
for _,it in pairs(v.inventory) do
if it.mode==1 then
wep_count=wep_count+1
end
end
for i=1,wep_count do
for _,it in pairs(v.inventory) do
if it.mode==1 then
dfhack.items.moveToGround(it.item,v.pos)
break
end
end
end
end
return true
end},
--[=[
{name="roam not working :<",f=function (unit_list,pos,dist) --does not work
if not CheckCursor(pos) then
return false
end
dist=dist or 5
for k,v in pairs(unit_list) do
v.idle_area:assign(pos)
v.idle_area_threshold=dist
end
return true
end},
--]=]
{name="wait",f=function (unit_list)
for k,v in pairs(unit_list) do
v.relations.group_leader_id=-1
end
return true
end},
{name="follow",f=function (unit_list)
local adv=df.global.world.units.active[0]
for k,v in pairs(unit_list) do
v.relations.group_leader_id=adv.id
end
return true
end},
{name="leave",f=function (unit_list)
local adv=df.global.world.units.active[0]
local t_nem=dfhack.units.getNemesis(adv)
for k,v in pairs(unit_list) do
v.relations.group_leader_id=-1
local u_nem=dfhack.units.getNemesis(v)
if u_nem then
u_nem.group_leader_id=-1
end
if t_nem and u_nem then
for k,v in pairs(t_nem.companions) do
if v==u_nem.id then
t_nem.companions:erase(k)
break
end
end
end
end
return true
end},
}
local cheats={
{name="Patch up",f=function (unit_list)
local dft=require("plugins.dfusion.tools")
for k,v in pairs(unit_list) do
dft.healunit(v)
end
return true
end},
{name="Power up",f=function (unit_list)
local dft=require("plugins.dfusion.tools")
for k,d in pairs(unit_list) do
dft.powerup(d)
end
return true
end},
{name="get in",f=function (unit_list,pos)
if not CheckCursor(pos) then
return false
end
adv=df.global.world.units.active[0]
item=getItemsAtPos(getxyz())[1]
print(item.id)
for k,v in pairs(unit_list) do
v.riding_item_id=item.id
local ref=df.general_ref_unit_riderst:new()
ref.unit_id=v.id
item.general_refs:insert("#",ref)
end
return true
end},
}
--[[ todo: add cheats...]]--
function getCompanions(unit)
unit=unit or df.global.world.units.active[0]
local t_nem=dfhack.units.getNemesis(unit)
if t_nem==nil then
qerror("Invalid unit! No nemesis record")
end
local ret={}
for k,v in pairs(t_nem.companions) do
local u=df.nemesis_record.find(v)
if u.unit then
table.insert(ret,u.unit)
end
end
return ret
end
CompanionUi=defclass(CompanionUi,gui.FramedScreen)
CompanionUi.ATTRS{
frame_title = "Companions",
}
function CompanionUi:init(args)
self.unit_list=args.unit_list
self.selected={}
for i=0,26 do
self.selected[i]=true
end
end
function CompanionUi:GetSelectedUnits()
local ret={}
for k,v in pairs(self.unit_list) do
if self.selected[k] then
table.insert(ret,v)
end
end
return ret
end
function CompanionUi:onInput(keys)
if keys.LEAVESCREEN then
self:dismiss()
elseif keys._STRING then
local s=keys._STRING
if s==string.byte('*') then
local v=self.selected[1] or false
for i=0,26 do
self.selected[i]=not v
end
end
if s>=string.byte('a') and s<=string.byte('z') then
local idx=s-string.byte('a')+1
if self.selected[idx] then
self.selected[idx]=false
else
self.selected[idx]=true
end
end
if s>=string.byte('A') and s<=string.byte('Z') then
local idx=s-string.byte('A')+1
if orders[idx] and orders[idx].f then
if orders[idx].f(self:GetSelectedUnits(),cursor) then
self:dismiss()
end
end
if is_cheat then
idx=idx-#orders
if cheats[idx] and cheats[idx].f then
if cheats[idx].f(self:GetSelectedUnits(),cursor) then
self:dismiss()
end
end
end
end
end
end
function CompanionUi:onRenderBody( dc)
--list widget goes here...
local char_a=string.byte('a')-1
dc:newline(1):string("*. All")
for k,v in ipairs(self.unit_list) do
if self.selected[k] then
dc:pen(COLOR_GREEN)
else
dc:pen(COLOR_GREY)
end
dc:newline(1):string(string.char(k+char_a)..". "):string(dfhack.TranslateName(v.name));
end
dc:pen(COLOR_GREY)
local w,h=self:getWindowSize()
local char_A=string.byte('A')-1
for k,v in ipairs(orders) do
dc:seek(w/2,k):string(string.char(k+char_A)..". "):string(v.name);
end
if is_cheat then
for k,v in ipairs(cheats) do
dc:seek(w/2,k+#orders):string(string.char(k+#orders+char_A)..". "):string(v.name);
end
end
end
local screen=CompanionUi{unit_list=getCompanions()}
screen:show()

@ -1,74 +0,0 @@
-- confirm plugin options
--[[=begin
gui/confirm-opts
================
A basic configuration interface for the `confirm` plugin.
=end]]
confirm = require 'plugins.confirm'
gui = require 'gui'
Opts = defclass(Opts, gui.FramedScreen)
Opts.ATTRS = {
frame_style = gui.GREY_LINE_FRAME,
frame_title = 'Confirmation dialogs',
frame_width = 32,
frame_height = 20,
frame_inset = 1,
focus_path = 'confirm/opts',
}
function Opts:init()
self:refresh()
self.cursor = 1
local active_id = confirm.get_active_id()
for i, c in pairs(self.data) do
if c.id == active_id then
self.cursor = i
break
end
end
end
function Opts:refresh()
self.data = confirm.get_conf_data()
self.frame_height = #self.data
end
function Opts:onRenderBody(p)
for i, c in pairs(self.data) do
local highlight = (i == self.cursor and 8 or 0)
p:pen(COLOR_GREY + highlight)
p:string(c.id .. ': ')
p:pen((c.enabled and COLOR_GREEN or COLOR_RED) + highlight)
p:string(c.enabled and 'Enabled' or 'Disabled')
p:newline()
end
end
function Opts:onInput(keys)
local conf = self.data[self.cursor]
if keys.LEAVESCREEN then
self:dismiss()
elseif keys.SELECT then
confirm.set_conf_state(conf.id, not conf.enabled)
self:refresh()
elseif keys.SEC_SELECT then
for _, c in pairs(self.data) do
confirm.set_conf_state(c.id, not conf.enabled)
end
self:refresh()
elseif keys.STANDARDSCROLL_UP or keys.STANDARDSCROLL_DOWN then
self.cursor = self.cursor + (keys.STANDARDSCROLL_UP and -1 or 1)
if self.cursor < 1 then
self.cursor = #self.data
elseif self.cursor > #self.data then
self.cursor = 1
end
end
end
Opts():show()

@ -1,260 +0,0 @@
-- create-item.lua
-- A gui-based item creation script.
-- author Putnam
-- edited by expwnent
--@module = true
--[[=begin
gui/create-item
===============
A graphical interface for creating items.
=end]]
local function getGenderString(gender)
local genderStr
if gender==0 then
genderStr=string.char(12)
elseif gender==1 then
genderStr=string.char(11)
else
return ""
end
return string.char(40)..genderStr..string.char(41)
end
local function getCreatureList()
local crList={}
for k,cr in ipairs(df.global.world.raws.creatures.alphabetic) do
for kk,ca in ipairs(cr.caste) do
local str=ca.caste_name[0]
str=str..' '..getGenderString(ca.gender)
table.insert(crList,{str,nil,ca})
end
end
return crList
end
local function getRestrictiveMatFilter(itemType)
if not args.restrictive then return nil end
local itemTypes={
WEAPON=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_WEAPON or mat.flags.ITEMS_WEAPON_RANGED)
end,
AMMO=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_AMMO)
end,
ARMOR=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_ARMOR)
end,
INSTRUMENT=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_HARD)
end,
AMULET=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_SOFT or mat.flags.ITEMS_HARD)
end,
ROCK=function(mat,parent,typ,idx)
return (mat.flags.IS_STONE)
end,
BOULDER=ROCK,
BAR=function(mat,parent,typ,idx)
return (mat.flags.IS_METAL or mat.flags.SOAP or mat.id==COAL)
end
}
for k,v in ipairs({'GOBLET','FLASK','TOY','RING','CROWN','SCEPTER','FIGURINE','TOOL'}) do
itemTypes[v]=itemTypes.INSTRUMENT
end
for k,v in ipairs({'SHOES','SHIELD','HELM','GLOVES'}) do
itemTypes[v]=itemTypes.ARMOR
end
for k,v in ipairs({'EARRING','BRACELET'}) do
itemTypes[v]=itemTypes.AMULET
end
itemTypes.BOULDER=itemTypes.ROCK
return itemTypes[df.item_type[itemType]]
end
local function getMatFilter(itemtype)
local itemTypes={
SEEDS=function(mat,parent,typ,idx)
return mat.flags.SEED_MAT
end,
PLANT=function(mat,parent,typ,idx)
return mat.flags.STRUCTURAL_PLANT_MAT
end,
LEAVES=function(mat,parent,typ,idx)
return mat.flags.LEAF_MAT
end,
MEAT=function(mat,parent,typ,idx)
return mat.flags.MEAT
end,
CHEESE=function(mat,parent,typ,idx)
return (mat.flags.CHEESE_PLANT or mat.flags.CHEESE_CREATURE)
end,
LIQUID_MISC=function(mat,parent,typ,idx)
return (mat.flags.LIQUID_MISC_PLANT or mat.flags.LIQUID_MISC_CREATURE or mat.flags.LIQUID_MISC_OTHER)
end,
POWDER_MISC=function(mat,parent,typ,idx)
return (mat.flags.POWDER_MISC_PLANT or mat.flags.POWDER_MISC_CREATURE)
end,
DRINK=function(mat,parent,typ,idx)
return (mat.flags.ALCOHOL_PLANT or mat.flags.ALCOHOL_CREATURE)
end,
GLOB=function(mat,parent,typ,idx)
return (mat.flags.STOCKPILE_GLOB)
end,
WOOD=function(mat,parent,typ,idx)
return (mat.flags.WOOD)
end,
THREAD=function(mat,parent,typ,idx)
return (mat.flags.THREAD_PLANT)
end,
LEATHER=function(mat,parent,typ,idx)
return (mat.flags.LEATHER)
end
}
return itemTypes[df.item_type[itemtype]] or getRestrictiveMatFilter(itemtype)
end
local function createItem(mat,itemType,quality,creator,description,amount)
local item=df.item.find(dfhack.items.createItem(itemType[1], itemType[2], mat[1], mat[2], creator))
assert(item, 'failed to create item')
quality = math.max(0, math.min(5, quality - 1))
item:setQuality(quality)
if df.item_type[itemType[1]]=='SLAB' then
item.description=description
end
if tonumber(amount) > 1 then
item:setStackSize(amount)
end
end
local function qualityTable()
return {{'None'},
{'-Well-crafted-'},
{'+Finely-crafted+'},
{'*Superior*'},
{string.char(240)..'Exceptional'..string.char(240)},
{string.char(15)..'Masterwork'..string.char(15)}
}
end
local script=require('gui.script')
local function showItemPrompt(text,item_filter,hide_none)
require('gui.materials').ItemTypeDialog{
prompt=text,
item_filter=item_filter,
hide_none=hide_none,
on_select=script.mkresume(true),
on_cancel=script.mkresume(false),
on_close=script.qresume(nil)
}:show()
return script.wait()
end
local function showMaterialPrompt(title, prompt, filter, inorganic, creature, plant) --the one included with DFHack doesn't have a filter or the inorganic, creature, plant things available
require('gui.materials').MaterialDialog{
frame_title = title,
prompt = prompt,
mat_filter = filter,
use_inorganic = inorganic,
use_creature = creature,
use_plant = plant,
on_select = script.mkresume(true),
on_cancel = script.mkresume(false),
on_close = script.qresume(nil)
}:show()
return script.wait()
end
local function usesCreature(itemtype)
typesThatUseCreatures={REMAINS=true,FISH=true,FISH_RAW=true,VERMIN=true,PET=true,EGG=true,CORPSE=true,CORPSEPIECE=true}
return typesThatUseCreatures[df.item_type[itemtype]]
end
local function getCreatureRaceAndCaste(caste)
return df.global.world.raws.creatures.list_creature[caste.index],df.global.world.raws.creatures.list_caste[caste.index]
end
function hackWish(unit)
script.start(function()
local amountok, amount
local matok,mattype,matindex,matFilter
local itemok,itemtype,itemsubtype=showItemPrompt('What item do you want?',function(itype) return df.item_type[itype]~='CORPSE' and df.item_type[itype]~='FOOD' end ,true)
if not itemok then return end
if not args.notRestrictive then
matFilter=getMatFilter(itemtype)
end
if not usesCreature(itemtype) then
matok,mattype,matindex=showMaterialPrompt('Wish','And what material should it be made of?',matFilter)
if not matok then return end
else
local creatureok,useless,creatureTable=script.showListPrompt('Wish','What creature should it be?',COLOR_LIGHTGREEN,getCreatureList())
if not creatureok then return end
mattype,matindex=getCreatureRaceAndCaste(creatureTable[3])
end
local qualityok,quality=script.showListPrompt('Wish','What quality should it be?',COLOR_LIGHTGREEN,qualityTable())
if not qualityok then return end
local description
if df.item_type[itemtype]=='SLAB' then
local descriptionok
descriptionok,description=script.showInputPrompt('Slab','What should the slab say?',COLOR_WHITE)
if not descriptionok then return end
end
if args.multi then
repeat amountok,amount=script.showInputPrompt('Wish','How many do you want? (numbers only!)',COLOR_LIGHTGREEN) until tonumber(amount) or not amountok
if not amountok then return end
if mattype and itemtype then
if df.item_type.attrs[itemtype].is_stackable then
createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description,amount)
else
for i=1,amount do
createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description,1)
end
end
return true
end
return false
else
if mattype and itemtype then
createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description,1)
return true
end
return false
end
end)
end
scriptArgs={...}
utils=require('utils')
validArgs = validArgs or utils.invert({
'startup',
'all',
'restrictive',
'unit',
'multi'
})
args = utils.processArgs({...}, validArgs)
eventful=require('plugins.eventful')
if not args.startup then
local unit=args.unit and df.unit.find(args.unit) or dfhack.gui.getSelectedUnit(true)
if unit then
hackWish(unit)
else
qerror('A unit needs to be selected to use gui/create-item.')
end
else
eventful.onReactionComplete.hackWishP=function(reaction,unit,input_items,input_reagents,output_items,call_native)
if not reaction.code:find('DFHACK_WISH') then return nil end
hackWish(unit)
end
end

@ -1,229 +0,0 @@
-- a quick access status screen
-- originally written by enjia2000@gmail.com (stolencatkarma)
--[[=begin
gui/dfstatus
============
Show a quick overview of critical stock quantities, including food, drinks, wood, and various bars.
Sections can be enabled/disabled/configured by editing ``dfhack-config/dfstatus.lua``.
=end]]
local gui = require 'gui'
function warn(msg)
dfhack.color(COLOR_LIGHTRED)
print(msg)
dfhack.color(nil)
end
config = {
flags = {
drink = true,
wood = true,
fuel = true,
prepared_meals = true,
tanned_hides = true,
cloth = true,
metals = true,
},
metal_ids = {},
}
function parse_config()
local metal_map = {}
for id, raw in pairs(df.global.world.raws.inorganics) do
if raw.material.flags.IS_METAL then
metal_map[raw.id:upper()] = id
metal_map[id] = raw.id:upper()
end
end
local function add_metal(...)
for _, m in pairs({...}) do
id = metal_map[tostring(m):upper()]
if id ~= nil then
table.insert(config.metal_ids, id)
elseif m == '-' then
table.insert(config.metal_ids, '-')
else
warn('Invalid metal: ' .. tostring(m))
end
end
return add_metal
end
local env = {}
setmetatable(env, {
__index = function(_, k)
if k == 'metal' or k == 'metals' then
return add_metal
elseif k == 'flags' then
return config.flags
else
error('unknown name: ' .. k, 2)
end
end,
__newindex = function(_, k, v)
if config.flags[k] ~= nil then
if v ~= nil then
config.flags[k] = v
else
config.flags[k] = false
end
else
error('unknown flag: ' .. k, 2)
end
end,
})
local f, err = loadfile('dfhack-config/dfstatus.lua', 't', env)
if not f then
qerror('error loading config: ' .. err)
end
local ok, err = pcall(f)
if not ok then
qerror('error parsing config: ' .. err)
end
end
function getInorganicName(id)
return (df.inorganic_raw.find(id).material.state_name.Solid:gsub('^[a-z]', string.upper))
end
dfstatus = defclass(dfstatus, gui.FramedScreen)
dfstatus.ATTRS = {
frame_style = gui.GREY_LINE_FRAME,
frame_title = 'dfstatus',
frame_width = 16,
frame_height = 17,
frame_inset = 1,
focus_path = 'dfstatus',
}
function dfstatus:init()
self.text = {}
self.start = 1
local function write(line)
table.insert(self.text, line)
-- ensure that the window is wide enough for this line plus a scroll arrow
if #line + 1 > self.frame_width then
self.frame_width = #line + 1
end
end
local function newline() write('') end
local f = config.flags
local drink = 0
local wood = 0
local fuel = 0
local prepared_meals = 0
local tanned_hides = 0
local cloth = 0
local metals = {}
for _, id in pairs(config.metal_ids) do
metals[id] = 0
end
for _, item in ipairs(df.global.world.items.all) do
if not item.flags.rotten and not item.flags.dump and not item.flags.forbid then
if item:getType() == df.item_type.WOOD then
wood = wood + item:getStackSize()
elseif item:getType() == df.item_type.DRINK then
drink = drink + item:getStackSize()
elseif item:getType() == df.item_type.SKIN_TANNED then
tanned_hides = tanned_hides + item:getStackSize()
elseif item:getType() == df.item_type.CLOTH then
cloth = cloth + item:getStackSize()
elseif item:getType() == df.item_type.FOOD then
prepared_meals = prepared_meals + item:getStackSize()
elseif item:getType() == df.item_type.BAR then
if item:getMaterial() == df.builtin_mats.COAL then
fuel = fuel + item:getStackSize()
elseif item:getMaterial() == df.builtin_mats.INORGANIC then
local mat_idx = item:getMaterialIndex()
if metals[mat_idx] ~= nil then
metals[mat_idx] = metals[mat_idx] + item:getStackSize()
end
end
end
end
end
if f.drink then
write("Drinks: " .. drink)
end
if f.prepared_meals then
write("Meals: " .. prepared_meals)
end
if f.drink or f.prepared_meals then
newline()
end
if f.wood then
write("Wood: " .. wood)
end
if f.fuel then
write("Fuel: " .. fuel)
end
if f.wood or f.fuel then
newline()
end
if f.tanned_hides then
write("Hides: " .. tanned_hides)
end
if f.cloth then
write("Cloth: " .. cloth)
end
if f.tanned_hides or f.cloth then
newline()
end
if f.metals then
write("Metal bars:")
for _, id in pairs(config.metal_ids) do
if id == '-' then
newline()
else
write(' ' .. ('%-10s'):format(getInorganicName(id) .. ': ') .. metals[id])
end
end
end
self.start_min = 1
self.start_max = #self.text - self.frame_height + 1
end
function dfstatus:onRenderBody(dc)
dc:pen(COLOR_LIGHTGREEN)
for id, line in pairs(self.text) do
if id >= self.start then
dc:string(line):newline()
end
end
dc:pen(COLOR_LIGHTCYAN)
if self.start > self.start_min then
dc:seek(self.frame_width - 1, 0):char(24)
end
if self.start < self.start_max then
dc:seek(self.frame_width - 1, self.frame_height - 1):char(25)
end
end
function dfstatus:onInput(keys)
if keys.LEAVESCREEN or keys.SELECT then
self:dismiss()
scr = nil
elseif keys.STANDARDSCROLL_UP then
self.start = math.max(self.start - 1, self.start_min)
elseif keys.STANDARDSCROLL_DOWN then
self.start = math.min(self.start + 1, self.start_max)
end
end
if not scr then
parse_config()
scr = dfstatus()
scr:show()
else
scr:dismiss()
scr = nil
end

@ -1,292 +0,0 @@
-- gui/family-affairs
-- derived from v1.2 @ http://www.bay12forums.com/smf/index.php?topic=147779
local help = [[=begin
gui/family-affairs
==================
A user-friendly interface to view romantic relationships,
with the ability to add, remove, or otherwise change them at
your whim - fantastic for depressed dwarves with a dead spouse
(or matchmaking players...).
The target/s must be alive, sane, and in fortress mode.
.. image:: /docs/images/family-affairs.png
:align: center
``gui/family-affairs [unitID]``
shows GUI for the selected unit, or the specified unit ID
``gui/family-affairs divorce [unitID]``
removes all spouse and lover information from the unit
and it's partner, bypassing almost all checks.
``gui/family-affairs [unitID] [unitID]``
divorces the two specificed units and their partners,
then arranges for the two units to marry, bypassing
almost all checks. Use with caution.
=end]]
helpstr = help:gsub('=begin', ''):gsub('=end', '')
local dlg = require ('gui.dialogs')
function ErrorPopup (msg,color)
if not tostring(msg) then msg = "Error" end
if not color then color = COLOR_LIGHTRED end
dlg.showMessage("Dwarven Family Affairs", msg, color, nil)
end
function AnnounceAndGamelog(text)
dfhack.gui.showAnnouncement(text, COLOR_LIGHTMAGENTA)
end
function ListPrompt (msg, choicelist, bool, yes_func)
dlg.showListPrompt(
"Dwarven Family Affairs",
msg,
COLOR_WHITE,
choicelist,
--called if choice is yes
yes_func,
--called on cancel
function() end,
15,
bool
)
end
function GetMarriageSummary (source)
local familystate = ""
if source.relations.spouse_id ~= -1 then
if dfhack.units.isSane(df.unit.find(source.relations.spouse_id)) then
familystate = dfhack.TranslateName(source.name).." has a spouse ("..dfhack.TranslateName(df.unit.find(source.relations.spouse_id).name)..")"
end
if dfhack.units.isSane(df.unit.find(source.relations.spouse_id)) == false then
familystate = dfhack.TranslateName(source.name).."'s spouse is dead or not sane, would you like to choose a new one?"
end
end
if source.relations.spouse_id == -1 and source.relations.lover_id ~= -1 then
if dfhack.units.isSane(df.unit.find(source.relations.lover_id)) then
familystate = dfhack.TranslateName(source.name).." already has a lover ("..dfhack.TranslateName(df.unit.find(source.relations.spouse_id).name)..")"
end
if dfhack.units.isSane(df.unit.find(source.relations.lover_id)) == false then
familystate = dfhack.TranslateName(source.name).."'s lover is dead or not sane, would you like that love forgotten?"
end
end
if source.relations.spouse_id == -1 and source.relations.lover_id == -1 then
familystate = dfhack.TranslateName(source.name).." is not involved in romantic relationships with anyone"
end
if source.relations.pregnancy_timer > 0 then
familystate = familystate.."\nShe is pregnant."
local father = df.historical_figure.find(source.relations.pregnancy_spouse)
if father then
familystate = familystate.." The father is "..dfhack.TranslateName(father.name).."."
end
end
return familystate
end
function GetSpouseData (source)
local spouse = df.unit.find(source.relations.spouse_id)
local spouse_hf
if spouse then
spouse_hf = df.historical_figure.find (spouse.hist_figure_id)
end
return spouse,spouse_hf
end
function GetLoverData (source)
local lover = df.unit.find(source.relations.spouse_id)
local lover_hf
if lover then
lover_hf = df.historical_figure.find (lover.hist_figure_id)
end
return lover,lover_hf
end
function EraseHFLinksLoverSpouse (hf)
for i = #hf.histfig_links-1,0,-1 do
if hf.histfig_links[i]._type == df.histfig_hf_link_spousest or hf.histfig_links[i]._type == df.histfig_hf_link_loverst then
local todelete = hf.histfig_links[i]
hf.histfig_links:erase(i)
todelete:delete()
end
end
end
function Divorce (source)
local source_hf = df.historical_figure.find(source.hist_figure_id)
local spouse,spouse_hf = GetSpouseData (source)
local lover,lover_hf = GetLoverData (source)
source.relations.spouse_id = -1
source.relations.lover_id = -1
if source_hf then
EraseHFLinksLoverSpouse (source_hf)
end
if spouse then
spouse.relations.spouse_id = -1
spouse.relations.lover_id = -1
end
if lover then
spouse.relations.spouse_id = -1
spouse.relations.lover_id = -1
end
if spouse_hf then
EraseHFLinksLoverSpouse (spouse_hf)
end
if lover_hf then
EraseHFLinksLoverSpouse (lover_hf)
end
local partner = spouse or lover
if not partner then
AnnounceAndGamelog(dfhack.TranslateName(source.name).." is now single")
else
AnnounceAndGamelog(dfhack.TranslateName(source.name).." and "..dfhack.TranslateName(partner.name).." are now single")
end
end
function Marriage (source,target)
local source_hf = df.historical_figure.find(source.hist_figure_id)
local target_hf = df.historical_figure.find(target.hist_figure_id)
source.relations.spouse_id = target.id
target.relations.spouse_id = source.id
local new_link = df.histfig_hf_link_spousest:new() -- adding hf link to source
new_link.target_hf = target_hf.id
new_link.link_strength = 100
source_hf.histfig_links:insert('#',new_link)
new_link = df.histfig_hf_link_spousest:new() -- adding hf link to target
new_link.target_hf = source_hf.id
new_link.link_strength = 100
target_hf.histfig_links:insert('#',new_link)
end
function ChooseNewSpouse (source)
if not source then
qerror("no unit") return
end
if not dfhack.units.isAdult(source) then
ErrorPopup("target is too young") return
end
if not (source.relations.spouse_id == -1 and source.relations.lover_id == -1) then
ErrorPopup("target already has a spouse or a lover")
qerror("source already has a spouse or a lover")
return
end
local choicelist = {}
targetlist = {}
for k,v in pairs (df.global.world.units.active) do
if dfhack.units.isCitizen(v)
and v.race == source.race
and v.sex ~= source.sex
and v.relations.spouse_id == -1
and v.relations.lover_id == -1
and dfhack.units.isAdult(v)
then
table.insert(choicelist,dfhack.TranslateName(v.name)..', '..dfhack.units.getProfessionName(v))
table.insert(targetlist,v)
end
end
if #choicelist > 0 then
ListPrompt(
"Assign new spouse for "..dfhack.TranslateName(source.name),
choicelist,
true,
function(a,b)
local target = targetlist[a]
Marriage (source,target)
AnnounceAndGamelog(dfhack.TranslateName(source.name).." and "..dfhack.TranslateName(target.name).." have married!")
end)
else
ErrorPopup("No suitable candidates")
end
end
function MainDialog (source)
local familystate = GetMarriageSummary(source)
familystate = familystate.."\nSelect action:"
local choicelist = {}
local on_select = {}
local adult = dfhack.units.isAdult(source)
local single = source.relations.spouse_id == -1 and source.relations.lover_id == -1
local ready_for_marriage = single and adult
if adult then
table.insert(choicelist,"Remove romantic relationships (if any)")
table.insert(on_select, Divorce)
if ready_for_marriage then
table.insert(choicelist,"Assign a new spouse")
table.insert(on_select,ChooseNewSpouse)
end
if not ready_for_marriage then
table.insert(choicelist,"[Assign a new spouse]")
table.insert(on_select,function () ErrorPopup ("Existing relationships must be removed if you wish to assign a new spouse.") end)
end
else
table.insert(choicelist,"Leave this child alone")
table.insert(on_select,nil)
end
ListPrompt(familystate, choicelist, false,
function(a,b) if on_select[a] then on_select[a](source) end end)
end
local args = {...}
if args[1] == "help" or args[1] == "?" then print(helpstr) return end
if not dfhack.world.isFortressMode() then
print (helpstr) qerror ("invalid game mode") return
end
if args[1] == "divorce" and tonumber(args[2]) then
local unit = df.unit.find(args[2])
if unit then Divorce (unit) return end
end
if tonumber(args[1]) and tonumber(args[2]) then
local unit1 = df.unit.find(args[1])
local unit2 = df.unit.find(args[2])
if unit1 and unit2 then
Divorce (unit1)
Divorce (unit2)
Marriage (unit1,unit2)
return
end
end
local selected = dfhack.gui.getSelectedUnit(true)
if tonumber(args[1]) then
selected = df.unit.find(tonumber(args[1])) or selected
end
if selected then
if dfhack.units.isCitizen(selected) and dfhack.units.isSane(selected) then
MainDialog(selected)
else
qerror("You must select a sane fortress citizen.")
return
end
else
print (helpstr)
qerror("Select a sane fortress dwarf")
end

@ -1,536 +0,0 @@
-- Interface powered item editor.
--[[=begin
gui/gm-editor
=============
This editor allows to change and modify almost anything in df. Press :kbd:`?` for
in-game help. There are three ways to open this editor:
* Callling ``gui/gm-editor`` from a command or keybinding opens the editor
on whatever is selected or viewed (e.g. unit/item description screen)
* using gui/gm-editor <lua command> - executes lua command and opens editor on
its results (e.g. ``gui/gm-editor "df.global.world.items.all"`` shows all items)
* using gui/gm-editor dialog - shows an in game dialog to input lua command. Works
the same as version above.
.. image:: /docs/images/gm-editor.png
=end]]
local gui = require 'gui'
local dialog = require 'gui.dialogs'
local widgets =require 'gui.widgets'
local guiScript = require 'gui.script'
local args={...}
find_funcs = find_funcs or (function()
local t = {}
for k in pairs(df) do
pcall(function()
t[k] = df[k].find
end)
end
return t
end)()
local keybindings={
offset={key="CUSTOM_ALT_O",desc="Show current items offset"},
find={key="CUSTOM_F",desc="Find a value by entering a predicate"},
find_id={key="CUSTOM_I",desc="Find object with this ID"},
lua_set={key="CUSTOM_ALT_S",desc="Set by using a lua function"},
insert={key="CUSTOM_ALT_I",desc="Insert a new value to the vector"},
delete={key="CUSTOM_ALT_D",desc="Delete selected entry"},
reinterpret={key="CUSTOM_ALT_R",desc="Open selected entry as something else"},
start_filter={key="CUSTOM_S",desc="Start typing filter, Enter to finish"},
help={key="HELP",desc="Show this help"},
displace={key="STRING_A093",desc="Open reference offseted by index"},
NOT_USED={key="SEC_SELECT",desc="Edit selected entry as a number (for enums)"}, --not a binding...
}
function getTargetFromScreens()
local my_trg
if dfhack.gui.getCurFocus() == 'item' then
my_trg=dfhack.gui.getCurViewscreen().item
elseif dfhack.gui.getCurFocus() == 'joblist' then
local t_screen=dfhack.gui.getCurViewscreen()
my_trg=t_screen.jobs[t_screen.cursor_pos]
elseif dfhack.gui.getCurFocus() == 'createquota' then
local t_screen=dfhack.gui.getCurViewscreen()
my_trg=t_screen.orders[t_screen.sel_idx]
elseif dfhack.gui.getCurFocus() == 'dwarfmode/LookAround/Flow' then
local t_look=df.global.ui_look_list.items[df.global.ui_look_cursor]
my_trg=t_look.flow
elseif dfhack.gui.getSelectedUnit(true) then
my_trg=dfhack.gui.getSelectedUnit(true)
elseif dfhack.gui.getSelectedItem(true) then
my_trg=dfhack.gui.getSelectedItem(true)
elseif dfhack.gui.getSelectedJob(true) then
my_trg=dfhack.gui.getSelectedJob(true)
else
qerror("No valid target found")
end
return my_trg
end
function search_relevance(search, candidate)
local function clean(str)
return ' ' .. str:lower():gsub('[^a-z0-9]','') .. ' '
end
search = clean(search)
candidate = clean(candidate)
local ret = 0
while #search > 0 do
local pos = candidate:find(search:sub(1, 1), 1, true)
if pos then
ret = ret + (#search - pos)
candidate = candidate:sub(pos + 1)
end
search = search:sub(2)
end
return ret
end
GmEditorUi = defclass(GmEditorUi, gui.FramedScreen)
GmEditorUi.ATTRS={
frame_style = gui.GREY_LINE_FRAME,
frame_title = "GameMaster's editor",
}
function GmEditorUi:onHelp()
self.subviews.pages:setSelected(2)
end
function burning_red(input) -- todo does not work! bug angavrilov that so that he would add this, very important!!
local col=COLOR_LIGHTRED
return {text=input,pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}
end
function Disclaimer(tlb)
local dsc={"Association Of ",{text="Psychic ",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}},
"Dwarves (AOPD) is not responsible for all the damage",NEWLINE,"that this tool can (and will) cause to you and your loved dwarves",NEWLINE,"and/or saves.Please use with caution.",NEWLINE,{text="Magma not included.",pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}}
if tlb then
for _,v in ipairs(dsc) do
table.insert(tlb,v)
end
end
return dsc
end
function GmEditorUi:init(args)
self.stack={}
self.item_count=0
self.keys={}
local helptext={{text="Help"},NEWLINE,NEWLINE}
for k,v in pairs(keybindings) do
table.insert(helptext,{text=v.desc,key=v.key,key_sep=': '})
table.insert(helptext,NEWLINE)
end
table.insert(helptext,NEWLINE)
Disclaimer(helptext)
local helpPage=widgets.Panel{
subviews={widgets.Label{text=helptext,frame = {l=1,t=1,yalign=0}}}}
local mainList=widgets.List{view_id="list_main",choices={},frame = {l=1,t=3,yalign=0},on_submit=self:callback("editSelected"),
on_submit2=self:callback("editSelectedRaw"),
text_pen=dfhack.pen.parse{fg=COLOR_DARKGRAY,bg=0},cursor_pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}
local mainPage=widgets.Panel{
subviews={
mainList,
widgets.Label{text={{text="<no item>",id="name"},{gap=1,text="Help",key=keybindings.help.key,key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}},
widgets.Label{text={{text="Search",key=keybindings.start_filter.key,key_sep = '()'},{text=": "}},frame={l=1,t=2},
on_click=function() self:enable_input(true) end},
widgets.EditField{frame={l=12,t=2},active=false,on_change=self:callback('text_input'),on_submit=self:callback("enable_input",false),view_id="filter_input"},
--widgets.Label{text="BLAH2"}
}
,view_id='page_main'}
local pages=widgets.Pages{subviews={mainPage,helpPage},view_id="pages"}
self:addviews{
pages
}
self:pushTarget(args.target)
end
function GmEditorUi:text_input(new_text)
self:updateTarget(true,true)
end
function GmEditorUi:enable_input(enable)
self.subviews.filter_input.active=enable
end
function GmEditorUi:find(test)
local trg=self:currentTarget()
if test== nil then
dialog.showInputPrompt("Test function","Input function that tests(k,v as argument):",COLOR_WHITE,"",dfhack.curry(self.find,self))
return
end
local e,what=load("return function(k,v) return "..test.." end")
if e==nil then
dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_LIGHTRED)
end
if trg.target and trg.target._kind and trg.target._kind=="container" then
for k,v in pairs(trg.target) do
if e()(k,v)==true then
self:pushTarget(v)
return
end
end
else
local i=1
for k,v in pairs(trg.target) do
if e()(k,v)==true then
self.subviews.list_main:setSelected(i)
return
end
i=i+1
end
end
end
function GmEditorUi:find_id()
local key = tostring(self:getSelectedKey())
local id = tonumber(self:getSelectedValue())
if not id then return end
local opts = {}
for name, func in pairs(find_funcs) do
table.insert(opts, {text=name, callback=func, weight=search_relevance(key, name)})
end
table.sort(opts, function(a, b)
return a.weight > b.weight
end)
guiScript.start(function()
local ret,idx,choice=guiScript.showListPrompt("Choose type:",nil,3,opts,nil,true)
if ret then
local obj = choice.callback(id)
if obj then
self:pushTarget(obj)
else
dialog.showMessage("Error!", ('%s with ID %d not found'):format(choice.text, id), COLOR_LIGHTRED)
end
end
end)
end
function GmEditorUi:insertNew(typename)
local tp=typename
if typename == nil then
dialog.showInputPrompt("Class type","You can:\n * Enter type name (without 'df.')\n * Leave empty for default type and 'nil' value\n * Enter '*' for default type and 'new' constructed pointer value",COLOR_WHITE,"",self:callback("insertNew"))
return
end
local trg=self:currentTarget()
if trg.target and trg.target._kind and trg.target._kind=="container" then
if tp == "" then
trg.target:resize(#trg.target+1)
elseif tp== "*" then
trg.target:insert("#",{new=true})
else
local ntype=df[tp]
if ntype== nil then
dialog.showMessage("Error!","Type '"..tp.." not found",COLOR_RED)
return
end
trg.target:insert("#",{new=ntype})
end
self:updateTarget(true,true)
end
end
function GmEditorUi:deleteSelected(key)
local trg=self:currentTarget()
if trg.target and trg.target._kind and trg.target._kind=="container" then
trg.target:erase(key)
self:updateTarget(true,true)
end
end
function GmEditorUi:getSelectedKey()
return self:currentTarget().keys[self.subviews.list_main:getSelected()]
end
function GmEditorUi:getSelectedValue()
return self:currentTarget().target[self:getSelectedKey()]
end
function GmEditorUi:currentTarget()
return self.stack[#self.stack]
end
function GmEditorUi:getSelectedEnumType()
local trg=self:currentTarget()
local trg_key=trg.keys[self.subviews.list_main:getSelected()]
local ok,ret=pcall(function () --super safe way to check if the field has enum
return trg.target._field==nil or trg.target:_field(trg_key)==nil
end)
if not ok or ret==true then
return nil
end
local enum=trg.target:_field(trg_key)._type
if enum._kind=="enum-type" then
return enum
else
return nil
end
end
function GmEditorUi:editSelectedEnum(index,choice)
local enum=self:getSelectedEnumType()
if enum then
local trg=self:currentTarget()
local trg_key=self:getSelectedKey()
local list={}
for i=enum._first_item, enum._last_item do
table.insert(list,{text=('%s (%i)'):format(tostring(enum[i]), i),value=i})
end
guiScript.start(function()
local ret,idx,choice=guiScript.showListPrompt("Choose item:",nil,3,list,nil,true)
if ret then
trg.target[trg_key]=choice.value
self:updateTarget(true)
end
end)
else
qerror("not an enum")
end
end
function GmEditorUi:openReinterpret(key)
local trg=self:currentTarget()
dialog.showInputPrompt(tostring(trg_key),"Enter new type:",COLOR_WHITE,
"",function(choice)
local ntype=df[tp]
self:pushTarget(df.reinterpret_cast(ntype,trg.target[key]))
end)
end
function GmEditorUi:openOffseted(index,choice)
local trg=self:currentTarget()
local trg_key=trg.keys[index]
dialog.showInputPrompt(tostring(trg_key),"Enter offset:",COLOR_WHITE,"",
function(choice)
self:pushTarget(trg.target[trg_key]:_displace(tonumber(choice)))
end)
end
function GmEditorUi:editSelectedRaw(index,choice)
self:editSelected(index, choice, {raw=true})
end
function GmEditorUi:editSelected(index,choice,opts)
opts = opts or {}
local trg=self:currentTarget()
local trg_key=trg.keys[index]
if trg.target and trg.target._kind and trg.target._kind=="bitfield" then
trg.target[trg_key]= not trg.target[trg_key]
self:updateTarget(true)
else
--print(type(trg.target[trg.keys[trg.selected]]),trg.target[trg.keys[trg.selected]]._kind or "")
local trg_type=type(trg.target[trg_key])
if self:getSelectedEnumType() and not opts.raw then
self:editSelectedEnum()
elseif trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected
dialog.showInputPrompt(tostring(trg_key),"Enter new value:",COLOR_WHITE,
tostring(trg.target[trg_key]),self:callback("commitEdit",trg_key))
elseif trg_type == 'boolean' then
trg.target[trg_key] = not trg.target[trg_key]
self:updateTarget(true)
elseif trg_type == 'userdata' or trg_type == 'table' then
self:pushTarget(trg.target[trg_key])
elseif trg_type == 'nil' or trg_type == 'function' then
-- ignore
else
print("Unknown type:"..trg_type)
pcall(function() print("Subtype:"..tostring(trg.target[trg_key]._kind)) end)
end
end
end
function GmEditorUi:commitEdit(key,value)
local trg=self:currentTarget()
if type(trg.target[key])=='number' then
trg.target[key]=tonumber(value)
elseif type(trg.target[key])=='string' then
trg.target[key]=value
end
self:updateTarget(true)
end
function GmEditorUi:set(key,input)
local trg=self:currentTarget()
if input== nil then
dialog.showInputPrompt("Set to what?","Lua code to set to (v cur target):",COLOR_WHITE,"",self:callback("set",key))
return
end
local e,what=load("return function(v) return "..input.." end")
if e==nil then
dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_LIGHTRED)
return
end
trg.target[key]=e()(trg)
self:updateTarget(true)
end
function GmEditorUi:onInput(keys)
if keys.LEAVESCREEN then
if self.subviews.filter_input.active then
self:enable_input(false)
return
end
if self.subviews.pages:getSelected()==2 then
self.subviews.pages:setSelected(1)
else
self:popTarget()
end
end
if self.subviews.filter_input.active then
self.super.onInput(self,keys)
return
end
if keys[keybindings.offset.key] then
local trg=self:currentTarget()
local _,stoff=df.sizeof(trg.target)
local size,off=df.sizeof(trg.target:_field(self:getSelectedKey()))
dialog.showMessage("Offset",string.format("Size hex=%x,%x dec=%d,%d\nRelative hex=%x dec=%d",size,off,size,off,off-stoff,off-stoff),COLOR_WHITE)
elseif keys[keybindings.displace.key] then
self:openOffseted(self.subviews.list_main:getSelected())
elseif keys[keybindings.find.key] then
self:find()
elseif keys[keybindings.find_id.key] then
self:find_id()
elseif keys[keybindings.lua_set.key] then
self:set(self:getSelectedKey())
elseif keys[keybindings.insert.key] then --insert
self:insertNew()
elseif keys[keybindings.delete.key] then --delete
self:deleteSelected(self:getSelectedKey())
elseif keys[keybindings.reinterpret.key] then
self:openReinterpret(self:getSelectedKey())
elseif keys[keybindings.start_filter.key] then
self:enable_input(true)
return
end
self.super.onInput(self,keys)
end
function getStringValue(trg,field)
local obj=trg.target
local text=tostring(obj[field])
pcall(function()
if obj._field ~= nil then
local enum=obj:_field(field)._type
if enum._kind=="enum-type" then
text=text.." ("..tostring(enum[obj[field]])..")"
end
end
end)
return text
end
function GmEditorUi:updateTarget(preserve_pos,reindex)
local trg=self:currentTarget()
local filter=self.subviews.filter_input.text
if reindex then
trg.keys={}
for k,v in pairs(trg.target) do
if filter~= "" then
local ok,ret=dfhack.pcall(string.match,tostring(k),filter)
if not ok then
table.insert(trg.keys,k)
elseif ret then
table.insert(trg.keys,k)
end
else
table.insert(trg.keys,k)
end
end
end
self.subviews.lbl_current_item:itemById('name').text=tostring(trg.target)
local t={}
for k,v in pairs(trg.keys) do
table.insert(t,{text={{text=string.format("%-25s",tostring(v))},{gap=1,text=getStringValue(trg,v)}}})
end
local last_pos
if preserve_pos then
last_pos=self.subviews.list_main:getSelected()
end
self.subviews.list_main:setChoices(t)
if last_pos then
self.subviews.list_main:setSelected(last_pos)
else
self.subviews.list_main:setSelected(trg.selected)
end
end
function GmEditorUi:pushTarget(target_to_push)
local new_tbl={}
new_tbl.target=target_to_push
new_tbl.keys={}
new_tbl.selected=1
new_tbl.filter=""
if self:currentTarget()~=nil then
self:currentTarget().selected=self.subviews.list_main:getSelected()
self.stack[#self.stack].filter=self.subviews.filter_input.text
end
for k,v in pairs(target_to_push) do
table.insert(new_tbl.keys,k)
end
new_tbl.item_count=#new_tbl.keys
table.insert(self.stack,new_tbl)
self.subviews.filter_input.text=""
self:updateTarget()
end
function GmEditorUi:popTarget()
table.remove(self.stack) --removes last element
if #self.stack==0 then
self:dismiss()
return
end
self.subviews.filter_input.text=self.stack[#self.stack].filter --restore filter
self:updateTarget()
end
function show_editor(trg)
if not trg then
qerror('Target not found')
end
local screen = GmEditorUi{target=trg}
screen:show()
end
eval_env = {}
setmetatable(eval_env, {__index = function(_, k)
if k == 'scr' or k == 'screen' then
return dfhack.gui.getCurViewscreen()
elseif k == 'bld' or k == 'building' then
return dfhack.gui.getSelectedBuilding()
elseif k == 'item' then
return dfhack.gui.getSelectedItem()
elseif k == 'job' then
return dfhack.gui.getSelectedJob()
elseif k == 'wsjob' or k == 'workshop_job' then
return dfhack.gui.getSelectedWorkshopJob()
elseif k == 'unit' then
return dfhack.gui.getSelectedUnit()
else
for g in pairs(df.global) do
if g == k then
return df.global[k]
end
end
return _G[k]
end
end})
function eval(s)
local f, err = load("return " .. s, "expression", "t", eval_env)
if err then qerror(err) end
return f()
end
if #args~=0 then
if args[1]=="dialog" then
function thunk(entry)
show_editor(eval(entry))
end
dialog.showInputPrompt("Gm Editor", "Object to edit:", COLOR_GRAY, "",thunk)
elseif args[1]=="free" then
show_editor(df.reinterpret_cast(df[args[2]],args[3]))
else
show_editor(eval(args[1]))
end
else
show_editor(getTargetFromScreens())
end

@ -1,604 +0,0 @@
-- Interface powered, user friendly, unit editor
--[[=begin
gui/gm-unit
===========
An editor for various unit attributes.
=end]]
local gui = require 'gui'
local dialog = require 'gui.dialogs'
local widgets =require 'gui.widgets'
local guiScript = require 'gui.script'
local utils = require 'utils'
local args={...}
local target
--TODO: add more ways to guess what unit you want to edit
if args[1]~= nil then
target=df.units.find(args[1])
else
target=dfhack.gui.getSelectedUnit(true)
end
if target==nil then
qerror("No unit to edit") --TODO: better error message
end
local editors={}
function add_editor(editor_class)
table.insert(editors,{text=editor_class.ATTRS.frame_title,on_submit=function ( unit )
editor_class{target_unit=unit}:show()
end})
end
-------------------------------various subeditors---------
--TODO set local sould or better yet skills vector to reduce long skill list access typing
editor_skills=defclass(editor_skills,gui.FramedScreen)
editor_skills.ATTRS={
frame_style = gui.GREY_LINE_FRAME,
frame_title = "Skill editor",
target_unit = DEFAULT_NIL,
learned_only= false,
}
function list_skills(unit,learned_only)
local s_=df.job_skill
local u_skills=unit.status.current_soul.skills
local ret={}
for i,v in ipairs(s_) do
if i>0 then
local u_skill=utils.binsearch(u_skills,i,"id")
if u_skill or not learned_only then
if not u_skill then
u_skill={rating=-1,experience=0}
end
local rating
if u_skill.rating >=0 then
rating=df.skill_rating.attrs[u_skill.rating]
else
rating={caption="<unlearned>",xp_threshold=0}
end
local text=string.format("%s: %s %d %d/%d",df.job_skill.attrs[i].caption,rating.caption,u_skill.rating,u_skill.experience,rating.xp_threshold)
table.insert(ret,{text=text,id=i})
end
end
end
return ret
end
function editor_skills:update_list(no_save_place)
local skill_list=list_skills(self.target_unit,self.learned_only)
if no_save_place then
self.subviews.skills:setChoices(skill_list)
else
self.subviews.skills:setChoices(skill_list,self.subviews.skills:getSelected())
end
end
function editor_skills:init( args )
if self.target_unit.status.current_soul==nil then
qerror("Unit does not have soul, can't edit skills")
end
local skill_list=list_skills(self.target_unit,self.learned_only)
self:addviews{
widgets.FilteredList{
choices=skill_list,
frame = {t=0, b=1,l=1},
view_id="skills",
},
widgets.Label{
frame = { b=0,l=1},
text ={{text= ": exit editor ",
key = "LEAVESCREEN",
on_activate= self:callback("dismiss")
},
{text=": remove level ",
key = "SECONDSCROLL_UP",
on_activate=self:callback("level_skill",-1)},
{text=": add level ",
key = "SECONDSCROLL_DOWN",
on_activate=self:callback("level_skill",1)}
,
{text=": show learned only ",
key = "CHANGETAB",
on_activate=function ()
self.learned_only=not self.learned_only
self:update_list(true)
end}
}
},
}
end
function editor_skills:get_cur_skill()
local list_wid=self.subviews.skills
local _,choice=list_wid:getSelected()
if choice==nil then
qerror("Nothing selected")
end
local u_skill=utils.binsearch(self.target_unit.status.current_soul.skills,choice.id,"id")
return choice,u_skill
end
function editor_skills:level_skill(lvl)
local sk_en,sk=self:get_cur_skill()
if lvl >0 then
local rating
if sk then
rating=sk.rating+lvl
else
rating=lvl-1
end
utils.insert_or_update(self.target_unit.status.current_soul.skills, {new=true, id=sk_en.id, rating=rating}, 'id') --TODO set exp?
elseif sk and sk.rating==0 and lvl<0 then
utils.erase_sorted_key(self.target_unit.status.current_soul.skills,sk_en.id,"id")
elseif sk and lvl<0 then
utils.insert_or_update(self.target_unit.status.current_soul.skills, {new=true, id=sk_en.id, rating=sk.rating+lvl}, 'id') --TODO set exp?
end
self:update_list()
end
function editor_skills:remove_rust(skill)
--TODO
end
add_editor(editor_skills)
------- civ editor
RaceBox = defclass(RaceBox, dialog.ListBox)
RaceBox.focus_path = 'RaceBox'
RaceBox.ATTRS{
format_name="$NAME ($TOKEN)",
with_filter=true,
allow_none=false,
}
function RaceBox:format_creature(creature_raw)
local t = {NAME=creature_raw.name[0],TOKEN=creature_raw.creature_id}
return string.gsub(self.format_name, "%$(%w+)", t)
end
function RaceBox:preinit(info)
self.format_name=RaceBox.ATTRS.format_name or info.format_name -- preinit does not have ATTRS set yet
local choices={}
if RaceBox.ATTRS.allow_none or info.allow_none then
table.insert(choices,{text="<none>",num=-1})
end
for i,v in ipairs(df.global.world.raws.creatures.all) do
local text=self:format_creature(v)
table.insert(choices,{text=text,raw=v,num=i,search_key=text:lower()})
end
info.choices=choices
end
function showRacePrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_none)
RaceBox{
frame_title = title,
text = text,
text_pen = tcolor,
on_select = on_select,
on_cancel = on_cancel,
frame_width = min_width,
allow_none = allow_none,
}:show()
end
CivBox = defclass(CivBox,dialog.ListBox)
CivBox.focus_path = "CivBox"
CivBox.ATTRS={
format_name="$NAME ($ENGLISH):$ID",
format_no_name="<unnamed>:$ID",
name_other="<other(-1)>",
with_filter=true,
allow_other=false,
}
function civ_name(id,format_name,format_no_name,name_other,name_invalid)
if id==-1 then
return name_other or "<other (-1)>"
end
local civ
if type(id)=='userdata' then
civ=id
else
civ=df.historical_entity.find(id)
if civ==nil then
return name_invalid or "<invalid>"
end
end
local t={NAME=dfhack.TranslateName(civ.name),ENGLISH=dfhack.TranslateName(civ.name,true),ID=civ.id} --TODO race?, maybe something from raws?
if t.NAME=="" then
return string.gsub(format_no_name or "<unnamed>:$ID", "%$(%w+)", t)
end
return string.gsub(format_name or "$NAME ($ENGLISH):$ID", "%$(%w+)", t)
end
function CivBox:update_choices()
local choices={}
if self.allow_other then
table.insert(choices,{text=self.name_other,num=-1})
end
for i,v in ipairs(df.global.world.entities.all) do
if not self.race_filter or (v.race==self.race_filter) then --TODO filter type
local text=civ_name(v,self.format_name,self.format_no_name,self.name_other,self.name_invalid)
table.insert(choices,{text=text,raw=v,num=i})
end
end
self.choices=choices
if self.subviews.list then
self.subviews.list:setChoices(self.choices)
end
end
function CivBox:update_race_filter(id)
local raw=df.creature_raw.find(id)
if raw then
self.subviews.race_label:setText(": "..raw.name[0])
self.race_filter=id
else
self.subviews.race_label:setText(": <none>")
self.race_filter=nil
end
self:update_choices()
end
function CivBox:choose_race()
showRacePrompt("Choose race","Select new race:",nil,function (id,choice)
self:update_race_filter(choice.num)
end,nil,nil,true)
end
function CivBox:init(info)
self.subviews.list.frame={t=3,r=0,l=0}
self:addviews{
widgets.Label{frame={t=1,l=0},text={
{text="Filter race ",key="CUSTOM_CTRL_A",key_sep="()",on_activate=self:callback("choose_race")},
}},
widgets.Label{frame={t=1,l=21},view_id="race_label",
text=": <none>",
}
}
self:update_choices()
end
function showCivPrompt(title, text, tcolor, on_select, on_cancel, min_width,allow_other)
CivBox{
frame_title = title,
text = text,
text_pen = tcolor,
on_select = on_select,
on_cancel = on_cancel,
frame_width = min_width,
allow_other = allow_other,
}:show()
end
editor_civ=defclass(editor_civ,gui.FramedScreen)
editor_civ.ATTRS={
frame_style = gui.GREY_LINE_FRAME,
frame_title = "Civilization editor",
target_unit = DEFAULT_NIL,
}
function editor_civ:update_curren_civ()
self.subviews.civ_name:setText("Currently: "..civ_name(self.target_unit.civ_id))
end
function editor_civ:init( args )
if self.target_unit==nil then
qerror("invalid unit")
end
self:addviews{
widgets.Label{view_id="civ_name",frame = { t=1,l=1}, text="Currently: "..civ_name(self.target_unit.civ_id)},
widgets.Label{frame = { t=2,l=1}, text={{text=": set to other (-1, usually enemy)",key="CUSTOM_N",
on_activate= function() self.target_unit.civ_id=-1;self:update_curren_civ() end}}},
widgets.Label{frame = { t=3,l=1}, text={{text=": set to current civ("..df.global.ui.civ_id..")",key="CUSTOM_C",
on_activate= function() self.target_unit.civ_id=df.global.ui.civ_id;self:update_curren_civ() end}}},
widgets.Label{frame = { t=4,l=1}, text={{text=": manually enter",key="CUSTOM_E",
on_activate=function ()
dialog.showInputPrompt("Civ id","Enter new civ id:",COLOR_WHITE,
tostring(self.target_unit.civ_id),function(new_value)
self.target_unit.civ_id=new_value
self:update_curren_civ()
end)
end}}
},
widgets.Label{frame= {t=5,l=1}, text={{text=": select from list",key="CUSTOM_L",
on_activate=function ( )
showCivPrompt("Choose civilization", "Select units civilization",nil,function ( id,choice )
self.target_unit.civ_id=choice.num
self:update_curren_civ()
end,nil,nil,true)
end
}}},
widgets.Label{
frame = { b=0,l=1},
text ={{text= ": exit editor ",
key = "LEAVESCREEN",
on_activate= self:callback("dismiss")
},
}
},
}
end
add_editor(editor_civ)
------- counters editor
editor_counters=defclass(editor_counters,gui.FramedScreen)
editor_counters.ATTRS={
frame_style = gui.GREY_LINE_FRAME,
frame_title = "Counters editor",
target_unit = DEFAULT_NIL,
counters1={
"think_counter",
"job_counter",
"swap_counter",
"winded",
"stunned",
"unconscious",
"suffocation",
"webbed",
"soldier_mood_countdown",
"soldier_mood", --todo enum,
"pain",
"nausea",
"dizziness",
},
counters2={
"paralysis",
"numbness",
"fever",
"exhaustion",
"hunger_timer",
"thirst_timer",
"sleepiness_timer",
"stomach_content",
"stomach_food",
"vomit_timeout",
"stored_fat" --TODO what to reset to?
}
}
function editor_counters:fill_counters()
local ret={}
local u=self.target_unit
for i,v in ipairs(self.counters1) do
table.insert(ret,{f=u.counters:_field(v),name=v})
end
for i,v in ipairs(self.counters2) do
table.insert(ret,{f=u.counters2:_field(v),name=v})
end
return ret
end
function editor_counters:update_counters()
for i,v in ipairs(self.counter_list) do
v.text=string.format("%s:%d",v.name,v.f.value)
end
self.subviews.counters:setChoices(self.counter_list)
end
function editor_counters:set_cur_counter(value,index,choice)
choice.f.value=value
self:update_counters()
end
function editor_counters:choose_cur_counter(index,choice)
dialog.showInputPrompt(choice.name,"Enter new value:",COLOR_WHITE,
tostring(choice.f.value),function(new_value)
self:set_cur_counter(new_value,index,choice)
end)
end
function editor_counters:init( args )
if self.target_unit==nil then
qerror("invalid unit")
end
self.counter_list=self:fill_counters()
self:addviews{
widgets.FilteredList{
choices=self.counter_list,
frame = {t=0, b=1,l=1},
view_id="counters",
on_submit=self:callback("choose_cur_counter"),
on_submit2=self:callback("set_cur_counter",0),--TODO some things need to be set to different defaults
},
widgets.Label{
frame = { b=0,l=1},
text ={{text= ": exit editor ",
key = "LEAVESCREEN",
on_activate= self:callback("dismiss")
},
{text=": reset counter ",
key = "SEC_SELECT",
},
{text=": set counter ",
key = "SELECT",
}
}
},
}
self:update_counters()
end
add_editor(editor_counters)
wound_creator=defclass(wound_creator,gui.FramedScreen)
wound_creator.ATTRS={
frame_style = gui.GREY_LINE_FRAME,
frame_title = "Wound creator",
target_wound = DEFAULT_NIL,
--filter
}
function wound_creator:init( args )
if self.target_wound==nil then
qerror("invalid wound")
end
self:addviews{
widgets.List{
frame = {t=0, b=1,l=1},
view_id="fields",
on_submit=self:callback("edit_cur_wound"),
on_submit2=self:callback("delete_current_wound")
},
widgets.Label{
frame = { b=0,l=1},
text ={{text= ": exit editor ",
key = "LEAVESCREEN",
on_activate= self:callback("dismiss")},
{text=": edit wound ",
key = "SELECT"},
{text=": delete wound ",
key = "SEC_SELECT"},
{text=": create wound ",
key = "CUSTOM_CTRL_I",
on_activate= self:callback("create_new_wound")},
}
},
}
self:update_wounds()
end
-------------------
editor_wounds=defclass(editor_wounds,gui.FramedScreen)
editor_wounds.ATTRS={
frame_style = gui.GREY_LINE_FRAME,
frame_title = "Wound editor",
target_unit = DEFAULT_NIL,
--filter
}
function is_scar( wound_part )
return wound_part.flags1.scar_cut or wound_part.flags1.scar_smashed or
wound_part.flags1.scar_edged_shake1 or wound_part.flags1.scar_blunt_shake1
end
function format_flag_name( fname )
return fname:sub(1,1):upper()..fname:sub(2):gsub("_"," ")
end
function name_from_flags( wp )
for i,v in ipairs(wp.flags1) do
if v then
return format_flag_name(df.wound_damage_flags1[i])
end
end
for i,v in ipairs(wp.flags2) do
if v then
return format_flag_name(df.wound_damage_flags2[i])
end
end
return "<unnamed wound>"
end
function format_wound( list_id,wound, unit)
local name="<unnamed wound>"
if #wound.parts>0 and #wound.parts[0].effect_type>0 then --try to make wound name by effect...
name=tostring(df.wound_effect_type[wound.parts[0].effect_type[0]])
if #wound.parts>1 then --cheap and probably incorrect...
name=name.."s"
end
elseif #wound.parts>0 and is_scar(wound.parts[0]) then
name="Scar"
elseif #wound.parts>0 then
local wp=wound.parts[0]
name=name_from_flags(wp)
end
return string.format("%d. %s id=%d",list_id,name,wound.id)
end
function editor_wounds:update_wounds()
local ret={}
for i,v in ipairs(self.trg_wounds) do
table.insert(ret,{text=format_wound(i,v,self.target_unit),wound=v})
end
self.subviews.wounds:setChoices(ret)
self.wound_list=ret
end
function editor_wounds:dirty_unit()
print("todo: implement unit status recalculation")
end
function editor_wounds:get_cur_wound()
local list_wid=self.subviews.wounds
local _,choice=list_wid:getSelected()
if choice==nil then
qerror("Nothing selected")
end
local ret_wound=utils.binsearch(self.trg_wounds,choice.id,"id")
return choice,ret_wound
end
function editor_wounds:delete_current_wound(index,choice)
utils.erase_sorted(self.trg_wounds,choice.wound,"id")
choice.wound:delete()
self:dirty_unit()
self:update_wounds()
end
function editor_wounds:create_new_wound()
print("Creating")
end
function editor_wounds:edit_cur_wound(index,choice)
end
function editor_wounds:init( args )
if self.target_unit==nil then
qerror("invalid unit")
end
self.trg_wounds=self.target_unit.body.wounds
self:addviews{
widgets.List{
frame = {t=0, b=1,l=1},
view_id="wounds",
on_submit=self:callback("edit_cur_wound"),
on_submit2=self:callback("delete_current_wound")
},
widgets.Label{
frame = { b=0,l=1},
text ={{text= ": exit editor ",
key = "LEAVESCREEN",
on_activate= self:callback("dismiss")},
{text=": edit wound ",
key = "SELECT"},
{text=": delete wound ",
key = "SEC_SELECT"},
{text=": create wound ",
key = "CUSTOM_CTRL_I",
on_activate= self:callback("create_new_wound")},
}
},
}
self:update_wounds()
end
add_editor(editor_wounds)
-------------------------------main window----------------
unit_editor = defclass(unit_editor, gui.FramedScreen)
unit_editor.ATTRS={
frame_style = gui.GREY_LINE_FRAME,
frame_title = "GameMaster's unit editor",
target_unit = DEFAULT_NIL,
}
function unit_editor:init(args)
self:addviews{
widgets.FilteredList{
choices=editors,
on_submit=function (idx,choice)
if choice.on_submit then
choice.on_submit(self.target_unit)
end
end
},
widgets.Label{
frame = { b=0,l=1},
text ={{text= ": exit editor",
key = "LEAVESCREEN",
on_activate= self:callback("dismiss")
},
}
},
}
end
unit_editor{target_unit=target}:show()

@ -1,205 +0,0 @@
-- Show/change the path used by Guide Cart orders
--[[=begin
gui/guide-path
==============
Bind to a key (the example config uses :kbd:`Alt`:kbd:`P`), and activate in the Hauling menu with
the cursor over a Guide order.
.. image:: /docs/images/guide-path.png
The script displays the cached path that will be used by the order; the game
computes it when the order is executed for the first time.
=end]]
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local dlg = require 'gui.dialogs'
local tile_attrs = df.tiletype.attrs
GuidePathUI = defclass(GuidePathUI, guidm.MenuOverlay)
GuidePathUI.focus_path = 'guide-path'
GuidePathUI.ATTRS {
route = DEFAULT_NIL,
stop = DEFAULT_NIL,
order = DEFAULT_NIL,
}
function GuidePathUI:init()
self.saved_mode = df.global.ui.main.mode
for i=0,#self.route.stops-1 do
if self.route.stops[i] == self.stop then
self.stop_idx = i
break
end
end
if not self.stop_idx then
error('Could not find stop index')
end
self.next_stop = self.route.stops[(self.stop_idx+1)%#self.route.stops]
end
function GuidePathUI:onShow()
-- with cursor, but without those ugly lines from native hauling mode
df.global.ui.main.mode = df.ui_sidebar_mode.Stockpiles
end
function GuidePathUI:onDestroy()
df.global.ui.main.mode = self.saved_mode
end
local function getTileType(cursor)
local block = dfhack.maps.getTileBlock(cursor)
if block then
return block.tiletype[cursor.x%16][cursor.y%16]
else
return 0
end
end
local function isTrackTile(tile)
return tile_attrs[tile].special == df.tiletype_special.TRACK
end
local function paintMapTile(dc, vp, cursor, pos, ...)
if not same_xyz(cursor, pos) then
local stile = vp:tileToScreen(pos)
if stile.z == 0 then
dc:seek(stile.x,stile.y):char(...)
end
end
end
local function get_path_point(gpath,i)
return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i])
end
function GuidePathUI:renderPath(cursor)
local gpath = self.order.guide_path
local startp = self.stop.pos
local endp = self.next_stop.pos
local vp = self:getViewport()
local dc = gui.Painter.new(self.df_layout.map)
local visible = gui.blink_visible(500)
if visible then
paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN)
end
local ok = nil
local pcnt = #gpath.x
if pcnt > 0 then
ok = true
for i = 0,pcnt-1 do
local pt = get_path_point(gpath, i)
if i == 0 and not same_xyz(startp,pt) then
ok = false
end
if i == pcnt-1 and not same_xyz(endp,pt) then
ok = false
end
local tile = getTileType(pt)
if not isTrackTile(tile) then
ok = false
end
if visible then
local char = '+'
if i < pcnt-1 then
local npt = get_path_point(gpath, i+1)
if npt.x == pt.x+1 then
char = 26
elseif npt.x == pt.x-1 then
char = 27
elseif npt.y == pt.y+1 then
char = 25
elseif npt.y == pt.y-1 then
char = 24
end
end
local color = COLOR_LIGHTGREEN
if not ok then color = COLOR_LIGHTRED end
paintMapTile(dc, vp, cursor, pt, char, color)
end
end
end
if gui.blink_visible(120) then
paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN)
end
return ok
end
function GuidePathUI:onRenderBody(dc)
dc:clear():seek(1,1):pen(COLOR_WHITE):string("Guide Path")
dc:seek(2,3)
local cursor = guidm.getCursorPos()
local path_ok = self:renderPath(cursor)
if path_ok == nil then
dc:string('No saved path', COLOR_DARKGREY)
elseif path_ok then
dc:string('Valid path', COLOR_GREEN)
else
dc:string('Invalid path', COLOR_RED)
end
dc:newline():newline(1)
dc:key('CUSTOM_Z'):string(": Reset path",COLOR_GREY,nil,path_ok~=nil)
--dc:key('CUSTOM_P'):string(": Find path",COLOR_GREY,nil,false)
dc:newline(1)
dc:key('CUSTOM_C'):string(": Zoom cur, ")
dc:key('CUSTOM_N'):string(": Zoom next")
dc:newline():newline(1):string('At cursor:')
dc:newline():newline(2)
local tile = getTileType(cursor)
if isTrackTile(tile) then
dc:string('Track '..tile_attrs[tile].direction, COLOR_GREEN)
else
dc:string('No track', COLOR_DARKGREY)
end
dc:newline():newline(1)
dc:key('LEAVESCREEN'):string(": Back")
end
function GuidePathUI:onInput(keys)
if keys.CUSTOM_C then
self:moveCursorTo(copyall(self.stop.pos))
elseif keys.CUSTOM_N then
self:moveCursorTo(copyall(self.next_stop.pos))
elseif keys.CUSTOM_Z then
local gpath = self.order.guide_path
gpath.x:resize(0)
gpath.y:resize(0)
gpath.z:resize(0)
elseif keys.LEAVESCREEN then
self:dismiss()
elseif self:propagateMoveKeys(keys) then
return
end
end
if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/Hauling/DefineStop/Cond/Guide') then
qerror("This script requires the main dwarfmode view in 'h' mode over a Guide order")
end
local hauling = df.global.ui.hauling
local route = hauling.view_routes[hauling.cursor_top]
local stop = hauling.view_stops[hauling.cursor_top]
local order = hauling.stop_conditions[hauling.cursor_stop]
local list = GuidePathUI{ route = route, stop = stop, order = order }
list:show()

@ -1,8 +0,0 @@
--@ alias = 'gui/create-item'
--[[=begin
gui/hack-wish
=============
An alias for `gui/create-item`. Deprecated.
=end]]

@ -1,31 +0,0 @@
-- Test lua viewscreens.
--[[=begin
gui/hello-world
===============
A basic example for testing, or to start your own script from.
=end]]
local gui = require 'gui'
local text = 'Woohoo, lua viewscreen :)'
local screen = gui.FramedScreen{
frame_style = gui.GREY_LINE_FRAME,
frame_title = 'Hello World',
frame_width = #text,
frame_height = 1,
frame_inset = 1,
}
function screen:onRenderBody(dc)
dc:string(text, COLOR_LIGHTGREEN)
end
function screen:onInput(keys)
if keys.LEAVESCREEN or keys.SELECT then
self:dismiss()
end
end
screen:show()

@ -1,326 +0,0 @@
-- Interface front-end for liquids plugin.
--[[=begin
gui/liquids
===========
To use, bind to a key (the example config uses Alt-L) and activate in the :kbd:`k` mode.
.. image:: /docs/images/liquids.png
This script is a gui front-end to `liquids` and works similarly,
allowing you to add or remove water & magma, and create obsidian walls & floors.
.. warning::
There is **no undo support**. Bugs in this plugin have been
known to create pathfinding problems and heat traps.
The :kbd:`b` key changes how the affected area is selected. The default :guilabel:`Rectangle`
mode works by selecting two corners like any ordinary designation. The :kbd:`p`
key chooses between adding water, magma, obsidian walls & floors, or just
tweaking flags.
When painting liquids, it is possible to select the desired level with :kbd:`+`:kbd:`-`,
and choose between setting it exactly, only increasing or only decreasing
with :kbd:`s`.
In addition, :kbd:`f` allows disabling or enabling the flowing water computations
for an area, and :kbd:`r` operates on the "permanent flow" property that makes
rivers power water wheels even when full and technically not flowing.
After setting up the desired operations using the described keys, use :kbd:`Enter` to apply them.
=end]]
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local dlg = require 'gui.dialogs'
local liquids = require('plugins.liquids')
local sel_rect = df.global.selection_rect
local brushes = {
{ tag = 'range', caption = 'Rectangle', range = true },
{ tag = 'block', caption = '16x16 block' },
{ tag = 'column', caption = 'Column' },
{ tag = 'flood', caption = 'Flood' },
}
local paints = {
{ tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'D_LOOK_ARENA_WATER' },
{ tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'D_LOOK_ARENA_MAGMA' },
{ tag = 'obsidian', caption = 'Obsidian Wall' },
{ tag = 'obsidian_floor', caption = 'Obsidian Floor' },
{ tag = 'riversource', caption = 'River Source' },
{ tag = 'flowbits', caption = 'Flow Updates', flow = true },
{ tag = 'wclean', caption = 'Clean Salt/Stagnant' },
}
local flowbits = {
{ tag = '+', caption = 'Enable Updates' },
{ tag = '-', caption = 'Disable Updates' },
{ tag = '.', caption = 'Keep Updates' },
}
local setmode = {
{ tag = '.', caption = 'Set Exactly' },
{ tag = '+', caption = 'Only Increase' },
{ tag = '-', caption = 'Only Decrease' },
}
local permaflows = {
{ tag = '.', caption = "Keep Permaflow" },
{ tag = '-', caption = 'Remove Permaflow' },
{ tag = 'N', caption = 'Set Permaflow N' },
{ tag = 'S', caption = 'Set Permaflow S' },
{ tag = 'E', caption = 'Set Permaflow E' },
{ tag = 'W', caption = 'Set Permaflow W' },
{ tag = 'NE', caption = 'Set Permaflow NE' },
{ tag = 'NW', caption = 'Set Permaflow NW' },
{ tag = 'SE', caption = 'Set Permaflow SE' },
{ tag = 'SW', caption = 'Set Permaflow SW' },
}
Toggle = defclass(Toggle)
Toggle.ATTRS{ items = {}, selected = 1 }
function Toggle:get()
return self.items[self.selected]
end
function Toggle:render(dc)
local item = self:get()
if item then
dc:string(item.caption)
if item.key then
dc:string(" ("):key(item.key):string(")")
end
else
dc:string('NONE', COLOR_RED)
end
end
function Toggle:step(delta)
if #self.items > 1 then
delta = delta or 1
self.selected = 1 + (self.selected + delta - 1) % #self.items
end
end
LiquidsUI = defclass(LiquidsUI, guidm.MenuOverlay)
LiquidsUI.focus_path = 'liquids'
function LiquidsUI:init()
self:assign{
brush = Toggle{ items = brushes },
paint = Toggle{ items = paints },
flow = Toggle{ items = flowbits },
set = Toggle{ items = setmode },
permaflow = Toggle{ items = permaflows },
amount = 7,
}
end
function LiquidsUI:onDestroy()
guidm.clearSelection()
end
function render_liquid(dc, block, x, y)
local dsgn = block.designation[x%16][y%16]
if dsgn.flow_size > 0 then
if dsgn.liquid_type == df.tile_liquid.Magma then
dc:pen(COLOR_RED):string("Magma")
else
dc:pen(COLOR_BLUE)
if dsgn.water_stagnant then dc:string("Stagnant ") end
if dsgn.water_salt then dc:string("Salty ") end
dc:string("Water")
end
dc:string(" ["..dsgn.flow_size.."/7]")
else
dc:string('No Liquid')
end
end
local permaflow_abbr = {
north = 'N', south = 'S', east = 'E', west = 'W',
northeast = 'NE', northwest = 'NW', southeast = 'SE', southwest = 'SW'
}
function render_flow_state(dc, block, x, y)
local flow = block.liquid_flow[x%16][y%16]
if block.flags.update_liquid then
dc:string("Updating", COLOR_GREEN)
else
dc:string("Static")
end
dc:string(", ")
if flow.perm_flow_dir ~= 0 then
local tag = df.tile_liquid_flow_dir[flow.perm_flow_dir]
dc:string("Permaflow "..(permaflow_abbr[tag] or tag), COLOR_CYAN)
elseif flow.temp_flow_timer > 0 then
dc:string("Flowing "..flow.temp_flow_timer, COLOR_GREEN)
else
dc:string("No Flow")
end
end
function LiquidsUI:onRenderBody(dc)
dc:clear():seek(1,1):string("Paint Liquids Cheat", COLOR_WHITE)
local cursor = guidm.getCursorPos()
local block = dfhack.maps.getTileBlock(cursor)
if block then
local x, y = pos2xyz(cursor)
local tile = block.tiletype[x%16][y%16]
dc:seek(2,3):string(df.tiletype.attrs[tile].caption, COLOR_CYAN)
dc:newline(2):pen(COLOR_DARKGREY)
render_liquid(dc, block, x, y)
dc:newline(2):pen(COLOR_DARKGREY)
render_flow_state(dc, block, x, y)
else
dc:seek(2,3):string("No map data", COLOR_RED):advance(0,2)
end
dc:newline():pen(COLOR_GREY)
dc:newline(1):key('CUSTOM_B'):string(": ")
self.brush:render(dc)
dc:newline(1):key('CUSTOM_P'):string(": ")
self.paint:render(dc)
local paint = self.paint:get()
dc:newline()
if paint.liquid then
dc:newline(1):string("Amount: "..self.amount)
dc:advance(1):string("("):key('SECONDSCROLL_UP'):key('SECONDSCROLL_DOWN'):string(")")
dc:newline(3):key('CUSTOM_S'):string(": ")
self.set:render(dc)
else
dc:advance(0,2)
end
dc:newline()
if paint.flow then
dc:newline(1):key('CUSTOM_F'):string(": ")
self.flow:render(dc)
dc:newline(1):key('CUSTOM_R'):string(": ")
self.permaflow:render(dc)
else
dc:advance(0,2)
end
dc:newline():newline(1):pen(COLOR_WHITE)
dc:key('LEAVESCREEN'):string(": Back, ")
dc:key('SELECT'):string(": Paint")
end
function ensure_blocks(cursor, size, cb)
size = size or xyz2pos(1,1,1)
local cx,cy,cz = pos2xyz(cursor)
local all = true
for x=1,size.x or 1,16 do
for y=1,size.y or 1,16 do
for z=1,size.z do
if not dfhack.maps.getTileBlock(cx+x-1, cy+y-1, cz+z-1) then
all = false
end
end
end
end
if all then
cb()
return
end
dlg.showYesNoPrompt(
'Instantiate Blocks',
'Not all map blocks are allocated - instantiate?\n\nWarning: new untested feature.',
COLOR_YELLOW,
function()
for x=1,size.x or 1,16 do
for y=1,size.y or 1,16 do
for z=1,size.z do
dfhack.maps.ensureTileBlock(cx+x-1, cy+y-1, cz+z-1)
end
end
end
cb()
end,
function()
cb()
end
)
end
function LiquidsUI:onInput(keys)
local paint = self.paint:get()
local liquid = paint.liquid
if keys.CUSTOM_B then
self.brush:step()
elseif keys.CUSTOM_P then
self.paint:step()
elseif liquid and keys.SECONDSCROLL_UP then
self.amount = math.max(0, self.amount-1)
elseif liquid and keys.SECONDSCROLL_DOWN then
self.amount = math.min(7, self.amount+1)
elseif liquid and keys.CUSTOM_S then
self.set:step()
elseif paint.flow and keys.CUSTOM_F then
self.flow:step()
elseif paint.flow and keys.CUSTOM_R then
self.permaflow:step()
elseif keys.LEAVESCREEN then
if guidm.getSelection() then
guidm.clearSelection()
return
end
self:dismiss()
self:sendInputToParent('CURSOR_DOWN_Z')
self:sendInputToParent('CURSOR_UP_Z')
elseif keys.SELECT then
local cursor = guidm.getCursorPos()
local sp = guidm.getSelection()
local size = nil
if self.brush:get().range then
if not sp then
guidm.setSelectionStart(cursor)
return
else
guidm.clearSelection()
cursor, size = guidm.getSelectionRange(cursor, sp)
end
else
guidm.clearSelection()
end
local cb = curry(
liquids.paint,
cursor,
self.brush:get().tag, self.paint:get().tag,
self.amount, size,
self.set:get().tag, self.flow:get().tag,
self.permaflow:get().tag
)
ensure_blocks(cursor, size, cb)
elseif self:propagateMoveKeys(keys) then
return
elseif keys.D_LOOK_ARENA_WATER then
self.paint.selected = 1
elseif keys.D_LOOK_ARENA_MAGMA then
self.paint.selected = 2
end
end
if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/LookAround') then
qerror("This script requires the main dwarfmode view in 'k' mode")
end
local list = LiquidsUI()
list:show()

@ -1,146 +0,0 @@
-- Shows mechanisms linked to the current building.
--[[=begin
gui/mechanisms
==============
To use, bind to a key (the example config uses :kbd:`Ctrl`:kbd:`M`)
and activate in :kbd:`q` mode.
.. image:: /docs/images/mechanisms.png
Lists mechanisms connected to the building, and their links. Navigating
the list centers the view on the relevant linked buildings.
To exit, press :kbd:`Esc` or :kbd:`Enter`; :kbd:`Esc` recenters on
the original building, while :kbd:`Enter` leaves focus on the current
one. :kbd:`Shift`:kbd:`Enter` has an effect equivalent to pressing
:kbd:`Enter`, and then re-entering the mechanisms UI.
=end]]
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
function listMechanismLinks(building)
local lst = {}
local function push(item, mode)
if item then
lst[#lst+1] = {
obj = item, mode = mode,
name = utils.getBuildingName(item)
}
end
end
push(building, 'self')
if not df.building_actual:is_instance(building) then
return lst
end
local item, tref, tgt
for _,v in ipairs(building.contained_items) do
item = v.item
if df.item_trappartsst:is_instance(item) then
tref = dfhack.items.getGeneralRef(item, df.general_ref_type.BUILDING_TRIGGER)
if tref then
push(tref:getBuilding(), 'trigger')
end
tref = dfhack.items.getGeneralRef(item, df.general_ref_type.BUILDING_TRIGGERTARGET)
if tref then
push(tref:getBuilding(), 'target')
end
end
end
return lst
end
MechanismList = defclass(MechanismList, guidm.MenuOverlay)
MechanismList.focus_path = 'mechanisms'
function MechanismList:init(info)
self:assign{
links = {}, selected = 1
}
self:fillList(info.building)
end
function MechanismList:fillList(building)
local links = listMechanismLinks(building)
self.old_viewport = self:getViewport()
self.old_cursor = guidm.getCursorPos()
if #links <= 1 then
links[1].mode = 'none'
end
self.links = links
self.selected = 1
end
local colors = {
self = COLOR_CYAN, none = COLOR_CYAN,
trigger = COLOR_GREEN, target = COLOR_GREEN
}
local icons = {
self = 128, none = 63, trigger = 27, target = 26
}
function MechanismList:onRenderBody(dc)
dc:clear()
dc:seek(1,1):string("Mechanism Links", COLOR_WHITE):newline()
for i,v in ipairs(self.links) do
local pen = { fg=colors[v.mode], bold = (i == self.selected) }
dc:newline(1):pen(pen):char(icons[v.mode])
dc:advance(1):string(v.name)
end
local nlinks = #self.links
if nlinks <= 1 then
dc:newline():newline(1):string("This building has no links", COLOR_LIGHTRED)
end
dc:newline():newline(1):pen(COLOR_WHITE)
dc:key('LEAVESCREEN'):string(": Back, ")
dc:key('SELECT'):string(": Switch")
end
function MechanismList:changeSelected(delta)
if #self.links <= 1 then return end
self.selected = 1 + (self.selected + delta - 1) % #self.links
self:selectBuilding(self.links[self.selected].obj)
end
function MechanismList:onInput(keys)
if keys.SECONDSCROLL_UP then
self:changeSelected(-1)
elseif keys.SECONDSCROLL_DOWN then
self:changeSelected(1)
elseif keys.LEAVESCREEN then
self:dismiss()
if self.selected ~= 1 then
self:selectBuilding(self.links[1].obj, self.old_cursor, self.old_view)
end
elseif keys.SELECT_ALL then
if self.selected > 1 then
self:fillList(self.links[self.selected].obj)
end
elseif keys.SELECT then
self:dismiss()
elseif self:simulateViewScroll(keys) then
return
end
end
if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then
qerror("This script requires the main dwarfmode view in 'q' mode")
end
local list = MechanismList{ building = df.global.world.selected_building }
list:show()
list:changeSelected(1)

Some files were not shown because too many files have changed in this diff Show More