--lists and optionally logs most valuable items
---------------CONFIGURATION---------------
MULTICOLOR = true -- change to false to prevent use of different colours
SHOW_OWNERSHIP = true -- checks ownership (apart from trader's, which is always checked), items not owned by fortress are red
SHOW_STATE = true --shows another line with more info, if item is in building, or inventory, or owned by someone etc., implies enabled SHOW_OWNERSHIP
SHOW_FORBID = true --shows curly brackets around forbidden item
SHOW_TOTAL = false --change to true to show number of items shown and their total value
DEEP_CHECK = true --checks item presence on map in non-dwarf modes, slow if there are many items
------------END-OF-CONFIGURATION-----------
local help = [====[
list-valuables
========
Shows a list of most valuable items on a map.
``list-valuables -show N`` shows N most valuable items instead of 10
``list-valuables -state`` shows state if not shown, or hides if shown
``list-valuables -hide N`` hides/unhides Nth item to help localize it
``list-valuables -filter NAME`` shows items containing NAME only
``list-valuables -total`` shows number and total value of items
``list-valuables -log`` logs the list to a file in main DF directory
]====]
local fval_tmp,fval_item,fval_posx,fval_posy,fval_posz,my_file=nil,nil,nil,nil,nil,nil
local tmp_item_list,tmp_str,form_str,fnum_str,state_t,TOP_N,hide_N,h_str,restricted,offmap,inv,nofilter,items_n,total_v={},"",nil,nil,{},10,nil,"",false,false,false,true,0,0
local mapx,mapy,mapz=dfhack.maps.getTileSize()
local utils = require 'utils'
kon = dfhack.df2console
kol = dfhack.color
validArgs = validArgs or utils.invert({
'help',
'show',
'state',
'log',
'hide',
'filter',
'total'
})
function list_table (input_table)
local my_str=table.concat(input_table,", ")
local my_tmp=nil
if my_str then
my_tmp=string.find(my_str,",[^,]*$")
if my_tmp then
my_str=table.concat{my_str:sub(1,my_tmp-1), " and", my_str:sub(my_tmp+1)}
end
my_str=my_str.."."
return my_str
end
return nil
end
function on_map(my_x,my_y,my_z,my_id,my_g)
if my_x<0 or my_y<0 then return nil end
if my_x>mapx-1 or my_y>mapy-1 then return nil end
if df.global.gamemode~=0 and DEEP_CHECK and my_g then
local my_block=dfhack.maps.getTileBlock(my_x,my_y,my_z)
if my_block then for k,v in ipairs(my_block.items) do if v==my_id then return true end end end
return nil
end
return true
end
function do_nothing (col)
end
-- Main --
local args = utils.processArgs({...}, validArgs)
if args.help then
print(help)
return
end
if not MULTICOLOR then kol = do_nothing end
if args.show then
if #args.show==0 then TOP_N=10
else
TOP_N=tonumber(args.show)
if not TOP_N then TOP_N=10 end
if TOP_N<1 then TOP_N=1 end
end
else
TOP_N=10
end
TOP_N=math.floor(TOP_N)
if args.hide then
if #args.hide>0 then
hide_N=tonumber(args.hide)
if hide_N and hide_N>=1 then hide_N=math.floor(hide_N) else hide_N=nil end
else
hide_N=nil
end
end
if args.filter then
if #args.filter>0 then nofilter=false end
end
if args.state then
if SHOW_STATE then SHOW_STATE=false
else SHOW_STATE=true end
end
for k,v in ipairs(df.global.world.items.all) do
fval_posx,fval_posy,fval_posz=dfhack.items.getPosition(v)
if fval_posx and on_map(fval_posx,fval_posy,fval_posz,v.id,v.flags.on_ground) then
fval_tmp=dfhack.items.getValue(v)
tmp_item_list[k]=fval_tmp
end
end
function getKeysSortedByValue(tbl, sortFunction)
local keys = {}
for key in pairs(tbl) do
table.insert(keys, key)
end
table.sort(keys, function(a, b) return sortFunction(tbl[a], tbl[b]) end)
return keys
end
local sortedKeys = getKeysSortedByValue(tmp_item_list, function(a, b) return a > b end)
if sortedKeys and TOP_N>#sortedKeys then TOP_N=#sortedKeys end
local tpn=tostring(TOP_N) tpn=#tpn tpn=tostring(tpn) tpn="%"..tpn.."d"
form_str=tpn.." %s (%s%s) at x=%s y=%s z=%s%s"
if args.log then
if df.global.world.world_data then
if #df.global.world.world_data.active_site>0 then
my_fortA=dfhack.TranslateName(df.global.world.world_data.active_site[0].name,true)
my_fortB=dfhack.TranslateName(df.global.world.world_data.active_site[0].name,false)
else
my_fortA=df.game_mode[df.global.gamemode] my_fortB=df.game_type[df.global.gametype]
end
my_filename=my_fortA.."_valuables_"..df.global.cur_year.."_"..df.global.cur_year_tick..".txt"
my_file = assert(io.open(my_filename, "w"))
if my_file then print(string.format("Logging to %s",my_filename))
my_file:write("Valuables of "..my_fortA.."/"..my_fortB)
if not nofilter then my_file:write(" (FILTER: "..args.filter..")") end
end
end
end
for i, key in ipairs(sortedKeys) do
if i<=TOP_N then
tmp_str="" restricted=false state_t={}
fval_item=df.global.world.items.all[key]
tmp_str=dfhack.items.getDescription(fval_item,0,true)
if SHOW_FORBID and tmp_str and fval_item.flags.forbid then tmp_str="{"..tmp_str.."}" end
if nofilter or string.match(kon(tmp_str),args.filter) then
fval_tmp=dfhack.items.getValue(fval_item)
items_n=items_n+1 total_v=total_v+fval_tmp
fval_posx,fval_posy,fval_posz=dfhack.items.getPosition(fval_item)
if fval_item.flags.trader then restricted=true table.insert(state_t,"owned by trader")end
if fval_item.flags.in_inventory then inv=true else inv=false end
if SHOW_OWNERSHIP or SHOW_STATE then offmap=false
for ii,vv in ipairs(fval_item.general_refs) do
if df.general_ref_unit_holderst:is_instance(vv) then offmap=true if inv then table.insert(state_t,"in inventory (unit)") inv=false end
for iii,vvv in ipairs(df.global.world.units.active) do
if vvv.id==vv.unit_id then offmap=false
if vvv.flags2.visitor or vvv.flags2.visitor_uninvited or vvv.flags1.forest or vvv.flags1.merchant or vvv.flags1.diplomat then restricted=true table.insert(state_t,"held by visitor") end
end
end
end
end
if inv then table.insert(state_t,"in inventory (container)") end
end
if hide_N and hide_N==i then if fval_item.flags.hidden then fval_item.flags.hidden=false else fval_item.flags.hidden=true end end
if fval_item.flags.hidden then h_str=" H" else h_str="" end
if restricted then kol(COLOR_RED) elseif offmap then kol(COLOR_YELLOW) else kol(nil) end
print(string.format(form_str,i,kon(tmp_str),fval_tmp,kon(string.char(15)),fval_posx,fval_posy,fval_posz,h_str))
if my_file then my_file:write(string.format("\n"..form_str,i,kon(tmp_str),fval_tmp,kon(string.char(164)),fval_posx,fval_posy,fval_posz,h_str)) end
if SHOW_STATE then
if fval_item.flags.in_building then table.insert(state_t,"in a building") end
if fval_item.flags.owned and not fval_item.flags.trader then table.insert(state_t,"owned") end
if fval_item.flags.owned and fval_item.flags.trader then table.insert(state_t,"not for sale") end
if offmap then table.insert(state_t,"off-map") end
if fval_item.flags2[3] then table.insert(state_t,"in location") end
if fval_item.flags.in_chest then table.insert(state_t,"stored") end
if fval_item.flags.in_job then table.insert(state_t,"in JOB") end
kol(COLOR_DARKGREY)
if #state_t>0 then
dfhack.print(string.format(tpn,i)) print(string.format("->Item %s",list_table(state_t)))
if my_file then
my_file:write(string.format("\n"..tpn,i))
my_file:write(string.format("->Item %s",list_table(state_t)))
end
end
end
end
end
end
if SHOW_TOTAL or args.total then kol(nil)
print(string.format("Items shown: %s (total value: %s%s)",items_n,total_v,kon(string.char(15))))
if my_file then my_file:write(string.format("\nItems shown: %s (total value: %s%s)",items_n,total_v,kon(string.char(164)))) end
end
if my_file then my_file:close() end
kol(nil)