Add debug-mode pump toggle
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/source
|
||||||
887
42/media/lua/shared/WaterpipesB42Patch.lua
Normal file
887
42/media/lua/shared/WaterpipesB42Patch.lua
Normal file
@@ -0,0 +1,887 @@
|
|||||||
|
WaterpipesB42Patch = WaterpipesB42Patch or {}
|
||||||
|
|
||||||
|
local Patch = WaterpipesB42Patch
|
||||||
|
local Z_OFFSETS = {0, -1, 1, 2, 3}
|
||||||
|
|
||||||
|
local function parseSquareKey(tileKey)
|
||||||
|
local x, y, z = string.match(tileKey, "^(-?%d+),(-?%d+),(-?%d+)$")
|
||||||
|
if not x then
|
||||||
|
x, y, z = string.match(tileKey, "^(-?%d+)-(-?%d+)-(-?%d+)$")
|
||||||
|
end
|
||||||
|
if not x then
|
||||||
|
return nil, nil, nil
|
||||||
|
end
|
||||||
|
return tonumber(x), tonumber(y), tonumber(z)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getVerticalSearchSquares(square)
|
||||||
|
local squares = {}
|
||||||
|
if not square then
|
||||||
|
return squares
|
||||||
|
end
|
||||||
|
|
||||||
|
local cell = square:getCell()
|
||||||
|
local sx, sy, sz = square:getX(), square:getY(), square:getZ()
|
||||||
|
local seen = {}
|
||||||
|
|
||||||
|
for _, offset in ipairs(Z_OFFSETS) do
|
||||||
|
local z = sz + offset
|
||||||
|
local key = tostring(z)
|
||||||
|
if not seen[key] then
|
||||||
|
seen[key] = true
|
||||||
|
local sq = cell and cell:getGridSquare(sx, sy, z) or nil
|
||||||
|
if sq then
|
||||||
|
table.insert(squares, sq)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return squares
|
||||||
|
end
|
||||||
|
|
||||||
|
local function findNearbyLinkedBid(square, xyRadius, zRadius)
|
||||||
|
if not square or not GetWPModData or not WPUtils or not WPUtils.Coords2Id then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local gmd = GetWPModData()
|
||||||
|
if not gmd then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local sx, sy, sz = square:getX(), square:getY(), square:getZ()
|
||||||
|
local zr = zRadius or 1
|
||||||
|
local rr = xyRadius or 1
|
||||||
|
|
||||||
|
for dz = -zr, zr do
|
||||||
|
local z = sz + dz
|
||||||
|
for dy = -rr, rr do
|
||||||
|
for dx = -rr, rr do
|
||||||
|
local x = sx + dx
|
||||||
|
local y = sy + dy
|
||||||
|
local id = WPUtils.Coords2Id(x, y, z)
|
||||||
|
|
||||||
|
local buildingNode = gmd.Buildings and gmd.Buildings[id] or nil
|
||||||
|
if buildingNode and buildingNode.bid and buildingNode.bid ~= "" then
|
||||||
|
return buildingNode.bid
|
||||||
|
end
|
||||||
|
|
||||||
|
local barrelNode = gmd.Barrels and gmd.Barrels[id] or nil
|
||||||
|
if barrelNode and barrelNode.bid and barrelNode.bid ~= "" then
|
||||||
|
return barrelNode.bid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getBuildingBidFromDef(def)
|
||||||
|
if not def then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local bid = def:getIDString()
|
||||||
|
if bid and bid ~= "" then
|
||||||
|
return bid
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getBuildingBidFromObject(building)
|
||||||
|
if not building then
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local def = building:getDef()
|
||||||
|
local bid = getBuildingBidFromDef(def)
|
||||||
|
if bid then
|
||||||
|
return bid, def
|
||||||
|
end
|
||||||
|
|
||||||
|
if building.getID then
|
||||||
|
local id = building:getID()
|
||||||
|
if id ~= nil then
|
||||||
|
return "WPBuilding_" .. tostring(id), nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getRoomSignature(room, square)
|
||||||
|
local rects = room and room.getRects and room:getRects()
|
||||||
|
if rects and rects:size() > 0 then
|
||||||
|
local minX, minY, minZ
|
||||||
|
local maxX, maxY, maxZ
|
||||||
|
|
||||||
|
for i = 0, rects:size() - 1 do
|
||||||
|
local rect = rects:get(i)
|
||||||
|
local rx1, ry1 = rect:getX(), rect:getY()
|
||||||
|
local rx2, ry2 = rect:getX2(), rect:getY2()
|
||||||
|
local rz = square:getZ()
|
||||||
|
if rect.getZ then
|
||||||
|
rz = rect:getZ()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not minX then
|
||||||
|
minX, minY, minZ = rx1, ry1, rz
|
||||||
|
maxX, maxY, maxZ = rx2, ry2, rz
|
||||||
|
else
|
||||||
|
if rx1 < minX then minX = rx1 end
|
||||||
|
if ry1 < minY then minY = ry1 end
|
||||||
|
if rz < minZ then minZ = rz end
|
||||||
|
|
||||||
|
if rx2 > maxX then maxX = rx2 end
|
||||||
|
if ry2 > maxY then maxY = ry2 end
|
||||||
|
if rz > maxZ then maxZ = rz end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tostring(minX) .. "_" .. tostring(minY) .. "_" .. tostring(minZ) .. "_" ..
|
||||||
|
tostring(maxX) .. "_" .. tostring(maxY) .. "_" .. tostring(maxZ)
|
||||||
|
end
|
||||||
|
|
||||||
|
return tostring(square:getX()) .. "_" .. tostring(square:getY()) .. "_" .. tostring(square:getZ())
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getBuildingDescriptorFromPoweredSquares(square)
|
||||||
|
if not ModData or not ModData.getOrCreate then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local modData = ModData.getOrCreate("PoweredBuildings_Squares")
|
||||||
|
if not modData or not modData.PoweredSquares then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local searchSquares = getVerticalSearchSquares(square)
|
||||||
|
for _, sq in ipairs(searchSquares) do
|
||||||
|
local squareKey = sq:getX() .. "," .. sq:getY() .. "," .. sq:getZ()
|
||||||
|
local selectedGenKey, selectedSquares = nil, nil
|
||||||
|
|
||||||
|
for genKey, poweredSquares in pairs(modData.PoweredSquares) do
|
||||||
|
if poweredSquares and poweredSquares[squareKey] then
|
||||||
|
local genKeyStr = tostring(genKey)
|
||||||
|
if not selectedGenKey or genKeyStr < selectedGenKey then
|
||||||
|
selectedGenKey = genKeyStr
|
||||||
|
selectedSquares = poweredSquares
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if selectedGenKey then
|
||||||
|
return {
|
||||||
|
bid = "WPGen_" .. selectedGenKey,
|
||||||
|
poweredSquares = selectedSquares,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getBuildingDescriptorFromRoom(square)
|
||||||
|
local searchSquares = getVerticalSearchSquares(square)
|
||||||
|
for _, sq in ipairs(searchSquares) do
|
||||||
|
local room = sq:getRoom()
|
||||||
|
if room then
|
||||||
|
return {
|
||||||
|
bid = "WPRoom_" .. getRoomSignature(room, sq),
|
||||||
|
room = room,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function patchWPIso()
|
||||||
|
if not WPIso or not WPVirtual or not WPUtils then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if WPIso.__WaterpipesB42PatchApplied then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getBuildingDescriptorAtSquare(square)
|
||||||
|
if not square then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Prefer existing linked IDs to keep all taps in one network bid.
|
||||||
|
local linkedBid = findNearbyLinkedBid(square, 2, 1)
|
||||||
|
if linkedBid then
|
||||||
|
return { bid = linkedBid }
|
||||||
|
end
|
||||||
|
|
||||||
|
local searchSquares = getVerticalSearchSquares(square)
|
||||||
|
for _, sq in ipairs(searchSquares) do
|
||||||
|
local building = sq:getBuilding()
|
||||||
|
if building then
|
||||||
|
local bid, def = getBuildingBidFromObject(building)
|
||||||
|
if bid then
|
||||||
|
return {
|
||||||
|
bid = bid,
|
||||||
|
building = building,
|
||||||
|
def = def,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local poweredDescriptor = getBuildingDescriptorFromPoweredSquares(square)
|
||||||
|
if poweredDescriptor then
|
||||||
|
return poweredDescriptor
|
||||||
|
end
|
||||||
|
|
||||||
|
return getBuildingDescriptorFromRoom(square)
|
||||||
|
end
|
||||||
|
|
||||||
|
WPIso.GetBuildingDescriptor = function(square)
|
||||||
|
if not square then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local descriptor = getBuildingDescriptorAtSquare(square)
|
||||||
|
if descriptor then
|
||||||
|
return descriptor
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Fallback for moveables/taps placed on edge or outside-flagged tiles:
|
||||||
|
-- scan neighboring squares and resolve against the nearest valid descriptor.
|
||||||
|
local cell = square:getCell()
|
||||||
|
local sx, sy, sz = square:getX(), square:getY(), square:getZ()
|
||||||
|
for dy = -1, 1 do
|
||||||
|
for dx = -1, 1 do
|
||||||
|
if not (dx == 0 and dy == 0) then
|
||||||
|
local nsq = cell and cell:getGridSquare(sx + dx, sy + dy, sz) or nil
|
||||||
|
if nsq then
|
||||||
|
descriptor = getBuildingDescriptorAtSquare(nsq)
|
||||||
|
if descriptor then
|
||||||
|
return descriptor
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Final fallback: slightly wider linked-bid scan.
|
||||||
|
local linkedBid = findNearbyLinkedBid(square, 4, 2)
|
||||||
|
if linkedBid then
|
||||||
|
return { bid = linkedBid }
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
WPIso.GetBuildingBid = function(square)
|
||||||
|
local descriptor = WPIso.GetBuildingDescriptor(square)
|
||||||
|
if descriptor then
|
||||||
|
return descriptor.bid
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
WPIso.GetBuilding = function(square)
|
||||||
|
return WPIso.GetBuildingDescriptor(square)
|
||||||
|
end
|
||||||
|
|
||||||
|
WPIso.ConnectBuilding = function(square, building)
|
||||||
|
if not square then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local sx, sy, sz = square:getX(), square:getY(), square:getZ()
|
||||||
|
local cell = square:getCell()
|
||||||
|
|
||||||
|
local descriptor = building
|
||||||
|
if descriptor and descriptor.getDef then
|
||||||
|
local bid, def = getBuildingBidFromObject(descriptor)
|
||||||
|
descriptor = {
|
||||||
|
bid = bid,
|
||||||
|
building = descriptor,
|
||||||
|
def = def,
|
||||||
|
}
|
||||||
|
elseif not descriptor then
|
||||||
|
descriptor = WPIso.GetBuildingDescriptor(square)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not descriptor or not descriptor.bid then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local bid = descriptor.bid
|
||||||
|
local traversed = {}
|
||||||
|
|
||||||
|
local function syncBarrelInSquare(sq)
|
||||||
|
if not sq then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local x, y, z = sq:getX(), sq:getY(), sq:getZ()
|
||||||
|
local key = WPUtils.Coords2Id(x, y, z)
|
||||||
|
if traversed[key] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
traversed[key] = true
|
||||||
|
|
||||||
|
local isoBarrel = WPIso.GetBarrel(sq)
|
||||||
|
if isoBarrel then
|
||||||
|
local _, wmax = WPIso.GetWaterStatus(isoBarrel)
|
||||||
|
WPVirtual.BarrelAdd(x, y, z, wmax * 100, bid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if descriptor.def then
|
||||||
|
local def = descriptor.def
|
||||||
|
local bx1, bx2 = def:getX(), def:getX2()
|
||||||
|
local by1, by2 = def:getY(), def:getY2()
|
||||||
|
local bz1, bz2 = def:getMinLevel(), def:getMaxLevel()
|
||||||
|
|
||||||
|
for z = bz1, bz2 do
|
||||||
|
for y = by1, by2 do
|
||||||
|
for x = bx1, bx2 do
|
||||||
|
syncBarrelInSquare(cell:getGridSquare(x, y, z))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif descriptor.poweredSquares then
|
||||||
|
for tileKey, _ in pairs(descriptor.poweredSquares) do
|
||||||
|
local x, y, z = parseSquareKey(tileKey)
|
||||||
|
if x and y and z then
|
||||||
|
syncBarrelInSquare(cell:getGridSquare(x, y, z))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif descriptor.room then
|
||||||
|
local room = descriptor.room
|
||||||
|
local rects = room:getRects()
|
||||||
|
if rects then
|
||||||
|
for i = 0, rects:size() - 1 do
|
||||||
|
local rect = rects:get(i)
|
||||||
|
local rx1, ry1 = rect:getX(), rect:getY()
|
||||||
|
local rx2, ry2 = rect:getX2(), rect:getY2()
|
||||||
|
local rz = sz
|
||||||
|
if rect.getZ then
|
||||||
|
rz = rect:getZ()
|
||||||
|
end
|
||||||
|
|
||||||
|
for y = ry1, ry2 do
|
||||||
|
for x = rx1, rx2 do
|
||||||
|
local sq = cell:getGridSquare(x, y, rz)
|
||||||
|
if sq and sq:getRoom() == room then
|
||||||
|
syncBarrelInSquare(sq)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
WPVirtual.BuildingAdd(sx, sy, sz, bid)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not WPVirtual.__WaterpipesB42PatchApplied then
|
||||||
|
local originalBarrelAdd = WPVirtual.BarrelAdd
|
||||||
|
WPVirtual.BarrelAdd = function(x, y, z, wmax, bid)
|
||||||
|
if (not bid or bid == "") and WPIso and WPIso.GetBuildingBid and getCell then
|
||||||
|
local sq = getCell():getGridSquare(x, y, z)
|
||||||
|
if sq then
|
||||||
|
bid = WPIso.GetBuildingBid(sq)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return originalBarrelAdd(x, y, z, wmax, bid)
|
||||||
|
end
|
||||||
|
WPVirtual.__WaterpipesB42PatchApplied = true
|
||||||
|
end
|
||||||
|
|
||||||
|
WPIso.__WaterpipesB42PatchApplied = true
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tryApplyPatch()
|
||||||
|
if Patch.applied then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if patchWPIso() then
|
||||||
|
Patch.applied = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local TRACE_MATRIX = {
|
||||||
|
ne = { n = {{x=1,y=0,z=0}}, e = {{x=0,y=-1,z=0}} },
|
||||||
|
se = { s = {{x=1,y=0,z=0}}, e = {{x=0,y=1,z=0}} },
|
||||||
|
sw = { s = {{x=-1,y=0,z=0}}, w = {{x=0,y=1,z=0}} },
|
||||||
|
nw = { n = {{x=-1,y=0,z=0}}, w = {{x=0,y=-1,z=0}} },
|
||||||
|
ns = { n = {{x=0,y=1,z=0}}, s = {{x=0,y=-1,z=0}} },
|
||||||
|
we = { w = {{x=1,y=0,z=0}}, e = {{x=-1,y=0,z=0}} },
|
||||||
|
nd = { n = {{x=0,y=0,z=-1}}, d = {{x=0,y=-1,z=0}} },
|
||||||
|
sd = { s = {{x=0,y=0,z=-1}}, d = {{x=0,y=1,z=0}} },
|
||||||
|
wd = { w = {{x=0,y=0,z=-1}}, d = {{x=-1,y=0,z=0}} },
|
||||||
|
ed = { e = {{x=0,y=0,z=-1}}, d = {{x=1,y=0,z=0}} },
|
||||||
|
nu = { n = {{x=0,y=0,z=1}}, u = {{x=0,y=-1,z=0}} },
|
||||||
|
su = { s = {{x=0,y=0,z=1}}, u = {{x=0,y=1,z=0}} },
|
||||||
|
wu = { w = {{x=0,y=0,z=1}}, u = {{x=-1,y=0,z=0}} },
|
||||||
|
eu = { e = {{x=0,y=0,z=1}}, u = {{x=1,y=0,z=0}} },
|
||||||
|
nsw = { n = {{x=-1,y=0,z=0},{x=0,y=1,z=0}}, s = {{x=-1,y=0,z=0},{x=0,y=-1,z=0}}, w = {{x=0,y=-1,z=0},{x=0,y=1,z=0}} },
|
||||||
|
swe = { s = {{x=-1,y=0,z=0},{x=1,y=0,z=0}}, w = {{x=1,y=0,z=0},{x=0,y=1,z=0}}, e = {{x=-1,y=0,z=0},{x=0,y=1,z=0}} },
|
||||||
|
nse = { n = {{x=1,y=0,z=0},{x=0,y=1,z=0}}, s = {{x=1,y=0,z=0},{x=0,y=-1,z=0}}, e = {{x=0,y=-1,z=0},{x=0,y=1,z=0}} },
|
||||||
|
nwe = { n = {{x=-1,y=0,z=0},{x=1,y=0,z=0}}, w = {{x=1,y=0,z=0},{x=0,y=-1,z=0}}, e = {{x=-1,y=0,z=0},{x=0,y=-1,z=0}} },
|
||||||
|
nswe = { n = {{x=-1,y=0,z=0},{x=1,y=0,z=0},{x=0,y=1,z=0}}, s = {{x=-1,y=0,z=0},{x=1,y=0,z=0},{x=0,y=-1,z=0}}, w = {{x=0,y=-1,z=0},{x=0,y=1,z=0},{x=1,y=0,z=0}}, e = {{x=0,y=-1,z=0},{x=0,y=1,z=0},{x=-1,y=0,z=0}} },
|
||||||
|
}
|
||||||
|
|
||||||
|
local function countEntries(t)
|
||||||
|
local c = 0
|
||||||
|
for _ in pairs(t) do
|
||||||
|
c = c + 1
|
||||||
|
end
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getTraceVectors(shape, dir)
|
||||||
|
local vectors = {}
|
||||||
|
local shapeData = TRACE_MATRIX[shape]
|
||||||
|
if shapeData and shapeData[dir] then
|
||||||
|
for _, vector in ipairs(shapeData[dir]) do
|
||||||
|
local d
|
||||||
|
if vector.x == 1 then d = "w" end
|
||||||
|
if vector.x == -1 then d = "e" end
|
||||||
|
if vector.y == 1 then d = "n" end
|
||||||
|
if vector.y == -1 then d = "s" end
|
||||||
|
if vector.z == 1 then d = "d" end
|
||||||
|
if vector.z == -1 then d = "u" end
|
||||||
|
|
||||||
|
table.insert(vectors, {x = vector.x, y = vector.y, z = vector.z, d = d})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return vectors
|
||||||
|
end
|
||||||
|
|
||||||
|
local function addDebugTile(set, x, y, z, symbol)
|
||||||
|
local id = WPUtils and WPUtils.Coords2Id and WPUtils.Coords2Id(x, y, z) or (tostring(x) .. "-" .. tostring(y) .. "-" .. tostring(z))
|
||||||
|
if not set[id] then
|
||||||
|
set[id] = {x = x, y = y, z = z, symbol = symbol}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tracePumpNetwork(gmd, pump)
|
||||||
|
local traced = {
|
||||||
|
pump = pump,
|
||||||
|
pipes = {},
|
||||||
|
targets = {},
|
||||||
|
closedValves = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
local dirs = {
|
||||||
|
w = {x=1, y=0, z=0},
|
||||||
|
e = {x=-1, y=0, z=0},
|
||||||
|
n = {x=0, y=1, z=0},
|
||||||
|
s = {x=0, y=-1, z=0},
|
||||||
|
}
|
||||||
|
|
||||||
|
local stack = {}
|
||||||
|
for dir, vector in pairs(dirs) do
|
||||||
|
table.insert(stack, {x = pump.x + vector.x, y = pump.y + vector.y, z = pump.z + vector.z, dir = dir, depth = 0})
|
||||||
|
end
|
||||||
|
|
||||||
|
local visited = {}
|
||||||
|
local buildingBidsExpanded = {}
|
||||||
|
local safety = 0
|
||||||
|
|
||||||
|
while #stack > 0 do
|
||||||
|
local state = table.remove(stack)
|
||||||
|
safety = safety + 1
|
||||||
|
if safety > 5000 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local stateKey = tostring(state.x) .. ":" .. tostring(state.y) .. ":" .. tostring(state.z) .. ":" .. tostring(state.dir)
|
||||||
|
if not visited[stateKey] and state.depth <= 128 then
|
||||||
|
visited[stateKey] = true
|
||||||
|
|
||||||
|
local nodeId = WPUtils.Coords2Id(state.x, state.y, state.z)
|
||||||
|
local pipe = gmd.Pipes[nodeId]
|
||||||
|
local barrel = gmd.Barrels[nodeId]
|
||||||
|
local building = gmd.Buildings[nodeId]
|
||||||
|
|
||||||
|
if pipe then
|
||||||
|
addDebugTile(traced.pipes, state.x, state.y, state.z, ".")
|
||||||
|
|
||||||
|
if gmd.Sprinklers[nodeId] then
|
||||||
|
addDebugTile(traced.targets, state.x, state.y, state.z, "S")
|
||||||
|
end
|
||||||
|
|
||||||
|
local valve = gmd.Valves[nodeId]
|
||||||
|
if valve and valve.c then
|
||||||
|
addDebugTile(traced.closedValves, state.x, state.y, state.z, "X")
|
||||||
|
else
|
||||||
|
local vectors = getTraceVectors(pipe.s, state.dir)
|
||||||
|
for _, vector in ipairs(vectors) do
|
||||||
|
table.insert(stack, {
|
||||||
|
x = state.x + vector.x,
|
||||||
|
y = state.y + vector.y,
|
||||||
|
z = state.z + vector.z,
|
||||||
|
dir = vector.d,
|
||||||
|
depth = state.depth + 1
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif building then
|
||||||
|
addDebugTile(traced.targets, state.x, state.y, state.z, "B")
|
||||||
|
|
||||||
|
if building.bid and not buildingBidsExpanded[building.bid] then
|
||||||
|
buildingBidsExpanded[building.bid] = true
|
||||||
|
for _, barrelNode in pairs(gmd.Barrels) do
|
||||||
|
if barrelNode.bid == building.bid then
|
||||||
|
addDebugTile(traced.targets, barrelNode.x, barrelNode.y, barrelNode.z, "T")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif barrel then
|
||||||
|
addDebugTile(traced.targets, state.x, state.y, state.z, "R")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
traced.pipeCount = countEntries(traced.pipes)
|
||||||
|
traced.targetCount = countEntries(traced.targets)
|
||||||
|
traced.closedValveCount = countEntries(traced.closedValves)
|
||||||
|
|
||||||
|
return traced
|
||||||
|
end
|
||||||
|
|
||||||
|
Patch.debugOverlay = Patch.debugOverlay or {
|
||||||
|
enabled = false,
|
||||||
|
recomputeEveryTicks = 30,
|
||||||
|
tick = 0,
|
||||||
|
cache = {},
|
||||||
|
unbound = {},
|
||||||
|
missingWorld = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
local function isDebugModeEnabled()
|
||||||
|
if isDebugEnabled then
|
||||||
|
local ok, enabled = pcall(isDebugEnabled)
|
||||||
|
if ok and enabled then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local core = getCore and getCore() or nil
|
||||||
|
if core then
|
||||||
|
if core.isDebug then
|
||||||
|
local ok, enabled = pcall(function() return core:isDebug() end)
|
||||||
|
if ok and enabled then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if core.getDebug then
|
||||||
|
local ok, enabled = pcall(function() return core:getDebug() end)
|
||||||
|
if ok and enabled then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function scanAndRegisterNearbyWorldBarrels()
|
||||||
|
if isServer() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local player = getSpecificPlayer(0)
|
||||||
|
if not player then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local gmd = GetWPModData and GetWPModData() or nil
|
||||||
|
local cell = getCell and getCell() or nil
|
||||||
|
if not gmd or not cell or not WPIso or not WPIso.GetBarrel then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local px, py, pz = math.floor(player:getX()), math.floor(player:getY()), math.floor(player:getZ())
|
||||||
|
local radius = 22
|
||||||
|
local zMin = math.max(0, pz - 1)
|
||||||
|
local zMax = math.min(8, pz + 3)
|
||||||
|
|
||||||
|
local missing = {}
|
||||||
|
for z = zMin, zMax do
|
||||||
|
for y = py - radius, py + radius do
|
||||||
|
for x = px - radius, px + radius do
|
||||||
|
local sq = cell:getGridSquare(x, y, z)
|
||||||
|
if sq then
|
||||||
|
local isoBarrel = WPIso.GetBarrel(sq)
|
||||||
|
if isoBarrel then
|
||||||
|
local id = WPUtils.Coords2Id(x, y, z)
|
||||||
|
local barrelNode = gmd.Barrels and gmd.Barrels[id] or nil
|
||||||
|
if not barrelNode then
|
||||||
|
addDebugTile(missing, x, y, z, "M")
|
||||||
|
|
||||||
|
local _, wmax = WPIso.GetWaterStatus(isoBarrel)
|
||||||
|
local bid = WPIso.GetBuildingBid and WPIso.GetBuildingBid(sq) or nil
|
||||||
|
if (not bid or bid == "") then
|
||||||
|
bid = findNearbyLinkedBid(sq, 4, 2)
|
||||||
|
end
|
||||||
|
if WPVirtual and WPVirtual.BarrelAdd then
|
||||||
|
WPVirtual.BarrelAdd(x, y, z, (wmax or 40) * 100, bid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Patch.debugOverlay.missingWorld = missing
|
||||||
|
end
|
||||||
|
|
||||||
|
local function relinkUnboundBarrels()
|
||||||
|
if isServer() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local player = getSpecificPlayer(0)
|
||||||
|
if not player then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local gmd = GetWPModData and GetWPModData() or nil
|
||||||
|
if not gmd or not gmd.Barrels then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local cell = getCell and getCell() or nil
|
||||||
|
if not cell or not WPIso or not WPIso.GetBuildingBid then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, barrel in pairs(gmd.Barrels) do
|
||||||
|
local sq = cell:getGridSquare(barrel.x, barrel.y, barrel.z)
|
||||||
|
if sq then
|
||||||
|
local bid = WPIso.GetBuildingBid(sq)
|
||||||
|
if not bid or bid == "" then
|
||||||
|
bid = findNearbyLinkedBid(sq, 3, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
if bid and bid ~= barrel.bid and sendClientCommand then
|
||||||
|
barrel.bid = bid
|
||||||
|
sendClientCommand(player, "Commands", "BarrelMod", {
|
||||||
|
x = barrel.x,
|
||||||
|
y = barrel.y,
|
||||||
|
z = barrel.z,
|
||||||
|
bid = bid,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Also discover physical sinks/taps that never entered virtual data.
|
||||||
|
scanAndRegisterNearbyWorldBarrels()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function recomputeDebugOverlay()
|
||||||
|
if isServer() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local gmd = GetWPModData and GetWPModData() or nil
|
||||||
|
if not gmd or not gmd.Pumps then
|
||||||
|
Patch.debugOverlay.cache = {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local out = {}
|
||||||
|
local unbound = {}
|
||||||
|
for _, pump in pairs(gmd.Pumps) do
|
||||||
|
table.insert(out, tracePumpNetwork(gmd, pump))
|
||||||
|
end
|
||||||
|
for _, barrel in pairs(gmd.Barrels or {}) do
|
||||||
|
if not barrel.bid or barrel.bid == "" then
|
||||||
|
addDebugTile(unbound, barrel.x, barrel.y, barrel.z, "U")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Patch.debugOverlay.cache = out
|
||||||
|
Patch.debugOverlay.unbound = unbound
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawTextSafe(x, y, text, r, g, b, a)
|
||||||
|
local tm = getTextManager and getTextManager() or nil
|
||||||
|
if not tm then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok = false
|
||||||
|
if UIFont and tm.DrawString then
|
||||||
|
ok = pcall(function()
|
||||||
|
tm:DrawString(UIFont.Small, x, y, text, r, g, b, a)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not ok and tm.DrawString then
|
||||||
|
pcall(function()
|
||||||
|
tm:DrawString(x, y, text, r, g, b, a)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawTileSymbol(playerNum, tile, text, r, g, b, a)
|
||||||
|
local tx = isoToScreenX(playerNum, tile.x + 0.5, tile.y + 0.5, tile.z)
|
||||||
|
local ty = isoToScreenY(playerNum, tile.x + 0.5, tile.y + 0.5, tile.z)
|
||||||
|
drawTextSafe(tx, ty, text, r, g, b, a)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function renderDebugOverlay()
|
||||||
|
if isServer() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not Patch.debugOverlay.enabled then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not isIngameState() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local player = getSpecificPlayer(0)
|
||||||
|
if not player then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Patch.debugOverlay.tick = Patch.debugOverlay.tick + 1
|
||||||
|
if Patch.debugOverlay.tick % Patch.debugOverlay.recomputeEveryTicks == 0 then
|
||||||
|
recomputeDebugOverlay()
|
||||||
|
end
|
||||||
|
|
||||||
|
local playerNum = player:getPlayerNum()
|
||||||
|
local px, py, pz = player:getX(), player:getY(), player:getZ()
|
||||||
|
local range = 55
|
||||||
|
local yLine = 186
|
||||||
|
local unboundCount = countEntries(Patch.debugOverlay.unbound or {})
|
||||||
|
local missingWorldCount = countEntries(Patch.debugOverlay.missingWorld or {})
|
||||||
|
|
||||||
|
drawTextSafe(20, 150, "[WP DEBUG] Pump grid overlay ON (P pump, . pipe, B building node, T tap, R barrel, S sprinkler, X closed valve)", 0.8, 0.95, 1.0, 0.95)
|
||||||
|
drawTextSafe(20, 166, string.format("[WP DEBUG] Unbound taps (U): %d (auto relink every 1 in-game minute)", unboundCount), 1.0, 0.45, 0.45, 0.95)
|
||||||
|
drawTextSafe(20, 182, string.format("[WP DEBUG] Missing virtual taps (M): %d (physical object found, auto-registering)", missingWorldCount), 1.0, 0.65, 0.2, 0.95)
|
||||||
|
|
||||||
|
for _, traced in ipairs(Patch.debugOverlay.cache) do
|
||||||
|
local pump = traced.pump
|
||||||
|
local pActive = tostring(pump.active == true)
|
||||||
|
local pSource = pump.source and tostring(pump.source) or "none"
|
||||||
|
local summary = string.format("Pump %d,%d,%d active=%s source=%s pipes=%d targets=%d closedValves=%d",
|
||||||
|
pump.x, pump.y, pump.z, pActive, pSource, traced.pipeCount or 0, traced.targetCount or 0, traced.closedValveCount or 0)
|
||||||
|
drawTextSafe(20, yLine, summary, 0.6, 0.9, 1.0, 0.95)
|
||||||
|
yLine = yLine + 16
|
||||||
|
|
||||||
|
if math.abs(pump.x - px) <= range and math.abs(pump.y - py) <= range and math.abs(pump.z - pz) <= 4 then
|
||||||
|
drawTileSymbol(playerNum, {x = pump.x, y = pump.y, z = pump.z}, "P", 0.25, 0.65, 1.0, 1.0)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, tile in pairs(traced.pipes) do
|
||||||
|
if math.abs(tile.x - px) <= range and math.abs(tile.y - py) <= range and math.abs(tile.z - pz) <= 4 then
|
||||||
|
drawTileSymbol(playerNum, tile, ".", 0.35, 0.95, 1.0, 0.9)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, tile in pairs(traced.targets) do
|
||||||
|
if math.abs(tile.x - px) <= range and math.abs(tile.y - py) <= range and math.abs(tile.z - pz) <= 4 then
|
||||||
|
local symbol = tile.symbol or "T"
|
||||||
|
if symbol == "B" then
|
||||||
|
drawTileSymbol(playerNum, tile, symbol, 1.0, 0.9, 0.2, 1.0)
|
||||||
|
elseif symbol == "S" then
|
||||||
|
drawTileSymbol(playerNum, tile, symbol, 0.8, 0.9, 1.0, 1.0)
|
||||||
|
elseif symbol == "R" then
|
||||||
|
drawTileSymbol(playerNum, tile, symbol, 0.95, 0.65, 1.0, 1.0)
|
||||||
|
else
|
||||||
|
drawTileSymbol(playerNum, tile, symbol, 0.35, 1.0, 0.4, 1.0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, tile in pairs(traced.closedValves) do
|
||||||
|
if math.abs(tile.x - px) <= range and math.abs(tile.y - py) <= range and math.abs(tile.z - pz) <= 4 then
|
||||||
|
drawTileSymbol(playerNum, tile, "X", 1.0, 0.25, 0.25, 1.0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, tile in pairs(Patch.debugOverlay.unbound or {}) do
|
||||||
|
if math.abs(tile.x - px) <= range and math.abs(tile.y - py) <= range and math.abs(tile.z - pz) <= 4 then
|
||||||
|
drawTileSymbol(playerNum, tile, "U", 1.0, 0.2, 0.2, 1.0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, tile in pairs(Patch.debugOverlay.missingWorld or {}) do
|
||||||
|
if math.abs(tile.x - px) <= range and math.abs(tile.y - py) <= range and math.abs(tile.z - pz) <= 4 then
|
||||||
|
drawTileSymbol(playerNum, tile, "M", 1.0, 0.7, 0.1, 1.0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function onPumpDebugToggle(_player, _context, _worldobjects, _test)
|
||||||
|
if isServer() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not isDebugModeEnabled() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local fetch = ISWorldObjectContextMenu and ISWorldObjectContextMenu.fetchVars or nil
|
||||||
|
local square = fetch and fetch.clickedSquare or nil
|
||||||
|
if not square or not WPIso or not WPIso.GetPump then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local isoPump = WPIso.GetPump(square)
|
||||||
|
if not isoPump then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local context = _context
|
||||||
|
if not context then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local label
|
||||||
|
if Patch.debugOverlay.enabled then
|
||||||
|
label = "[WP DEBUG] Hide Pump Overlay"
|
||||||
|
else
|
||||||
|
label = "[WP DEBUG] Show Pump Overlay"
|
||||||
|
end
|
||||||
|
|
||||||
|
context:addOption(label, nil, function()
|
||||||
|
Patch.debugOverlay.enabled = not Patch.debugOverlay.enabled
|
||||||
|
if Patch.debugOverlay.enabled then
|
||||||
|
scanAndRegisterNearbyWorldBarrels()
|
||||||
|
recomputeDebugOverlay()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
if Events and Events.OnGameBoot then
|
||||||
|
Events.OnGameBoot.Add(tryApplyPatch)
|
||||||
|
end
|
||||||
|
if Events and Events.OnGameStart then
|
||||||
|
Events.OnGameStart.Add(tryApplyPatch)
|
||||||
|
end
|
||||||
|
if Events and Events.OnInitGlobalModData then
|
||||||
|
Events.OnInitGlobalModData.Add(tryApplyPatch)
|
||||||
|
end
|
||||||
|
if Events and Events.OnPreUIDraw then
|
||||||
|
Events.OnPreUIDraw.Add(renderDebugOverlay)
|
||||||
|
end
|
||||||
|
if Events and Events.OnPreFillWorldObjectContextMenu then
|
||||||
|
Events.OnPreFillWorldObjectContextMenu.Add(onPumpDebugToggle)
|
||||||
|
end
|
||||||
|
if Events and Events.EveryOneMinute then
|
||||||
|
Events.EveryOneMinute.Add(relinkUnboundBarrels)
|
||||||
|
end
|
||||||
|
|
||||||
|
tryApplyPatch()
|
||||||
6
42/mod.info
Normal file
6
42/mod.info
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
name=Waterpipes B42 Patch
|
||||||
|
id=hrsys_waterpipes_b42_patch
|
||||||
|
description=Runtime compatibility patch for Waterpipes B42.13: player-constructed buildings + multi-level discovery (-1 to +3).
|
||||||
|
author=Riggs0
|
||||||
|
versionMin=42.13
|
||||||
|
require=\Waterpipes
|
||||||
0
common/placeholder.txt
Normal file
0
common/placeholder.txt
Normal file
Reference in New Issue
Block a user