This commit is contained in:
2026-02-13 13:00:40 -05:00
parent e694f746f8
commit a9c1d8201b
4 changed files with 317 additions and 4 deletions

View File

@@ -3,6 +3,10 @@ if isClient() then return end
local TowingCommands = {}
local Commands = {}
local TowBarItemType = "TowBar.TowBar"
local SyncDelayTicks = 2
local SnapshotIntervalTicks = 120
local pendingSync = {}
local snapshotTickCounter = 0
TowingCommands.wantNoise = getDebug() or false
@@ -12,6 +16,133 @@ local noise = function(msg)
end
end
local function queueSync(kind, player, args)
if not args then return end
table.insert(pendingSync, {
kind = kind,
ticks = SyncDelayTicks,
player = player,
args = args
})
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
return "trailer"
end
local function resolveAttachmentB(args, vehicleB)
if args and args.attachmentB then return args.attachmentB end
if vehicleB and vehicleB:getTowAttachmentSelf() then return vehicleB:getTowAttachmentSelf() end
return "trailerfront"
end
local function isLinked(vehicleA, vehicleB)
if not vehicleA or not vehicleB then return false end
return vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA
end
local function hasTowBarState(vehicle)
if not vehicle then return false end
local md = vehicle:getModData()
if not md then return false end
return md["isTowingByTowBar"] == true
end
local function broadcastAttach(vehicleA, vehicleB, attachmentA, attachmentB)
if not vehicleA or not vehicleB then return end
sendServerCommand("towbar", "forceAttachSync", {
vehicleA = vehicleA:getId(),
vehicleB = vehicleB:getId(),
attachmentA = attachmentA,
attachmentB = attachmentB
})
end
local function broadcastDetach(vehicleAId, vehicleBId)
sendServerCommand("towbar", "forceDetachSync", {
vehicleA = vehicleAId,
vehicleB = vehicleBId
})
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
end
local attachmentA = resolveAttachmentA(args, vehicleA)
local attachmentB = resolveAttachmentB(args, vehicleB)
if not isLinked(vehicleA, vehicleB) then
vehicleA:addPointConstraint(item.player, vehicleB, attachmentA, attachmentB)
end
if isLinked(vehicleA, vehicleB) then
broadcastAttach(vehicleA, vehicleB, attachmentA, attachmentB)
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
broadcastDetach(vehicleAId, vehicleBId)
end
local function snapshotActiveTowbarLinksServer()
local cell = getCell()
if not cell then return end
local list = cell:getVehicles()
if not list then return end
for i = 0, list:size() - 1 do
local towingVehicle = list:get(i)
local towedVehicle = towingVehicle and towingVehicle:getVehicleTowing() or nil
if towingVehicle and towedVehicle and towedVehicle:getVehicleTowedBy() == towingVehicle then
if hasTowBarState(towingVehicle) or hasTowBarState(towedVehicle) then
local attachmentA = resolveAttachmentA(nil, towingVehicle)
local towedMd = towedVehicle:getModData()
local attachmentB = (towedMd and towedMd["towBarChangedAttachmentId"]) or resolveAttachmentB(nil, towedVehicle)
if attachmentA == attachmentB then
attachmentA = "trailer"
attachmentB = "trailerfront"
end
broadcastAttach(towingVehicle, towedVehicle, attachmentA, attachmentB)
end
end
end
end
local function processPendingSync()
snapshotTickCounter = snapshotTickCounter + 1
if snapshotTickCounter >= SnapshotIntervalTicks then
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
processAttachSync(item)
elseif item.kind == "detach" then
processDetachSync(item)
end
else
table.insert(remaining, item)
end
end
pendingSync = remaining
end
function Commands.attachTowBar(player, args)
local vehicleA = getVehicleById(args.vehicleA)
local vehicleB = getVehicleById(args.vehicleB)
@@ -92,6 +223,16 @@ Commands.attachConstraint = Commands.attachTowBar
Commands.detachConstraint = Commands.detachTowBar
TowingCommands.OnClientCommand = function(module, command, player, args)
if module == "vehicle" and command == "attachTrailer" then
queueSync("attach", player, args)
elseif module == "vehicle" and command == "detachTrailer" then
queueSync("detach", player, args)
elseif module == "towbar" and command == "attachTowBar" then
queueSync("attach", player, args)
elseif module == "towbar" and command == "detachTowBar" then
queueSync("detach", player, args)
end
if module == "towbar" and Commands[command] then
local argStr = ""
args = args or {}
@@ -104,3 +245,4 @@ TowingCommands.OnClientCommand = function(module, command, player, args)
end
Events.OnClientCommand.Add(TowingCommands.OnClientCommand)
Events.OnTick.Add(processPendingSync)