fixed
This commit is contained in:
@@ -3,7 +3,7 @@ if not TowBarMod.Config then TowBarMod.Config = {} end
|
|||||||
|
|
||||||
TowBarMod.Config.lowLevelAnimation = "RemoveGrass"
|
TowBarMod.Config.lowLevelAnimation = "RemoveGrass"
|
||||||
TowBarMod.Config.rigidTowbarDistance = 1.0
|
TowBarMod.Config.rigidTowbarDistance = 1.0
|
||||||
TowBarMod.Config.devMode = true
|
TowBarMod.Config.devMode = false
|
||||||
TowBarMod.Config.vanillaTowbarModelScaleMin = 1.5
|
TowBarMod.Config.vanillaTowbarModelScaleMin = 1.5
|
||||||
TowBarMod.Config.vanillaTowbarModelScaleMax = 2.0
|
TowBarMod.Config.vanillaTowbarModelScaleMax = 2.0
|
||||||
TowBarMod.Config.smallScaleTowbarIndexOffset = 2
|
TowBarMod.Config.smallScaleTowbarIndexOffset = 2
|
||||||
|
|||||||
125
42.13/media/lua/client/TowBar/TowSyncClient.lua
Normal file
125
42.13/media/lua/client/TowBar/TowSyncClient.lua
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
if isServer() then return end
|
||||||
|
|
||||||
|
if not TowBarMod then TowBarMod = {} end
|
||||||
|
TowBarMod.Sync = TowBarMod.Sync or {}
|
||||||
|
if TowBarMod.Sync._towSyncClientLoaded then return end
|
||||||
|
TowBarMod.Sync._towSyncClientLoaded = true
|
||||||
|
|
||||||
|
local function resolveVehicle(id)
|
||||||
|
if not id then return nil end
|
||||||
|
return getVehicleById(id)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ensureAttachment(vehicle, attachmentId)
|
||||||
|
if not vehicle or not attachmentId then return false end
|
||||||
|
|
||||||
|
local script = vehicle:getScript()
|
||||||
|
if not script then return false end
|
||||||
|
if script:getAttachmentById(attachmentId) ~= nil then return true end
|
||||||
|
|
||||||
|
local wheelCount = script:getWheelCount()
|
||||||
|
local yOffset = -0.5
|
||||||
|
if wheelCount > 0 then
|
||||||
|
local wheel = script:getWheel(0)
|
||||||
|
if wheel and wheel:getOffset() then
|
||||||
|
yOffset = wheel:getOffset():y() + 0.1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local chassis = script:getPhysicsChassisShape()
|
||||||
|
if not chassis then return false end
|
||||||
|
|
||||||
|
local attach = ModelAttachment.new(attachmentId)
|
||||||
|
if attachmentId == "trailer" then
|
||||||
|
attach:getOffset():set(0, yOffset, -chassis:z() / 2 - 0.1)
|
||||||
|
attach:setZOffset(-1)
|
||||||
|
else
|
||||||
|
attach:getOffset():set(0, yOffset, chassis:z() / 2 + 0.1)
|
||||||
|
attach:setZOffset(1)
|
||||||
|
end
|
||||||
|
script:addAttachment(attach)
|
||||||
|
return true
|
||||||
|
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 reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
|
||||||
|
if TowBarMod.Utils and TowBarMod.Utils.updateAttachmentsForRigidTow then
|
||||||
|
TowBarMod.Utils.updateAttachmentsForRigidTow(vehicleA, vehicleB, attachmentA, attachmentB)
|
||||||
|
end
|
||||||
|
|
||||||
|
local towingMd = vehicleA:getModData()
|
||||||
|
local towedMd = vehicleB:getModData()
|
||||||
|
local currentScript = vehicleB:getScriptName()
|
||||||
|
|
||||||
|
if towingMd then
|
||||||
|
towingMd["isTowingByTowBar"] = true
|
||||||
|
vehicleA:transmitModData()
|
||||||
|
end
|
||||||
|
if towedMd then
|
||||||
|
if towedMd.towBarOriginalScriptName == nil and currentScript ~= "notTowingA_Trailer" then
|
||||||
|
towedMd.towBarOriginalScriptName = currentScript
|
||||||
|
end
|
||||||
|
if towedMd.towBarOriginalMass == nil then
|
||||||
|
towedMd.towBarOriginalMass = vehicleB:getMass()
|
||||||
|
end
|
||||||
|
if towedMd.towBarOriginalBrakingForce == nil then
|
||||||
|
towedMd.towBarOriginalBrakingForce = vehicleB:getBrakingForce()
|
||||||
|
end
|
||||||
|
towedMd["isTowingByTowBar"] = true
|
||||||
|
towedMd["towed"] = true
|
||||||
|
vehicleB:transmitModData()
|
||||||
|
end
|
||||||
|
|
||||||
|
vehicleB:setScriptName("notTowingA_Trailer")
|
||||||
|
if TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
|
||||||
|
pcall(TowBarMod.Hook.setVehiclePostAttach, nil, vehicleB)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function applyAttachSync(args)
|
||||||
|
if not args then return end
|
||||||
|
|
||||||
|
local vehicleA = resolveVehicle(args.vehicleA)
|
||||||
|
local vehicleB = resolveVehicle(args.vehicleB)
|
||||||
|
if not vehicleA or not vehicleB then return end
|
||||||
|
|
||||||
|
local attachmentA = args.attachmentA or "trailer"
|
||||||
|
local attachmentB = args.attachmentB or "trailerfront"
|
||||||
|
if not ensureAttachment(vehicleA, attachmentA) or not ensureAttachment(vehicleB, attachmentB) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not isLinked(vehicleA, vehicleB) then
|
||||||
|
vehicleA:addPointConstraint(nil, vehicleB, attachmentA, attachmentB)
|
||||||
|
end
|
||||||
|
|
||||||
|
reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function safeBreak(vehicle)
|
||||||
|
if not vehicle then return end
|
||||||
|
if vehicle:getVehicleTowing() == nil and vehicle:getVehicleTowedBy() == nil then return end
|
||||||
|
vehicle:breakConstraint(true, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function applyDetachSync(args)
|
||||||
|
if not args then return end
|
||||||
|
safeBreak(resolveVehicle(args.vehicleA))
|
||||||
|
safeBreak(resolveVehicle(args.vehicleB))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function onServerCommand(module, command, args)
|
||||||
|
if module ~= "towbar" then return end
|
||||||
|
|
||||||
|
if command == "forceAttachSync" then
|
||||||
|
applyAttachSync(args)
|
||||||
|
elseif command == "forceDetachSync" then
|
||||||
|
applyDetachSync(args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Events.OnServerCommand.Add(onServerCommand)
|
||||||
@@ -33,6 +33,19 @@ local function getTowBarItem(playerObj)
|
|||||||
return inventory:getItemFromTypeRecurse("TowBar.TowBar")
|
return inventory:getItemFromTypeRecurse("TowBar.TowBar")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function sendTowAttachCommand(playerObj, args)
|
||||||
|
if not playerObj or not args then return end
|
||||||
|
|
||||||
|
-- MP-safe/server-authoritative attach path (Landtrain style).
|
||||||
|
if isClient() and isMultiplayer() then
|
||||||
|
sendClientCommand(playerObj, "towbar", "attachTowBar", args)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Keep vanilla attach path for SP/local behavior.
|
||||||
|
sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
|
||||||
|
end
|
||||||
|
|
||||||
local TowbarVariantSize = 24
|
local TowbarVariantSize = 24
|
||||||
local TowbarNormalStart = 0
|
local TowbarNormalStart = 0
|
||||||
local TowbarLargeStart = 24
|
local TowbarLargeStart = 24
|
||||||
@@ -278,11 +291,38 @@ local function reattachTowBarPair(playerObj, towingVehicle, towedVehicle, requir
|
|||||||
attachmentA = attachmentA,
|
attachmentA = attachmentA,
|
||||||
attachmentB = attachmentB
|
attachmentB = attachmentB
|
||||||
}
|
}
|
||||||
sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
|
sendTowAttachCommand(playerObj, args)
|
||||||
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
|
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function reattachTowBarPairAfterCleanDetach(playerObj, towingVehicle, towedVehicle, requireDriver)
|
||||||
|
if not playerObj or not towingVehicle or not towedVehicle then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if requireDriver and not towingVehicle:isDriver(playerObj) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local detachArgs = {
|
||||||
|
towingVehicle = towingVehicle:getId(),
|
||||||
|
vehicle = towedVehicle:getId()
|
||||||
|
}
|
||||||
|
sendClientCommand(playerObj, "towbar", "detachTowBar", detachArgs)
|
||||||
|
|
||||||
|
-- World load/spawn can restore constraints in a bad state. Reattach one
|
||||||
|
-- short tick later so the detach is fully applied first.
|
||||||
|
ISTimedActionQueue.add(TowBarScheduleAction:new(
|
||||||
|
playerObj,
|
||||||
|
1,
|
||||||
|
reattachTowBarPair,
|
||||||
|
towingVehicle,
|
||||||
|
towedVehicle,
|
||||||
|
requireDriver
|
||||||
|
))
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
local function recoverTowBarVehicleAfterLoad(playerObj, vehicle, retriesLeft)
|
local function recoverTowBarVehicleAfterLoad(playerObj, vehicle, retriesLeft)
|
||||||
if not vehicle then return end
|
if not vehicle then return end
|
||||||
|
|
||||||
@@ -295,8 +335,14 @@ local function recoverTowBarVehicleAfterLoad(playerObj, vehicle, retriesLeft)
|
|||||||
local localPlayer = playerObj or getPlayer()
|
local localPlayer = playerObj or getPlayer()
|
||||||
local towingVehicle = vehicle:getVehicleTowedBy()
|
local towingVehicle = vehicle:getVehicleTowedBy()
|
||||||
|
|
||||||
|
if towingVehicle then
|
||||||
|
-- Apply rigid spacing as soon as the tow link exists to avoid a visible
|
||||||
|
-- bumper-to-bumper snap while waiting for reattach recovery.
|
||||||
|
TowBarMod.Hook.setVehiclePostAttach(nil, vehicle)
|
||||||
|
end
|
||||||
|
|
||||||
if localPlayer and towingVehicle then
|
if localPlayer and towingVehicle then
|
||||||
if reattachTowBarPair(localPlayer, towingVehicle, vehicle, false) then
|
if reattachTowBarPairAfterCleanDetach(localPlayer, towingVehicle, vehicle, false) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -376,7 +422,7 @@ function TowBarMod.Hook.performAttachTowBar(playerObj, towingVehicle, towedVehic
|
|||||||
attachmentA = attachmentA,
|
attachmentA = attachmentA,
|
||||||
attachmentB = attachmentB
|
attachmentB = attachmentB
|
||||||
}
|
}
|
||||||
sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
|
sendTowAttachCommand(playerObj, args)
|
||||||
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
|
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ if isClient() then return end
|
|||||||
local TowingCommands = {}
|
local TowingCommands = {}
|
||||||
local Commands = {}
|
local Commands = {}
|
||||||
local TowBarItemType = "TowBar.TowBar"
|
local TowBarItemType = "TowBar.TowBar"
|
||||||
|
local SyncDelayTicks = 2
|
||||||
|
local SnapshotIntervalTicks = 120
|
||||||
|
local pendingSync = {}
|
||||||
|
local snapshotTickCounter = 0
|
||||||
|
|
||||||
TowingCommands.wantNoise = getDebug() or false
|
TowingCommands.wantNoise = getDebug() or false
|
||||||
|
|
||||||
@@ -12,6 +16,133 @@ local noise = function(msg)
|
|||||||
end
|
end
|
||||||
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)
|
function Commands.attachTowBar(player, args)
|
||||||
local vehicleA = getVehicleById(args.vehicleA)
|
local vehicleA = getVehicleById(args.vehicleA)
|
||||||
local vehicleB = getVehicleById(args.vehicleB)
|
local vehicleB = getVehicleById(args.vehicleB)
|
||||||
@@ -92,6 +223,16 @@ Commands.attachConstraint = Commands.attachTowBar
|
|||||||
Commands.detachConstraint = Commands.detachTowBar
|
Commands.detachConstraint = Commands.detachTowBar
|
||||||
|
|
||||||
TowingCommands.OnClientCommand = function(module, command, player, args)
|
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
|
if module == "towbar" and Commands[command] then
|
||||||
local argStr = ""
|
local argStr = ""
|
||||||
args = args or {}
|
args = args or {}
|
||||||
@@ -104,3 +245,4 @@ TowingCommands.OnClientCommand = function(module, command, player, args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
Events.OnClientCommand.Add(TowingCommands.OnClientCommand)
|
Events.OnClientCommand.Add(TowingCommands.OnClientCommand)
|
||||||
|
Events.OnTick.Add(processPendingSync)
|
||||||
|
|||||||
Reference in New Issue
Block a user