Files
Towbar/42.13/media/lua/client/TowBar/TowingHooking.lua
2026-02-06 19:02:36 -05:00

594 lines
23 KiB
Lua

if not TowBarMod then TowBarMod = {} end
if not TowBarMod.Hook then TowBarMod.Hook = {} end
---------------------------------------------------------------------------
--- Tow bar functions
---------------------------------------------------------------------------
local TowBarTowMass = 200
local CannotDriveWhileTowedFallbackText = "Cannot drive while being towed"
local CannotDriveMessageCooldownHours = 1 / 1800 -- 2 seconds
local ForcedTowBarReapplyCooldownHours = 1 / 3600 -- 1 second
TowBarMod.Hook.reappliedVehicleIds = TowBarMod.Hook.reappliedVehicleIds or {}
TowBarMod.Hook.pendingReapplyVehicleIds = TowBarMod.Hook.pendingReapplyVehicleIds or {}
TowBarMod.Hook.reapplyTickCounter = TowBarMod.Hook.reapplyTickCounter or 0
TowBarMod.Hook.lastCannotDriveMessageAtByPlayer = TowBarMod.Hook.lastCannotDriveMessageAtByPlayer or {}
TowBarMod.Hook.lastForcedReapplyAtByVehicle = TowBarMod.Hook.lastForcedReapplyAtByVehicle or {}
TowBarMod.Hook.lastTowBarVehicleIdByPlayer = TowBarMod.Hook.lastTowBarVehicleIdByPlayer or {}
function TowBarMod.Hook.isTowedByTowBar(vehicle)
if not vehicle then return false end
local modData = vehicle:getModData()
if not modData or not modData["isTowingByTowBar"] or not modData["towed"] then
return false
end
local towingVehicle = vehicle:getVehicleTowedBy()
if not towingVehicle then return false end
local towingModData = towingVehicle:getModData()
return towingModData and towingModData["isTowingByTowBar"] == true
end
function TowBarMod.Hook.getCannotDriveWhileTowedText()
local msg = getText("UI_Text_Towing_cannotDriveWhileTowed")
if not msg or msg == "UI_Text_Towing_cannotDriveWhileTowed" then
return CannotDriveWhileTowedFallbackText
end
return msg
end
function TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
if not playerObj then return end
local playerNum = playerObj:getPlayerNum()
local nowHours = getGameTime() and getGameTime():getWorldAgeHours() or nil
local lastHours = TowBarMod.Hook.lastCannotDriveMessageAtByPlayer[playerNum]
if nowHours and lastHours and (nowHours - lastHours) < CannotDriveMessageCooldownHours then
return
end
TowBarMod.Hook.lastCannotDriveMessageAtByPlayer[playerNum] = nowHours or 0
-- Match the overhead-style skill/feedback text.
HaloTextHelper.addBadText(playerObj, TowBarMod.Hook.getCannotDriveWhileTowedText())
end
function TowBarMod.Hook.installStartEngineBlock()
if not ISVehicleMenu or not ISVehicleMenu.onStartEngine then
return
end
if TowBarMod.Hook._startEngineBlockInstalled then
return
end
TowBarMod.Hook.defaultOnStartEngine = ISVehicleMenu.onStartEngine
ISVehicleMenu.onStartEngine = function(playerObj)
local vehicle = playerObj and playerObj:getVehicle() or nil
if TowBarMod.Hook.isTowedByTowBar(vehicle) then
TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
return
end
TowBarMod.Hook.defaultOnStartEngine(playerObj)
end
TowBarMod.Hook._startEngineBlockInstalled = true
end
function TowBarMod.Hook.getVehicleByIdSafe(vehicleId)
if getVehicleById then
return getVehicleById(vehicleId)
end
local cell = getCell()
if not cell then return nil end
local vehicles = cell:getVehicles()
if not vehicles then return nil end
for i = 0, vehicles:size() - 1 do
local vehicle = vehicles:get(i)
if vehicle and vehicle:getId() == vehicleId then
return vehicle
end
end
return nil
end
function TowBarMod.Hook.shouldBlockDriverSeatForTowBar(playerObj, vehicle, seat)
if not playerObj or not vehicle then return false end
if seat ~= 0 then return false end
return TowBarMod.Hook.isTowedByTowBar(vehicle)
end
function TowBarMod.Hook.hasPendingSeatSafetyAction(playerObj)
if not playerObj or not ISTimedActionQueue then return false end
return ISTimedActionQueue.hasActionType(playerObj, "ISSwitchVehicleSeat")
or ISTimedActionQueue.hasActionType(playerObj, "ISExitVehicle")
or ISTimedActionQueue.hasActionType(playerObj, "ISStopVehicle")
end
function TowBarMod.Hook.getBestSeatForDriverKick(playerObj, vehicle)
if not playerObj or not vehicle then return nil end
if ISVehicleMenu and ISVehicleMenu.getBestSwitchSeatExit then
local best = ISVehicleMenu.getBestSwitchSeatExit(playerObj, vehicle, 0)
if best and best > 0 and not vehicle:isSeatOccupied(best) and vehicle:canSwitchSeat(0, best) then
return best
end
end
for seat = 1, vehicle:getMaxPassengers() - 1 do
if not vehicle:isSeatOccupied(seat) and vehicle:canSwitchSeat(0, seat) then
return seat
end
end
return nil
end
function TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
if not playerObj or not vehicle then return false end
if not TowBarMod.Hook.isTowedByTowBar(vehicle) then return false end
if not vehicle:isDriver(playerObj) and vehicle:getSeat(playerObj) ~= 0 then return false end
if TowBarMod.Hook.hasPendingSeatSafetyAction(playerObj) then return false end
local seatTo = TowBarMod.Hook.getBestSeatForDriverKick(playerObj, vehicle)
if seatTo then
if ISVehicleMenu and ISVehicleMenu.onSwitchSeat then
ISVehicleMenu.onSwitchSeat(playerObj, seatTo)
elseif ISSwitchVehicleSeat then
ISTimedActionQueue.add(ISSwitchVehicleSeat:new(playerObj, seatTo))
end
return true
end
if not vehicle:isStopped() then
return false
end
if ISVehicleMenu and ISVehicleMenu.onExit then
ISVehicleMenu.onExit(playerObj, 0)
return true
end
if ISExitVehicle then
ISTimedActionQueue.add(ISExitVehicle:new(playerObj))
return true
end
return false
end
function TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
if not TowBarMod.Hook.hasTowBarTowData(vehicle) then return false end
local vehicleId = vehicle:getId()
local nowHours = getGameTime() and getGameTime():getWorldAgeHours() or nil
local lastHours = TowBarMod.Hook.lastForcedReapplyAtByVehicle[vehicleId]
if nowHours and lastHours and (nowHours - lastHours) < ForcedTowBarReapplyCooldownHours then
return false
end
TowBarMod.Hook.lastForcedReapplyAtByVehicle[vehicleId] = nowHours or 0
TowBarMod.Hook.clearReapplied(vehicle)
TowBarMod.Hook.queueTowBarReapply(vehicle)
return TowBarMod.Hook.tryTowBarReapply(vehicle, playerObj)
end
function TowBarMod.Hook.installSeatEntryBlock()
if not ISVehicleMenu or not ISVehicleMenu.onEnter or not ISVehicleMenu.onSwitchSeat then
return
end
if TowBarMod.Hook._seatEntryBlockInstalled then
return
end
TowBarMod.Hook.defaultOnEnter = ISVehicleMenu.onEnter
TowBarMod.Hook.defaultOnEnter2 = ISVehicleMenu.onEnter2
TowBarMod.Hook.defaultOnSwitchSeat = ISVehicleMenu.onSwitchSeat
ISVehicleMenu.onEnter = function(playerObj, vehicle, seat)
if TowBarMod.Hook.shouldBlockDriverSeatForTowBar(playerObj, vehicle, seat) then
TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
return
end
TowBarMod.Hook.defaultOnEnter(playerObj, vehicle, seat)
end
if TowBarMod.Hook.defaultOnEnter2 then
ISVehicleMenu.onEnter2 = function(playerObj, vehicle, seat)
if TowBarMod.Hook.shouldBlockDriverSeatForTowBar(playerObj, vehicle, seat) then
TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
return
end
TowBarMod.Hook.defaultOnEnter2(playerObj, vehicle, seat)
end
end
ISVehicleMenu.onSwitchSeat = function(playerObj, seatTo)
local vehicle = playerObj and playerObj:getVehicle() or nil
if TowBarMod.Hook.shouldBlockDriverSeatForTowBar(playerObj, vehicle, seatTo) then
TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
return
end
TowBarMod.Hook.defaultOnSwitchSeat(playerObj, seatTo)
end
TowBarMod.Hook._seatEntryBlockInstalled = true
end
function TowBarMod.Hook.enforceTowedVehicleEngineOff(playerObj)
if not playerObj or not instanceof(playerObj, "IsoPlayer") or not playerObj:isLocalPlayer() then return end
local vehicle = playerObj:getVehicle()
if not TowBarMod.Hook.isTowedByTowBar(vehicle) then return end
if vehicle:isEngineRunning() or vehicle:isEngineStarted() or vehicle:isStarting() then
vehicle:shutOff()
end
end
function TowBarMod.Hook.enforceTowedVehicleSeatSafety(playerObj)
if not playerObj or not instanceof(playerObj, "IsoPlayer") or not playerObj:isLocalPlayer() then return end
local vehicle = playerObj:getVehicle()
if not TowBarMod.Hook.isTowedByTowBar(vehicle) then return end
TowBarMod.Hook.lastTowBarVehicleIdByPlayer[playerObj:getPlayerNum()] = vehicle:getId()
if vehicle:isDriver(playerObj) or vehicle:getSeat(playerObj) == 0 then
TowBarMod.Hook.showCannotDriveWhileTowed(playerObj)
TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
TowBarMod.Hook.tryMoveOrExitTowedDriver(playerObj, vehicle)
end
end
function TowBarMod.Hook.handleTowBarSeatEvent(character)
if not character or not instanceof(character, "IsoPlayer") or not character:isLocalPlayer() then return end
local playerObj = character
local vehicle = playerObj:getVehicle()
if not TowBarMod.Hook.isTowedByTowBar(vehicle) then return end
TowBarMod.Hook.lastTowBarVehicleIdByPlayer[playerObj:getPlayerNum()] = vehicle:getId()
TowBarMod.Hook.forceTowBarReapply(vehicle, playerObj)
TowBarMod.Hook.enforceTowedVehicleSeatSafety(playerObj)
end
function TowBarMod.Hook.OnEnterVehicle(character)
TowBarMod.Hook.handleTowBarSeatEvent(character)
end
function TowBarMod.Hook.OnSwitchVehicleSeat(character)
TowBarMod.Hook.handleTowBarSeatEvent(character)
end
function TowBarMod.Hook.OnExitVehicle(character)
if not character or not instanceof(character, "IsoPlayer") or not character:isLocalPlayer() then return end
local playerNum = character:getPlayerNum()
local vehicleId = TowBarMod.Hook.lastTowBarVehicleIdByPlayer[playerNum]
if not vehicleId then return end
local vehicle = TowBarMod.Hook.getVehicleByIdSafe(vehicleId)
if vehicle and TowBarMod.Hook.hasTowBarTowData(vehicle) then
TowBarMod.Hook.forceTowBarReapply(vehicle, character)
end
end
function TowBarMod.Hook.markReapplied(vehicle)
if not vehicle then return end
local vehicleId = vehicle:getId()
TowBarMod.Hook.reappliedVehicleIds[vehicleId] = true
TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
end
function TowBarMod.Hook.clearReapplied(vehicle)
if not vehicle then return end
local vehicleId = vehicle:getId()
TowBarMod.Hook.reappliedVehicleIds[vehicleId] = nil
TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
end
function TowBarMod.Hook.hasTowBarTowData(vehicle)
if not vehicle then return false end
local modData = vehicle:getModData()
return modData and modData["isTowingByTowBar"] and modData["towed"]
end
function TowBarMod.Hook.isTowBarLinkActive(towingVehicle, towedVehicle)
if not towingVehicle or not towedVehicle then return false end
return towingVehicle:getVehicleTowing() == towedVehicle
and towedVehicle:getVehicleTowedBy() == towingVehicle
end
function TowBarMod.Hook.queueTowBarReapply(vehicle)
if not TowBarMod.Hook.hasTowBarTowData(vehicle) then return end
local vehicleId = vehicle:getId()
TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = true
end
function TowBarMod.Hook.tryTowBarReapply(vehicle, playerObj)
if not TowBarMod.Hook.hasTowBarTowData(vehicle) then
if vehicle then
TowBarMod.Hook.pendingReapplyVehicleIds[vehicle:getId()] = nil
end
return false
end
local vehicleId = vehicle:getId()
if TowBarMod.Hook.reappliedVehicleIds[vehicleId] then
TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
return true
end
local towingVehicle = vehicle:getVehicleTowedBy()
if not towingVehicle then return false end
local towingModData = towingVehicle:getModData()
if not towingModData or towingModData["isTowingByTowBar"] ~= true then return false end
local modData = vehicle:getModData()
if not modData.towBarOriginalScriptName then
modData.towBarOriginalScriptName = vehicle:getScriptName()
vehicle:transmitModData()
end
-- Save/load already restores a valid tow link. Do not stack another attach on top.
if TowBarMod.Hook.isTowBarLinkActive(towingVehicle, vehicle) then
TowBarMod.Hook.setVehiclePostAttach(playerObj, vehicle)
TowBarMod.Hook.markReapplied(vehicle)
return true
end
playerObj = playerObj or getPlayer()
if not playerObj then return false end
local attachmentA = towingVehicle:getTowAttachmentSelf() or "trailer"
local attachmentB = vehicle:getTowAttachmentSelf() or "trailerfront"
-- Re-run the same rigid tow offset + fake-trailer flow used during normal attach.
TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, vehicle, attachmentA, attachmentB)
vehicle:setScriptName("notTowingA_Trailer")
local args = { vehicleA = towingVehicle:getId(), vehicleB = vehicle:getId(), attachmentA = attachmentA, attachmentB = attachmentB }
sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, vehicle))
TowBarMod.Hook.markReapplied(vehicle)
return true
end
function TowBarMod.Hook.reapplyTowBarPostLoad(vehicle)
TowBarMod.Hook.queueTowBarReapply(vehicle)
TowBarMod.Hook.tryTowBarReapply(vehicle, getPlayer())
end
function TowBarMod.Hook.processPendingReapplies()
TowBarMod.Hook.reapplyTickCounter = TowBarMod.Hook.reapplyTickCounter + 1
if TowBarMod.Hook.reapplyTickCounter < 15 then
return
end
TowBarMod.Hook.reapplyTickCounter = 0
local playerObj = getPlayer()
if not playerObj then return end
local resolvedVehicleIds = {}
for vehicleId, _ in pairs(TowBarMod.Hook.pendingReapplyVehicleIds) do
local vehicle = TowBarMod.Hook.getVehicleByIdSafe(vehicleId)
if vehicle and (TowBarMod.Hook.tryTowBarReapply(vehicle, playerObj) or not TowBarMod.Hook.hasTowBarTowData(vehicle)) then
table.insert(resolvedVehicleIds, vehicleId)
end
end
for _, vehicleId in ipairs(resolvedVehicleIds) do
TowBarMod.Hook.pendingReapplyVehicleIds[vehicleId] = nil
end
end
function TowBarMod.Hook.OnSpawnVehicle(vehicle)
TowBarMod.Hook.reapplyTowBarPostLoad(vehicle)
end
function TowBarMod.Hook.performAttachTowBar(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB)
if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end
local towBarItem = playerObj:getInventory():getItemFromType("TowBar.TowBar")
if towBarItem ~= nil then
sendClientCommand(playerObj, "towbar", "consumeTowBar", { itemId = towBarItem:getID() })
end
playerObj:setPrimaryHandItem(nil)
TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
local towingModData = towingVehicle:getModData()
local towedModData = towedVehicle:getModData()
towedModData.towBarOriginalScriptName = towedVehicle:getScriptName()
towedModData.towBarOriginalMass = towedVehicle:getMass()
towedModData.towBarOriginalBrakingForce = towedVehicle:getBrakingForce()
towingModData["isTowingByTowBar"] = true
towedModData["isTowingByTowBar"] = true
towedModData["towed"] = true
towingVehicle:transmitModData()
towedVehicle:transmitModData()
local part = towedVehicle:getPartById("towbar")
if part ~= nil then
if towedVehicle:getScript():getModelScale() >= 1.5 and towedVehicle:getScript():getModelScale() <= 2 then
local z = towedVehicle:getScript():getPhysicsChassisShape():z()/2 - 0.1
part:setModelVisible("towbar" .. math.floor((z*2/3-1)*10), true)
end
end
towedVehicle:doDamageOverlay()
-- Fake a trailer script so the base "attachTrailer" command creates rigid point-constraint towing.
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))
TowBarMod.Hook.markReapplied(towedVehicle)
end
function TowBarMod.Hook.setVehiclePostAttach(playerObj, towedVehicle)
if not towedVehicle then return end
local towedModData = towedVehicle:getModData()
if towedModData.towBarOriginalScriptName then
towedVehicle:setScriptName(towedModData.towBarOriginalScriptName)
end
towedVehicle:setMass(TowBarTowMass)
towedVehicle:setBrakingForce(0)
towedVehicle:constraintChanged()
towedVehicle:updateTotalMass()
end
function TowBarMod.Hook.attachByTowBarAction(playerObj, towingVehicle, towedVehicle)
if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end
local item = playerObj:getInventory():getItemFromTypeRecurse("TowBar.TowBar")
if item == nil then return end
if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end
local hookPoint = towedVehicle:getAttachmentWorldPos("trailerfront", TowBarMod.Utils.tempVector1)
if hookPoint == nil then return end
ISTimedActionQueue.add(TowBarCustomPathFind:pathToLocationF(playerObj, hookPoint:x(), hookPoint:y(), hookPoint:z()))
if not playerObj:getInventory():contains("TowBar.TowBar") then
ISTimedActionQueue.add(ISInventoryTransferAction:new(playerObj, item, item:getContainer(), playerObj:getInventory(), nil))
end
local storePrim = playerObj:getPrimaryHandItem()
if storePrim == nil or storePrim ~= item then
ISTimedActionQueue.add(ISEquipWeaponAction:new(playerObj, item, 12, true))
end
ISTimedActionQueue.add(TowBarHookVehicle:new(playerObj, 300, TowBarMod.Config.lowLevelAnimation))
hookPoint = towingVehicle:getAttachmentWorldPos("trailer", TowBarMod.Utils.tempVector1)
if hookPoint == nil then return end
ISTimedActionQueue.add(TowBarCustomPathFind:pathToLocationF(playerObj, hookPoint:x(), hookPoint:y(), hookPoint:z()))
ISTimedActionQueue.add(TowBarHookVehicle:new(
playerObj,
100,
TowBarMod.Config.lowLevelAnimation,
TowBarMod.Hook.performAttachTowBar,
towingVehicle,
towedVehicle,
"trailer",
"trailerfront"
))
end
function TowBarMod.Hook.performDeattachTowBar(playerObj, towingVehicle, towedVehicle)
TowBarMod.Utils.updateAttachmentsOnDefaultValues(towingVehicle, towedVehicle)
-- Detach from the towing side to mirror vanilla trailer detach behavior.
local args = { vehicle = towingVehicle:getId() }
sendClientCommand(playerObj, "vehicle", "detachTrailer", args)
local towedModData = towedVehicle:getModData()
if towedModData.towBarOriginalScriptName then
towedVehicle:setScriptName(towedModData.towBarOriginalScriptName)
end
if towedModData.towBarOriginalMass ~= nil then
towedVehicle:setMass(towedModData.towBarOriginalMass)
end
if towedModData.towBarOriginalBrakingForce ~= nil then
towedVehicle:setBrakingForce(towedModData.towBarOriginalBrakingForce)
end
towedVehicle:constraintChanged()
towedVehicle:updateTotalMass()
sendClientCommand(playerObj, "towbar", "giveTowBar", { equipPrimary = true })
towingVehicle:getModData()["isTowingByTowBar"] = false
towedModData["isTowingByTowBar"] = false
towedModData["towed"] = false
towedModData.towBarOriginalScriptName = nil
towedModData.towBarOriginalMass = nil
towedModData.towBarOriginalBrakingForce = nil
towingVehicle:transmitModData()
towedVehicle:transmitModData()
TowBarMod.Hook.clearReapplied(towedVehicle)
TowBarMod.Hook.lastForcedReapplyAtByVehicle[towedVehicle:getId()] = nil
local part = towedVehicle:getPartById("towbar")
if part ~= nil then
for j=0, 23 do
part:setModelVisible("towbar" .. j, false)
end
end
towedVehicle:doDamageOverlay()
end
function TowBarMod.Hook.deattachTowBarAction(playerObj, vehicle)
local towingVehicle = vehicle
local towedVehicle = vehicle:getVehicleTowing()
if vehicle:getVehicleTowedBy() then
towingVehicle = vehicle:getVehicleTowedBy()
towedVehicle = vehicle
end
if towingVehicle == nil or towedVehicle == nil then return end
local localPoint = towingVehicle:getAttachmentLocalPos(towingVehicle:getTowAttachmentSelf(), TowBarMod.Utils.tempVector1)
local shift = 0
if towingVehicle:getModData()["isChangedTowedAttachment"] then
shift = localPoint:z() > 0 and -1 or 1
end
local hookPoint = towingVehicle:getWorldPos(localPoint:x(), localPoint:y(), localPoint:z() + shift, TowBarMod.Utils.tempVector2)
if hookPoint == nil then return end
ISTimedActionQueue.add(TowBarCustomPathFind:pathToLocationF(playerObj, hookPoint:x(), hookPoint:y(), hookPoint:z()))
local storePrim = playerObj:getPrimaryHandItem()
if storePrim ~= nil then
ISTimedActionQueue.add(ISUnequipAction:new(playerObj, storePrim, 12))
end
ISTimedActionQueue.add(TowBarHookVehicle:new(playerObj, 100, TowBarMod.Config.lowLevelAnimation))
localPoint = towedVehicle:getAttachmentLocalPos(towedVehicle:getTowAttachmentSelf(), TowBarMod.Utils.tempVector1)
shift = 0
if towedVehicle:getModData()["isChangedTowedAttachment"] then
shift = localPoint:z() > 0 and -1 or 1
end
hookPoint = towedVehicle:getWorldPos(localPoint:x(), localPoint:y(), localPoint:z() + shift, TowBarMod.Utils.tempVector2)
if hookPoint == nil then return end
ISTimedActionQueue.add(TowBarCustomPathFind:pathToLocationF(playerObj, hookPoint:x(), hookPoint:y(), hookPoint:z()))
ISTimedActionQueue.add(TowBarHookVehicle:new(
playerObj,
300,
TowBarMod.Config.lowLevelAnimation,
TowBarMod.Hook.performDeattachTowBar,
towingVehicle,
towedVehicle
))
end
TowBarMod.Hook.installStartEngineBlock()
TowBarMod.Hook.installSeatEntryBlock()
Events.OnGameStart.Add(TowBarMod.Hook.installStartEngineBlock)
Events.OnGameStart.Add(TowBarMod.Hook.installSeatEntryBlock)
Events.OnPlayerUpdate.Add(TowBarMod.Hook.enforceTowedVehicleEngineOff)
Events.OnPlayerUpdate.Add(TowBarMod.Hook.enforceTowedVehicleSeatSafety)
Events.OnSpawnVehicleEnd.Add(TowBarMod.Hook.OnSpawnVehicle)
Events.OnEnterVehicle.Add(TowBarMod.Hook.OnEnterVehicle)
Events.OnSwitchVehicleSeat.Add(TowBarMod.Hook.OnSwitchVehicleSeat)
Events.OnExitVehicle.Add(TowBarMod.Hook.OnExitVehicle)
Events.OnTick.Add(TowBarMod.Hook.processPendingReapplies)