diff --git a/LUA_API.rst b/LUA_API.rst index bb1ef803e..c42785278 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -971,6 +971,80 @@ Low-level building creation functions; More high-level functions are implemented in lua and can be loaded by ``require('dfhack.buildings')``. See ``hack/lua/dfhack/buildings.lua``. +Among them are: + +* ``dfhack.buildings.getFiltersByType(argtable,type,subtype,custom)`` + + Returns a sequence of lua structures, describing input item filters + suitable for the specified building type, or *nil* if unknown or invalid. + The returned sequence is suitable for use as the ``job_items`` argument + of ``constructWithFilters``. + Uses tables defined in ``buildings.lua``. + + Argtable members ``material`` (the default name), ``bucket``, ``barrel``, + ``chain``, ``mechanism``, ``screw``, ``pipe``, ``anvil``, ``weapon`` are used to + augment the basic attributes with more detailed information if the + building has input items with the matching name (see the tables for naming details). + Note that it is impossible to *override* any properties this way, only supply those that + are not mentioned otherwise; one exception is that flags2.non_economic + is automatically cleared if an explicit material is specified. + +* ``dfhack.buildings.constructBuilding{...}`` + + Creates a building in one call, using options contained + in the argument table. Returns the building, or *nil, error*. + + **NOTE:** Despite the name, unless the building is abstract, + the function creates it in an 'unconstructed' stage, with + a queued in-game job that will actually construct it. I.e. + the function replicates programmatically what can be done + through the construct building menu in the game ui, except + that it does less environment constraint checking. + + The following options can be used: + + - ``pos = coordinates``, or ``x = ..., y = ..., z = ...`` + + Mandatory. Specifies the left upper corner of the building. + + - ``type = df.building_type.FOO, subtype = ..., custom = ...`` + + Mandatory. Specifies the type of the building. Obviously, subtype + and custom are only expected if the type requires them. + + - ``fields = { ... }`` + + Initializes fields of the building object after creation with ``df.assign``. + + - ``width = ..., height = ..., direction = ...`` + + Sets size and orientation of the building. If it is + fixed-size, specified dimensions are ignored. + + - ``full_rectangle = true`` + + For buildings like stockpiles or farm plots that can normally + accomodate individual tile exclusion, forces an error if any + tiles within the specified width*height are obstructed. + + - ``items = { item, item ... }``, or ``filters = { {...}, {...}... }`` + + Specifies explicit items or item filters to use in construction. + It is the job of the user to ensure they are correct for the building type. + + - ``abstract = true`` + + Specifies that the building is abstract and does not require construction. + Required for stockpiles and civzones; an error otherwise. + + - ``material = {...}, mechanism = {...}, ...`` + + If none of ``items``, ``filter``, or ``abstract`` is used, + the function uses ``getFiltersByType`` to compute the input + item filters, and passes the argument table through. If no filters + can be determined this way, ``constructBuilding`` throws an error. + + Constructions module -------------------- diff --git a/Lua API.html b/Lua API.html index 3e023ebd3..ed2e9f6a9 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1163,6 +1163,69 @@ Returns true if the building was destroyed and deallocated immediately.

More high-level functions are implemented in lua and can be loaded by require('dfhack.buildings'). See hack/lua/dfhack/buildings.lua.

+

Among them are:

+

Constructions module

