42.20 and Towtrucks
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
if isClient() then return end
|
||||
require("TowBar/Persistence")
|
||||
|
||||
local TowingCommands = {}
|
||||
local Commands = {}
|
||||
@@ -10,6 +11,9 @@ local pendingSync = {}
|
||||
local snapshotTickCounter = 0
|
||||
local brokenPairAuditTickCounter = 0
|
||||
local confirmedTowPairs = {}
|
||||
local towPairRuntime = {}
|
||||
local RestoreRetryMs = 2000
|
||||
local SustainedBreakMs = 5000
|
||||
local giveTowBar
|
||||
|
||||
TowingCommands.wantNoise = getDebug() or false
|
||||
@@ -88,6 +92,22 @@ local function isLinked(vehicleA, vehicleB)
|
||||
return vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA
|
||||
end
|
||||
|
||||
local function getPersistentVehicleId(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 matchesSavedVehicle(runtimeId, sqlId, vehicle)
|
||||
if not vehicle then return false end
|
||||
local stableId = tonumber(sqlId)
|
||||
if stableId and stableId >= 0 then
|
||||
return getPersistentVehicleId(vehicle) == stableId
|
||||
end
|
||||
return tonumber(runtimeId) == vehicle:getId()
|
||||
end
|
||||
|
||||
local function hasAnyTowLink(vehicle)
|
||||
if not vehicle then return false end
|
||||
return vehicle:getVehicleTowing() ~= nil or vehicle:getVehicleTowedBy() ~= nil
|
||||
@@ -95,12 +115,17 @@ end
|
||||
|
||||
local function getTowBarPairKey(vehicleA, vehicleB)
|
||||
if not vehicleA or not vehicleB then return nil end
|
||||
return tostring(vehicleA:getId()) .. ":" .. tostring(vehicleB:getId())
|
||||
local vehicleAId = getPersistentVehicleId(vehicleA) or vehicleA:getId()
|
||||
local vehicleBId = getPersistentVehicleId(vehicleB) or vehicleB:getId()
|
||||
return tostring(vehicleAId) .. ":" .. tostring(vehicleBId)
|
||||
end
|
||||
|
||||
local function markTowBarPairConfirmed(vehicleA, vehicleB)
|
||||
local key = getTowBarPairKey(vehicleA, vehicleB)
|
||||
if key then confirmedTowPairs[key] = true end
|
||||
if key then
|
||||
confirmedTowPairs[key] = true
|
||||
towPairRuntime[key] = { confirmed = true }
|
||||
end
|
||||
end
|
||||
|
||||
local function isTowBarPairConfirmed(vehicleA, vehicleB)
|
||||
@@ -110,7 +135,10 @@ end
|
||||
|
||||
local function forgetTowBarPair(vehicleA, vehicleB)
|
||||
local key = getTowBarPairKey(vehicleA, vehicleB)
|
||||
if key then confirmedTowPairs[key] = nil end
|
||||
if key then
|
||||
confirmedTowPairs[key] = nil
|
||||
towPairRuntime[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function hasTowBarState(vehicle)
|
||||
@@ -130,25 +158,35 @@ local function markExpectedTowBarPair(vehicleA, vehicleB, attachmentA, attachmen
|
||||
towingModData["isTowingByTowBar"] = true
|
||||
towingModData["towed"] = false
|
||||
towingModData["towBarTowedVehicleId"] = vehicleB:getId()
|
||||
towingModData["towBarTowedVehicleSqlId"] = getPersistentVehicleId(vehicleB)
|
||||
towingModData["towBarTowingVehicleId"] = nil
|
||||
towingModData["towBarTowingVehicleSqlId"] = nil
|
||||
towingModData["towBarExpectedAttachment"] = attachmentA
|
||||
towedModData["isTowingByTowBar"] = true
|
||||
towedModData["towed"] = true
|
||||
towedModData["towBarTowedVehicleId"] = nil
|
||||
towedModData["towBarTowedVehicleSqlId"] = nil
|
||||
towedModData["towBarTowingVehicleId"] = vehicleA:getId()
|
||||
towedModData["towBarTowingVehicleSqlId"] = getPersistentVehicleId(vehicleA)
|
||||
towedModData["towBarExpectedAttachment"] = attachmentB
|
||||
vehicleA:transmitModData()
|
||||
vehicleB:transmitModData()
|
||||
TowBarMod.Persistence.savePair("towbar", vehicleA, vehicleB, attachmentA, attachmentB)
|
||||
end
|
||||
|
||||
local function clearExpectedTowBarPair(vehicleA, vehicleB)
|
||||
if vehicleA and vehicleB then
|
||||
TowBarMod.Persistence.removePair("towbar", vehicleA, vehicleB)
|
||||
end
|
||||
if vehicleA then
|
||||
local towingModData = vehicleA:getModData()
|
||||
if towingModData then
|
||||
towingModData["isTowingByTowBar"] = false
|
||||
towingModData["towed"] = false
|
||||
towingModData["towBarTowedVehicleId"] = nil
|
||||
towingModData["towBarTowedVehicleSqlId"] = nil
|
||||
towingModData["towBarTowingVehicleId"] = nil
|
||||
towingModData["towBarTowingVehicleSqlId"] = nil
|
||||
towingModData["towBarExpectedAttachment"] = nil
|
||||
vehicleA:transmitModData()
|
||||
end
|
||||
@@ -160,7 +198,9 @@ local function clearExpectedTowBarPair(vehicleA, vehicleB)
|
||||
towedModData["isTowingByTowBar"] = false
|
||||
towedModData["towed"] = false
|
||||
towedModData["towBarTowedVehicleId"] = nil
|
||||
towedModData["towBarTowedVehicleSqlId"] = nil
|
||||
towedModData["towBarTowingVehicleId"] = nil
|
||||
towedModData["towBarTowingVehicleSqlId"] = nil
|
||||
towedModData["towBarExpectedAttachment"] = nil
|
||||
vehicleB:transmitModData()
|
||||
end
|
||||
@@ -174,8 +214,11 @@ local function isExpectedTowBarPair(vehicleA, vehicleB)
|
||||
local towedModData = vehicleB:getModData()
|
||||
if not towingModData or not towedModData then return false end
|
||||
|
||||
return towingModData["towBarTowedVehicleId"] == vehicleB:getId()
|
||||
and towedModData["towBarTowingVehicleId"] == vehicleA:getId()
|
||||
return matchesSavedVehicle(
|
||||
towingModData["towBarTowedVehicleId"], towingModData["towBarTowedVehicleSqlId"], vehicleB
|
||||
) and matchesSavedVehicle(
|
||||
towedModData["towBarTowingVehicleId"], towedModData["towBarTowingVehicleSqlId"], vehicleA
|
||||
)
|
||||
end
|
||||
|
||||
local function isLegacyTowBarPair(vehicleA, vehicleB)
|
||||
@@ -295,14 +338,34 @@ local function forEachCollectionItem(collection, callback)
|
||||
end
|
||||
end
|
||||
|
||||
local function findLoadedVehicle(runtimeId, sqlId)
|
||||
local stableId = tonumber(sqlId)
|
||||
if stableId and stableId >= 0 then
|
||||
local cell = getCell()
|
||||
local vehicles = cell and cell:getVehicles() or nil
|
||||
local match
|
||||
forEachCollectionItem(vehicles, function(vehicle)
|
||||
if not match and getPersistentVehicleId(vehicle) == stableId then match = vehicle end
|
||||
end)
|
||||
return match
|
||||
end
|
||||
return runtimeId and getVehicleById(tonumber(runtimeId)) or nil
|
||||
end
|
||||
|
||||
local function broadcastAttach(vehicleA, vehicleB, attachmentA, attachmentB)
|
||||
if not vehicleA or not vehicleB then return end
|
||||
sendServerCommand("towbar", "forceAttachSync", {
|
||||
local args = {
|
||||
vehicleA = vehicleA:getId(),
|
||||
vehicleB = vehicleB:getId(),
|
||||
attachmentA = attachmentA,
|
||||
attachmentB = attachmentB
|
||||
})
|
||||
}
|
||||
if isServer() then
|
||||
sendServerCommand("towbar", "forceAttachSync", args)
|
||||
elseif not isClient() and TowBarMod and TowBarMod.Sync
|
||||
and TowBarMod.Sync.applyAttachSync then
|
||||
TowBarMod.Sync.applyAttachSync(args)
|
||||
end
|
||||
end
|
||||
|
||||
local function broadcastDetach(vehicleAId, vehicleBId)
|
||||
@@ -320,6 +383,33 @@ local function broadcastSpontaneousDetach(vehicleA, vehicleB)
|
||||
})
|
||||
end
|
||||
|
||||
local function restorePersistedTowBarPair(towingVehicle, towedVehicle)
|
||||
if not isExpectedTowBarPair(towingVehicle, towedVehicle)
|
||||
or hasAnyTowLink(towingVehicle) or hasAnyTowLink(towedVehicle) then
|
||||
return false
|
||||
end
|
||||
|
||||
local towingModData = towingVehicle:getModData()
|
||||
local towedModData = towedVehicle:getModData()
|
||||
local attachmentA = towingModData["towBarExpectedAttachment"] or "trailer"
|
||||
local attachmentB = towedModData["towBarExpectedAttachment"] or "trailerfront"
|
||||
if not towingVehicle:attachmentExist(attachmentA)
|
||||
or not towedVehicle:attachmentExist(attachmentB) then
|
||||
return false
|
||||
end
|
||||
|
||||
markExpectedTowBarPair(towingVehicle, towedVehicle, attachmentA, attachmentB)
|
||||
if isServer() then
|
||||
towingVehicle:addPointConstraint(nil, towedVehicle, attachmentA, attachmentB)
|
||||
end
|
||||
broadcastAttach(towingVehicle, towedVehicle, attachmentA, attachmentB)
|
||||
if isLinked(towingVehicle, towedVehicle) then
|
||||
markTowBarPairConfirmed(towingVehicle, towedVehicle)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function breakTowBarConstraint(towingVehicle, towedVehicle)
|
||||
if not towingVehicle or not towedVehicle then return end
|
||||
|
||||
@@ -400,19 +490,80 @@ local function reconcileBrokenTowBarPairsServer()
|
||||
local vehicles = cell:getVehicles()
|
||||
if not vehicles then return end
|
||||
|
||||
local loadedBySqlId = {}
|
||||
forEachCollectionItem(vehicles, function(vehicle)
|
||||
local sqlId = getPersistentVehicleId(vehicle)
|
||||
if sqlId then loadedBySqlId[sqlId] = vehicle end
|
||||
end)
|
||||
|
||||
local processed = {}
|
||||
local function reconcilePair(towingVehicle, towedVehicle, attachmentA, attachmentB)
|
||||
if not towingVehicle or not towedVehicle then return end
|
||||
local key = getTowBarPairKey(towingVehicle, towedVehicle)
|
||||
if not key or processed[key] then return end
|
||||
processed[key] = true
|
||||
|
||||
if not isExpectedTowBarPair(towingVehicle, towedVehicle) then
|
||||
markExpectedTowBarPair(
|
||||
towingVehicle, towedVehicle,
|
||||
attachmentA or "trailer", attachmentB or "trailerfront"
|
||||
)
|
||||
end
|
||||
if not isExpectedTowBarPair(towingVehicle, towedVehicle) then return end
|
||||
|
||||
local now = getTimestampMs()
|
||||
local state = towPairRuntime[key] or {}
|
||||
local linked = isLinked(towingVehicle, towedVehicle)
|
||||
local occupied = hasAnyTowLink(towingVehicle) or hasAnyTowLink(towedVehicle)
|
||||
local action
|
||||
state, action = TowBarMod.Persistence.advanceRecoveryState(
|
||||
state, now, linked, occupied, RestoreRetryMs, SustainedBreakMs
|
||||
)
|
||||
towPairRuntime[key] = state
|
||||
if action == "adopt" then
|
||||
local towingMd = towingVehicle:getModData()
|
||||
local towedMd = towedVehicle:getModData()
|
||||
local savedAttachmentA = towingMd["towBarExpectedAttachment"] or attachmentA or "trailer"
|
||||
local savedAttachmentB = towedMd["towBarExpectedAttachment"] or attachmentB or "trailerfront"
|
||||
if tonumber(towingMd["towBarTowedVehicleId"]) ~= towedVehicle:getId()
|
||||
or tonumber(towedMd["towBarTowingVehicleId"]) ~= towingVehicle:getId() then
|
||||
markExpectedTowBarPair(
|
||||
towingVehicle, towedVehicle,
|
||||
savedAttachmentA, savedAttachmentB
|
||||
)
|
||||
end
|
||||
TowBarMod.Persistence.savePair(
|
||||
"towbar", towingVehicle, towedVehicle, savedAttachmentA, savedAttachmentB
|
||||
)
|
||||
confirmedTowPairs[key] = true
|
||||
elseif action == "break" then
|
||||
finalizeBrokenTowBarPair(towingVehicle, towedVehicle, "sustained-physical-link-audit")
|
||||
elseif action == "restore" then
|
||||
restorePersistedTowBarPair(towingVehicle, towedVehicle)
|
||||
end
|
||||
end
|
||||
|
||||
TowBarMod.Persistence.forEachPair("towbar", function(record)
|
||||
local towingVehicle = loadedBySqlId[tonumber(record.towingSqlId)]
|
||||
local towedVehicle = loadedBySqlId[tonumber(record.towedSqlId)]
|
||||
local key = tostring(record.towingSqlId) .. ":" .. tostring(record.towedSqlId)
|
||||
towPairRuntime[key] = TowBarMod.Persistence.notePeerAvailability(
|
||||
towPairRuntime[key], towingVehicle ~= nil and towedVehicle ~= nil
|
||||
)
|
||||
reconcilePair(
|
||||
towingVehicle,
|
||||
towedVehicle,
|
||||
record.attachmentA,
|
||||
record.attachmentB
|
||||
)
|
||||
end)
|
||||
|
||||
forEachCollectionItem(vehicles, function(towingVehicle)
|
||||
local towingModData = towingVehicle and towingVehicle:getModData() or nil
|
||||
local towedVehicleId = towingModData and towingModData["towBarTowedVehicleId"] or nil
|
||||
local towedVehicle = towedVehicleId and getVehicleById(towedVehicleId) or nil
|
||||
if towingVehicle and towedVehicle and isExpectedTowBarPair(towingVehicle, towedVehicle) then
|
||||
if isLinked(towingVehicle, towedVehicle) then
|
||||
markTowBarPairConfirmed(towingVehicle, towedVehicle)
|
||||
elseif isTowBarPairConfirmed(towingVehicle, towedVehicle)
|
||||
and not hasAnyTowLink(towingVehicle)
|
||||
and not hasAnyTowLink(towedVehicle) then
|
||||
finalizeBrokenTowBarPair(towingVehicle, towedVehicle, "physical-link-audit")
|
||||
end
|
||||
end
|
||||
local towedVehicleSqlId = towingModData and towingModData["towBarTowedVehicleSqlId"] or nil
|
||||
local towedVehicle = findLoadedVehicle(towedVehicleId, towedVehicleSqlId)
|
||||
if towingVehicle and towedVehicle then reconcilePair(towingVehicle, towedVehicle) end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user