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.
User:Fleeting Frames/constructmultiz
Jump to navigation
Jump to search
See Forum thread for usage.
--lets one construct simultaneously on z-axis
--[[TODO list:
Add checking for minimap being present when rendering
Construct # previous buildings (optionally #a to #b previous buildings or selected building(s))
Allow constructing constructmultiz buildings by simply copying job_filter.
Construct in x, y or fii° direction. Allow cursor position to determine fii° (useful when combined with previous one)
This means way to place cursor. mouse_x,_y will work - on same z-level. Burrow named 'cursor' would work on different z-level.
With lineiterator already present, could theoretically use paintstring to paint the expected line.
Construct on designations of given type, priority and marked/not /construct on adjacent matching designations.
Construct all buildings in a burrow.
Given a set of buildings and set of locations, loop through the buildings when copying.
High priority corners
trigger designation seek when placing on a designation
Better automaterial support: Keeping constructmultiz settings and automaterial's both visible by masking with display and listening to relevant keys
]]
local helptext = [===[
constructonmultiplezlevels
==========================
version 1.34
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
local getBlock = dfhack.maps.getTileBlock
local getFlags = dfhack.maps.getTileFlags
local tin = table.insert
local gui = require 'gui'
local utils = require 'utils'
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
local function getLen(data)
-- Can't # a hashed table, must use pairs.
local len = 0
for i, val in pairs(data) do
len = len +1
end
return len
end
designationMapTable = designationMapTable or false
function getDesignationMapTable()
--doesn't currently support digs and plant gathering/ramp removal
--tree cutting? Irrelevant I guess.
if not designationMapTable then
designationMapTable = {}
designationMapTable[gui.getKeyDisplay("DESIGNATE_DIG")] = {dig = df.tile_dig_designation.Default}
designationMapTable[gui.getKeyDisplay("DESIGNATE_STAIR_UPDOWN")] = {dig = df.tile_dig_designation.UpDownStair}
designationMapTable[gui.getKeyDisplay("DESIGNATE_CHANNEL")] = {dig = df.tile_dig_designation.Channel}
designationMapTable[gui.getKeyDisplay("DESIGNATE_RAMP")] = {dig = df.tile_dig_designation.Ramp}
designationMapTable[gui.getKeyDisplay("DESIGNATE_STAIR_DOWN")] = {dig = df.tile_dig_designation.DownStair}
designationMapTable[gui.getKeyDisplay("DESIGNATE_STAIR_UP")] = {dig = df.tile_dig_designation.UpStair}
--smoothing, plant gathering, probably other designations that don't set dig to 2+ have issue of losing themselves over time (with save and reload?)
designationMapTable[gui.getKeyDisplay("DESIGNATE_SMOOTH")] = {smooth = 1}
designationMapTable[gui.getKeyDisplay("DESIGNATE_ENGRAVE")] = {smooth = 2}
--problematic designations: plant gathering, ramp removing, deconstructing, tree cutting, fortifying, carving tracks
designationMapTable[gui.getKeyDisplay("DESIGNATE_TRACK")] = {carve_track_north =false, carve_track_south =false, carve_track_east=false, carve_track_west=false}
designationMapTable[gui.getKeyDisplay("DESIGNATE_DIG_REMOVE_STAIRS_RAMPS")] = {dig = df.tile_dig_designation.Default, targettype = "^[^C].*Ramp"}
--matching non-constructed ramps
designationMapTable[gui.getKeyDisplay("DESIGNATE_PLANTS")] = {dig = df.tile_dig_designation.Default, targettype = "Shrub"}
--Following two need to check they're on construction
designationMapTable[gui.getKeyDisplay("DESIGNATE_REMOVE_CONSTRUCTION")] = {dig = df.tile_dig_designation.Default, targettype = "Constructed"}
designationMapTable[gui.getKeyDisplay("DESIGNATE_FORTIFY")] = {smooth = 1, targettype = "Constructed"}
end
return designationMapTable
end
local function getPriorities(map_block)
for i,v in pairs(map_block.block_events) do
if v._type == df.block_square_event_designation_priorityst then return v.priority end
end
end
local function decodeDesignation(designationstring)
--designationstring is something like d7mO
--marked is in tileoccupancy[x][y].
--designationtype is in designation[x][y].dig or .smooth
--priority is in blockevents[#ofblock_square_event_designation_priorityst].priority[x][y]
local designationtype = getDesignationMapTable()[designationstring:match('.')]
if getLen(designationtype) == 4 then
designationtype = {
carve_track_north =designationstring:match('n') and true or false,
carve_track_south =designationstring:match('s') and true or false,
carve_track_east=designationstring:match('e') and true or false,
carve_track_west=designationstring:match('w') and true or false}
end
local targettype = designationtype.targettype
designationtype.targettype = nil
local designationpriority = tonumber(designationstring:match('[%d|%,]+'))
local ismarked = designationstring:match('m') and true or false
local checkold = designationstring:match('O') and true or false
return designationtype, designationpriority, ismarked, targettype, checkold
end
function isbetween(b,a,c)
if a<=b and b<=c then return true end
if a>=b and b>=c then return true end
end
local once = true
function isTileInArea(pos,area)
if true then once = false
--print(area, type(area),type(area)=="number",pos.x,pos.y,pos.z,df.global.window_z,(df.global.window_z+area)) --stops script cold for some reason
end--]]
if pos.x < 0 then return false end
if not area then return true end
--print("isbetween",(type(area)=="number") and isbetween(pos.z,df.global.window_z,df.global.window_z+area))
--if type(area)=="number" and (pos.z==27) then print("pos is 27") else print ("pos is "..tostring(pos.z)) end
if type(area)=="number" and isbetween(pos.z,df.global.window_z,(df.global.window_z+area)) then return true
end
if type(area) =="userdata" and area._type == df.burrow and dfhack.burrows.isAssignedTile(area,pos) then return true end
end
local area
--Iterator for iterating through designations of a type in an area or everywhere.
function targetDesignations(designationstring, sarea)
--returns designationIterator, state, key
if sarea and type(sarea)=="string" then
area = dfhack.burrows.findByName(sarea) or
(sarea:find("Burrow %i") and df.burrow.find(tonumber(sarea:match("%i")))) or
tonumber(sarea)
end
local dtype, dpriority, ismarked, targettype, checkold = decodeDesignation(designationstring)
local function iterateBlocks(area)
if not area or type(area)=="number" then
for iB = 0, #df.global.world.map.map_blocks -1 do
if(checkold or df.global.world.map.map_blocks[iB].flags.designated)
and (not area or isbetween(df.global.world.map.map_blocks[iB].map_pos.z,df.global.window_z,df.global.window_z+area)) then
coroutine.yield(df.global.world.map.map_blocks[iB])
end
end
else
for i,v in pairs(dfhack.burrows.listBlocks(area)) do
coroutine.yield(v)
end
end
end
local blockroutine = coroutine.create(iterateBlocks)
local function getNextBlock(area)
local errorfree, value = coroutine.resume(blockroutine,area)
if not errorfree then return nil end
return value
end
local jobroutine,getNextJob
local checkJobs = getJobbedDesignation()[designationstring:sub(0,1)]
print(checkJobs, df.job_type[checkJobs])
if checkJobs then
local function iterateJobs(area)
local job
local IsOld, JobListItem = pcall(function() return df.global.world.job_list end)--TODO replace slow pcall
if not IsOld then JobListItem=df.global.world.jobs.list end
while(JobListItem.next) and ((os.clock()-starttime)<5) do
job = JobListItem.item
rcount = rcount +1
if job and isTileInArea(job.pos,area) then
coroutine.yield(job)
end
JobListItem=JobListItem.next
end
end
jobroutine = coroutine.create(iterateJobs)
getNextJob = function(area)
local errorfree, value = coroutine.resume(jobroutine, area)
if not errorfree then return nil else return value end
end
end
function blockChanged(tx,ty,tz)--TODO do I use dfhack.maps??
local blockx,blocky,blockz
if (tx%16) ~=blockx or (ty%16) ~=blocky or tz ~=blockz then
blockx =tx%16
blocky =ty%16
blockz=tz
return true
end
end
local dtypel = getLen(dtype)
local x,y,z,cblock, priorities
local cy,cx,ct = 0,-1,0 --start for cx -1 so I can add before returning.
cblock, curJob = getNextBlock(area), true
--return #(.th designation) and it's x, y and z coordinates
local function designationIterator(_,nthdesignation)
nthdesignation = nthdesignation +1
--TODO If area was burrow check only burrow tiles not burrow block tiles
while cblock do
priorities = getPriorities(cblock)
while(cy<16) do
while(cx<15) do
cx = cx+1
if not area or area and type(area)=="number" or (type(area) == "userdata" and area._type == df.burrow and dfhack.burrows.isAssignedBlockTile(area,cblock,cx,cy)) then
if(cblock.occupancy[cx][cy].dig_marked == ismarked) then
if((not dpriority) or priorities and priorities[cx][cy] == (dpriority*1000)) then
ct = 0
for key, value in pairs(dtype) do
if(dtypel == 1 and (cblock.designation[cx][cy][key] == value or checkOld and priorities and targettype and cblock.designation[cx][cy][key] == 0)) then
--in case of offloaded designations, designation is zero but priority/marked is kept.
--of course, neither of those are necessarily checked so this might need a TODO fix
--also what's the point of checkOld+targettype here if I check jobs anyway??
--checkold should be more like checkoldmarked, and priorities shouldn't matter
if not targettype or df.tiletype[cblock.tiletype[cx][cy]]:match(targettype) then
return nthdesignation, (cblock.map_pos.x+cx), (cblock.map_pos.y+cy), cblock.map_pos.z
end
elseif(dtypel == 4) and (cblock.occupancy[cx][cy][key] == value) then
ct = ct + 1
if(ct == 4) then return nthdesignation, (cblock.map_pos.x+cx), (cblock.map_pos.y+cy), cblock.map_pos.z end
end
end
end
end
end
end
cy = cy +1
cx = -1
end
if cy == 16 then cy, cx = 0, -1 end
cblock = getNextBlock(area)
end
--tempBlocks = nil
--Things don't end here; less simple designations can get offloaded into joblist!
if checkJobs then
while curJob and ((os.clock()-starttime)<5) do
curJob = getNextJob(area)-- must be before return, or it doesn't get the next one.
if(curJob) then
if dpriority and blockChanged(curJob.pos.x, curJob.pos.y, curJob.pos.z) then
priorities = getPriorities(dfhack.maps.getTileBlock(curJob.pos.x, curJob.pos.y, curJob.pos.z))
end
jcount= jcount +1
if checkJobs == "Smooth" and ( curJob.job_type == df.job_type.DetailFloor or curJob.job_type == df.job_type.DetailWall) or curJob.job_type == checkJobs then
if ((not dpriority) or priorities and priorities[curJob.pos.x%16][curJob.pos.y%16] == (dpriority*1000)) then
return nthdesignation, curJob.pos.x, curJob.pos.y, curJob.pos.z
end
end
end
end
end
end
return designationIterator, nil, 0
end
--Iterator for for-loop for getting nr,x,y,z of next element in line
function amathline(desiredlength,width,length,height)
--returns lineiterator, state, curlength
local width = width --masking with locals since going to modify them
local length = length
local height = height
if not width or width==1 then width = 0 end --boxes of a,b and a,b,0 and a,b,1 are equivalent
if not length or length==1 then length = 0 end
if not height or height==1 then height = 0 end
local xm = width<0 and -1 or 1 --used only for returning
local ym = length<0 and -1 or 1
local zm = height<0 and -1 or 1
width = width<0 and -width or width
length = length<0 and -length or length
height = height<0 and -height or height
local cx, cy, cz = 1,1,1 --first building is on 1,1,1
--ok, so, for boxes to be coordinate-agnostic, each coordinate must be projected off a plane, which means taking into account both coordinates off that plane
local wlmax = width>length and width or length
local whmax = width>height and width or height
local lhmax = length>height and length or height
local cxy, cxz, cyz=1,1,1
local function lineiterator(desiredlength, curlength)
if curlength<desiredlength then
curlength = curlength+1
cxy = cy > cx and cy or cx
cxz = cz > cx and cz or cx
cyz = cy > cz and cy or cz
--simultaneous assignment avoids off-by-1 errors, projecting off a plane avoids coordinate dependence
cx, cy, cz = cx+ (width>=lhmax and 1 or ((cx/cyz <= width/lhmax) and 1 or 0)),
cy+ (length>=whmax and 1 or ((cy/cxz <= length/whmax) and 1 or 0)),
cz+ (height>=wlmax and 1 or ((cz/cxy <= height/wlmax) and 1 or 0))
return curlength, cx*xm, cy*ym, cz*zm
end
end
return lineiterator, desiredlength, 1 --first building is already in place
end
jobbedDesignations = jobbedDesignations or false
function getJobbedDesignation()--designation_key)
if not jobbedDesignations then
--accessible designations get offloaded into joblist, have to look for them there.
jobbedDesignations = {}
jobbedDesignations[gui.getKeyDisplay("DESIGNATE_DIG")] = df.job_type.Dig
jobbedDesignations[gui.getKeyDisplay("DESIGNATE_STAIR_UPDOWN")] = df.job_type.CarveUpDownStaircase
jobbedDesignations[gui.getKeyDisplay("DESIGNATE_CHANNEL")] = df.job_type.DigChannel
jobbedDesignations[gui.getKeyDisplay("DESIGNATE_RAMP")] = df.job_type.CarveRamp
jobbedDesignations[gui.getKeyDisplay("DESIGNATE_STAIR_DOWN")] = df.job_type.CarveDownwardStaircase
jobbedDesignations[gui.getKeyDisplay("DESIGNATE_STAIR_UP")] = df.job_type.CarveUpwardStaircase
jobbedDesignations[gui.getKeyDisplay("DESIGNATE_SMOOTH")] = "Smooth" --smooth and engrave are both either DetailFloor or DetailWall jobs
jobbedDesignations[gui.getKeyDisplay("DESIGNATE_ENGRAVE")] = "Smooth" --"Engrave" can't be currently checked
jobbedDesignations[gui.getKeyDisplay("DESIGNATE_DIG_REMOVE_STAIRS_RAMPS")] = df.job_type.RemoveStairs
jobbedDesignations[gui.getKeyDisplay("DESIGNATE_PLANTS")] = df.job_type.GatherPlants
jobbedDesignations[gui.getKeyDisplay("DESIGNATE_REMOVE_CONSTRUCTION")] = df.job_type.RemoveConstruction
jobbedDesignations[gui.getKeyDisplay("DESIGNATE_FORTIFY")] = df.job_type.CarveFortification
--jobbedDesignations[gui.getKeyDisplay("DESIGNATE_TRACK")] = df.job_type.CarveTrack --TODO check job.item_category 18-21 NSWE is saved
end
return jobbedDesignations
end
jobbedDesignationsInv = jobbedDesignationsInv or false
if not jobbedDesignationsInv then
local dummy = copyall(getJobbedDesignation())
dummy[gui.getKeyDisplay("DESIGNATE_SMOOTH")] = nil
dummy[gui.getKeyDisplay("DESIGNATE_ENGRAVE")] = nil
jobbedDesignationsInv = utils.invert(dummy)
jobbedDesignationsInv[df.job_type.DetailFloor] = gui.getKeyDisplay("DESIGNATE_SMOOTH")
jobbedDesignationsInv[df.job_type.DetailWall] = gui.getKeyDisplay("DESIGNATE_SMOOTH")
end
--Iterator for iterating through designations of a type in an area or everywhere.
z = z or -999
function normalDesignations()
--returns designationIterator, state, key
local function iterateBlocks()
for areax = 1, df.global.world.map.x_count, 16 do
for areay = 1, df.global.world.map.y_count, 16 do
coroutine.yield(getBlock(areax,areay,z))
end
end
end
local blockroutine = coroutine.create(iterateBlocks)
local function getNextBlock()
local errorfree, value = coroutine.resume(blockroutine)
if not errorfree then return nil end
return value
end
function blockChanged(tx,ty,tz)
local blockx,blocky,blockz
if (tx%16) ~=blockx or (ty%16) ~=blocky or tz ~=blockz then
blockx =tx%16
blocky =ty%16
blockz=tz
return true
end
end
local x,y,z,cblock, priorities
local cy,cx,ct = 0,-1,0 --start for cx -1 so I can add before returning.
cblock, curJob = getNextBlock(), true
--return #(.th designation) and it's x, y and z coordinates
local ddig, dsmooth, dmarked, dpriority, dn, ds, de, dw
local function designationIterator(_,nthdesignation)
nthdesignation = nthdesignation +1
while cblock do
priorities = getPriorities(cblock)
while(cy<16) do
while(cx<15) do
cx = cx+1
if cblock.occupancy[cx][cy].building == 0 then --no point watching tiles with buildings as those can't have buildings placed on top anyway
ddig=cblock.designation[cx][cy].dig
dsmooth=cblock.designation[cx][cy].smooth
dmarked = cblock.occupancy[cx][cy].dig_marked
dpriority = priorities and priorities[cx][cy] or false
dn=cblock.occupancy[cx][cy].carve_track_north
ds=cblock.occupancy[cx][cy].carve_track_south
de=cblock.occupancy[cx][cy].carve_track_east
dw=cblock.occupancy[cx][cy].carve_track_west
dtt = (ddig==df.tile_dig_designation.Default or dsmooth == 1) and cblock.tiletype[cx][cy] or 0
if not (ddig == 0 and dsmooth == 0 --[=[ and dpriority == 0 --]=] and (not dn) and (not ds) and (not de) and (not dw)) then
return nthdesignation, {x=cblock.map_pos.x+cx, y=cblock.map_pos.y+cy, dig=ddig, smooth=dsmooth, marked=dmarked, priority=dpriority, n=dn, s=ds, e=de, w=dw, tt=dtt}
end
end
end
cy = cy +1
cx = -1
end
if cy == 16 then cy, cx = 0, -1 end
cblock = getNextBlock()
end
end
return designationIterator, nil, 0
end
function jobDesignations()
--returns designationIterator, state, key
local curJob = true
--return #(.th designation) and it's x, y and z coordinates
local dn, ds, de, dw
local job
local IsOld, JobListItem = pcall(function() return df.global.world.job_list end)--TODO replace slow pcall
if not IsOld then JobListItem=df.global.world.jobs.list end
starttime=os.clock()
local function designationIterator(_,nthdesignation)
while JobListItem.next do --and ((os.clock()-starttime)<1) do
curJob = JobListItem.item
JobListItem = JobListItem.next
if (curJob and curJob.pos.z == z) then
if jobbedDesignationsInv[curJob.job_type] and select(2,getFlags(curJob.pos.x, curJob.pos.y, z)).building==0 then
-- designation job on a tile without building
-- priority can be still checked after building is placed
dn= curJob.job_type == df.job_type.CarveTrack and job.item_category[18]
ds= curJob.job_type == df.job_type.CarveTrack and job.item_category[19]
dw= curJob.job_type == df.job_type.CarveTrack and job.item_category[20]
de= curJob.job_type == df.job_type.CarveTrack and job.item_category[21]
--tiletype is necessary to tell apart smooth and carve - but that check can be done when there's a difference.
nthdesignation = nthdesignation +1
return nthdesignation, {x=curJob.pos.x, y=curJob.pos.y, letter = jobbedDesignationsInv[curJob.job_type], n=dn, s=ds, e=de, w=dw}
end
end
end
end
return designationIterator, nil, 0
end
function makeDesignationTable(updatez)
DesignationTable = updatez and DesignationTable or {}
DesignationTable[z]={normal = {}, job = {} }
for dnr, dtab in normalDesignations() do
tin(DesignationTable[z].normal, dtab)
end
for dnr, jtab in jobDesignations() do
tin(DesignationTable[z].job, jtab)
end
end
function findSameZDiff()
local diffTable = {}
local dflag, oflag
local letter, ddig, dsmooth, dmarked, dpriority, dn, ds, de, dw
local designationString, digString, digSmooth
for i, dtab in pairs(DesignationTable[z].normal) do
dflag, oflag = getFlags(dtab.x, dtab.y, z)
if oflag.building > 0 then --building was placed where previously it wasn't.
--how do I handle simultaneous smooth and dig? As two separate?
if dtab.dig == df.tile_dig_designation.Default then
for dletter, identity in pairs(getDesignationMapTable()) do
if identity.dig == dtab.dig and identity.targettype and df.tiletype[dtab.tt]:match(identity.targettype) then
digString=dletter
end
end
if not digString then
digString = gui.getKeyDisplay("DESIGNATE_DIG")
end
else
for dletter, identity in pairs(getDesignationMapTable()) do
if dtab.dig > 0 and identity.dig == dtab.dig then
digString=dletter
end
end
end
if dtab.smooth == 1 then
for dletter, identity in pairs(getDesignationMapTable()) do
if identity.smooth == dtab.smooth and identity.targettype and df.tiletype[dtab.tt]:match(identity.targettype) then
digSmooth=dletter
end
end
if not digSmooth then
digSmooth = gui.getKeyDisplay("DESIGNATE_SMOOTH")
end
else
for dletter, identity in pairs(getDesignationMapTable()) do
if dtab.smooth > 0 and identity.smooth == dtab.smooth then
digSmooth=dletter
end
end
end
designationString = (dtab.marked and 'm' or '') .. (dtab.priority and dtab.priority/1000 or '') .. 'O'
if digString then
digString = digString .. designationString
diffTable[digString] = true
digString = nil
end
if digSmooth then
digSmooth = digSmooth .. designationString
diffTable[digSmooth] = true
digSmooth = nil
end
DesignationTable[z].normal[i] = nil
end
--ddig=cblock.designation[cx][cy].dig --only digs are zeroed when floor is placed on top
--dsmooth=cblock.designation[cx][cy].smooth
--dmarked = cblock.occupancy[cx][cy].dig_marked
--dpriority = priorities and priorities[cx][cy] or 0
--dn=cblock.occupancy[cx][cy].carve_track_north
--ds=cblock.occupancy[cx][cy].carve_track_south
--de=cblock.occupancy[cx][cy].carve_track_east
--dw=cblock.occupancy[cx][cy].carve_track_west
--dtt = (ddig==df.tile_dig_designation.Default or dsmooth == 1) and cblock.tiletype[cx][cy] or 0
end
for i, jtab in pairs(DesignationTable[z].job) do
dflag, oflag = getFlags(jtab.x, jtab.y, z)
if oflag.building > 0 then --building was placed where previously it wasn't.
--designationString='j2mO'
dpriority = getPriorities(getBlock(jtab.x, jtab.y, z))
dpriority = dpriority and dpriority[jtab.x%16][jtab.y%16] or false
designationString = jtab.letter .. (dpriority and dpriority/1000 or '') .. (oflag.dig_marked and 'm' or '' ) ..'O' -- always Old/job tiles
--Also still doesn't match tracks properly
diffTable[designationString] = true
DesignationTable[z].job[i] = nil
end
end
return diffTable
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
print(curheight .. " " .. curdepth)
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
print("p " .. curheight .. " " .. curdepth)
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 dlg = require 'gui.dialogs'
z = df.global.window_z
makeDesignationTable()
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)--TODO adapt for new indi in 4303
if z ~= df.global.window_z then
z = df.global.window_z
makeDesignationTable(true)
end
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 and self._native 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(dfvalue, key)
-- Utility function for safely requesting info from df data
if not dfvalue or not key then return nil end --pairs crash prevention
flagtypetable = flagtypetable or {} --memoization so it doesn't iterate through the dfvalue if we've already checked that type of value for given flag
if flagtypetable[dfvalue._type] and flagtypetable[dfvalue._type][key] then return dfvalue[key] end
if flagtypetable[dfvalue._type] and flagtypetable[dfvalue._type][key]==false then return nil end
if not flagtypetable[dfvalue._type] then flagtypetable[dfvalue._type] = {} end
for akey, avalue in pairs(dfvalue) do
if akey == key then
flagtypetable[dfvalue._type][key] = true
return dfvalue[akey]
end
end
flagtypetable[dfvalue._type][key] = false
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])
local zDiff = findSameZDiff()
local difflen = getLen(zDiff)
if zpositive > 0 or znegative > 0 or difflen > 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, xoffset, yoffset, zoffset)
bd = {}
bd.x = lbd.x1+xoffset
bd.width = 1+lbd.x2-lbd.x1
bd.height = 1+lbd.y2-lbd.y1
bd.y = lbd.y1+yoffset
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 #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+xoffset
createdbuilding.y1 = lbd.y1+yoffset
createdbuilding.x2 = lbd.x2+xoffset
createdbuilding.y2 = lbd.y2+yoffset
end
createdbuilding.centerx = lbd.centerx+xoffset
createdbuilding.centery = lbd.centery+yoffset
--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
--[[
for curlength, x, y,z in a_line(17,1,-17,1) do
copy1building(df.global.world.buildings.all[lenb-1], x-1, y, z-1)
end
--]]
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
local xoff = 0
local yoff = 0
local nr = 0
if difflen > 0 then
local hdif = zpositive-znegative
if zpositive > 0 and znegative > 0 then
hdif = zpositive+znegative
df.global.window_z = df.global.window_z - znegative
end
for dstring, _ in pairs(zDiff) do --typically 1 member
for curlength, x, y,z in targetDesignations(dstring, tostring(hdif)) do --only current z for now, could use zpositive or znegative to do on other zs
local tbld = df.global.world.buildings.all[lenb-1-curlength%w*h]
copy1building(tbld, x-tbld.x1, y-tbld.y1, z-tbld.z)
nr = nr + 1
end
end
if zpositive > 0 and znegative > 0 then
df.global.window_z = df.global.window_z + znegative
end
elseif zpositive ~= 0 or znegative ~= 0 then
for i = 1, w*h do
if zpositive > 0 then
for zoff = 1, zpositive do
copy1building(df.global.world.buildings.all[lenb-i], xoff, yoff, zoff)
nr = nr +1
end
end
if znegative > 0 then
for zoff = -1, -znegative, -1 do
copy1building(df.global.world.buildings.all[lenb-i], xoff, yoff, zoff)
nr = nr +1
end
end
end
end
if (nr > 0) then print("Placed " .. nr .. " buildings.") end
end
end