From 191071beb6730f9575b737fdae39db6572ebd3f4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 8 May 2012 12:55:06 +0400 Subject: [PATCH] Add more lua scripts. --- library/lua/dumper.lua | 234 ++++++++++++++++++++++++++++ library/lua/utils.lua | 173 +++++++++++++++++++- scripts/devel/list-filters.lua | 96 ++++++++++++ scripts/{ => devel}/lua-example.lua | 0 scripts/fix/item-occupancy.lua | 125 +++++++++++++++ 5 files changed, 623 insertions(+), 5 deletions(-) create mode 100644 library/lua/dumper.lua create mode 100644 scripts/devel/list-filters.lua rename scripts/{ => devel}/lua-example.lua (100%) create mode 100644 scripts/fix/item-occupancy.lua diff --git a/library/lua/dumper.lua b/library/lua/dumper.lua new file mode 100644 index 000000000..44e787db2 --- /dev/null +++ b/library/lua/dumper.lua @@ -0,0 +1,234 @@ +--[[ DataDumper.lua +Copyright (c) 2007 Olivetti-Engineering SA + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +]] + +local _ENV = mkmodule('dumper') + +local dumplua_closure = [[ +local closures = {} +local function closure(t) + closures[#closures+1] = t + t[1] = assert(loadstring(t[1])) + return t[1] +end + +for _,t in pairs(closures) do + for i = 2,#t do + debug.setupvalue(t[1], i-1, t[i]) + end +end +]] + +local lua_reserved_keywords = { + 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', + 'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', + 'return', 'then', 'true', 'until', 'while' } + +local function keys(t) + local res = {} + local oktypes = { stringstring = true, numbernumber = true } + local function cmpfct(a,b) + if oktypes[type(a)..type(b)] then + return a < b + else + return type(a) < type(b) + end + end + for k in pairs(t) do + res[#res+1] = k + end + table.sort(res, cmpfct) + return res +end + +local c_functions = {} +for _,lib in pairs{'_G', 'string', 'table', 'math', + 'io', 'os', 'coroutine', 'package', 'debug'} do + local t = _G[lib] or {} + lib = lib .. "." + if lib == "_G." then lib = "" end + for k,v in pairs(t) do + if type(v) == 'function' and not pcall(string.dump, v) then + c_functions[v] = lib..k + end + end +end + +function DataDumper(value, varname, fastmode, ident, indent_step) + indent_step = indent_step or 2 + local defined, dumplua = {} + -- Local variables for speed optimization + local string_format, type, string_dump, string_rep = + string.format, type, string.dump, string.rep + local tostring, pairs, table_concat = + tostring, pairs, table.concat + local keycache, strvalcache, out, closure_cnt = {}, {}, {}, 0 + setmetatable(strvalcache, {__index = function(t,value) + local res = string_format('%q', value) + t[value] = res + return res + end}) + local fcts = { + string = function(value) return strvalcache[value] end, + number = function(value) return value end, + boolean = function(value) return tostring(value) end, + ['nil'] = function(value) return 'nil' end, + ['function'] = function(value) + return string_format("loadstring(%q)", string_dump(value)) + end, + userdata = function() error("Cannot dump userdata") end, + thread = function() error("Cannot dump threads") end, + } + local function test_defined(value, path) + if defined[value] then + if path:match("^getmetatable.*%)$") then + out[#out+1] = string_format("s%s, %s)\n", path:sub(2,-2), defined[value]) + else + out[#out+1] = path .. " = " .. defined[value] .. "\n" + end + return true + end + defined[value] = path + end + local function make_key(t, key) + local s + if type(key) == 'string' and key:match('^[_%a][_%w]*$') then + s = key .. "=" + else + s = "[" .. dumplua(key, 0) .. "]=" + end + t[key] = s + return s + end + for _,k in ipairs(lua_reserved_keywords) do + keycache[k] = '["'..k..'"] = ' + end + if fastmode then + fcts.table = function (value) + -- Table value + local numidx = 1 + out[#out+1] = "{" + for key,val in pairs(value) do + if key == numidx then + numidx = numidx + 1 + else + out[#out+1] = keycache[key] + end + local str = dumplua(val) + out[#out+1] = str.."," + end + if string.sub(out[#out], -1) == "," then + out[#out] = string.sub(out[#out], 1, -2); + end + out[#out+1] = "}" + return "" + end + else + fcts.table = function (value, ident, path) + if test_defined(value, path) then return "nil" end + -- Table value + local sep, str, numidx, totallen = " ", {}, 1, 0 + local meta, metastr = (debug or getfenv()).getmetatable(value) + if meta then + ident = ident + 1 + metastr = dumplua(meta, ident, "getmetatable("..path..")") + totallen = totallen + #metastr + 16 + end + for _,key in pairs(keys(value)) do + local val = value[key] + local s = "" + local subpath = path + if key == numidx then + subpath = subpath .. "[" .. numidx .. "]" + numidx = numidx + 1 + else + s = keycache[key] + if not s:match "^%[" then subpath = subpath .. "." end + subpath = subpath .. s:gsub("%s*=%s*$","") + end + s = s .. dumplua(val, ident+1, subpath) + str[#str+1] = s + totallen = totallen + #s + 2 + end + if totallen > 80 then + sep = "\n" .. string_rep(' ', indent_step*(ident+1)) + end + str = "{"..sep..table_concat(str, ","..sep).." "..sep:sub(1,-1-indent_step).."}" + if meta then + sep = sep:sub(1,-3) + return "setmetatable("..sep..str..","..sep..metastr..sep:sub(1,-3)..")" + end + return str + end + fcts['function'] = function (value, ident, path) + if test_defined(value, path) then return "nil" end + if c_functions[value] then + return c_functions[value] + elseif debug == nil or debug.getupvalue(value, 1) == nil then + return string_format("loadstring(%q)", string_dump(value)) + end + closure_cnt = closure_cnt + 1 + local res = {string.dump(value)} + for i = 1,math.huge do + local name, v = debug.getupvalue(value,i) + if name == nil then break end + res[i+1] = v + end + return "closure " .. dumplua(res, ident, "closures["..closure_cnt.."]") + end + end + function dumplua(value, ident, path) + return fcts[type(value)](value, ident, path) + end + if varname == nil then + varname = "return " + elseif varname:match("^[%a_][%w_]*$") then + varname = varname .. " = " + end + if fastmode then + setmetatable(keycache, {__index = make_key }) + out[1] = varname + table.insert(out,dumplua(value, 0)) + return table.concat(out) + else + setmetatable(keycache, {__index = make_key }) + local items = {} + for i=1,10 do items[i] = '' end + items[3] = dumplua(value, ident or 0, "t") + if closure_cnt > 0 then + items[1], items[6] = dumplua_closure:match("(.*\n)\n(.*)") + out[#out+1] = "" + end + if #out > 0 then + items[2], items[4] = "local t = ", "\n" + items[5] = table.concat(out) + items[7] = varname .. "t" + else + items[2] = varname + end + return table.concat(items) + end +end + +return _ENV diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 3eeb0cc6f..68f5731a4 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -1,5 +1,7 @@ local _ENV = mkmodule('utils') +local df = df + -- Comparator function function compare(a,b) if a < b then @@ -26,6 +28,43 @@ function compare_name(a,b) end end +-- Make a field comparator +function compare_field(field,cmp) + cmp = cmp or compare + if field then + return function (a,b) + return cmp(a[field],b[field]) + end + else + return cmp + end +end + +-- Make a comparator of field vs key +function compare_field_key(field,cmp) + cmp = cmp or compare + if field then + return function (a,b) + return cmp(a[field],b) + end + else + return cmp + end +end + +function is_container(obj) + return df.isvalid(obj) == 'ref' and obj._kind == 'container' +end + +-- Make a sequence of numbers in 1..size +function make_index_sequence(size) + local index = {} + for i=1,size do + index[i] = i + end + return index +end + --[[ Sort items in data according to ordering. @@ -75,10 +114,7 @@ function make_sort_order(data,ordering) end -- Make an order table - local index = {} - for i=1,size do - index[i] = i - end + local index = make_index_sequence(size) -- Sort the ordering table table.sort(index, function(ia,ib) @@ -119,7 +155,7 @@ function assign(tgt,src) df.assign(tgt, src) elseif type(tgt) == 'table' then for k,v in pairs(src) do - if type(v) == 'table' or df.isvalid(v) == 'ref' then + if type(v) == 'table' then local cv = tgt[k] if cv == nil then cv = {} @@ -136,4 +172,131 @@ function assign(tgt,src) return tgt end +local function copy_field(obj,k,v,deep) + if v == nil then + return NULL + end + if deep then + local field = obj:_field(k) + if field == v then + return clone(v,deep) + end + end + return v +end + +-- Copy the object as lua data structures. +function clone(obj,deep) + if type(obj) == 'table' then + if deep then + return assign({},obj) + else + return copyall(obj) + end + elseif df.isvalid(obj) == 'ref' then + local kind = obj._kind + if kind == 'primitive' then + return obj.value + elseif kind == 'bitfield' then + local rv = {} + for k,v in pairs(obj) do + rv[k] = v + end + return rv + elseif kind == 'container' then + local rv = {} + for k,v in ipairs(obj) do + rv[k+1] = copy_field(obj,k,v,deep) + end + return rv + else -- struct + local rv = {} + for k,v in pairs(obj) do + rv[k] = copy_field(obj,k,v,deep) + end + return rv + end + else + return obj + end +end + +-- Sort a vector or lua table +function sort_vector(vector,field,cmp) + local fcmp = compare_field(field,cmp) + local scmp = function(a,b) + return fcmp(a,b) < 0 + end + if df.isvalid(vector) then + if vector._kind ~= 'container' then + error('Container expected: '..tostring(vector)) + end + local items = clone(vector, true) + table.sort(items, scmp) + vector:assign(items) + else + table.sort(vector, scmp) + end + return vector +end + +-- Binary search in a vector or lua table +function binsearch(vector,key,field,cmp,min,max) + if not(min and max) then + if df.isvalid(vector) then + min = -1 + max = #vector + else + min = 0 + max = #vector+1 + end + end + local mf = math.floor + local fcmp = compare_field_key(field,cmp) + while true do + local mid = mf((min+max)/2) + if mid <= min then + return nil, false, max + end + local item = vector[mid] + local cv = fcmp(item, key) + if cv == 0 then + return item, true, mid + elseif cv < 0 then + min = mid + else + max = mid + end + end +end + +-- Binary search and insert +function insert_sorted(vector,item,field,cmp) + local key = item + if field and item then + key = item[field] + end + local cur,found,pos = binsearch(vector,key,field,cmp) + if found then + return false,cur,pos + else + if df.isvalid(vector) then + vector:insert(pos, item) + else + table.insert(vector, pos, item) + end + return true,vector[pos],pos + end +end + +-- Binary search, then insert or overwrite +function insert_or_replace(vector,item,field,cmp) + local added,cur,pos = insert_sorted(vector,item,field,cmp) + if not added then + vector[pos] = item + cur = vector[pos] + end + return added,cur,pos +end + return _ENV \ No newline at end of file diff --git a/scripts/devel/list-filters.lua b/scripts/devel/list-filters.lua new file mode 100644 index 000000000..69fb57164 --- /dev/null +++ b/scripts/devel/list-filters.lua @@ -0,0 +1,96 @@ +-- List input items for the building currently being built. + +local dumper = require 'dumper' + +local function copy_flags(tgt,src,name) + local val = src[name] + if val.whole == 0 then + return + end + local out = {} + tgt[name] = out + for k,v in pairs(val) do + if v then + out[k] = v + end + end +end + +local function copy_value(tgt,src,name,defval) + if src[name] ~= defval then + tgt[name] = src[name] + end +end +local function copy_enum(tgt,src,name,defval,ename,enum) + if src[name] ~= defval then + tgt[name] = ename..'.'..enum[src[name]] + end +end + +local lookup = {} + +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 = df.global.world.items.all + elseif id == df.items_other_id.BAD then + ptr = df.global.world.items.bad + else + ptr = df.global.world.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 = {} + src.flags2.allow_artifact = false + if quantity ~= 1 then + tgt.quantity = quantity + end + copy_enum(tgt, src, 'item_type', -1, 'df.item_type', df.item_type) + copy_value(tgt, src, 'item_subtype', -1) + copy_value(tgt, src, 'mat_type', -1) + copy_value(tgt, src, 'mat_index', -1) + copy_flags(tgt, src, 'flags1') + copy_flags(tgt, src, 'flags2') + copy_flags(tgt, src, 'flags3') + copy_value(tgt, src, 'reaction_class', '') + copy_value(tgt, src, 'has_material_reaction_product', '') + copy_value(tgt, src, 'metal_ore', -1) + copy_value(tgt, src, 'min_dimension', -1) + copy_enum(tgt, src, 'has_tool_use', -1, '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) + local out = string.gsub(fmt, '"', '') + print(out) +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 diff --git a/scripts/lua-example.lua b/scripts/devel/lua-example.lua similarity index 100% rename from scripts/lua-example.lua rename to scripts/devel/lua-example.lua diff --git a/scripts/fix/item-occupancy.lua b/scripts/fix/item-occupancy.lua new file mode 100644 index 000000000..b5466b7a8 --- /dev/null +++ b/scripts/fix/item-occupancy.lua @@ -0,0 +1,125 @@ +-- Verify item occupancy and block item list integrity. +-- +-- Checks: +-- 1) Item has flags.on_ground <=> it is in the correct block item list +-- 2) A tile has items in block item list <=> it has occupancy.item +-- 3) The block item lists are sorted. + +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 + dfhack.printerr('Invalid option: '..opt) + return + end +end + +print("Checking item occupancy - this will take a few seconds.") +check_block_items(fix)