From 34b8f0953359dc83510ae3e2dffeed9206a9aa07 Mon Sep 17 00:00:00 2001 From: HRiggs Date: Sat, 7 Feb 2026 17:07:16 -0500 Subject: [PATCH] Finished --- .../media/lua/client/TowBar/TowingHooking.lua | 235 +++++++++++++----- 1 file changed, 177 insertions(+), 58 deletions(-) diff --git a/42.13/media/lua/client/TowBar/TowingHooking.lua b/42.13/media/lua/client/TowBar/TowingHooking.lua index d63d257..aa5dede 100644 --- a/42.13/media/lua/client/TowBar/TowingHooking.lua +++ b/42.13/media/lua/client/TowBar/TowingHooking.lua @@ -63,14 +63,177 @@ local function setTowBarModelVisible(vehicle, isVisible) vehicle:doDamageOverlay() end -function TowBarMod.Hook.setVehiclePostAttach(playerObj, towedVehicle) +local function resolveTowAttachmentsForPair(towingVehicle, towedVehicle, towedModData) + if not towingVehicle or not towedVehicle then + return nil, nil + end + + local attachmentA = towingVehicle:getTowAttachmentSelf() or "trailer" + local attachmentB = towingVehicle:getTowAttachmentOther() + or (towedModData and towedModData["towBarChangedAttachmentId"]) + or "trailerfront" + + if not towingVehicle:canAttachTrailer(towedVehicle, attachmentA, attachmentB) then + if towingVehicle:canAttachTrailer(towedVehicle, "trailer", "trailerfront") then + attachmentA = "trailer" + attachmentB = "trailerfront" + elseif towingVehicle:canAttachTrailer(towedVehicle, "trailerfront", "trailer") then + attachmentA = "trailerfront" + attachmentB = "trailer" + end + end + + return attachmentA, attachmentB +end + +local function hasTowBarTowState(modData) + if not modData then + return false + end + + if modData["isTowingByTowBar"] and modData["towed"] then + return true + end + + -- Rejoin fallback: legacy saves may only have the original-script marker. + if modData.towBarOriginalScriptName ~= nil then + return true + end + + return false +end + +local function isActiveTowBarTowedVehicle(vehicle, modData) + if not vehicle or not modData then + return false + end + + if modData["isTowingByTowBar"] and modData["towed"] then + return true + end + + -- Rejoin fallback: if the tow link exists, original-script marker is enough. + if vehicle:getVehicleTowedBy() and modData.towBarOriginalScriptName ~= nil then + return true + end + + return false +end + +local function reattachTowBarPair(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 towingModData = towingVehicle:getModData() + local towedModData = towedVehicle:getModData() + if not towingModData or not towedModData then + return false + end + if requireDriver then + if not isTowBarTowPair(towingVehicle, towedVehicle) then + return false + end + else + if not isActiveTowBarTowedVehicle(towedVehicle, towedModData) then + return false + end + end + + local attachmentA, attachmentB = resolveTowAttachmentsForPair(towingVehicle, towedVehicle, towedModData) + if not attachmentA or not attachmentB then + return false + end + + local towingScript = towingVehicle:getScript() + local towedScript = towedVehicle:getScript() + if not towingScript or not towedScript then + return false + end + if not towingScript:getAttachmentById(attachmentA) or not towedScript:getAttachmentById(attachmentB) then + return false + end + + TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB) + + towedModData.towBarOriginalScriptName = towedModData.towBarOriginalScriptName or towedVehicle:getScriptName() + if towedModData.towBarOriginalMass == nil then + towedModData.towBarOriginalMass = towedVehicle:getMass() + end + if towedModData.towBarOriginalBrakingForce == nil then + towedModData.towBarOriginalBrakingForce = towedVehicle:getBrakingForce() + end + + towingModData["isTowingByTowBar"] = true + towedModData["isTowingByTowBar"] = true + towedModData["towed"] = true + towingVehicle:transmitModData() + towedVehicle:transmitModData() + + setTowBarModelVisible(towedVehicle, true) + towedVehicle:setScriptName("notTowingA_Trailer") + + local args = { + vehicleA = towingVehicle:getId(), + vehicleB = towedVehicle:getId(), + attachmentA = attachmentA, + attachmentB = attachmentB + } + sendClientCommand(playerObj, "vehicle", "attachTrailer", args) + ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle)) + return true +end + +local function recoverTowBarVehicleAfterLoad(playerObj, vehicle, retriesLeft) + if not vehicle then return end + + local modData = vehicle:getModData() + if not hasTowBarTowState(modData) then + return + end + + local retries = tonumber(retriesLeft) or 0 + local localPlayer = playerObj or getPlayer() + local towingVehicle = vehicle:getVehicleTowedBy() + + if localPlayer and towingVehicle then + if reattachTowBarPair(localPlayer, towingVehicle, vehicle, false) then + return + end + end + + if localPlayer and retries > 0 then + -- During world load, tow links can become available a few ticks later. + ISTimedActionQueue.add(TowBarScheduleAction:new(localPlayer, 10, recoverTowBarVehicleAfterLoad, vehicle, retries - 1)) + return + end + + -- Fallback: keep original post-attach restoration behavior. + setTowBarModelVisible(vehicle, true) + TowBarMod.Hook.setVehiclePostAttach(nil, vehicle) +end + +function TowBarMod.Hook.setVehiclePostAttach(playerObj, towedVehicle, retriesLeft) if not towedVehicle then return end local towedModData = towedVehicle:getModData() + if not isActiveTowBarTowedVehicle(towedVehicle, towedModData) then return end + if towedModData and towedModData.towBarOriginalScriptName then towedVehicle:setScriptName(towedModData.towBarOriginalScriptName) end + local towingVehicle = towedVehicle:getVehicleTowedBy() + if towingVehicle then + local attachmentA, attachmentB = resolveTowAttachmentsForPair(towingVehicle, towedVehicle, towedModData) + if attachmentA and attachmentB then + TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB) + end + end + towedVehicle:setMass(TowBarTowMass) towedVehicle:setBrakingForce(0) towedVehicle:constraintChanged() @@ -157,55 +320,11 @@ end function TowBarMod.Hook.reattachTowBarFromDriverSeat(playerObj, towingVehicle) if not playerObj or not towingVehicle then return end - if not towingVehicle:isDriver(playerObj) then return end local towedVehicle = towingVehicle:getVehicleTowing() if not towedVehicle then return end - local towingModData = towingVehicle:getModData() - local towedModData = towedVehicle:getModData() - if not towingModData or not towedModData then return end - if not isTowBarTowPair(towingVehicle, towedVehicle) then return end - - local attachmentA = towingVehicle:getTowAttachmentSelf() or "trailer" - local attachmentB = towingVehicle:getTowAttachmentOther() or "trailerfront" - if not towingVehicle:canAttachTrailer(towedVehicle, attachmentA, attachmentB) then - if towingVehicle:canAttachTrailer(towedVehicle, "trailer", "trailerfront") then - attachmentA = "trailer" - attachmentB = "trailerfront" - elseif towingVehicle:canAttachTrailer(towedVehicle, "trailerfront", "trailer") then - attachmentA = "trailerfront" - attachmentB = "trailer" - end - end - - TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB) - - towedModData.towBarOriginalScriptName = towedModData.towBarOriginalScriptName or towedVehicle:getScriptName() - if towedModData.towBarOriginalMass == nil then - towedModData.towBarOriginalMass = towedVehicle:getMass() - end - if towedModData.towBarOriginalBrakingForce == nil then - towedModData.towBarOriginalBrakingForce = towedVehicle:getBrakingForce() - end - - towingModData["isTowingByTowBar"] = true - towedModData["isTowingByTowBar"] = true - towedModData["towed"] = true - towingVehicle:transmitModData() - towedVehicle:transmitModData() - - setTowBarModelVisible(towedVehicle, true) - towedVehicle:setScriptName("notTowingA_Trailer") - - local args = { - vehicleA = towingVehicle:getId(), - vehicleB = towedVehicle:getId(), - attachmentA = attachmentA, - attachmentB = attachmentB - } - sendClientCommand(playerObj, "vehicle", "attachTrailer", args) - ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle)) + reattachTowBarPair(playerObj, towingVehicle, towedVehicle, true) end local function tryAutoReattachFromCharacter(character) @@ -328,25 +447,25 @@ function TowBarMod.Hook.deattachTowBarAction(playerObj, vehicle) end function TowBarMod.Hook.OnSpawnVehicle(vehicle) - if not vehicle then return end + recoverTowBarVehicleAfterLoad(nil, vehicle, 6) +end - local modData = vehicle:getModData() - if not (modData and modData["isTowingByTowBar"] and modData["towed"]) then - return - end +function TowBarMod.Hook.OnGameStart() + local cell = getCell() + if not cell then return end - -- Keep behavior consistent after load/rejoin for active towbar tows. - vehicle:setScriptName("notTowingA_Trailer") + local vehicles = cell:getVehicles() + if not vehicles then return end local playerObj = getPlayer() - if playerObj then - ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, vehicle)) - else - TowBarMod.Hook.setVehiclePostAttach(nil, vehicle) + for i = 0, vehicles:size() - 1 do + recoverTowBarVehicleAfterLoad(playerObj, vehicles:get(i), 6) end - setTowBarModelVisible(vehicle, true) end Events.OnSpawnVehicleEnd.Add(TowBarMod.Hook.OnSpawnVehicle) +if Events.OnGameStart then + Events.OnGameStart.Add(TowBarMod.Hook.OnGameStart) +end Events.OnEnterVehicle.Add(TowBarMod.Hook.OnEnterVehicle) Events.OnSwitchVehicleSeat.Add(TowBarMod.Hook.OnSwitchVehicleSeat)