if isServer() then return end if not TowBarMod then TowBarMod = {} end TowBarMod.RigidTow = TowBarMod.RigidTow or {} local RigidTow = TowBarMod.RigidTow local function isExactPairLinked(towingVehicle, towedVehicle) return towingVehicle and towedVehicle and towingVehicle:getVehicleTowing() == towedVehicle and towedVehicle:getVehicleTowedBy() == towingVehicle end local function hasConflictingLink(vehicle, expectedOther) if not vehicle or not expectedOther then return true end local towing = vehicle:getVehicleTowing() local towedBy = vehicle:getVehicleTowedBy() return (towing ~= nil and towing ~= expectedOther) or (towedBy ~= nil and towedBy ~= expectedOther) end local function attachmentExists(vehicle, attachmentId) if not vehicle or type(attachmentId) ~= "string" then return false end local script = vehicle:getScript() return script ~= nil and script:getAttachmentById(attachmentId) ~= nil end local function breakExactPair(towingVehicle, towedVehicle) if not towingVehicle or not towedVehicle then return end if towingVehicle:getVehicleTowing() == towedVehicle or towingVehicle:getVehicleTowedBy() == towedVehicle then towingVehicle:breakConstraint(true, true) elseif towedVehicle:getVehicleTowing() == towingVehicle or towedVehicle:getVehicleTowedBy() == towingVehicle then towedVehicle:breakConstraint(true, true) end end function RigidTow.attach(towingVehicle, towedVehicle, attachmentA, attachmentB) if not towingVehicle or not towedVehicle or towingVehicle == towedVehicle then return false end if hasConflictingLink(towingVehicle, towedVehicle) or hasConflictingLink(towedVehicle, towingVehicle) then return false end if not attachmentExists(towingVehicle, attachmentA) or not attachmentExists(towedVehicle, attachmentB) then return false end if TowBarMod.Utils and TowBarMod.Utils.updateAttachmentsForRigidTow then TowBarMod.Utils.updateAttachmentsForRigidTow( towingVehicle, towedVehicle, attachmentA, attachmentB ) end -- Match the proven wrecker sequence exactly. The fake trailer script is -- used only while Bullet creates the rigid constraint; leaving it active -- until the broader post-attach hook is unreliable in multiplayer because -- that hook may return early while vehicle state is still replicating. local towedModData = towedVehicle:getModData() local currentScript = towedVehicle:getScriptName() local originalScript = towedModData and towedModData.towBarOriginalScriptName or nil if originalScript == nil and currentScript ~= "notTowingA_Trailer" then originalScript = currentScript if towedModData then towedModData.towBarOriginalScriptName = originalScript end end if originalScript == nil or originalScript == "notTowingA_Trailer" then return false end breakExactPair(towingVehicle, towedVehicle) if TowBarMod.Hook and TowBarMod.Hook.applyFreeRollingTowState then TowBarMod.Hook.applyFreeRollingTowState(towedVehicle) elseif TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then -- Compatibility fallback for older hook modules and the regression -- harness. Current builds expose the narrower free-roll helper. TowBarMod.Hook.setVehiclePostAttach(nil, towedVehicle, towingVehicle) end if TowBarMod.Hook and TowBarMod.Hook.setVehicleScriptWithTowBarHidden then TowBarMod.Hook.setVehicleScriptWithTowBarHidden(towedVehicle, "notTowingA_Trailer") else towedVehicle:setScriptName("notTowingA_Trailer") end towingVehicle:addPointConstraint(nil, towedVehicle, attachmentA, attachmentB, true) if TowBarMod.Hook and TowBarMod.Hook.setVehicleScriptWithTowBarHidden then TowBarMod.Hook.setVehicleScriptWithTowBarHidden(towedVehicle, originalScript) else towedVehicle:setScriptName(originalScript) end if TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then -- Multiplayer updates the reciprocal towing getters after this Lua -- call. The authoritative sync already validated the pair, so finalize -- with the known towing vehicle instead of treating that delay as a -- failed constraint submission. TowBarMod.Hook.setVehiclePostAttach(nil, towedVehicle, towingVehicle) end return true end RigidTow.isExactPairLinked = isExactPairLinked RigidTow.breakExactPair = breakExactPair