Files
Towbar/42.20/media/lua/shared/TowBar/WreckerUtils.lua
T
2026-08-13 18:59:54 -04:00

164 lines
6.0 KiB
Lua

if not TowBarMod then TowBarMod = {} end
TowBarMod.Wrecker = TowBarMod.Wrecker or {}
local Wrecker = TowBarMod.Wrecker
Wrecker.MaxAttachDistance = 0.6
Wrecker.HeightStep = 0.30
Wrecker.MinHeightLevel = 0
Wrecker.MaxHeightLevel = 2
Wrecker.ScanRadius = 8
local SupportedWreckers = {
-- KI5 '76 Chevrolet K Series (Workshop 3161951724)
["Base.76chevyC30CCwrecker"] = true,
["Base.76chevyC30SCwrecker"] = true,
["Base.76chevyK30CCwrecker"] = true,
["Base.76chevyK30SCwrecker"] = true,
-- KI5 '93 Chevrolet Suburban / Silverado (Workshop 3152529790).
-- The mechanic variant inherits the K3500 wrecker template.
["Base.93chevySilveradoK3500wrecker"] = true,
["Base.93chevySilveradoK3500mechanic"] = true,
-- KI5 '78 AM General M35 Series Trucks (Workshop 2799152995).
["Base.78amgeneralM62"] = true
}
local HeightAttachmentIds = {
[0] = "towbarWreckerHookLow",
[1] = "towbarWreckerHookMid",
[2] = "towbarWreckerHookHigh"
}
local function getScriptFullName(script)
if not script then return nil end
local ok, fullName = pcall(function() return script:getFullName() end)
if ok and fullName then return tostring(fullName) end
local name = script:getName()
return name and ("Base." .. tostring(name)) or nil
end
function Wrecker.isSupportedWrecker(vehicle)
if not vehicle then return false end
local script = vehicle:getScript()
local fullName = getScriptFullName(script)
return SupportedWreckers[fullName] == true
and vehicle:attachmentExist("hook")
end
function Wrecker.getHeightAttachmentId(level)
return HeightAttachmentIds[Wrecker.normalizeHeightLevel(level)]
end
function Wrecker.normalizeHeightLevel(level)
local numeric = tonumber(level) or Wrecker.MinHeightLevel
numeric = math.floor(numeric + 0.5)
return math.max(Wrecker.MinHeightLevel, math.min(Wrecker.MaxHeightLevel, numeric))
end
function Wrecker.nextHeightLevel(currentLevel, direction)
local current = Wrecker.normalizeHeightLevel(currentLevel)
local delta = tonumber(direction)
if delta ~= -1 and delta ~= 1 then return nil end
return math.max(Wrecker.MinHeightLevel, math.min(Wrecker.MaxHeightLevel, current + delta))
end
local function distanceSquared(pointA, pointB)
if not pointA or not pointB then return nil end
local dx = pointA:x() - pointB:x()
local dy = pointA:y() - pointB:y()
return dx * dx + dy * dy
end
local function forEachVehicle(collection, callback)
if not collection then return end
if type(collection) == "table" then
for _, vehicle in ipairs(collection) do callback(vehicle) end
return
end
local ok, iterator = pcall(function() return collection:iterator() end)
if ok and iterator then
while iterator:hasNext() do callback(iterator:next()) end
return
end
local sizeOk, size = pcall(function() return collection:size() end)
if not sizeOk then return end
for index = 0, size - 1 do callback(collection:get(index)) end
end
local function isTargetEligible(wrecker, vehicle)
if not vehicle or vehicle == wrecker then return false end
if vehicle:getVehicleTowing() or vehicle:getVehicleTowedBy() then return false end
if wrecker:getVehicleTowing() or wrecker:getVehicleTowedBy() then return false end
local script = vehicle:getScript()
if not script then return false end
local name = string.lower(tostring(script:getName() or ""))
return not string.find(name, "trailer", 1, true)
end
function Wrecker.resolveNearestTarget(wrecker, candidates)
if not Wrecker.isSupportedWrecker(wrecker) then return nil end
local hookPoint = wrecker:getAttachmentWorldPos("hook", Vector3f.new())
if not hookPoint then return nil end
local best
local maxDistanceSquared = Wrecker.MaxAttachDistance * Wrecker.MaxAttachDistance
forEachVehicle(candidates, function(vehicle)
if not isTargetEligible(wrecker, vehicle) then return end
for _, attachmentId in ipairs({ "trailerfront", "trailer" }) do
if vehicle:attachmentExist(attachmentId) then
local targetPoint = vehicle:getAttachmentWorldPos(attachmentId, Vector3f.new())
local score = distanceSquared(hookPoint, targetPoint)
if score and score <= maxDistanceSquared then
local vehicleId = vehicle:getId()
if not best or score < best.distanceSquared
or (score == best.distanceSquared and vehicleId < best.vehicle:getId()) then
best = {
vehicle = vehicle,
attachment = attachmentId,
distanceSquared = score
}
end
end
end
end
end)
return best
end
function Wrecker.getWorldVehicles()
local cell = getCell()
return cell and cell:getVehicles() or nil
end
local function registerHookAttachments()
local manager = getScriptManager()
if not manager then return end
for fullName in pairs(SupportedWreckers) do
local script = manager:getVehicle(fullName)
local baseAttachment = script and script:getAttachmentById("hook") or nil
local baseOffset = baseAttachment and baseAttachment:getOffset() or nil
if baseOffset then
for level = Wrecker.MinHeightLevel, Wrecker.MaxHeightLevel do
local attachmentId = HeightAttachmentIds[level]
if script:getAttachmentById(attachmentId) == nil then
local attachment = ModelAttachment.new(attachmentId)
attachment:getOffset():set(
baseOffset:x(),
baseOffset:y() + level * Wrecker.HeightStep,
baseOffset:z()
)
attachment:setUpdateConstraint(false)
script:addAttachment(attachment)
end
end
end
end
end
Wrecker.registerHookAttachments = registerHookAttachments
Events.OnGameBoot.Add(registerHookAttachments)