diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8eaa9db --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/source \ No newline at end of file diff --git a/42/media/lua/shared/WaterpipesB42Patch.lua b/42/media/lua/shared/WaterpipesB42Patch.lua new file mode 100644 index 0000000..14365df --- /dev/null +++ b/42/media/lua/shared/WaterpipesB42Patch.lua @@ -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() diff --git a/42/mod.info b/42/mod.info new file mode 100644 index 0000000..8db32a5 --- /dev/null +++ b/42/mod.info @@ -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 diff --git a/common/placeholder.txt b/common/placeholder.txt new file mode 100644 index 0000000..e69de29