So, I initially started to edit the prefchange.lua script to add a bit more flexibility to do what I was trying to accomplish but it turned into a complete rewrite/rework with a backwards compatibility mode to prefchange.lua and pref-adjust.lua style preference changes. Please take a look at the embedded script (it is quite long so I'll try to put it in a spoiler/code section).
I'd especially like feedback from Max™, vjek and Putnam because their work was my starting point. I'd also love to hear from lethosor, Warmist, ab9rf, PeridexistErrant and Quietust. I'm certain I've missed some of the other DFHack wizards so my apologies; I'd be happy to hear from you as well.
If you have the time to look please note that I make a fairly extensive look-up table. If this functionality is better done a different way or already existing in DFHack please let me know. If not, I'd like to humbly request that we have one. It could be modeled after what I've done here or improved.
-- Tool to view, clear set and change preferences. Useful for moods or skilling
-- by Ygvir - 20161220
-- orignally based on a "hacked together version by some jerk named Max™ on the 11th of April 2015" which was based based on pref_adjust by vjek, version 4, 20141016, for DF(hack) 40.08, with the getselectedunit portion from a Putnam script (and the dfhack lua API but he's still a wizard)
-- almost all of the internals have been rewritten and backwards compatibility created for both of the above scripts with the -b option
-- this implements a rather large look-up table that at least some portions of should probably be dfhack utilities.
-- Praise Armok!
--[====[
preferences
==========
Show, clear, set or replace preferences on one or all units
Useful for mooding, increased skill or quality and representations in artwork.
Usage:
preferences {Preference Type}>{New Prefernce raw}(>{Preference to be replced - optional})
Examples:
preferences -as -- shows the preferences for all units
preferences MATERIAL>STEEL -- adds preference for steel
preferences MATERIAL>STEEL>COPPER -- changes preference for copper to steel
preferences ITEM>AXE_BATTLE>MACE -- changes preference to mace to preference to battle axe
preferences CONSUME>MUSHROOM_HELMET_PLUMP:DRINK -- add preference to consume dwarven wine
preferences -b axp -- use backward compatibility to Max™ preferences "axp"
Valid options:
:-s: show unit preferences
:-c: clear unit preferences
:-a: perform operations on all units
:-b: use backword compatability options -- see below
:-d: run in DEBUG mode -- prints a lot more detail
Bacward compatability options:
:vjek: likes weapons, armor, shields, plump helmet, dwarven wine, steel, iron,
detests trolls, buzzards, vultures and crundles
:axp: likes axes, breastplates, and steel
:has: likes hammers, mail shirts, and steel
:swb: likes short swords, high boots, and steel
:spb: likes spears, high boots, and steel
:mas: likes maces, shields, and steel
:xbh: likes crossbows, helms, and steel
:pig: likes picks, gauntlets, and steel
:log: likes long swords, gauntlets, and steel
:dap: likes daggers, greaves, and steel
Feel free to adjust the values as you see fit, change the has steel to
platinum, change the axp axes to great axes, whatnot.
]====]
-- ---------------------------------------------------------------------------
function process_args(args, allowed)
local opts, prefs = {}, {}
for _,arg in pairs(args) do
if string.sub(arg, 1, 1) == "-" then
for i=2,#arg do
opts[string.sub(arg, i, i)] = true
end
else
prefs[#prefs+1] = arg
end
end
return opts, prefs
end
-- ---------------------------------------------------------------------------
function back_compat(opts, prefs)
print("Running in compatibility mode!!")
-- printall(prefs)
if (prefs[1] == "vjek") then
print("Setting preferences to vjek settings")
opts["c"] = true
opts["a"] = true
prefs = { "DETEST>TROLL", "DETEST>BIRD_BUZZARD", "DETEST>BIRD_VULTURE", "DETEST>CRUNDLE",
"ITEM>WEAPON", "ITEM>ARMOR", "ITEM>SHIELD",
"ADMIRE>MUSHROOM_HELMET_PLUMP:STRUCTURAL", "CONSUME>MUSHROOM_HELMET_PLUMP:DRINK",
"MATERIAL>STEEL", "MATERIAL>IRON"}
return opts, prefs
end
if (prefs[1] == "axp") then prefs = { "ITEM>AXE_BATTLE", "ITEM>BREASTPLATE", "MATERIAL>STEEL" }
elseif (prefs[1] == "has") then prefs = { "ITEM>HAMMER_WAR", "ITEM>MAIL_SHIRT", "MATERIAL>STEEL" }
elseif (prefs[1] == "swb") then prefs = { "ITEM>SWORD_SHORT", "ITEM>BOOTS", "MATERIAL>STEEL" }
elseif (prefs[1] == "spb") then prefs = { "ITEM>SPEAR", "ITEM>BOOTS", "MATERIAL>STEEL" }
elseif (prefs[1] == "mas") then prefs = { "ITEM>MACE", "ITEM>SHIELD", "MATERIAL>STEEL" }
elseif (prefs[1] == "xbh") then prefs = { "ITEM>CROSSBOW", "ITEM>HELM", "MATERIAL>STEEL" }
elseif (prefs[1] == "pig") then prefs = { "ITEM>PICK", "ITEM>GAUNTLETS", "MATERIAL>STEEL" }
elseif (prefs[1] == "log") then prefs = { "ITEM>SWORD_LONG", "ITEM>GAUNTLETS", "MATERIAL>STEEL" }
elseif (prefs[1] == "dap") then prefs = { "ITEM>DAGGER_LARGE", "ITEM>GREAVES", "MATERIAL>STEEL" }
end
return opts, prefs
end
-- ---------------------------------------------------------------------------
function create_lookup()
-- at this point the lookup is fairly complete... there are a lot of cases still missing but most common cases are covered
local t = { }
add_item_lookup(t)
-- add inorganic materials
for i,v in ipairs (df.global.world.raws.inorganics) do
add_lookup(t, v.id, v, -1, -1, dfhack.matinfo.find(v.id).type, i)
end
-- add creatures
add_creature_lookup(t)
add_plant_lookup(t)
-- also need color, shape and artforms...
return t
end
-- ---------------------------------------------------------------------------
function add_lookup(t, name, def, i_type, i_subtype, m_type, m_index)
t[name] = { item_def = def, item_type = i_type, item_subtype = i_subtype, mattype = m_type, matindex = m_index }
end
-- ---------------------------------------------------------------------------
function add_item_lookup(t)
-- add all of the basic item types: furniture, finished goods and etc...
for i,v in ipairs(df.item_type) do
add_lookup(t, df.item_type[i], nil, i, -1, -1, -1)
end
-- add items with suptypes: weapons, armor, gloves, shoes, shields, helms, pants...
for k,l in pairs(df.global.world.raws.itemdefs) do
if (k ~="all" and k ~= "tools_by_type") then
for i,v in ipairs (l) do
local v_type, v_name = string.match(v.id, "ITEM_(%a+)_(.+)")
if (v_name) then
add_lookup(t, v_name, v, df.item_type[v_type], v.subtype, 0, -1)
end
end
end
end
end
-- ---------------------------------------------------------------------------
function add_plant_lookup(t) -- add plant based items and materials: wood, food, drink...
-- TODO!! add wood, other plant products? - should be easy, there is a wood material under .material
local DRINK = 68 -- df.item_type.DRINK == 68
for i,v in ipairs (df.global.world.raws.plants.all) do
for ii,vv in ipairs(v.material) do
if (vv.food_mat_index.PlantDrink ~= -1) then
add_lookup(t, v.id..":"..vv.id, v, df.item_type.DRINK, -1, v.material_defs.type_drink, i)
end
-- in all of the remaining... I need to verify the numbers below.
if (vv.food_mat_index.EdiblePlant ~= -1) then
add_lookup(t, v.id..":"..vv.id, v, i, -1, -1, -1)
end
if (vv.food_mat_index.CookableLiquid ~= -1) then
add_lookup(t, v.id..":"..vv.id, v, i, -1, -1, -1)
end
if (vv.food_mat_index.CookablePowder ~= -1) then
add_lookup(t, v.id..":"..vv.id, v, i, -1, -1, -1)
end
if (vv.food_mat_index.CookableSeed ~= -1) then
add_lookup(t, v.id..":"..vv.id, v, i, -1, -1, -1)
end
if (vv.food_mat_index.CookablePowder ~= -1) then
add_lookup(t, v.id..":"..vv.id, v, i, -1, -1, -1)
end
if (vv.food_mat_index.CookableSeed ~= -1) then
add_lookup(t, v.id..":"..vv.id, v, i, -1, -1, -1)
end
end
end
end
-- ---------------------------------------------------------------------------
function add_creature_lookup(t)
-- TODO!! add creature products: meat, milk, cheese, leather
for i,v in pairs(df.global.world.raws.creatures.all) do
add_lookup(t, v.creature_id, v, i, -1, -1, -1)
end
end
-- ---------------------------------------------------------------------------
function adjust_prefs(unit, prefs, opts, counter)
-- print_pref_count("Before ", unit)
num_opts = table_length(opts)
local TYPE, TARGET, REPLACE = 1, 2, 3
if (opts['s']) then
print_all(unit)
if(num_opts == 1) then return end
end
if (opts['c']) then
print("Clearing preferences for "..dfhack.TranslateName(dfhack.units.getVisibleName(unit)))
clear_preferences(unit)
if(num_opts == 1 or (num_opts == 2 and opts['s'])) then return end
end
local pss_counter=31415926
print_pref_count(unit,"Before, ")
for _,pref in ipairs(prefs) do
local p = string.split(pref, ">")
if(preftype_lookup[p[TYPE]] ~= nil) then
if (target_lookup[p[TARGET]]) then
if (p[REPLACE] ~= nil) then
-- replace preference type if possible add if nothing to replace
print("Replace found attempting replacement")
if (DEBUG) then printall(target_lookup[p[REPLACE]]) end
if (target_lookup[p[REPLACE]] ~= nil) then
replace_pref(unit, preftype_lookup[p[TYPE]], target_lookup[p[TARGET]], target_lookup[p[REPLACE]], pss_counter)
else
print("Replace target "..p[REPLACE].." not supported - skipping this change.")
end
else
-- print("REPLACE is nil")
add_pref(unit, preftype_lookup[p[TYPE]], target_lookup[p[TARGET]], pss_counter)
pss_counter = pss_counter + 1
end
else
print("Preference target "..p[TARGET].." not supported - skipping this change.")
end
else
print("Preference type "..p[TYPE].." not supported")
end
end
print_pref_count(unit, "After, ")
end
-- ---------------------------------------------------------------------------
function table_length (t)
local count = 0
for _ in pairs(t) do count = count + 1 end
return count
end
-- ---------------------------------------------------------------------------
function print_pref_count(unit, message)
local prefcount = #(unit.status.current_soul.preferences)
print (message..dfhack.TranslateName(dfhack.units.getVisibleName(unit)).." has "..prefcount.." preferences")
end
-- ---------------------------------------------------------------------------
function print_all(unit) -- I think 8, 9, 10 ,11 are artforms and type 7 appears to be color? type 6 might be fruit...
print_pref_count(unit, "\n")
for i,v in ipairs(unit.status.current_soul.preferences) do
local p_type = preftype_lookup[v.type]
if (p_type == "MATERIAL") then
print(p_type.." "..tostring(dfhack.matinfo.decode(v.mattype,v.matindex)))
elseif (p_type == "ANIMAL") then
print(p_type.." "..df.global.world.raws.creatures.all[v.creature_id].creature_id)
elseif(p_type == "CONSUME") then
if (v.item_type == 68) then
print(p_type.." "..df.global.world.raws.plants.all[v.matindex].id..":"..df.item_type[v.item_type])
else
print(p_type.." "..tostring(dfhack.matinfo.decode(v.mattype,v.matindex))..":"..df.item_type[v.item_type])
end
elseif(p_type == "DETEST") then
print(p_type.." "..df.global.world.raws.creatures.all[v.creature_id].creature_id)
elseif(p_type == "ITEM") then
i_type = df.item_type[v.item_type]
local match = false
for kk,vv in pairs(df.global.world.raws.itemdefs) do
if (string.find(kk, string.lower(i_type))) then
if (v.item_subtype ~= -1) then
print(p_type.." "..i_type.." "..vv[v.item_subtype].id)
match = true
end
end
end
if (not match) then
print(p_type.." "..i_type)
end
elseif(p_type =="ADMIRE") then
print(p_type.." "..df.global.world.raws.plants.all[v.item_type].id)
else
print("Preference type "..v.type.." Not supported")
end
if (DEBUG == true) then
printall(v)
end
end
end
-- ---------------------------------------------------------------------------
function clear_preferences(unit)
local prefs=unit.status.current_soul.preferences
for _,pref in ipairs(prefs) do
pref:delete()
end
prefs:resize(0)
end
-- ---------------------------------------------------------------------------
function replace_pref(unit, t, d, r, counter)
local match = true
for k,v in ipairs(unit.status.current_soul.preferences) do
if (v.type == t) then
local keys = { }
match = true
-- search for the prefernce that we want to replace
for kk,vv in pairs(r) do
if (kk ~= "item_def") then
if (v[kk] ~= r[kk]) then
match = false
break
elseif (v[kk] ~= d[kk]) then
keys[kk] = d[kk]
end
end
end
-- if we find a match we'll update it's keys to the new preference
if (match) then
print("Match found! Replacing")
for kk,vv in pairs(keys) do
v[kk] = keys[kk]
end
break
end
end
end
if (not match) then
print("No match found adding pref")
add_pref(unit, t, d, counter)
end
end
-- ---------------------------------------------------------------------------
function add_pref(unit, t, d, counter)
utils = require 'utils'
utils.insert_or_update(unit.status.current_soul.preferences, { new = true, type = t, item_type = d.item_type, creature_id = d.item_type, color_id = d.item_type, shape_id = d.item_type, plant_id = d.item_type, item_subtype = d.item_subtype, mattype = d.mattype, matindex = d.matindex, active = true, prefstring_seed = counter }, 'prefstring_seed')
end
-- ---------------------------------------------------------------------------
-- main script operation starts here
-- ---------------------------------------------------------------------------
args = {...}
local opts, prefs
opts, prefs = process_args(args, {"a", "c", "d", "s", "h"})
DEBUG = opts.d
if(opts.b) then
opts, prefs = back_compat(opts, prefs)
printall(prefs)
end
if (#args ~= 0 and not opts.h) then
-- making the lookup tables a global because it really should be a utility
preftype_lookup = { "ANIMAL", "CONSUME", "DETEST", "ITEM", "ADMIRE",
MATERIAL = 0, ANIMAL = 1, CONSUME = 2, DETEST = 3, ITEM = 4, ADMIRE = 5 }
preftype_lookup[0] = "MATERIAL" -- I can't seem to add index any other way... odd lua thing, I think.
-- there are other types as well... up to 11, including fruit, color(7), shape and artforms (8-11)?
target_lookup = create_lookup()
if (opts['a'] ~= nil) then
for _,unit in ipairs(df.global.world.units.active) do
if unit.race == df.global.ui.race_id then
adjust_prefs(unit, prefs, opts)
end
end
return
else
local unit = dfhack.gui.getSelectedUnit()
if (unit ~= nil) then
adjust_prefs(unit, prefs, opts)
return
end
end
end
print ("Show, clear, set or replace preferences on one or all units")
print ("Useful for mooding, increased skill or quality and representations in artwork.")
print ("")
print ("Usage:")
print ("preferences {Preference Type}>{New Prefernce raw}(>{Preference to be replced - optional}) ")
print ("")
print ("Examples:")
print ("preferences -as -- shows the preferences for all units")
print ("preferences MATERIAL>STEEL -- adds preference for steel")
print ("preferences MATERIAL>STEEL>COPPER -- changes preference for copper to steel")
print ("preferences ITEM>AXE_BATTLE>MACE -- changes preference to mace to preference to battle axe")
print ("preferences CONSUME>MUSHROOM_HELMET_PLUMP:DRINK -- add preference to consume dwarven wine")
print ("preferences -b axp -- use backward compatibility to Max™ preferences (axp)")
print ("")
print ("Valid options:")
print (":-s: show unit preferences")
print (":-c: clear unit preferences")
print (":-a: perform operations on all units")
print (":-b: use backword compatability options -- see below")
print (":-d: run in DEBUG mode -- prints a lot more detail")
print ("")
print ("Bacward compatability options:")
print (":vjek: likes weapons, armor, shields, plump helmet, dwarven wine, steel, iron, ")
print (" detests trolls, buzzards, vultures and crundles ")
print (":axp: likes axes, breastplates, and steel")
print (":has: likes hammers, mail shirts, and steel")
print (":swb: likes short swords, high boots, and steel")
print (":spb: likes spears, high boots, and steel")
print (":mas: likes maces, shields, and steel")
print (":xbh: likes crossbows, helms, and steel")
print (":pig: likes picks, gauntlets, and steel")
print (":log: likes long swords, gauntlets, and steel")
print (":dap: likes daggers, greaves, and steel")
I know it would be better to set this up as a pull request through github but I'm just not set up for that yet, sorry.