Files
Towbar/42.20/media/lua/client/TowBar/WreckerSyncClient.lua
T
2026-08-13 18:59:54 -04:00

217 lines
8.3 KiB
Lua

if isServer() then return end
if not TowBarMod then TowBarMod = {} end
TowBarMod.WreckerSync = TowBarMod.WreckerSync or {}
local Sync = TowBarMod.WreckerSync
Sync.appliedLevels = Sync.appliedLevels 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 clearAppliedLevelForVehicle(vehicle)
if not vehicle then return end
local vehicleId = vehicle:getId()
for key in pairs(Sync.appliedLevels) do
if pairKeyContainsVehicle(key, vehicleId) then
Sync.appliedLevels[key] = nil
end
end
end
local function pairKey(wrecker, target)
return tostring(wrecker:getId()) .. ":" .. tostring(target:getId())
end
local function callVehicle(vehicle, methodName, value)
local method = vehicle and vehicle[methodName] or nil
if not method then return false, nil end
return pcall(function()
if value ~= nil then return method(vehicle, value) end
return method(vehicle)
end)
end
local function applyFreeRollingState(vehicle)
local md = vehicle and vehicle:getModData() or nil
if not md then return end
local capturedOriginal = false
if md.wreckerOriginalMass == nil then
md.wreckerOriginalMass = vehicle:getMass()
capturedOriginal = true
end
if md.wreckerOriginalBrakingForce == nil then
md.wreckerOriginalBrakingForce = vehicle:getBrakingForce()
capturedOriginal = true
end
if md.wreckerOriginalParkingBrakeOn == nil then
local ok, value = callVehicle(vehicle, "isParkingBrakeOn")
if ok then
md.wreckerOriginalParkingBrakeOn = value
capturedOriginal = true
end
end
if md.wreckerOriginalParkingBrake == nil then
local ok, value = callVehicle(vehicle, "getParkingBrake")
if ok then
md.wreckerOriginalParkingBrake = value
capturedOriginal = true
end
end
if md.wreckerOriginalHandbrake == nil then
local ok, value = callVehicle(vehicle, "isHandbrake")
if ok then
md.wreckerOriginalHandbrake = value
capturedOriginal = true
end
end
vehicle:setMass(200)
vehicle:setBrakingForce(0)
if md.wreckerOriginalParkingBrakeOn ~= nil then callVehicle(vehicle, "setParkingBrakeOn", false) end
if md.wreckerOriginalParkingBrake ~= nil then callVehicle(vehicle, "setParkingBrake", false) end
if md.wreckerOriginalHandbrake ~= nil then callVehicle(vehicle, "setHandbrake", false) end
if capturedOriginal then vehicle:transmitModData() end
end
local function restoreFreeRollingState(vehicle)
local md = vehicle and vehicle:getModData() or nil
if not md then return end
if md.wreckerOriginalMass ~= nil then vehicle:setMass(md.wreckerOriginalMass) end
if md.wreckerOriginalBrakingForce ~= nil then vehicle:setBrakingForce(md.wreckerOriginalBrakingForce) end
if md.wreckerOriginalParkingBrakeOn ~= nil then
callVehicle(vehicle, "setParkingBrakeOn", md.wreckerOriginalParkingBrakeOn)
end
if md.wreckerOriginalParkingBrake ~= nil then
callVehicle(vehicle, "setParkingBrake", md.wreckerOriginalParkingBrake)
end
if md.wreckerOriginalHandbrake ~= nil then
callVehicle(vehicle, "setHandbrake", md.wreckerOriginalHandbrake)
end
md.wreckerOriginalMass = nil
md.wreckerOriginalBrakingForce = nil
md.wreckerOriginalParkingBrakeOn = nil
md.wreckerOriginalParkingBrake = nil
md.wreckerOriginalHandbrake = nil
vehicle:transmitModData()
end
local function isPairLinked(wrecker, target)
return wrecker and target
and wrecker:getVehicleTowing() == target
and target:getVehicleTowedBy() == wrecker
end
local function hasConflictingLink(vehicle, other)
return vehicle and ((vehicle:getVehicleTowing() and vehicle:getVehicleTowing() ~= other)
or (vehicle:getVehicleTowedBy() and vehicle:getVehicleTowedBy() ~= other))
end
local function breakWreckerPair(wrecker, target)
if not wrecker or not target then return end
if isPairLinked(wrecker, target) then
wrecker:breakConstraint(true, true)
end
end
local function setScriptSafely(vehicle, scriptName)
if TowBarMod.Hook and TowBarMod.Hook.setVehicleScriptWithTowBarHidden then
return TowBarMod.Hook.setVehicleScriptWithTowBarHidden(vehicle, scriptName)
end
vehicle:setScriptName(scriptName)
return true
end
local function applyAttachSync(args)
if not args then return end
local wrecker = getVehicleById(args.wrecker)
local target = getVehicleById(args.target)
if not wrecker or not target then return end
if hasConflictingLink(wrecker, target) or hasConflictingLink(target, wrecker) then return end
local hookAttachment = TowBarMod.Wrecker.getHeightAttachmentId(args.heightLevel)
local targetAttachment = args.targetAttachment
if not hookAttachment or (targetAttachment ~= "trailer" and targetAttachment ~= "trailerfront") then return end
if not wrecker:attachmentExist(hookAttachment) or not target:attachmentExist(targetAttachment) then return end
local key = pairKey(wrecker, target)
local canonicalLevel = tonumber(args.heightLevel) or 0
local appliedLevel = Sync.appliedLevels[key]
if isPairLinked(wrecker, target) and appliedLevel == canonicalLevel then
Sync.appliedLevels[key] = canonicalLevel
applyFreeRollingState(target)
return
end
breakWreckerPair(wrecker, target)
local targetMd = target:getModData()
local originalScript = targetMd.wreckerOriginalScriptName or target:getScriptName()
targetMd.wreckerOriginalScriptName = originalScript
applyFreeRollingState(target)
setScriptSafely(target, "notTowingA_Trailer")
wrecker:addPointConstraint(nil, target, hookAttachment, targetAttachment, true)
setScriptSafely(target, originalScript)
local wreckerMd = wrecker:getModData()
wreckerMd.wreckerTowActive = true
wreckerMd.wreckerTowedVehicleId = target:getId()
wreckerMd.wreckerTowedVehicleSqlId = tonumber(args.targetSqlId)
wreckerMd.wreckerTargetAttachment = targetAttachment
wreckerMd.wreckerHeightLevel = canonicalLevel
targetMd.wreckerTowingVehicleId = wrecker:getId()
targetMd.wreckerTowingVehicleSqlId = tonumber(args.wreckerSqlId)
Sync.appliedLevels[key] = canonicalLevel
end
local function applyDetachSync(args)
if not args then return end
local wrecker = getVehicleById(args.wrecker)
local target = getVehicleById(args.target)
if not target then return end
local targetMd = target:getModData()
local expectedWreckerId = tonumber(targetMd.wreckerTowingVehicleId)
local expectedWreckerSqlId = tonumber(targetMd.wreckerTowingVehicleSqlId)
local commandWreckerSqlId = tonumber(args.wreckerSqlId)
if expectedWreckerSqlId and commandWreckerSqlId then
if expectedWreckerSqlId ~= commandWreckerSqlId then return end
elseif expectedWreckerId and expectedWreckerId ~= tonumber(args.wrecker) then
return
end
if wrecker and (hasConflictingLink(wrecker, target) or hasConflictingLink(target, wrecker)) then return end
if wrecker then breakWreckerPair(wrecker, target) end
if targetMd.wreckerOriginalScriptName and target:getScriptName() ~= targetMd.wreckerOriginalScriptName then
setScriptSafely(target, targetMd.wreckerOriginalScriptName)
end
restoreFreeRollingState(target)
if wrecker then Sync.appliedLevels[pairKey(wrecker, target)] = nil end
if wrecker then
local wreckerMd = wrecker:getModData()
wreckerMd.wreckerTowActive = nil
wreckerMd.wreckerTowedVehicleId = nil
wreckerMd.wreckerTowedVehicleSqlId = nil
wreckerMd.wreckerTargetAttachment = nil
wreckerMd.wreckerHeightLevel = nil
end
targetMd.wreckerTowingVehicleId = nil
targetMd.wreckerTowingVehicleSqlId = nil
targetMd.wreckerOriginalScriptName = nil
end
Sync.applyAttachSync = applyAttachSync
Sync.applyDetachSync = applyDetachSync
Events.OnServerCommand.Add(function(module, command, args)
if module ~= "towbar" then return end
if command == "wreckerAttachSync" then
applyAttachSync(args)
elseif command == "wreckerDetachSync" then
applyDetachSync(args)
end
end)
if Events.OnSpawnVehicleEnd then
Events.OnSpawnVehicleEnd.Add(clearAppliedLevelForVehicle)
end