Working 42.20 MP
This commit is contained in:
@@ -4,10 +4,8 @@ require("TowBar/Persistence")
|
||||
local TowingCommands = {}
|
||||
local Commands = {}
|
||||
local TowBarItemType = "TowBar.TowBar"
|
||||
local SyncDelayTicks = 2
|
||||
local SnapshotIntervalTicks = 120
|
||||
local BrokenPairAuditIntervalTicks = 5
|
||||
local pendingSync = {}
|
||||
local snapshotTickCounter = 0
|
||||
local brokenPairAuditTickCounter = 0
|
||||
local confirmedTowPairs = {}
|
||||
@@ -24,57 +22,6 @@ local noise = function(msg)
|
||||
end
|
||||
end
|
||||
|
||||
local function queueSync(kind, player, args, reservedTowBar)
|
||||
if not args then return end
|
||||
table.insert(pendingSync, {
|
||||
kind = kind,
|
||||
ticks = SyncDelayTicks,
|
||||
attempts = 0,
|
||||
reservedTowBar = reservedTowBar == true,
|
||||
player = player,
|
||||
args = args
|
||||
})
|
||||
end
|
||||
|
||||
local function cancelPendingAttach(vehicleA, vehicleB)
|
||||
if not vehicleA or not vehicleB then return end
|
||||
|
||||
local vehicleAId = vehicleA:getId()
|
||||
local vehicleBId = vehicleB:getId()
|
||||
local remaining = {}
|
||||
for i = 1, #pendingSync do
|
||||
local item = pendingSync[i]
|
||||
local itemArgs = item.args or {}
|
||||
local isThisAttach = item.kind == "attach"
|
||||
and itemArgs.vehicleA == vehicleAId
|
||||
and itemArgs.vehicleB == vehicleBId
|
||||
if isThisAttach then
|
||||
-- The reserved item is about to become the single ground drop.
|
||||
item.reservedTowBar = false
|
||||
else
|
||||
table.insert(remaining, item)
|
||||
end
|
||||
end
|
||||
pendingSync = remaining
|
||||
end
|
||||
|
||||
local function hasPendingAttach(vehicleA, vehicleB)
|
||||
if not vehicleA or not vehicleB then return false end
|
||||
|
||||
local vehicleAId = vehicleA:getId()
|
||||
local vehicleBId = vehicleB:getId()
|
||||
for i = 1, #pendingSync do
|
||||
local item = pendingSync[i]
|
||||
local itemArgs = item.args or {}
|
||||
if item.kind == "attach"
|
||||
and itemArgs.vehicleA == vehicleAId
|
||||
and itemArgs.vehicleB == vehicleBId then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function resolveAttachmentA(args, vehicleA)
|
||||
if args and args.attachmentA then return args.attachmentA end
|
||||
if vehicleA and vehicleA:getTowAttachmentSelf() then return vehicleA:getTowAttachmentSelf() end
|
||||
@@ -369,10 +316,16 @@ local function broadcastAttach(vehicleA, vehicleB, attachmentA, attachmentB)
|
||||
end
|
||||
|
||||
local function broadcastDetach(vehicleAId, vehicleBId)
|
||||
sendServerCommand("towbar", "forceDetachSync", {
|
||||
local args = {
|
||||
vehicleA = vehicleAId,
|
||||
vehicleB = vehicleBId
|
||||
})
|
||||
}
|
||||
if isServer() then
|
||||
sendServerCommand("towbar", "forceDetachSync", args)
|
||||
elseif not isClient() and TowBarMod and TowBarMod.Sync
|
||||
and TowBarMod.Sync.applyDetachSync then
|
||||
TowBarMod.Sync.applyDetachSync(args)
|
||||
end
|
||||
end
|
||||
|
||||
local function broadcastSpontaneousDetach(vehicleA, vehicleB)
|
||||
@@ -458,7 +411,6 @@ local function finalizeBrokenTowBarPair(towingVehicle, towedVehicle, reason)
|
||||
return false
|
||||
end
|
||||
|
||||
cancelPendingAttach(towingVehicle, towedVehicle)
|
||||
forgetTowBarPair(towingVehicle, towedVehicle)
|
||||
breakTowBarConstraint(towingVehicle, towedVehicle)
|
||||
clearExpectedTowBarPair(towingVehicle, towedVehicle)
|
||||
@@ -567,54 +519,6 @@ local function reconcileBrokenTowBarPairsServer()
|
||||
end)
|
||||
end
|
||||
|
||||
local function processAttachSync(item)
|
||||
local args = item.args or {}
|
||||
local vehicleA = args.vehicleA and getVehicleById(args.vehicleA) or nil
|
||||
local vehicleB = args.vehicleB and getVehicleById(args.vehicleB) or nil
|
||||
if not vehicleA or not vehicleB then
|
||||
noise("attach sync skipped missing vehicles A=" .. tostring(args.vehicleA) .. " B=" .. tostring(args.vehicleB))
|
||||
return "failed"
|
||||
end
|
||||
|
||||
local attachmentA = resolveAttachmentA(args, vehicleA)
|
||||
local attachmentB = resolveAttachmentB(args, vehicleB)
|
||||
if not isLinked(vehicleA, vehicleB) then
|
||||
if isTowBarPairConfirmed(vehicleA, vehicleB) then return "broken" end
|
||||
if item.attempts >= 3 then return "failed" end
|
||||
vehicleA:addPointConstraint(item.player, vehicleB, attachmentA, attachmentB)
|
||||
return "retry"
|
||||
end
|
||||
|
||||
markExpectedTowBarPair(vehicleA, vehicleB, attachmentA, attachmentB)
|
||||
markTowBarPairConfirmed(vehicleA, vehicleB)
|
||||
broadcastAttach(vehicleA, vehicleB, attachmentA, attachmentB)
|
||||
return "complete"
|
||||
end
|
||||
|
||||
local function failAttachSync(item)
|
||||
local args = item.args or {}
|
||||
local vehicleA = args.vehicleA and getVehicleById(args.vehicleA) or nil
|
||||
local vehicleB = args.vehicleB and getVehicleById(args.vehicleB) or nil
|
||||
|
||||
if isLinked(vehicleA, vehicleB) then
|
||||
vehicleA:breakConstraint(true, false)
|
||||
end
|
||||
clearExpectedTowBarPair(vehicleA, vehicleB)
|
||||
broadcastDetach(args.vehicleA, args.vehicleB)
|
||||
|
||||
if item.reservedTowBar then
|
||||
giveTowBar(item.player, true)
|
||||
item.reservedTowBar = false
|
||||
end
|
||||
end
|
||||
|
||||
local function processDetachSync(item)
|
||||
local args = item.args or {}
|
||||
local vehicleAId = args.towingVehicle or args.vehicleA or args.vehicle
|
||||
local vehicleBId = args.vehicleB or args.vehicle
|
||||
broadcastDetach(vehicleAId, vehicleBId)
|
||||
end
|
||||
|
||||
local function snapshotActiveTowbarLinksServer()
|
||||
local cell = getCell()
|
||||
if not cell then return end
|
||||
@@ -643,7 +547,7 @@ local function snapshotActiveTowbarLinksServer()
|
||||
end)
|
||||
end
|
||||
|
||||
local function processPendingSync()
|
||||
local function processTowBarServerTick()
|
||||
reconcileBrokenTowBarPairsServer()
|
||||
|
||||
snapshotTickCounter = snapshotTickCounter + 1
|
||||
@@ -651,37 +555,6 @@ local function processPendingSync()
|
||||
snapshotTickCounter = 0
|
||||
snapshotActiveTowbarLinksServer()
|
||||
end
|
||||
|
||||
if #pendingSync == 0 then return end
|
||||
|
||||
local remaining = {}
|
||||
for i = 1, #pendingSync do
|
||||
local item = pendingSync[i]
|
||||
item.ticks = item.ticks - 1
|
||||
if item.ticks <= 0 then
|
||||
if item.kind == "attach" then
|
||||
local status = processAttachSync(item)
|
||||
if status == "retry" and item.attempts < 3 then
|
||||
item.attempts = item.attempts + 1
|
||||
item.ticks = SyncDelayTicks
|
||||
table.insert(remaining, item)
|
||||
elseif status == "broken" then
|
||||
finalizeBrokenTowBarPair(
|
||||
item.args.vehicleA and getVehicleById(item.args.vehicleA) or nil,
|
||||
item.args.vehicleB and getVehicleById(item.args.vehicleB) or nil,
|
||||
"attach-confirmation-break"
|
||||
)
|
||||
elseif status ~= "complete" then
|
||||
failAttachSync(item)
|
||||
end
|
||||
elseif item.kind == "detach" then
|
||||
processDetachSync(item)
|
||||
end
|
||||
else
|
||||
table.insert(remaining, item)
|
||||
end
|
||||
end
|
||||
pendingSync = remaining
|
||||
end
|
||||
|
||||
function Commands.attachTowBar(player, args)
|
||||
@@ -707,9 +580,7 @@ function Commands.attachTowBar(player, args)
|
||||
return
|
||||
end
|
||||
if isExpectedTowBarPair(vehicleA, vehicleB)
|
||||
or isExpectedTowBarPair(vehicleB, vehicleA)
|
||||
or hasPendingAttach(vehicleA, vehicleB)
|
||||
or hasPendingAttach(vehicleB, vehicleA) then
|
||||
or isExpectedTowBarPair(vehicleB, vehicleA) then
|
||||
noise("rejected duplicate pending towbar attach")
|
||||
return
|
||||
end
|
||||
@@ -724,12 +595,14 @@ function Commands.attachTowBar(player, args)
|
||||
return
|
||||
end
|
||||
|
||||
-- Constraint creation may complete on a later server tick in multiplayer.
|
||||
-- Record and queue the reserved item first so an immediate Build 42.20
|
||||
-- spontaneous break can resolve the pair and consume the reservation once.
|
||||
-- Match the working wrecker path: persist the accepted logical pair, ask
|
||||
-- the server for its native relation, then immediately tell clients to
|
||||
-- replace that relation with the local rigid fake-trailer constraint.
|
||||
markExpectedTowBarPair(vehicleA, vehicleB, args.attachmentA, args.attachmentB)
|
||||
queueSync("attach", player, args, true)
|
||||
vehicleA:addPointConstraint(player, vehicleB, args.attachmentA, args.attachmentB)
|
||||
if isServer() then
|
||||
vehicleA:addPointConstraint(player, vehicleB, args.attachmentA, args.attachmentB)
|
||||
end
|
||||
broadcastAttach(vehicleA, vehicleB, args.attachmentA, args.attachmentB)
|
||||
if isLinked(vehicleA, vehicleB) then
|
||||
markTowBarPairConfirmed(vehicleA, vehicleB)
|
||||
end
|
||||
@@ -749,10 +622,6 @@ function Commands.detachTowBar(player, args)
|
||||
noise("rejected unauthorized towbar detach")
|
||||
return
|
||||
end
|
||||
if not isLinked(towingVehicle, towedVehicle) then
|
||||
noise("rejected mismatched or stale towbar detach")
|
||||
return
|
||||
end
|
||||
if not isExpectedTowBarPair(towingVehicle, towedVehicle)
|
||||
and not isLegacyTowBarPair(towingVehicle, towedVehicle) then
|
||||
noise("rejected detach for a non-towbar or stale linked pair")
|
||||
@@ -764,7 +633,7 @@ function Commands.detachTowBar(player, args)
|
||||
breakTowBarConstraint(towingVehicle, towedVehicle)
|
||||
forgetTowBarPair(towingVehicle, towedVehicle)
|
||||
clearExpectedTowBarPair(towingVehicle, towedVehicle)
|
||||
queueSync("detach", player, args)
|
||||
broadcastDetach(towingVehicle:getId(), towedVehicle:getId())
|
||||
if shouldRefund then
|
||||
giveTowBar(player, true)
|
||||
end
|
||||
@@ -817,4 +686,4 @@ TowingCommands.OnClientCommand = function(module, command, player, args)
|
||||
end
|
||||
|
||||
Events.OnClientCommand.Add(TowingCommands.OnClientCommand)
|
||||
Events.OnTick.Add(processPendingSync)
|
||||
Events.OnTick.Add(processTowBarServerTick)
|
||||
|
||||
Reference in New Issue
Block a user