diff options
| -rw-r--r-- | LUA_API.rst | 74 | ||||
| -rw-r--r-- | Lua API.html | 63 | ||||
| -rw-r--r-- | library/lua/dfhack/buildings.lua | 65 | ||||
| -rw-r--r-- | library/lua/utils.lua | 62 | ||||
| -rw-r--r-- | scripts/devel/list-filters.lua | 58 |
5 files changed, 273 insertions, 49 deletions
diff --git a/LUA_API.rst b/LUA_API.rst index bb1ef803..c4278527 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 3e023ebd..ed2e9f6a 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1163,6 +1163,69 @@ Returns <em>true</em> if the building was destroyed and deallocated immediately. </ul> <p>More high-level functions are implemented in lua and can be loaded by <tt class="docutils literal"><span class="pre">require('dfhack.buildings')</span></tt>. See <tt class="docutils literal">hack/lua/dfhack/buildings.lua</tt>.</p> +<p>Among them are:</p> +<ul> +<li><p class="first"><tt class="docutils literal">dfhack.buildings.getFiltersByType(argtable,type,subtype,custom)</tt></p> +<p>Returns a sequence of lua structures, describing input item filters +suitable for the specified building type, or <em>nil</em> if unknown or invalid. +The returned sequence is suitable for use as the <tt class="docutils literal">job_items</tt> argument +of <tt class="docutils literal">constructWithFilters</tt>. +Uses tables defined in <tt class="docutils literal">buildings.lua</tt>.</p> +<p>Argtable members <tt class="docutils literal">material</tt> (the default name), <tt class="docutils literal">bucket</tt>, <tt class="docutils literal">barrel</tt>, +<tt class="docutils literal">chain</tt>, <tt class="docutils literal">mechanism</tt>, <tt class="docutils literal">screw</tt>, <tt class="docutils literal">pipe</tt>, <tt class="docutils literal">anvil</tt>, <tt class="docutils literal">weapon</tt> 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 <em>override</em> 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.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.buildings.constructBuilding{...}</span></tt></p> +<p>Creates a building in one call, using options contained +in the argument table. Returns the building, or <em>nil, error</em>.</p> +<p><strong>NOTE:</strong> 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.</p> +<p>The following options can be used:</p> +<ul> +<li><p class="first"><tt class="docutils literal">pos = coordinates</tt>, or <tt class="docutils literal">x = <span class="pre">...,</span> y = <span class="pre">...,</span> z = ...</tt></p> +<p>Mandatory. Specifies the left upper corner of the building.</p> +</li> +<li><p class="first"><tt class="docutils literal">type = df.building_type.FOO, subtype = <span class="pre">...,</span> custom = ...</tt></p> +<p>Mandatory. Specifies the type of the building. Obviously, subtype +and custom are only expected if the type requires them.</p> +</li> +<li><p class="first"><tt class="docutils literal">fields = { ... }</tt></p> +<p>Initializes fields of the building object after creation with <tt class="docutils literal">df.assign</tt>.</p> +</li> +<li><p class="first"><tt class="docutils literal">width = <span class="pre">...,</span> height = <span class="pre">...,</span> direction = ...</tt></p> +<p>Sets size and orientation of the building. If it is +fixed-size, specified dimensions are ignored.</p> +</li> +<li><p class="first"><tt class="docutils literal">full_rectangle = true</tt></p> +<p>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.</p> +</li> +<li><p class="first"><tt class="docutils literal">items = { item, item ... }</tt>, or <tt class="docutils literal">filters = { <span class="pre">{...},</span> <span class="pre">{...}...</span> }</tt></p> +<p>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.</p> +</li> +<li><p class="first"><tt class="docutils literal">abstract = true</tt></p> +<p>Specifies that the building is abstract and does not require construction. +Required for stockpiles and civzones; an error otherwise.</p> +</li> +<li><p class="first"><tt class="docutils literal">material = <span class="pre">{...},</span> mechanism = <span class="pre">{...},</span> ...</tt></p> +<p>If none of <tt class="docutils literal">items</tt>, <tt class="docutils literal">filter</tt>, or <tt class="docutils literal">abstract</tt> is used, +the function uses <tt class="docutils literal">getFiltersByType</tt> to compute the input +item filters, and passes the argument table through. If no filters +can be determined this way, <tt class="docutils literal">constructBuilding</tt> throws an error.</p> +</li> +</ul> +</li> +</ul> </div> <div class="section" id="constructions-module"> <h3><a class="toc-backref" href="#id21">Constructions module</a></h3> diff --git a/library/lua/dfhack/buildings.lua b/library/lua/dfhack/buildings.lua index eb0a6a5b..ad82da89 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 96ab2b19..e67801f4 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 69fb5716..87b9f24b 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 |
