if not TowBarMod then TowBarMod = {} end TowBarMod.Persistence = TowBarMod.Persistence or {} local Persistence = TowBarMod.Persistence local RegistryName = "TowBar.PersistentPairsV2" local function getRegistry() if not ModData or not ModData.getOrCreate then return nil end local registry = ModData.getOrCreate(RegistryName) registry.pairs = registry.pairs or {} return registry end local function getSqlId(vehicle) if not vehicle or not vehicle.getSqlId then return nil end local ok, sqlId = pcall(function() return vehicle:getSqlId() end) if ok and type(sqlId) == "number" and sqlId >= 0 then return sqlId end return nil end local function makeKey(kind, towingSqlId, towedSqlId) if type(kind) ~= "string" or type(towingSqlId) ~= "number" or type(towedSqlId) ~= "number" then return nil end return kind .. ":" .. tostring(towingSqlId) .. ":" .. tostring(towedSqlId) end local function transmit() if isServer and isServer() and ModData.transmit then ModData.transmit(RegistryName) end end function Persistence.savePair(kind, towingVehicle, towedVehicle, attachmentA, attachmentB, heightLevel) local towingSqlId = getSqlId(towingVehicle) local towedSqlId = getSqlId(towedVehicle) local key = makeKey(kind, towingSqlId, towedSqlId) local registry = getRegistry() if not key or not registry then return false end local previous = registry.pairs[key] if previous and previous.attachmentA == attachmentA and previous.attachmentB == attachmentB and previous.heightLevel == heightLevel then return true end registry.pairs[key] = { kind = kind, towingSqlId = towingSqlId, towedSqlId = towedSqlId, attachmentA = attachmentA, attachmentB = attachmentB, heightLevel = heightLevel } transmit() return true end function Persistence.getPair(kind, towingVehicle, towedVehicle) local key = makeKey(kind, getSqlId(towingVehicle), getSqlId(towedVehicle)) local registry = getRegistry() return key and registry and registry.pairs[key] or nil end function Persistence.removePair(kind, towingVehicle, towedVehicle) local key = makeKey(kind, getSqlId(towingVehicle), getSqlId(towedVehicle)) local registry = getRegistry() if not key or not registry or registry.pairs[key] == nil then return false end registry.pairs[key] = nil transmit() return true end function Persistence.forEachPair(kind, callback) local registry = getRegistry() if not registry or not callback then return end for _, record in pairs(registry.pairs) do if type(record) == "table" and record.kind == kind then callback(record) end end end function Persistence.notePeerAvailability(state, available) state = state or {} if not available then state.peerMissing = true return state end if state.peerMissing then -- A missing vehicle means its cell was not loaded. When both members -- return, treat the absent native constraint as load recovery rather -- than a runtime break of a previously confirmed pair. state.peerMissing = nil state.confirmed = false state.pendingUntil = nil state.unlinkedSince = nil end return state end function Persistence.advanceRecoveryState(state, now, linked, occupied, retryMs, breakMs) state = state or {} if linked then state.confirmed = true state.pendingUntil = nil state.unlinkedSince = nil return state, "adopt" end if occupied then state.unlinkedSince = nil return state, "wait" end if state.confirmed then state.unlinkedSince = state.unlinkedSince or now if now - state.unlinkedSince >= breakMs then return state, "break" end return state, "wait" end if not state.pendingUntil or now >= state.pendingUntil then state.pendingUntil = now + retryMs return state, "restore" end return state, "wait" end Persistence.getSqlId = getSqlId