Skip to content

Commit bdeccb6

Browse files
authored
Update autoceiling.lua
I made some changes, and sorry it's late. I think the mod has potential and could be used in most sessions, so if anyone is interested in taking it over, you have my full permission.
1 parent 7c3af08 commit bdeccb6

File tree

1 file changed

+96
-83
lines changed

1 file changed

+96
-83
lines changed

autoceiling.lua

Lines changed: 96 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -10,51 +10,41 @@
1010
-- Configuration defaults
1111
-------------------------
1212
local CONFIG = {
13-
MAX_FILL_TILES = 4000, -- safety limit
14-
ALLOW_DIAGONALS = false -- can be overridden by parameter
13+
MAX_FILL_TILES = 2000, -- positive integer; safety limit
14+
ALLOW_DIAGONALS = false, -- set true to allow 8-way fill
15+
MAX_LIMIT_HARD = 4000 -- hard clamp to avoid runaway fills
1516
}
1617

1718
-------------------------
1819
-- Utilities and guards
1920
-------------------------
2021
local function err(msg) qerror('AutoCeiling: ' .. tostring(msg)) end
2122

22-
local function try_require(modname)
23-
local ok, mod = pcall(require, modname)
24-
if ok and mod then return mod end
25-
return nil
23+
local function xyz2pos(x, y, z)
24+
return { x = x, y = y, z = z }
2625
end
2726

27+
-- Cache frequently used modules/tables for readability
28+
local maps = dfhack.maps
29+
local constructions = dfhack.constructions
30+
local buildings = dfhack.buildings
31+
local tattrs = df.tiletype.attrs
32+
2833
-------------------------
2934
-- World and map helpers
3035
-------------------------
31-
local W = df.global.world
32-
local XMAX, YMAX, ZMAX = W.map.x_count, W.map.y_count, W.map.z_count
33-
3436
local function in_bounds(x, y, z)
35-
return x >= 0 and y >= 0 and z >= 0 and x < XMAX and y < YMAX and z < ZMAX
36-
end
37-
38-
local function get_block(x, y, z)
39-
return dfhack.maps.getTileBlock(x, y, z)
37+
return maps.isValidTilePos(x, y, z)
4038
end
4139

4240
local function get_tiletype(x, y, z)
43-
local b = get_block(x, y, z)
44-
if not b then return nil end
45-
return b.tiletype[x % 16][y % 16]
41+
return maps.getTileType(x, y, z)
4642
end
4743

4844
local function tile_shape(tt)
4945
if not tt then return nil end
50-
local a = df.tiletype.attrs[tt]
51-
return a and a.shape or nil
52-
end
53-
54-
local function tile_material(tt)
55-
if not tt then return nil end
56-
local a = df.tiletype.attrs[tt]
57-
return a and a.material or nil
46+
local a = tattrs[tt]
47+
return (a and a.shape ~= df.tiletype_shape.NONE) and a.shape or nil
5848
end
5949

6050
-------------------------
@@ -72,57 +62,51 @@ local function is_walkable_dug(tt)
7262
end
7363

7464
local function is_constructed_tile(x, y, z)
75-
local tt = get_tiletype(x, y, z)
76-
local mat = tile_material(tt)
77-
return mat == df.tiletype_material.CONSTRUCTION
65+
return constructions.findAtTile(x, y, z) ~= nil
7866
end
7967

8068
local function has_any_building(x, y, z)
81-
-- Also detects in-progress constructions as buildings
82-
return dfhack.buildings.findAtTile({ x = x, y = y, z = z }) ~= nil
69+
return buildings.findAtTile(xyz2pos(x, y, z)) ~= nil
8370
end
8471

