594 lines
23 KiB
Lua
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)
|
|
|
|
|