-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathroads.lua
More file actions
295 lines (252 loc) · 8.57 KB
/
roads.lua
File metadata and controls
295 lines (252 loc) · 8.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
-- roads.lua
-- Converts worldgen roads to asphalt on map load.
local ASPHALT_TOKEN = "INORGANIC:ASPHALT"
local ASPHALT_MAT_TYPE
local ASPHALT_MAT_INDEX
local CONSTRUCTED_FLOOR_TT = (function()
local floor_shape = df.tiletype_shape_basic.Floor
local constr_mat = df.tiletype_material.CONSTRUCTION
for i = 0, 1000 do
local attrs = df.tiletype.attrs[i]
if attrs and attrs.shape == floor_shape and attrs.material == constr_mat then
return i
end
end
return nil
end)()
local S = rawget(_G, "__roads_state")
if not S then
S = {
debug = false,
last_map_key = nil,
watcher_enabled = false,
watcher_gen = 0,
watcher_interval = 20,
}
_G.__roads_state = S
end
local function log(msg)
print("[roads] " .. msg)
end
local function dlog(msg)
if S.debug then
log("DIAG: " .. msg)
end
end
local function find_asphalt()
local info = dfhack.matinfo.find(ASPHALT_TOKEN)
if not info then
log(ASPHALT_TOKEN .. " material not found in raws")
return false
end
ASPHALT_MAT_TYPE = info.type
ASPHALT_MAT_INDEX = info.index
return true
end
local function is_path_tile(tt)
local name = df.tiletype[tt]
return name ~= nil and name:find("Path") ~= nil
end
local function is_natural_floor_or_ramp(tt)
local attrs = df.tiletype.attrs[tt]
if not attrs then return false end
return attrs.shape == df.tiletype_shape_basic.Floor or attrs.shape == df.tiletype_shape_basic.Ramp
end
local function map_key()
if not dfhack.isMapLoaded() then return nil end
local wm = df.global.world.map
local cons = df.global.world.event.constructions
return ("%d,%d,%d,%d,%d,%d"):format(
wm.region_x, wm.region_y, wm.x_count, wm.y_count, #wm.map_blocks, #cons)
end
local function block_key(x, y, z)
local bx = x - (x % 16)
local by = y - (y % 16)
return ("%d,%d,%d"):format(bx, by, z)
end
local function get_block_and_local(block_cache, x, y, z)
local bk = block_key(x, y, z)
local block = block_cache[bk]
if block == nil then
block = dfhack.maps.getTileBlock(x, y, z)
block_cache[bk] = block or false
elseif block == false then
block = nil
end
if not block then return nil end
return block, x % 16, y % 16
end
local function key_xyz(x, y, z)
return ("%d,%d,%d"):format(x, y, z)
end
local function is_building_tile(block, lx, ly)
local occ = block.occupancy[lx][ly]
return occ and occ.building
end
local function likely_worldgen_road_construction(c, block_cache)
local block, lx, ly = get_block_and_local(block_cache, c.pos.x, c.pos.y, c.pos.z)
if not block then return false end
local outside = block.designation[lx][ly].outside
if not outside then return false end
local tt = block.tiletype[lx][ly]
-- Do not treat generic constructed floors as roads.
if is_path_tile(tt) then
return true
end
if c.original_tile and c.original_tile >= 0 then
if is_path_tile(c.original_tile) or is_natural_floor_or_ramp(c.original_tile) then
return true
end
end
return false
end
local function convert_roads(force, quiet)
if not dfhack.isMapLoaded() then
if quiet then return end
log("No map loaded.")
return
end
if not find_asphalt() then return end
if not CONSTRUCTED_FLOOR_TT then
log("ERROR: cannot find constructed floor tiletype in enum")
return
end
local this_map_key = map_key()
if not force and S.last_map_key == this_map_key then
if quiet then return end
log("Already processed this map; skipping. Use 'roads force' to run again.")
return
end
local wm = df.global.world.map
local map_blocks = wm.map_blocks
local constructions = df.global.world.event.constructions
local block_cache = {}
local x_max = wm.x_count - 1
local y_max = wm.y_count - 1
local existing = {}
for i = 0, #constructions - 1 do
local c = constructions[i]
if c.pos.x >= 0 and c.pos.x <= x_max and c.pos.y >= 0 and c.pos.y <= y_max then
existing[key_xyz(c.pos.x, c.pos.y, c.pos.z)] = c
end
end
dlog(("region_x=%d region_y=%d blocks=%d cons=%d"):format(
wm.region_x, wm.region_y, #map_blocks, #constructions))
local updated = 0
local inserted = 0
local already_asphalt = 0
local path_tiles = 0
-- Pass 1: update existing likely worldgen road constructions.
for _, c in pairs(existing) do
if likely_worldgen_road_construction(c, block_cache) then
if c.mat_type == ASPHALT_MAT_TYPE and c.mat_index == ASPHALT_MAT_INDEX then
already_asphalt = already_asphalt + 1
else
c.mat_type = ASPHALT_MAT_TYPE
c.mat_index = ASPHALT_MAT_INDEX
updated = updated + 1
end
local block, lx, ly = get_block_and_local(block_cache, c.pos.x, c.pos.y, c.pos.z)
if block then
block.tiletype[lx][ly] = CONSTRUCTED_FLOOR_TT
end
end
end
-- Pass 2: path-tile fallback for roads that do not yet have construction records.
for _, block in ipairs(map_blocks) do
for lx = 0, 15 do
for ly = 0, 15 do
local tt = block.tiletype[lx][ly]
if is_path_tile(tt) then
if is_building_tile(block, lx, ly) then
goto continue_tile
end
if not block.designation[lx][ly].outside then
goto continue_tile
end
path_tiles = path_tiles + 1
local x = block.map_pos.x + lx
local y = block.map_pos.y + ly
local z = block.map_pos.z
local k = key_xyz(x, y, z)
local c = existing[k]
if c then
if c.mat_type == ASPHALT_MAT_TYPE and c.mat_index == ASPHALT_MAT_INDEX then
already_asphalt = already_asphalt + 1
else
c.mat_type = ASPHALT_MAT_TYPE
c.mat_index = ASPHALT_MAT_INDEX
updated = updated + 1
end
else
c = df.construction:new()
c.pos.x = x
c.pos.y = y
c.pos.z = z
c.mat_type = ASPHALT_MAT_TYPE
c.mat_index = ASPHALT_MAT_INDEX
c.original_tile = tt
if dfhack.constructions.insert(c) then
inserted = inserted + 1
existing[k] = c
end
end
block.tiletype[lx][ly] = CONSTRUCTED_FLOOR_TT
end
::continue_tile::
end
end
end
S.last_map_key = this_map_key
log(("Converted roads to asphalt: updated=%d inserted=%d already_asphalt=%d path_tiles_seen=%d"):format(
updated, inserted, already_asphalt, path_tiles))
end
local function tick_watch(gen)
if not S.watcher_enabled then return end
if gen ~= S.watcher_gen then return end
if dfhack.isMapLoaded() then
convert_roads(false, true)
end
dfhack.timeout(S.watcher_interval, "ticks", function()
tick_watch(gen)
end)
end
local function start_watcher()
if S.watcher_enabled then return end
S.watcher_enabled = true
S.watcher_gen = S.watcher_gen + 1
tick_watch(S.watcher_gen)
end
local function stop_watcher()
if not S.watcher_enabled then return end
S.watcher_enabled = false
S.watcher_gen = S.watcher_gen + 1
end
local function status()
log(("Status: debug=%s watcher=%s interval=%d last_map_key=%s"):format(
tostring(S.debug), tostring(S.watcher_enabled), S.watcher_interval, tostring(S.last_map_key)))
end
local args = {...}
local cmd = args[1] or "enable"
if cmd == "enable" then
convert_roads(false)
start_watcher()
elseif cmd == "force" then
convert_roads(true)
elseif cmd == "disable" then
stop_watcher()
elseif cmd == "status" then
status()
elseif cmd == "debug" then
local mode = args[2] or "toggle"
if mode == "on" then
S.debug = true
elseif mode == "off" then
S.debug = false
else
S.debug = not S.debug
end
log("Debug " .. (S.debug and "ON" or "OFF"))
else
log("Usage: roads [enable|force|disable|status|debug [on|off]]")
end