8572
-------------------------
8673
-- Flood fill
8774
-------------------------
88-
local function push_if_ok(q, visited, x, y, z)
89-
if not in_bounds(x, y, z) then return end
90-
local key = x .. ',' .. y
91-
if visited[key] then return end
92-
local tt = get_tiletype(x, y, z)
93-
if is_walkable_dug(tt) then
94-
visited[key] = true
95-
q[#q + 1] = { x, y }
96-
end
97-
end
98-
9975
local function flood_fill_footprint(seed_x, seed_y, z0)
10076
local footprint = {}
10177
local visited = {}
102-
local q = { { seed_x, seed_y } }
78+
local queue = { { seed_x, seed_y } }
10379
visited[seed_x .. ',' .. seed_y] = true
104-
local head = 1
105-
while head <= #q and #footprint < CONFIG.MAX_FILL_TILES do
106-
local x, y = table.unpack(q[head]); head = head + 1
107-
footprint[#footprint + 1] = { x = x, y = y }
80+
local queue_pos = 1
81+
82+
local function push_if_ok(x, y)
83+
if not in_bounds(x, y, z0) then return end
84+
local key = x .. ',' .. y
85+
if visited[key] then return end
86+
local tt = get_tiletype(x, y, z0)
87+
if is_walkable_dug(tt) then
88+
visited[key] = true
89+
table.insert(queue, { x, y })
90+
end
91+
end
92+
93+
while queue_pos <= #queue and #footprint < CONFIG.MAX_FILL_TILES do
94+
local x, y = table.unpack(queue[queue_pos])
95+
queue_pos = queue_pos + 1
96+
table.insert(footprint, { x = x, y = y })
97+
push_if_ok(x + 1, y)
98+
push_if_ok(x - 1, y)
99+
push_if_ok(x, y + 1)
100+
push_if_ok(x, y - 1)
108101
if CONFIG.ALLOW_DIAGONALS then
109-
push_if_ok(q, visited, x + 1, y, z0)
110-
push_if_ok(q, visited, x - 1, y, z0)
111-
push_if_ok(q, visited, x, y + 1, z0)
112-
push_if_ok(q, visited, x, y - 1, z0)
113-
push_if_ok(q, visited, x + 1, y + 1, z0)
114-
push_if_ok(q, visited, x + 1, y - 1, z0)
115-
push_if_ok(q, visited, x - 1, y + 1, z0)
116-
push_if_ok(q, visited, x - 1, y - 1, z0)
117-
else
118-
push_if_ok(q, visited, x + 1, y, z0)
119-
push_if_ok(q, visited, x - 1, y, z0)
120-
push_if_ok(q, visited, x, y + 1, z0)
121-
push_if_ok(q, visited, x, y - 1, z0)
102+
push_if_ok(x + 1, y + 1)
103+
push_if_ok(x + 1, y - 1)
104+
push_if_ok(x - 1, y + 1)
105+
push_if_ok(x - 1, y - 1)
122106
end
123107
end
124108

125-
if #q > CONFIG.MAX_FILL_TILES then
109+
if #queue > CONFIG.MAX_FILL_TILES then
126110
dfhack.printerr(('AutoCeiling: flood fill truncated at %d tiles'):format(CONFIG.MAX_FILL_TILES))
127111
end
128112
return footprint
@@ -131,53 +115,82 @@ end
131115
-------------------------
132116
-- Placement strategies
133117
-------------------------
134-
local function place_planned(bp, x, y, z)
118+
local function place_planned(bp, pos)
135119
local ok, bld = pcall(function()
136120
return dfhack.buildings.constructBuilding{
137121
type = df.building_type.Construction,
138122
subtype = df.construction_type.Floor,
139-
pos = { x = x, y = y, z = z }
123+
pos = pos
140124
}
141125
end)
142126
if not ok or not bld then return false, 'construct-error' end
143127
pcall(function() bp.addPlannedBuilding(bld) end)
144128
return true
145129
end
146130

147-
local function place_native(cons, x, y, z)
148-
if not cons or not cons.designate then return false, 'no-constructions-api' end
149-
local ok, derr = pcall(function()
150-
cons.designate{ pos = { x = x, y = y, z = z }, type = df.construction_type.Floor }
131+
local function place_native(cons, pos)
132+
if not cons or not cons.designateNew then return false, 'no-constructions-api' end
133+
134+
local ok, res = pcall(function()
135+
return cons.designateNew(pos, df.construction_type.Floor, -1, -1)
151136
end)
152-
if not ok then return false, 'designate-error' end
153-
return true
137+
if ok and res then return true end
138+
139+
local ok2, res2 = pcall(function()
140+
return cons.designateNew(pos, df.construction_type.Floor, df.item_type.BOULDER, -1)
141+
end)
142+
if ok2 and res2 then return true end
143+
144+
return false, 'designate-error'
154145
end
155146

156147
-------------------------
157148
-- Main
158149
-------------------------
150+
local utils = require('utils')
151+
159152
local function main(...)
160153
local args = {...}
161-
-- Allow user to set diagonals with parameter 't' or 'true'
162-
if #args > 0 and (args[1] == 't' or args[1] == 'true') then
163-
CONFIG.ALLOW_DIAGONALS = true
154+
155+
for _, raw in ipairs(args) do
156+
local s = tostring(raw):lower()
157+
local num = tonumber(s)
158+
if num then
159+
if num < 1 then err('MAX_FILL_TILES must be >= 1') end
160+
if num > CONFIG.MAX_LIMIT_HARD then
161+
dfhack.printerr(('clamping MAX_FILL_TILES from %d to %d'):format(num, CONFIG.MAX_LIMIT_HARD))
162+
num = CONFIG.MAX_LIMIT_HARD
163+
end
164+
CONFIG.MAX_FILL_TILES = math.floor(num)
165+
elseif s == 't' or s == 'true' then
166+
CONFIG.ALLOW_DIAGONALS = true
167+
elseif s == 'h' or s == 'help' then
168+
print('Usage: autoceiling [t] [<max_fill_tiles>]')
169+
print(' t: enable diagonal flood fill')
170+
print(' <max_fill_tiles>: positive integer, default ' .. CONFIG.MAX_FILL_TILES)
171+
return
172+
elseif s ~= '' then
173+
err('unknown argument: ' .. tostring(raw))
174+
end
164175
end
165176

166-
-- Validate cursor and tile
167-
local cur = df.global.cursor
177+
local cur = utils.clone(df.global.cursor)
168178
if cur.x == -30000 then err('cursor not set. Move to a dug tile and run again.') end
169179
local z0 = cur.z
170180
local seed_tt = get_tiletype(cur.x, cur.y, z0)
171181
if not is_walkable_dug(seed_tt) then err('cursor tile is not dug/open interior') end
172182

173-
-- Discover footprint and target surface level
174183
local footprint = flood_fill_footprint(cur.x, cur.y, z0)
184+
if #footprint == 0 then
185+
print('AutoCeiling: nothing to do — no connected dug tiles found at cursor')
186+
return
187+
end
175188
local z_surface = z0 + 1
176189

177-
-- Load optional DFHack helpers
178-
local bp = try_require('plugins.buildingplan')
190+
-- Require buildingplan directly; let it error if missing
191+
local bp = require('plugins.buildingplan')
179192
if bp and (not bp.isEnabled or not bp.isEnabled()) then bp = nil end
180-
local cons = try_require('dfhack.constructions')
193+
local cons = dfhack.constructions
181194

182195
local placed, skipped = 0, 0
183196
local reasons = {}
@@ -186,9 +199,9 @@ local function main(...)
186199
reasons[reason] = (reasons[reason] or 0) + 1
187200
end
188201

189-
-- Process each tile
190-
for i = 1, #footprint do
191-
local x, y = footprint[i].x, footprint[i].y
202+
for i, foot in ipairs(footprint) do
203+
local x, y = foot.x, foot.y
204+
local pos = xyz2pos(x, y, z_surface)
192205
if not in_bounds(x, y, z_surface) then
193206
skip('oob')
194207
elseif is_constructed_tile(x, y, z_surface) then
@@ -198,9 +211,9 @@ local function main(...)
198211
else
199212
local ok, why
200213
if bp then
201-
ok, why = place_planned(bp, x, y, z_surface)
214+
ok, why = place_planned(bp, pos)
202215
else
203-
ok, why = place_native(cons, x, y, z_surface)
216+
ok, why = place_native(cons, pos)
204217
end
205218
if ok then placed = placed + 1 else skip(why or 'unknown') end
206219
end
@@ -211,7 +224,7 @@ local function main(...)
211224
print(('AutoCeiling: placed %d floor construction(s); skipped %d'):format(placed, skipped))
212225
if bp then
213226
print('buildingplan active: created planned floors that will auto-assign materials')
214-
elseif cons and cons.designate then
227+
elseif cons and cons.designateNew then
215228
print('used native construction designations')
216229
else
217230
print('no buildingplan and no constructions API available')

0 commit comments

Comments
 (0)