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()