Support custom buildings in dfhack.buildings.getFiltersByType.

Also document it and constructBuilding in Lua API docs.
develop
Alexander Gavrilov 2012-05-13 18:39:00 +04:00
parent 87ec1de891
commit 642a625586
5 changed files with 273 additions and 49 deletions

@ -971,6 +971,80 @@ Low-level building creation functions;
More high-level functions are implemented in lua and can be loaded by More high-level functions are implemented in lua and can be loaded by
``require('dfhack.buildings')``. See ``hack/lua/dfhack/buildings.lua``. ``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 Constructions module
-------------------- --------------------

@ -1163,6 +1163,69 @@ Returns <em>true</em> if the building was destroyed and deallocated immediately.
</ul> </ul>
<p>More high-level functions are implemented in lua and can be loaded by <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> <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>
<div class="section" id="constructions-module"> <div class="section" id="constructions-module">
<h3><a class="toc-backref" href="#id21">Constructions module</a></h3> <h3><a class="toc-backref" href="#id21">Constructions module</a></h3>

@ -4,7 +4,32 @@ local buildings = dfhack.buildings
local utils = require 'utils' 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 = { local building_inputs = {
[df.building_type.Chair] = { { item_type=df.item_type.CHAIR, vector_id=df.job_item_vector_id.CHAIR } }, [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.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.Coffin] = { { item_type=df.item_type.COFFIN, vector_id=df.job_item_vector_id.COFFIN } },
[df.building_type.FarmPlot] = { }, [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.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.Door] = { { item_type=df.item_type.DOOR, vector_id=df.job_item_vector_id.DOOR } },
[df.building_type.Floodgate] = { [df.building_type.Floodgate] = {
@ -40,7 +64,6 @@ local building_inputs = {
vector_id=df.job_item_vector_id.ARMORSTAND vector_id=df.job_item_vector_id.ARMORSTAND
} }
}, },
[df.building_type.Workshop] = { { flags2={ building_material=true, non_economic=true } } },
[df.building_type.Cabinet] = { [df.building_type.Cabinet] = {
{ item_type=df.item_type.CABINET, vector_id=df.job_item_vector_id.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.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.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.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] = { [df.building_type.ScrewPump] = {
{ {
item_type=df.item_type.BLOCKS, 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 } } [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 = { local furnace_inputs = {
[df.furnace_type.WoodFurnace] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } }, [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 } } }, [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 } } } [df.furnace_type.MagmaKiln] = { { flags2={ building_material=true, magma_safe=true, non_economic=true } } }
} }
--[[ Workshop building input material table. ]]
local workshop_inputs = { local workshop_inputs = {
[df.workshop_type.Carpenters] = { { flags2={ building_material=true, non_economic=true } } }, [df.workshop_type.Carpenters] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Farmers] = { { 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 = { local trap_inputs = {
[df.trap_type.StoneFallTrap] = { [df.trap_type.StoneFallTrap] = {
{ {
@ -264,7 +293,10 @@ local trap_inputs = {
item_type=df.item_type.TRAPPARTS, item_type=df.item_type.TRAPPARTS,
vector_id=df.job_item_vector_id.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] = { [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) local function get_inputs_by_type(type,subtype,custom)
if type == df.building_type.Workshop then 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 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 elseif type == df.building_type.Trap then
return trap_inputs[subtype] return trap_inputs[subtype]
else else
@ -357,7 +406,7 @@ end
-- Materials: -- Materials:
items = { item, item ... }, items = { item, item ... },
-- OR -- OR
filter = { { ... }, { ... }... } filters = { { ... }, { ... }... }
-- OR -- OR
abstract = true abstract = true
-- OR -- OR

@ -221,6 +221,68 @@ function clone(obj,deep)
end end
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 -- Sort a vector or lua table
function sort_vector(vector,field,cmp) function sort_vector(vector,field,cmp)
local fcmp = compare_field(field,cmp) local fcmp = compare_field(field,cmp)

@ -1,43 +1,29 @@
-- List input items for the building currently being built. -- 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 dumper = require 'dumper'
local utils = require 'utils'
local buildings = require 'dfhack.buildings'
local function copy_flags(tgt,src,name) local function name_enum(tgt,name,ename,enum)
local val = src[name] if tgt[name] ~= nil then
if val.whole == 0 then tgt[name] = ename..'.'..enum[tgt[name]]
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
end end
local lookup = {} 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 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 id = df.job_item_vector_id.attrs[i].other
local ptr local ptr
if id == df.items_other_id.ANY then 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 elseif id == df.items_other_id.BAD then
ptr = df.global.world.items.bad ptr = items.bad
else else
ptr = df.global.world.items.other[id] ptr = items.other[id]
end end
if ptr then if ptr then
local _,addr = df.sizeof(ptr) 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 end
local function clone_filter(src,quantity) local function clone_filter(src,quantity)
local tgt = {} local tgt = utils.clone_with_default(src, buildings.input_filter_defaults, true)
src.flags2.allow_artifact = false
if quantity ~= 1 then if quantity ~= 1 then
tgt.quantity = quantity tgt.quantity = quantity
end end
copy_enum(tgt, src, 'item_type', -1, 'df.item_type', df.item_type) name_enum(tgt, 'item_type', 'df.item_type', df.item_type)
copy_value(tgt, src, 'item_subtype', -1) name_enum(tgt, 'has_tool_use', 'df.tool_uses', df.tool_uses)
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 local ptr = src.item_vector
if ptr and ptr ~= df.global.world.items.other[0] then if ptr and ptr ~= df.global.world.items.other[0] then
local _,addr = df.sizeof(ptr) local _,addr = df.sizeof(ptr)
@ -78,8 +53,9 @@ local function dump(name)
end end
local fmt = dumper.DataDumper(out,name,false,1,4) local fmt = dumper.DataDumper(out,name,false,1,4)
local out = string.gsub(fmt, '"', '') fmt = string.gsub(fmt, '"(df%.[^"]+)"','%1')
print(out) fmt = string.gsub(fmt, '%s+$', '')
print(fmt)
end end
local itype = df.global.ui_build_selector.building_type local itype = df.global.ui_build_selector.building_type