Files
Towbar/42.13/media/lua/client/TowBar/TowingUtils.lua
2026-02-12 11:07:04 -05:00

253 lines
9.8 KiB
Lua

if not TowBarMod then TowBarMod = {} end
if not TowBarMod.Utils then TowBarMod.Utils = {} end
TowBarMod.Utils.tempVector1 = Vector3f.new()
TowBarMod.Utils.tempVector2 = Vector3f.new()
---------------------------------------------------------------------------
--- Util functions
---------------------------------------------------------------------------
--- Compute the attachment Y offset for a vehicle so the towbar sits just
--- above the wheels (i.e. a fixed distance off the ground) regardless of
--- how the vehicle model is configured.
local function computeAttachmentHeight(vehicle)
local script = vehicle:getScript()
if not script then return -0.5 end
local wheelCount = script:getWheelCount()
if wheelCount > 0 then
return script:getWheel(0):getOffset():y() + 0.1
end
return -0.5
end
function TowBarMod.Utils.isTrailer(vehicle)
return string.match(string.lower(vehicle:getScript():getName()), "trailer")
end
--- Return vehicles from sector that player can tow by tow bar.
function TowBarMod.Utils.getAviableVehicles(mainVehicle, hasTowBar)
local vehicles = {}
if not hasTowBar then return vehicles end
local square = mainVehicle:getSquare()
if square == nil then return vehicles end
-- Match vanilla towing search radius.
for y=square:getY() - 6, square:getY() + 6 do
for x=square:getX() - 6, square:getX() + 6 do
local square2 = getCell():getGridSquare(x, y, square:getZ())
if square2 then
for i=1, square2:getMovingObjects():size() do
local obj = square2:getMovingObjects():get(i-1)
if obj ~= nil
and instanceof(obj, "BaseVehicle")
and obj ~= mainVehicle
and #(TowBarMod.Utils.getHookTypeVariants(mainVehicle, obj, hasTowBar)) ~= 0 then
table.insert(vehicles, obj)
end
end
end
end
end
return vehicles
end
--- Return a table with towbar-only hook options for vehicles.
function TowBarMod.Utils.getHookTypeVariants(vehicleA, vehicleB, hasTowBar)
local hookTypeVariants = {}
if not hasTowBar then return hookTypeVariants end
if vehicleA:getVehicleTowing() or vehicleA:getVehicleTowedBy()
or vehicleB:getVehicleTowing() or vehicleB:getVehicleTowedBy() then
return hookTypeVariants
end
-- Keep tow bars for vehicle-to-vehicle towing only.
if TowBarMod.Utils.isTrailer(vehicleA) or TowBarMod.Utils.isTrailer(vehicleB) then
return hookTypeVariants
end
if vehicleA:canAttachTrailer(vehicleB, "trailerfront", "trailer") then
local hookType = {}
hookType.name = getText("UI_Text_Towing_attach") .. "\n" .. ISVehicleMenu.getVehicleDisplayName(vehicleB) .. "\n" .. getText("UI_Text_Towing_byTowBar")
hookType.func = TowBarMod.Hook.attachByTowBarAction
hookType.towingVehicle = vehicleB
hookType.towedVehicle = vehicleA
hookType.textureName = "tow_bar_icon"
table.insert(hookTypeVariants, hookType)
elseif vehicleA:canAttachTrailer(vehicleB, "trailer", "trailerfront") then
local hookType = {}
hookType.name = getText("UI_Text_Towing_attach") .. "\n" .. ISVehicleMenu.getVehicleDisplayName(vehicleB) .. "\n" .. getText("UI_Text_Towing_byTowBar")
hookType.func = TowBarMod.Hook.attachByTowBarAction
hookType.towingVehicle = vehicleA
hookType.towedVehicle = vehicleB
hookType.textureName = "tow_bar_icon"
table.insert(hookTypeVariants, hookType)
end
return hookTypeVariants
end
function TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
local towingAttachment = towingVehicle:getScript():getAttachmentById(attachmentA)
local towedAttachment = towedVehicle:getScript():getAttachmentById(attachmentB)
if towingAttachment == nil or towedAttachment == nil then return end
towingAttachment:setUpdateConstraint(false)
towingAttachment:setZOffset(0)
towedAttachment:setUpdateConstraint(false)
towedAttachment:setZOffset(0)
-- Dynamic height: compute Y from wheel offset so the towbar never clips the floor.
local towingHeight = computeAttachmentHeight(towingVehicle)
local towedHeight = computeAttachmentHeight(towedVehicle)
-- Store and update the towing vehicle's attachment Y.
local towingModData = towingVehicle:getModData()
if towingModData["towBarOriginalTowingOffsetY"] == nil then
towingModData["towBarOriginalTowingOffsetY"] = towingAttachment:getOffset():y()
towingModData["towBarOriginalTowingAttachmentId"] = attachmentA
end
local towingOffset = towingAttachment:getOffset()
towingAttachment:getOffset():set(towingOffset:x(), towingHeight, towingOffset:z())
local towedModData = towedVehicle:getModData()
local spacingDistance = 1.0
if TowBarMod.Config and tonumber(TowBarMod.Config.rigidTowbarDistance) ~= nil then
spacingDistance = tonumber(TowBarMod.Config.rigidTowbarDistance)
end
local offset = towedAttachment:getOffset()
local storedBaseX = tonumber(towedModData["towBarBaseAttachmentOffsetX"])
local storedBaseY = tonumber(towedModData["towBarBaseAttachmentOffsetY"])
local storedBaseZ = tonumber(towedModData["towBarBaseAttachmentOffsetZ"])
local hasStoredBase = towedModData["towBarBaseAttachmentId"] == attachmentB
and storedBaseX ~= nil and storedBaseY ~= nil and storedBaseZ ~= nil
local baseX = hasStoredBase and storedBaseX or offset:x()
local baseY = hasStoredBase and storedBaseY or offset:y()
local baseZ = hasStoredBase and storedBaseZ or offset:z()
if not hasStoredBase then
towedModData["towBarBaseAttachmentId"] = attachmentB
towedModData["towBarBaseAttachmentOffsetX"] = baseX
towedModData["towBarBaseAttachmentOffsetY"] = baseY
towedModData["towBarBaseAttachmentOffsetZ"] = baseZ
end
local zDirection = baseZ >= 0 and 1 or -1
local zShift = zDirection * spacingDistance
towedAttachment:getOffset():set(baseX, towedHeight, baseZ + zShift)
towedModData["isChangedTowedAttachment"] = true
towedModData["towBarChangedAttachmentId"] = attachmentB
towedModData["towBarChangedOffsetZShift"] = zShift
towedVehicle:transmitModData()
towingVehicle:transmitModData()
end
function TowBarMod.Utils.updateAttachmentsOnDefaultValues(towingVehicle, towedVehicle)
local towingModData = towingVehicle:getModData()
local towingAttachmentId = towingModData["towBarOriginalTowingAttachmentId"]
or towingVehicle:getTowAttachmentSelf()
local towingAttachment = towingVehicle:getScript():getAttachmentById(towingAttachmentId)
if towingAttachment ~= nil then
towingAttachment:setUpdateConstraint(true)
local zOffset = (towingAttachmentId == "trailer") and -1 or 1
towingAttachment:setZOffset(zOffset)
-- Restore the original Y offset that was overridden by dynamic height.
local originalY = tonumber(towingModData["towBarOriginalTowingOffsetY"])
if originalY ~= nil then
local off = towingAttachment:getOffset()
towingAttachment:getOffset():set(off:x(), originalY, off:z())
end
end
towingModData["towBarOriginalTowingOffsetY"] = nil
towingModData["towBarOriginalTowingAttachmentId"] = nil
towingVehicle:transmitModData()
local towedModData = towedVehicle:getModData()
local changedAttachmentId = towedModData["towBarChangedAttachmentId"] or towedVehicle:getTowAttachmentSelf()
local towedAttachment = towedVehicle:getScript():getAttachmentById(changedAttachmentId)
if towedAttachment ~= nil then
towedAttachment:setUpdateConstraint(true)
local zOffset = (changedAttachmentId == "trailer") and -1 or 1
towedAttachment:setZOffset(zOffset)
if towedModData["isChangedTowedAttachment"] then
local storedBaseX = tonumber(towedModData["towBarBaseAttachmentOffsetX"])
local storedBaseY = tonumber(towedModData["towBarBaseAttachmentOffsetY"])
local storedBaseZ = tonumber(towedModData["towBarBaseAttachmentOffsetZ"])
local hasStoredBase = towedModData["towBarBaseAttachmentId"] == changedAttachmentId
and storedBaseX ~= nil and storedBaseY ~= nil and storedBaseZ ~= nil
if hasStoredBase then
towedAttachment:getOffset():set(storedBaseX, storedBaseY, storedBaseZ)
else
local offset = towedAttachment:getOffset()
local storedShift = tonumber(towedModData["towBarChangedOffsetZShift"])
if storedShift ~= nil then
towedAttachment:getOffset():set(offset:x(), offset:y(), offset:z() - storedShift)
else
local zShift = offset:z() > 0 and -1 or 1
towedAttachment:getOffset():set(offset:x(), offset:y(), offset:z() + zShift)
end
end
end
end
towedModData["isChangedTowedAttachment"] = false
towedModData["towBarChangedAttachmentId"] = nil
towedModData["towBarChangedOffsetZShift"] = nil
towedModData["towBarBaseAttachmentId"] = nil
towedModData["towBarBaseAttachmentOffsetX"] = nil
towedModData["towBarBaseAttachmentOffsetY"] = nil
towedModData["towBarBaseAttachmentOffsetZ"] = nil
towedVehicle:transmitModData()
end
-----------------------------------------------------------
--- Fix mods that add vehicles without tow attachments
local function fixTowAttachmentsForOtherVehicleMods()
local scriptManager = getScriptManager()
local vehicleScripts = scriptManager:getAllVehicleScripts()
for i = 0, vehicleScripts:size()-1 do
local script = vehicleScripts:get(i)
local wheelCount = script:getWheelCount()
local attachHeigtOffset = -0.5
if wheelCount > 0 then
attachHeigtOffset = script:getWheel(0):getOffset():y() + 0.1
end
if not string.match(string.lower(script:getName()), "trailer") then
local trailerAttachment = script:getAttachmentById("trailer")
if trailerAttachment == nil then
local attach = ModelAttachment.new("trailer")
attach:getOffset():set(0, attachHeigtOffset, -script:getPhysicsChassisShape():z()/2 - 0.1)
attach:setZOffset(-1)
script:addAttachment(attach)
end
local trailerFrontAttachment = script:getAttachmentById("trailerfront")
if trailerFrontAttachment == nil then
local attach = ModelAttachment.new("trailerfront")
attach:getOffset():set(0, attachHeigtOffset, script:getPhysicsChassisShape():z()/2 + 0.1)
attach:setZOffset(1)
script:addAttachment(attach)
end
end
end
end
Events.OnGameBoot.Add(fixTowAttachmentsForOtherVehicleMods)