Working 42.20 MP

This commit is contained in:
2026-08-19 12:12:12 -04:00
parent e403323017
commit e99f3bbff9
22 changed files with 2262 additions and 825 deletions
+19 -150
View File
@@ -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)