v50 Steam/Premium information for editors
- v50 information can now be added to pages in the main namespace. v0.47 information can still be found in the DF2014 namespace. See here for more details on the new versioning policy.
- Use this page to report any issues related to the migration.
This notice may be cached—the current version can be found here.
Difference between revisions of "User:Fleeting Frames/constructmultiz"
Jump to navigation
Jump to search
(Ver 1.01) |
(No difference)
|
Revision as of 12:21, 30 March 2020
See Forum thread for usage.
--lets one construct simultaneously on z-axis
local helptext = [===[
constructonmultiplezlevels
==========================
version 1.01 (beta)
Script to construct multiple z-levels of buildings at once.
Currently uses general seeking for matching material and type,
expect for bucket, barrel, chain, mechanism, screw, pipe, anvil.
General: Dwarf will use the matching item closest to them.
Usage:
constructmultiz x y
orders x above and y below same buildings as the last one.
constructmultiz display
Displays an indicator_screen in bottom-right for bound x and y
Also enables Ctrl+A and Ctrl+B to place bindings
constructmultiz bind
Adds keybinds to call this when building and exits
constructmultiz unbind
Removes keybinds to call this when building and exits
constructmultiz bindings
displays list of what bindings it would use and exits]===]
local args = {...}
local argsline = ""
if args then argsline = table.concat(args, " ") end
if not args or argsline:find("help") or argsline:find("?") then
print(helptext)
qerror("")
end
function getCurHeightAndDepth()
local keybinding_text = dfhack.run_command_silent("keybinding list Enter")
--get all enter keybinds
--I'd need to see D and shift-enter ones too if this was setting them
if not keybinding_text:find("No bindings") then --No bindings, ergo nothing to do
for single_bind in keybinding_text:gmatch("%s%s[^%s].-\n") do
--iterate through all Enter binds
if single_bind:find('constructmultiz') then
--found one constructmultiz under enter,
local twonumbers = single_bind:match(":.*")
--isolating it and returning first and last number in the command
return tonumber(twonumbers:match("%d+")), tonumber(twonumbers:match(" %d+%s$"))
end
end
end
return 0, 0 --if found nothing, reset to nothing
end
function initBindings()
local keybinding_list = {} --hides higher-lever list via same name
table.insert(keybinding_list, 'B@dwarfmode/Default "constructmultiz display"')
table.insert(keybinding_list, 'D@dwarfmode/Build/Material/Groups "constructmultiz ' .. curheight .. ' ' .. curdepth .. '"')
table.insert(keybinding_list, 'Enter@dwarfmode/Build/Position/FarmPlot "constructmultiz ' .. curheight .. ' ' .. curdepth .. '"')
table.insert(keybinding_list, 'Enter@dwarfmode/Build/Position/RoadDirt "constructmultiz ' .. curheight .. ' ' .. curdepth .. '"')
table.insert(keybinding_list, 'Enter@dwarfmode/Build/Material/Groups "constructmultiz ' .. curheight .. ' ' .. curdepth .. '"')
table.insert(keybinding_list, 'Shift-Enter@dwarfmode/Build/Material/Groups "constructmultiz ' .. curheight .. ' ' .. curdepth .. '"')
return keybinding_list
end
local function constructMultiZPowerState(enable)
for bindingi=1, #keybinding_list do
dfhack.run_command_silent("keybinding " .. (enable and "add " or "clear ") .. keybinding_list[bindingi])
end
end
curheight, curdepth = getCurHeightAndDepth()
keybinding_list = initBindings()
if argsline:find("unbind") then
constructMultiZPowerState(false)
qerror("")
elseif argsline:find("bindings") then
for bindingi=1, #keybinding_list do
dfhack.println(keybinding_list[bindingi])
end
dfhack.println('(Internal with gui) Ctrl-A@dwarfmode/Build/Type "constructmultiz display adjust height"')
dfhack.println('(Internal with gui) Ctrl-B@dwarfmode/Build/Type "constructmultiz display adjust depth"')
qerror("")
elseif argsline:find("bind") then
constructMultiZPowerState(true)
qerror("")
end
if argsline:find("display") then
local function adjustbinds(height, depth)
constructMultiZPowerState(false)
--first, remove old binds
curheight = height+curheight
curdepth = depth+curdepth
--figure out new heights
if curheight < 0 then curheight = 0 end
if curdepth < 0 then curdepth = 0 end
--if they're negative, limit them to 0
keybinding_list = initBindings()
--reconstruct binding table
constructMultiZPowerState(true)
--then add new bindings
end
local indipresent, indi = pcall(function() return dfhack.script_environment('gui/indicator_screen') end)
--attempt to get indicator_screen - not necessarily present.
local changeHeight --defined below
local gui = require 'gui'
local dlg = require 'gui.dialogs'
function numberprompt (above, initialvalue)
local retvalue = tostring(initialvalue)
dlg.showInputPrompt(
'Change number for ' .. (above and "above" or "below"),
('Enter a new number for the building ' .. (above and 'height:' or 'depth:')), COLOR_GREEN,
retvalue,
function(changedvalue)
changeHeight (above, (tonumber(changedvalue))- (above and curheight or curdepth))
end
)
end
if indipresent then
local texts
changeHeight = function(above, newvalue)
if above then
adjustbinds(newvalue, 0)
texts[3].text = " " .. curheight .. " "
texts[3].color = (curheight ~= 0 and 10 or 8)
else
adjustbinds(0, newvalue)
texts[5].text = " " .. curdepth .. " "
texts[5].color = (curdepth ~= 0 and 10 or 8)
end
end
texts = {
--Building # above and # below was initial text plan
--Build # up & # low(Ctrl+A/B) was new, to fit binds >_>
{text = "" }, --empty table to adjust signature location 1 up.
{text = "Build", notEndOfLine = true},
{text = " " .. curheight .. " ", color = (curheight ~= 0 and 10 or 8), notEndOfLine = true,
onclick = function() changeHeight(true,1) end, onrclick = function() changeHeight(true,-1) end},
{text = "up &", notEndOfLine = true},
{text = " " .. curdepth .. " ", color = (curdepth ~= 0 and 10 or 8), notEndOfLine = true,
onclick = function() changeHeight(false,1) end, onrclick = function() changeHeight(false,-1) end},
{text = "low(", notEndOfLine = true},
{text = "Ctrl+A", color = 12, notEndOfLine = true, onclick = function() numberprompt(true, curheight) end},
{text = "/", color = 4, notEndOfLine = true, onclick = function() numberprompt(false, curheight) end},
{text = "B", color = 12, notEndOfLine = true, onclick = function() numberprompt(false, curheight) end},
{text = ")"},
color = 7
}
if indi.indicator_screen_version >= 1.1 then
texts.onhoverfunction = function()
dfhack.screen.paintString(2, df.global.gps.dimx-30,df.global.gps.dimy -7,"(r-)click to nudge numbers")
end
end
local newscreen = indi.getScreen(texts, {x = -30, y = -7})
function newscreen:onResize() self:adjustDims(true, -30, -7) end --ordinary resize maintains topleft position
--newscreen.signature = false --would otherwise cover date indicator
local oldInput = newscreen.onInput
local function repeatfunction(rfunc, stopconditionfunc, N)
--takes two functions and number N, evalutes second every N frames until it is true, then calls first.
local myrfunc
myrfunc = function ()
dfhack.timeout(N, "frames", function()
if stopconditionfunc() then rfunc() else myrfunc() end
end)
end
myrfunc()
end
local function isInBuildType()
return dfhack.gui.getFocusString(df.global.gview.view.child):find("dwarfmode/Build/Type")
end
local notincountdown = true
local function multizonInput(self, keys)
oldInput(self, keys)
if (dfhack.gui.getFocusString(df.global.gview.view.child):find("dwarfmode/Build/Position/Construction")
or (keys.SELECT and
dfhack.gui.getFocusString(df.global.gview.view.child):find("dwarfmode/Build/Material/Groups") ) )
and notincountdown then
notincountdown = false
texts[1].text = "Hiding this indicator in 2"
texts[1].color = 12
dfhack.timeout(math.floor(df.global.enabler.fps), "frames", function()
texts[1].text = "Hiding this indicator in 1" end)
dfhack.timeout(math.floor(2*df.global.enabler.fps), "frames", function()
texts[1].text = ""
if indi.indicator_screen_version >= 1.1 then
self._native.parent.child = nil
repeatfunction(function()
indi.placeOnTop(self._native)
notincountdown = true
end, isInBuildType, 10)
else self:dismiss() end
end)
end
if keys.CUSTOM_CTRL_A then
numberprompt(true, curheight) --adusting height
elseif keys.CUSTOM_CTRL_B then
numberprompt(false, curdepth) --adjusting depth
end
end
newscreen.onInput = multizonInput
newscreen:show()
elseif argsline:find("display adjust") then
changeHeight =
function (above, newvalue)
if above then
adjustbinds(newvalue, 0)
else
adjustbinds(0, newvalue)
end
end
if argsline:find("display adjust height") then
numberprompt(true, curheight)
elseif argsline:find("display adjust depth") then
numberprompt(false, curdepth)
end
else
dfhack.run_command('keybinding add Ctrl-A@dwarfmode/Build/Type "constructmultiz display adjust height"')
dfhack.run_command('keybinding add Ctrl-B@dwarfmode/Build/Type "constructmultiz display adjust depth"')
dfhack.run_command_silent('keybinding clear B@dwarfmode/Default "constructmultiz display"')
end
end
function getFlag(object, flag)
-- Utility function for safely requesting info from userdata
-- Returns nil if the object doesn't have flag attribute, else returns it's value
-- Because well, ordinarily, {}[flag] returns nil.
-- However, if object is unit - or some other type, it may instead throw an error
local a = {}
if not object or not flag then return nil end
--Crash is still possible for attempting to pairs a nil
for index, value in pairs(object) do
a[index] = value
end
local returnvalue = a[flag]
a = nil --lua automatically garbage cleans tables without variable that links to them.
return returnvalue
end
local context = dfhack.gui.getCurFocus()
if #args == 2 and (
not (context:find("dwarfmode/Build/Position/FarmPlot")
or context:find("dwarfmode/Build/Position/RoadDirt")
or context:find("dwarfmode/Build/Material/Groups") )) then
--Because one can choose multiple materials with enter, the script should only launch after last material
function getBuildingTypeIndex(building_type_name)
local simplename = building_type_name
:gsub("_.", string.upper)
:gsub("_", "")
:gsub("<building","")
:gsub("st:.*","")
if df.building_type[simplename] then return df.building_type[simplename] end
--above fails for <building_farmplotst: 0xetcetera> resulting in Farmplot instead of FarmPlot, so...
for i = df.building_type._first_item, df.building_type._last_item do
if df.building_type[i]:lower() == simplename:lower() then
return i
end
end
end
local zpositive = tonumber(args[1]) -- 0 0 should equal nothing being done
local znegative = tonumber(args[2])
if zpositive > 0 or znegative > 0 then
local buildings = require('dfhack.buildings')
--never used that local, so idk how useful it is *shrug*
--copy1building takes a preexisting building and orders a really similar thing built x zlevels above or below
--its' called in a for loop, so better take variable declarations out of it when reasonable
local flippedPumps = 0 -- for multiz pumpstacks
local bd, argumenttable
local usemyfilterbuildings = {} -- Small list that gets special handling.
usemyfilterbuildings[df.building_type.RoadPaved] = true --Paved Road
usemyfilterbuildings[df.building_type.Bridge] = true --Bridge
usemyfilterbuildings[df.building_type.WindowGem] = true --Gem Window
usemyfilterbuildings[df.building_type.Weapon] = true --Upright weapon
usemyfilterbuildings[df.building_type.Bookcase] = true --Bookcase
local tostring2 = tostring
local tadd = table.insert --tadd is less missleading than tin or tins
function copy1building(lbd, zoffset)
bd = {}
bd.x = lbd.x1
bd.width = 1+lbd.x2-lbd.x1
bd.height = 1+lbd.y2-lbd.y1
bd.y = lbd.y1
bd.z = lbd.z+zoffset
bd.type = getBuildingTypeIndex(tostring2(lbd))
if getFlag(lbd, "type") then bd.subtype = lbd.type
elseif getFlag(lbd, "trap_type") then bd.subtype = lbd.trap_type
elseif getFlag(lbd, "bait_type") then bd.subtype = lbd.bait_type end
if getFlag(lbd, "custom_type") then bd.custom = lbd.custom_type end
--bd.items = {df.item.find(22603)} --how to build with items instead
bd.filters = {}
if not (usemyfilterbuildings[bd.type]
or (bd.type == df.building_type.Trap and bd.subtype == 4 )) then -- All traps other than weapon trap
argumenttable = {material = {mat_type = lbd.mat_type, mat_index = lbd.mat_index,}}
if getFlag(lbd.jobs[0].items, 0) then
argumenttable.material.item_type =
df.item_type[tostring2(lbd.jobs[0].items[0].item):gsub("st:.*",""):gsub("<item_",""):upper()]
end
local getfilters = dfhack.buildings.getFiltersByType(argumenttable, bd.type, bd.subtype, bd.custom)
--TODO: Figure out if fixing ash with item_type breaks anything due first item being not as intended
-- i.e. mat_index and mat_type legal mismatch.
--roads, maybe?
--if it is in order of age, maybe constructing ashery with ash bar and newer/older bucket?
--ditto for weapon traps and such.
--farm plot and dirt road doesn't have any items.
--[=====[ Machinery:
--lever, gear assembly, roller report unknown material?
-- Specificaly, for unknown mat gear assembly item_type and vector_id get passed, mat_type and mat_index not
-- The worker picks the mechanism closest to them, not closest to building site or newest or oldest.
-- Weapons:
-- upright spears only request 1 spike, weapon traps 1 any mechanism and 1 any weapon.
-- Multi-mat buildings:
-- Gem windows only request quantity 3 any small gem of first? newest? base material? Works fine, though
-- roads have same trouble AND soap+boulder road doesn't get design.flags.rough = true
-- bridges probably too
-- The lack of item type (being set to -1) might also be a problem. Can specify it via job items tho.
-- Also at least the road doesn't use willy-nilly highwood non-logs, so there's that.
--for bucket/weapon/mechanism/etc. it only specifies appropriate item type, vector id and flags, not material
--q: do I want to restrict these when mass-producing?
--a: weapon and mechanism maybe. anvil def not. screw, pipe, chain, bucket, barrel eh.
--instrument is untested
--]=====]
if getfilters then
--some cases, it can't get filters.
for i=1, #getfilters do
tadd(bd.filters,
getfilters[i])
end
else
qerror("Can't find appropriate building materials for building " .. tostring2(lbd))
end
elseif bd.type == df.building_type.Bookcase then --bookcase
tadd(bd.filters, {
item_type = df.item_type.TOOL,
item_subtype = lbd.jobs[0].items[0].item.subtype.subtype,
has_tool_use = lbd.jobs[0].items[0].item.subtype.tool_use[0], --both of these are unnecessary tbh.
mat_index = lbd.mat_index,
mat_type = lbd.mat_type,
new = true
})
elseif bd.type == df.building_type.RoadPaved or
bd.type == df.building_type.Bridge or
bd.type == df.building_type.WindowGem then
for itemindex = 0, #lbd.jobs[0].items-1 do
tadd(bd.filters, {
item_type = df.item_type[tostring2(lbd.jobs[0].items[itemindex].item)
:gsub("st:.*",""):gsub("<item_",""):upper()],
mat_index = lbd.jobs[0].items[itemindex].item.mat_index,
mat_type = lbd.jobs[0].items[itemindex].item.mat_type,
quantity = 1,
new = true,
flags2 = (bd.type ~= df.building_type.WindowGem and {building_material = true, non_economic = false} or nil)
})
end
elseif bd.type == df.building_type.Trap and bd.subtype == 4
or bd.type == df.building_type.Weapon then
--Weapon traps or upright weapons
--How specific do I want to be with those two?
--First is the matter of component weapon.
--Second is the matter of mechanism.
--When the hell does one even buid multiple zs of weapon traps?
--When building snaking dodge-me setups, perhaps.
--In that case, would want same quality mechanism (impossible)
--And either random weapons made from goblinite or all serrated glass discs and such.
--The latter is more important to not screw up than the former.
local itemfilter
for itemindex = 0, #lbd.jobs[0].items-1 do
itemfilter = {
item_type = df.item_type[tostring2(lbd.jobs[0].items[itemindex].item)
:gsub("st:.*",""):gsub("<item_",""):upper()],
quantity = 1,
new = true
}
if itemfilter.item_type == df.item_type.TRAPPARTS then
--Keep grabbing random closest mechanism.
itemfilter.vector_id = df.job_item_vector_id.TRAPPARTS --probs unnecessary
else
--Utilize weapon or trapcomp with exact same material and type.
itemfilter.mat_index = lbd.jobs[0].items[itemindex].item.mat_index
itemfilter.mat_type = lbd.jobs[0].items[itemindex].item.mat_type
itemfilter.item_subtype = lbd.jobs[0].items[itemindex].item.subtype.subtype
end
tadd(bd.filters, itemfilter)
end
end
local createdbuilding = dfhack.buildings.constructBuilding(bd)
--Here is where I would have to reorient pumps, rollers, bridges, horizontal axles, water wheels
-- Also, I'm getting inconsistencent failures now.
if createdbuilding then
if getFlag(createdbuilding,"direction") then --pump, roller, bridge
createdbuilding.direction = lbd.direction
if bd.type == df.building_type.ScrewPump then
--flip screw pump around
createdbuilding.direction = (createdbuilding.direction +2*(1+flippedPumps) ) % 4
flippedPumps = 1+flippedPumps
end
if bd.type == df.building_type.Rollers and lbd.speed ~= 50000 then
--roller speed adjusment, if necessary
createdbuilding.speed = lbd.speed
end
elseif getFlag(createdbuilding,"is_vertical") ~= nil then --horizontal axle, water wheel
--need to fix both orientation and dimensions
createdbuilding.is_vertical = lbd.is_vertical
end
if getFlag(createdbuilding, "friction") then
--probably just track stops, but gets given to all traps at least
createdbuilding.friction = lbd.friction
if getFlag(createdbuilding, "dump_x_shift") then --track stop dump direction
createdbuilding.dump_x_shift = lbd.dump_x_shift
createdbuilding.dump_y_shift = lbd.dump_y_shift
createdbuilding.use_dump = lbd.use_dump
end
end
if bd.type == 23 and bd.subtype == 1 then --pressure plate
for index, v in pairs(createdbuilding.plate_info) do
if index ~= "flags" then
--set all plate setting to prototype's
createdbuilding.plate_info[index] = lbd.plate_info[index]
else
for flagname, flagstate in pairs(v) do
--set all plate enabled states to prototype's
createdbuilding.plate_info[index][flagname] = lbd.plate_info[index][flagname]
end
end
end
end
if bd.type == df.building_type.RoadPaved or --Paved road
bd.type == df.building_type.Bridge then --Bridge
-- Buildings for which using boulders changes appearance.
-- Could also pick and iterate through the design of any architecture,
-- but that shouldn't be necessary.
createdbuilding.design.flags.rough = lbd.design.flags.rough
end
if (getFlag(createdbuilding,"is_vertical") or getFlag(createdbuilding,"direction") )
and bd.type ~= df.building_type.Bridge then --bridges are already perfect in x and y
createdbuilding.x1 = lbd.x1
createdbuilding.y1 = lbd.y1
createdbuilding.x2 = lbd.x2
createdbuilding.y2 = lbd.y2
end
createdbuilding.centerx = lbd.centerx
createdbuilding.centery = lbd.centery
--constructbuilding tends to round center up where df rounds down.
if flippedPumps % 2 ~= 0 and bd.type == df.building_type.ScrewPump then
--screw pump direction was swapped, so it'd center tile has to be swapped as well
--otherwise dwarf tries to build it on impassable tile.
if createdbuilding.direction % 2 == 0 then --northsouth
createdbuilding.centery = createdbuilding.centery == createdbuilding.y1 and createdbuilding.y2 or createdbuilding.y1
else --eastwest
createdbuilding.centerx = createdbuilding.centerx == createdbuilding.x1 and createdbuilding.x2 or createdbuilding.x1
end
end
end
end
local lenb = #df.global.world.buildings.all --changes during usage so must save beforehand
local w,h
if getBuildingTypeIndex(tostring(df.global.world.buildings.all[lenb-1])) ~= 34 then
--not construction
w, h = 1,1
else
w = df.global.world.building_width
h = df.global.world.building_height
end
for i = 1, w*h do
if zpositive > 0 then
for zoff = 1, zpositive do
copy1building(df.global.world.buildings.all[lenb-i],zoff)
end
end
if znegative > 0 then
for zoff = -1, -znegative, -1 do
copy1building(df.global.world.buildings.all[lenb-i],zoff)
end
end
end
end
end