diff --git a/library/lua/dfhack/buildings.lua b/library/lua/dfhack/buildings.lua index eb0a6a5b5..ad82da89a 100644 --- a/library/lua/dfhack/buildings.lua +++ b/library/lua/dfhack/buildings.lua @@ -4,7 +4,32 @@ local buildings = dfhack.buildings local utils = require 'utils' ---[[ Building input material tables. ]] +-- Uninteresting values for filter attributes when reading them from DF memory. +-- Differs from the actual defaults of the job_item constructor in allow_artifact. + +buildings.input_filter_defaults = { + item_type = -1, + item_subtype = -1, + mat_type = -1, + mat_index = -1, + flags1 = {}, + -- Instead of noting those that allow artifacts, mark those that forbid them. + -- Leaves actually enabling artifacts to the discretion of the API user, + -- which is the right thing because unlike the game UI these filters are + -- used in a way that does not give the user a chance to choose manually. + flags2 = { allow_artifact = true }, + flags3 = {}, + flags4 = 0, + flags5 = 0, + reaction_class = '', + has_material_reaction_product = '', + metal_ore = -1, + min_dimension = -1, + has_tool_use = -1, + quantity = 1 +} + +--[[ Building input material table. ]] local building_inputs = { [df.building_type.Chair] = { { item_type=df.item_type.CHAIR, vector_id=df.job_item_vector_id.CHAIR } }, @@ -12,7 +37,6 @@ local building_inputs = { [df.building_type.Table] = { { item_type=df.item_type.TABLE, vector_id=df.job_item_vector_id.TABLE } }, [df.building_type.Coffin] = { { item_type=df.item_type.COFFIN, vector_id=df.job_item_vector_id.COFFIN } }, [df.building_type.FarmPlot] = { }, - [df.building_type.Furnace] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } }, [df.building_type.TradeDepot] = { { flags2={ building_material=true, non_economic=true }, quantity=3 } }, [df.building_type.Door] = { { item_type=df.item_type.DOOR, vector_id=df.job_item_vector_id.DOOR } }, [df.building_type.Floodgate] = { @@ -40,7 +64,6 @@ local building_inputs = { vector_id=df.job_item_vector_id.ARMORSTAND } }, - [df.building_type.Workshop] = { { flags2={ building_material=true, non_economic=true } } }, [df.building_type.Cabinet] = { { item_type=df.item_type.CABINET, vector_id=df.job_item_vector_id.CABINET } }, @@ -89,7 +112,7 @@ local building_inputs = { [df.building_type.ArcheryTarget] = { { flags2={ building_material=true, non_economic=true } } }, [df.building_type.Chain] = { { item_type=df.item_type.CHAIN, vector_id=df.job_item_vector_id.CHAIN } }, [df.building_type.Cage] = { { item_type=df.item_type.CAGE, vector_id=df.job_item_vector_id.CAGE } }, - [df.building_type.Weapon] = { { vector_id=df.job_item_vector_id.ANY_SPIKE } }, + [df.building_type.Weapon] = { { name='weapon', vector_id=df.job_item_vector_id.ANY_SPIKE } }, [df.building_type.ScrewPump] = { { item_type=df.item_type.BLOCKS, @@ -158,6 +181,8 @@ local building_inputs = { [df.building_type.Hive] = { { has_tool_use=df.tool_uses.HIVE, item_type=df.item_type.TOOL } } } +--[[ Furnace building input material table. ]] + local furnace_inputs = { [df.furnace_type.WoodFurnace] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } }, [df.furnace_type.Smelter] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } }, @@ -168,6 +193,8 @@ local furnace_inputs = { [df.furnace_type.MagmaKiln] = { { flags2={ building_material=true, magma_safe=true, non_economic=true } } } } +--[[ Workshop building input material table. ]] + local workshop_inputs = { [df.workshop_type.Carpenters] = { { flags2={ building_material=true, non_economic=true } } }, [df.workshop_type.Farmers] = { { flags2={ building_material=true, non_economic=true } } }, @@ -250,6 +277,8 @@ local workshop_inputs = { } } +--[[ Trap building input material table. ]] + local trap_inputs = { [df.trap_type.StoneFallTrap] = { { @@ -264,7 +293,10 @@ local trap_inputs = { item_type=df.item_type.TRAPPARTS, vector_id=df.job_item_vector_id.TRAPPARTS }, - { vector_id=df.job_item_vector_id.ANY_WEAPON } + { + name='weapon', + vector_id=df.job_item_vector_id.ANY_WEAPON + } }, [df.trap_type.Lever] = { { @@ -289,11 +321,28 @@ local trap_inputs = { } } +--[[ Functions for lookup in tables. ]] + +local function get_custom_inputs(custom) + local defn = df.building_def.find(custom) + if defn ~= nil then + return utils.clone_with_default(defn.build_items, buildings.input_filter_defaults) + end +end + local function get_inputs_by_type(type,subtype,custom) if type == df.building_type.Workshop then - return workshop_inputs[subtype] + if subtype == df.workshop_type.Custom then + return get_custom_inputs(custom) + else + return workshop_inputs[subtype] + end elseif type == df.building_type.Furnace then - return furnace_inputs[subtype] + if subtype == df.furnace_type.Custom then + return get_custom_inputs(custom) + else + return furnace_inputs[subtype] + end elseif type == df.building_type.Trap then return trap_inputs[subtype] else @@ -357,7 +406,7 @@ end -- Materials: items = { item, item ... }, -- OR - filter = { { ... }, { ... }... } + filters = { { ... }, { ... }... } -- OR abstract = true -- OR diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 96ab2b190..e67801f4f 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -221,6 +221,68 @@ function clone(obj,deep) end end +local function get_default(default,key,base) + if type(default) == 'table' then + local dv = default[key] + if dv == nil then + dv = default._default + end + if dv == nil then + dv = base + end + return dv + else + return default + end +end + +-- Copy the object as lua data structures, skipping values matching defaults. +function clone_with_default(obj,default,force) + local rv = nil + local function setrv(k,v) + if v ~= nil then + if rv == nil then + rv = {} + end + rv[k] = v + end + end + if default == nil then + return nil + elseif type(obj) == 'table' then + for k,v in pairs(obj) do + setrv(k, clone_with_default(v, get_default(default,k))) + end + elseif df.isvalid(obj) == 'ref' then + local kind = obj._kind + if kind == 'primitive' then + return clone_with_default(obj.value,default,force) + elseif kind == 'bitfield' then + for k,v in pairs(obj) do + setrv(k, clone_with_default(v, get_default(default,k,false))) + end + elseif kind == 'container' then + for k,v in ipairs(obj) do + setrv(k+1, clone_with_default(v, default, true)) + end + else -- struct + for k,v in pairs(obj) do + setrv(k, clone_with_default(v, get_default(default,k))) + end + end + elseif obj == default and not force then + return nil + elseif obj == nil then + return NULL + else + return obj + end + if force and rv == nil then + rv = {} + end + return rv +end + -- Sort a vector or lua table function sort_vector(vector,field,cmp) local fcmp = compare_field(field,cmp) diff --git a/scripts/devel/list-filters.lua b/scripts/devel/list-filters.lua index 69fb57164..87b9f24b1 100644 --- a/scripts/devel/list-filters.lua +++ b/scripts/devel/list-filters.lua @@ -1,43 +1,29 @@ -- List input items for the building currently being built. +-- +-- This is where the filters in lua/dfhack/buildings.lua come from. local dumper = require 'dumper' +local utils = require 'utils' +local buildings = require 'dfhack.buildings' -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]] +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 = df.global.world.items.all + ptr = items.all elseif id == df.items_other_id.BAD then - ptr = df.global.world.items.bad + ptr = items.bad else - ptr = df.global.world.items.other[id] + ptr = items.other[id] end if ptr then local _,addr = df.sizeof(ptr) @@ -46,23 +32,12 @@ for i=df.job_item_vector_id._first_item,df.job_item_vector_id._last_item do end local function clone_filter(src,quantity) - local tgt = {} - src.flags2.allow_artifact = false + local tgt = utils.clone_with_default(src, buildings.input_filter_defaults, true) 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) + 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) @@ -78,8 +53,9 @@ local function dump(name) end local fmt = dumper.DataDumper(out,name,false,1,4) - local out = string.gsub(fmt, '"', '') - print(out) + fmt = string.gsub(fmt, '"(df%.[^"]+)"','%1') + fmt = string.gsub(fmt, '%s+$', '') + print(fmt) end local itype = df.global.ui_build_selector.building_type