42.20 and Towtrucks

This commit is contained in:
2026-08-13 18:59:54 -04:00
parent a6ba4877d0
commit e403323017
33 changed files with 2333 additions and 142 deletions
@@ -4,6 +4,23 @@ if not TowBarMod then TowBarMod = {} end
TowBarMod.Sync = TowBarMod.Sync or {}
if TowBarMod.Sync._towSyncClientLoaded then return end
TowBarMod.Sync._towSyncClientLoaded = true
TowBarMod.Sync.appliedPairs = TowBarMod.Sync.appliedPairs or {}
local function pairKeyContainsVehicle(key, vehicleId)
local id = tostring(vehicleId)
return string.sub(key, 1, #id + 1) == id .. ":"
or string.sub(key, -(#id + 1)) == ":" .. id
end
local function clearAppliedPairForVehicle(vehicle)
if not vehicle then return end
local vehicleId = vehicle:getId()
for key in pairs(TowBarMod.Sync.appliedPairs) do
if pairKeyContainsVehicle(key, vehicleId) then
TowBarMod.Sync.appliedPairs[key] = nil
end
end
end
local function resolveVehicle(id)
if not id then return nil end
@@ -89,6 +106,8 @@ local function reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
end
end
local breakTowBarPair
local function applyAttachSync(args)
if not args then return end
@@ -102,8 +121,25 @@ local function applyAttachSync(args)
return
end
if not isLinked(vehicleA, vehicleB) then
vehicleA:addPointConstraint(nil, vehicleB, attachmentA, attachmentB)
local key = tostring(vehicleA:getId()) .. ":" .. tostring(vehicleB:getId())
if TowBarMod.Sync.appliedPairs[key] and not isLinked(vehicleA, vehicleB) then
-- Vehicle streaming can remove the native constraint while the Lua
-- module remains loaded. Allow the authoritative recovery sync to
-- rebuild local physics for the returning pair.
TowBarMod.Sync.appliedPairs[key] = nil
end
if not TowBarMod.Sync.appliedPairs[key] then
-- The server initially creates a normal Build 42 tow relation. It may
-- also restore one from the save. Replace that native rope exactly
-- once with this mod's rigid fake-trailer constraint.
breakTowBarPair(vehicleA, vehicleB)
if TowBarMod.Hook and TowBarMod.Hook.setVehicleScriptWithTowBarHidden then
TowBarMod.Hook.setVehicleScriptWithTowBarHidden(vehicleB, "notTowingA_Trailer")
end
-- The final true keeps this a local physics rebuild. The server owns
-- the persisted logical relation and must not receive a detach/attach race.
vehicleA:addPointConstraint(nil, vehicleB, attachmentA, attachmentB, true)
TowBarMod.Sync.appliedPairs[key] = true
end
reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
@@ -130,18 +166,17 @@ local function hasConflictingTowLink(vehicle, expectedOther)
and modData["towBarTowingVehicleId"] ~= expectedOtherId)
end
local function breakTowBarPair(vehicleA, vehicleB)
breakTowBarPair = function(vehicleA, vehicleB)
if not vehicleA or not vehicleB then return end
local vehicleAReferencesB = vehicleA:getVehicleTowing() == vehicleB
or vehicleA:getVehicleTowedBy() == vehicleB
local vehicleBReferencesA = vehicleB:getVehicleTowing() == vehicleA
or vehicleB:getVehicleTowedBy() == vehicleA
if vehicleBReferencesA then
vehicleB:breakConstraint(true, true)
end
if vehicleAReferencesB then
vehicleA:breakConstraint(true, true)
elseif vehicleBReferencesA then
vehicleB:breakConstraint(true, true)
end
end
@@ -154,6 +189,7 @@ local function applyDetachSync(args)
return
end
breakTowBarPair(vehicleA, vehicleB)
TowBarMod.Sync.appliedPairs[tostring(vehicleA:getId()) .. ":" .. tostring(vehicleB:getId())] = nil
if TowBarMod.Hook and TowBarMod.Hook.cleanupDetachedTowBar then
pcall(TowBarMod.Hook.cleanupDetachedTowBar, vehicleA, vehicleB)
@@ -170,4 +206,10 @@ local function onServerCommand(module, command, args)
end
end
TowBarMod.Sync.applyAttachSync = applyAttachSync
TowBarMod.Sync.applyDetachSync = applyDetachSync
Events.OnServerCommand.Add(onServerCommand)
if Events.OnSpawnVehicleEnd then
Events.OnSpawnVehicleEnd.Add(clearAppliedPairForVehicle)
end