271 lines
10 KiB
Lua
271 lines
10 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 {}
|
|
Sync.desiredPairs = Sync.desiredPairs or {}
|
|
|
|
local function argsPairKey(args)
|
|
if type(args) ~= "table" then return nil end
|
|
local wreckerId = tonumber(args.wrecker)
|
|
local targetId = tonumber(args.target)
|
|
if not wreckerId or not targetId then return nil end
|
|
return tostring(wreckerId) .. ":" .. tostring(targetId)
|
|
end
|
|
|
|
local function copyAttachArgs(args)
|
|
return {
|
|
wrecker = tonumber(args.wrecker),
|
|
target = tonumber(args.target),
|
|
wreckerSqlId = tonumber(args.wreckerSqlId),
|
|
targetSqlId = tonumber(args.targetSqlId),
|
|
targetAttachment = args.targetAttachment,
|
|
heightLevel = tonumber(args.heightLevel) or 0
|
|
}
|
|
end
|
|
|
|
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, forceReattach)
|
|
if not args then return end
|
|
local desiredKey = argsPairKey(args)
|
|
if desiredKey then Sync.desiredPairs[desiredKey] = copyAttachArgs(args) 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 not forceReattach and 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 desiredKey = argsPairKey(args)
|
|
if desiredKey then Sync.desiredPairs[desiredKey] = nil 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
|
|
|
|
local function forceReattachForDriver(character)
|
|
if not character or type(character.getVehicle) ~= "function" then return end
|
|
local wrecker = character:getVehicle()
|
|
if not wrecker or not wrecker:isDriver(character) then return end
|
|
|
|
local wreckerMd = wrecker:getModData()
|
|
if not wreckerMd or wreckerMd.wreckerTowActive ~= true then return end
|
|
local targetId = tonumber(wreckerMd.wreckerTowedVehicleId)
|
|
local target = targetId and getVehicleById(targetId) or nil
|
|
if not target then return end
|
|
|
|
local targetMd = target:getModData()
|
|
if not targetMd or tonumber(targetMd.wreckerTowingVehicleId) ~= wrecker:getId() then return end
|
|
|
|
applyAttachSync({
|
|
wrecker = wrecker:getId(),
|
|
target = target:getId(),
|
|
wreckerSqlId = wrecker:getSqlId(),
|
|
targetSqlId = wreckerMd.wreckerTowedVehicleSqlId,
|
|
targetAttachment = wreckerMd.wreckerTargetAttachment,
|
|
heightLevel = tonumber(wreckerMd.wreckerHeightLevel) or 0
|
|
}, true)
|
|
end
|
|
|
|
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
|
|
if Events.OnEnterVehicle then
|
|
Events.OnEnterVehicle.Add(forceReattachForDriver)
|
|
end
|
|
if Events.OnSwitchVehicleSeat then
|
|
Events.OnSwitchVehicleSeat.Add(forceReattachForDriver)
|
|
end
|