local failures = 0 local function expect(condition, message) if not condition then failures = failures + 1 io.stderr:write("FAIL: " .. message .. "\n") end end Events = { OnGameBoot = { Add = function() end } } Vector3f = { new = function() return {} end } local function point(x, y, z) return { x = function() return x end, y = function() return y end, z = function() return z or 0 end } end local function vehicle(id, fullName, attachments) local linkedTowing local linkedTowedBy local script = { getFullName = function() return fullName end, getName = function() return string.gsub(fullName, "^Base%.", "") end } return { getId = function() return id end, getScript = function() return script end, attachmentExist = function(_, name) return attachments[name] ~= nil end, getAttachmentWorldPos = function(_, name) return attachments[name] end, getVehicleTowing = function() return linkedTowing end, getVehicleTowedBy = function() return linkedTowedBy end, setLinks = function(_, towing, towedBy) linkedTowing, linkedTowedBy = towing, towedBy end } end dofile("42.20/media/lua/shared/TowBar/WreckerUtils.lua") local supported = { "Base.76chevyC30CCwrecker", "Base.76chevyC30SCwrecker", "Base.76chevyK30CCwrecker", "Base.76chevyK30SCwrecker", "Base.93chevySilveradoK3500wrecker", "Base.93chevySilveradoK3500mechanic", "Base.78amgeneralM62" } for index, fullName in ipairs(supported) do expect(TowBarMod.Wrecker.isSupportedWrecker(vehicle(index, fullName, { hook = point(0, 0) })), fullName .. " should be supported") expect(not TowBarMod.Wrecker.isSupportedWrecker(vehicle(index + 100, fullName, {})), fullName .. " without a hook must be rejected") end expect(not TowBarMod.Wrecker.isSupportedWrecker(vehicle(10, supported[1], {})), "exact variant without hook must be rejected") expect(not TowBarMod.Wrecker.isSupportedWrecker(vehicle(11, "Base.76chevyK30CC", { hook = point(0, 0) })), "regular pickup must be rejected") expect(not TowBarMod.Wrecker.isSupportedWrecker(vehicle(12, supported[1] .. "Burnt", { hook = point(0, 0) })), "suffixed lookalike must be rejected") expect(not TowBarMod.Wrecker.isSupportedWrecker(vehicle(13, "Base.78amgeneralM35A2", { hook = point(0, 0) })), "ordinary M35A2 must be rejected") expect(not TowBarMod.Wrecker.isSupportedWrecker(vehicle(14, "Base.Chevalier_Rhino_TowTruck", { hook = point(0, 0) })), "competing Rhino tow truck must be rejected") expect(TowBarMod.Wrecker.nextHeightLevel(0, 1) == 1, "height should raise once from bottom") expect(TowBarMod.Wrecker.nextHeightLevel(1, 1) == 2, "height should raise twice from bottom") expect(TowBarMod.Wrecker.nextHeightLevel(2, 1) == 2, "height must clamp at the second upward step") expect(TowBarMod.Wrecker.nextHeightLevel(0, -1) == 0, "height must not move below the initial hook position") expect(TowBarMod.Wrecker.nextHeightLevel(2, -1) == 1, "height should lower one step") expect(TowBarMod.Wrecker.nextHeightLevel(0, 0) == nil, "invalid height direction must be rejected") expect(TowBarMod.Wrecker.getHeightAttachmentId(0) == "towbarWreckerHookLow", "level 0 must use the bottom hook") expect(TowBarMod.Wrecker.getHeightAttachmentId(1) == "towbarWreckerHookMid", "level 1 must use the first upward step") expect(TowBarMod.Wrecker.getHeightAttachmentId(2) == "towbarWreckerHookHigh", "level 2 must use the second upward step") expect(TowBarMod.Wrecker.normalizeHeightLevel(-1) == 0, "old below-bottom state must migrate to bottom") local wrecker = vehicle(20, supported[1], { hook = point(0, 0, 9) }) local nearRear = vehicle(21, "Base.CarA", { trailer = point(0.599, 0, -50), trailerfront = point(4, 0, 50) }) local nearFront = vehicle(22, "Base.CarB", { trailer = point(5, 0), trailerfront = point(0.4, 0.1) }) local result = TowBarMod.Wrecker.resolveNearestTarget(wrecker, { nearRear, nearFront }) expect(result and result.vehicle == nearFront and result.attachment == "trailerfront", "nearest endpoint across all vehicles must win") result = TowBarMod.Wrecker.resolveNearestTarget(wrecker, { nearRear }) expect(result and result.attachment == "trailer", "0.599 endpoint must be accepted and world Z ignored") local tooFar = vehicle(23, "Base.CarC", { trailer = point(0.601, 0) }) expect(TowBarMod.Wrecker.resolveNearestTarget(wrecker, { tooFar }) == nil, "0.601 endpoint must be rejected") local trailer = vehicle(24, "Base.UtilityTrailer", { trailerfront = point(0.1, 0) }) expect(TowBarMod.Wrecker.resolveNearestTarget(wrecker, { trailer }) == nil, "trailers must be excluded from wrecker vehicle towing") if failures > 0 then os.exit(1) end print("PASS: wrecker helper behavior")