diff --git a/42.13/media/lua/client/TowBar/TowingUI.lua b/42.13/media/lua/client/TowBar/TowingUI.lua
index 1e0c8bb..351beba 100644
--- a/42.13/media/lua/client/TowBar/TowingUI.lua
+++ b/42.13/media/lua/client/TowBar/TowingUI.lua
@@ -160,7 +160,8 @@ function TowBarMod.UI.showDevSingleTowbarMenu(playerObj, vehicle)
end
function TowBarMod.UI.addDevOptionsToMenu(playerObj, vehicle)
- if not TowBarMod.Config.devMode then return end
+ local devModeEnabled = (TowBarMod.Config and TowBarMod.Config.devMode) or getDebug()
+ if not devModeEnabled then return end
if not vehicle then return end
local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
@@ -225,4 +226,3 @@ function ISVehicleMenu.showRadialMenu(playerObj)
TowBarMod.UI.addDevOptionsToMenu(playerObj, vehicle)
end
-
diff --git a/42.13/mod.info b/42.13/mod.info
index 34d8a9b..b001592 100644
--- a/42.13/mod.info
+++ b/42.13/mod.info
@@ -6,5 +6,5 @@ author=Riggs0
category=vehicle
icon=../common/media/textures/tow_bar_icon.png
url=https://hudsonriggs.systems
-modversion=1.0.0
-versionMin=42.13.0
\ No newline at end of file
+modversion=1.0.1
+versionMin=42.13.0
diff --git a/42.17/media/lua/client/TowBar/Config.lua b/42.17/media/lua/client/TowBar/Config.lua
new file mode 100644
index 0000000..f956d1f
--- /dev/null
+++ b/42.17/media/lua/client/TowBar/Config.lua
@@ -0,0 +1,9 @@
+if not TowBarMod then TowBarMod = {} end
+if not TowBarMod.Config then TowBarMod.Config = {} end
+
+TowBarMod.Config.lowLevelAnimation = "RemoveGrass"
+TowBarMod.Config.rigidTowbarDistance = 1.0
+TowBarMod.Config.devMode = false
+TowBarMod.Config.vanillaTowbarModelScaleMin = 1.5
+TowBarMod.Config.vanillaTowbarModelScaleMax = 2.0
+TowBarMod.Config.smallScaleTowbarIndexOffset = 2
diff --git a/42.17/media/lua/client/TowBar/CustomPathFindAction.lua b/42.17/media/lua/client/TowBar/CustomPathFindAction.lua
new file mode 100644
index 0000000..75cfbcd
--- /dev/null
+++ b/42.17/media/lua/client/TowBar/CustomPathFindAction.lua
@@ -0,0 +1,72 @@
+require "TimedActions/ISBaseTimedAction"
+
+TowBarCustomPathFind = ISBaseTimedAction:derive("TowBarCustomPathFind")
+
+function TowBarCustomPathFind:isValid()
+ return true
+end
+
+function TowBarCustomPathFind:update()
+ if instanceof(self.character, "IsoPlayer") and
+ (self.character:pressedMovement(false) or self.character:pressedCancelAction()) then
+ self:forceStop()
+ return
+ end
+
+ local result = self.character:getPathFindBehavior2():update()
+ if result == BehaviorResult.Succeeded then
+ self:forceComplete()
+ end
+
+ local x = self.character:getX()
+ local y = self.character:getY()
+
+ if x == self.lastX and y == self.lastY then
+ self.currentTimeInOnePosition = self.currentTimeInOnePosition + 1
+ else
+ self.currentTimeInOnePosition = 0
+ self.lastX = x
+ self.lastY = y
+ end
+
+ if self.currentTimeInOnePosition > self.maxTimeInOnePosition then
+ self:forceComplete()
+ end
+end
+
+function TowBarCustomPathFind:start()
+ self.character:facePosition(self.goal[2], self.goal[3])
+ self.character:getPathFindBehavior2():pathToLocationF(self.goal[2], self.goal[3], self.goal[4])
+end
+
+function TowBarCustomPathFind:stop()
+ ISBaseTimedAction.stop(self)
+ self.character:getPathFindBehavior2():cancel()
+ self.character:setPath2(nil)
+end
+
+function TowBarCustomPathFind:perform()
+ self.character:getPathFindBehavior2():cancel()
+ self.character:setPath2(nil)
+ ISBaseTimedAction.perform(self)
+end
+
+function TowBarCustomPathFind:pathToLocationF(character, targetX, targetY, targetZ)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.character = character
+ o.stopOnWalk = false
+ o.stopOnRun = false
+ o.maxTime = -1
+
+ o.maxTimeInOnePosition = 15
+ o.currentTimeInOnePosition = 0
+ o.lastX = -1
+ o.lastY = -1
+
+ o.goal = { 'LocationF', targetX, targetY, targetZ }
+ return o
+end
+
+
diff --git a/42.17/media/lua/client/TowBar/HookVehicleAction.lua b/42.17/media/lua/client/TowBar/HookVehicleAction.lua
new file mode 100644
index 0000000..4144d8f
--- /dev/null
+++ b/42.17/media/lua/client/TowBar/HookVehicleAction.lua
@@ -0,0 +1,56 @@
+require('TimedActions/ISBaseTimedAction')
+
+TowBarHookVehicle = ISBaseTimedAction:derive("TowBarHookVehicle")
+
+
+-- The condition which tells the timed action if it is still valid
+function TowBarHookVehicle:isValid()
+ return true;
+end
+
+-- Starts the Timed Action
+function TowBarHookVehicle:start()
+ self:setActionAnim(self.animation)
+ self.sound = getSoundManager():PlayWorldSound("towbar_hookingSound", false, self.character:getSquare(), 0, 5, 1, true)
+end
+
+-- Is called when the time has passed
+function TowBarHookVehicle:perform()
+ self.sound:stop();
+
+ if self.performFunc ~= nil then
+ self.performFunc(self.character, self.arg1, self.arg2, self.arg3, self.arg4)
+ end
+
+ ISBaseTimedAction.perform(self);
+end
+
+
+function TowBarHookVehicle:stop()
+ if self.sound then
+ self.sound:stop()
+ end
+
+ ISBaseTimedAction.stop(self)
+end
+
+function TowBarHookVehicle:new(character, time, animation, performFunc, arg1, arg2, arg3, arg4)
+ local o = {};
+ setmetatable(o, self)
+ self.__index = self
+ o.stopOnWalk = true
+ o.stopOnRun = true
+ o.maxTime = time
+
+ o.character = character;
+ o.animation = animation
+
+ o.performFunc = performFunc
+ o.arg1 = arg1
+ o.arg2 = arg2
+ o.arg3 = arg3
+ o.arg4 = arg4
+
+ return o;
+end
+
diff --git a/42.17/media/lua/client/TowBar/ScheduleAction.lua b/42.17/media/lua/client/TowBar/ScheduleAction.lua
new file mode 100644
index 0000000..9baec82
--- /dev/null
+++ b/42.17/media/lua/client/TowBar/ScheduleAction.lua
@@ -0,0 +1,42 @@
+require("TimedActions/ISBaseTimedAction")
+
+TowBarScheduleAction = ISBaseTimedAction:derive("TowBarScheduleAction")
+
+function TowBarScheduleAction:isValid()
+ return true
+end
+
+function TowBarScheduleAction:start()
+end
+
+function TowBarScheduleAction:perform()
+ if self.performFunc ~= nil then
+ self.performFunc(self.character, self.arg1, self.arg2, self.arg3, self.arg4)
+ end
+
+ ISBaseTimedAction.perform(self)
+end
+
+function TowBarScheduleAction:stop()
+ ISBaseTimedAction.stop(self)
+end
+
+function TowBarScheduleAction:new(character, time, performFunc, arg1, arg2, arg3, arg4)
+ local o = ISBaseTimedAction.new(self, character)
+
+ o.useProgressBar = false
+ o.stopOnWalk = false
+ o.stopOnRun = false
+
+ o.maxTime = time
+ o.character = character
+
+ o.performFunc = performFunc
+ o.arg1 = arg1
+ o.arg2 = arg2
+ o.arg3 = arg3
+ o.arg4 = arg4
+
+ return o
+end
+
diff --git a/42.17/media/lua/client/TowBar/TowSyncClient.lua b/42.17/media/lua/client/TowBar/TowSyncClient.lua
new file mode 100644
index 0000000..f3f7771
--- /dev/null
+++ b/42.17/media/lua/client/TowBar/TowSyncClient.lua
@@ -0,0 +1,125 @@
+if isServer() then return end
+
+if not TowBarMod then TowBarMod = {} end
+TowBarMod.Sync = TowBarMod.Sync or {}
+if TowBarMod.Sync._towSyncClientLoaded then return end
+TowBarMod.Sync._towSyncClientLoaded = true
+
+local function resolveVehicle(id)
+ if not id then return nil end
+ return getVehicleById(id)
+end
+
+local function ensureAttachment(vehicle, attachmentId)
+ if not vehicle or not attachmentId then return false end
+
+ local script = vehicle:getScript()
+ if not script then return false end
+ if script:getAttachmentById(attachmentId) ~= nil then return true end
+
+ local wheelCount = script:getWheelCount()
+ local yOffset = -0.5
+ if wheelCount > 0 then
+ local wheel = script:getWheel(0)
+ if wheel and wheel:getOffset() then
+ yOffset = wheel:getOffset():y() + 0.1
+ end
+ end
+
+ local chassis = script:getPhysicsChassisShape()
+ if not chassis then return false end
+
+ local attach = ModelAttachment.new(attachmentId)
+ if attachmentId == "trailer" then
+ attach:getOffset():set(0, yOffset, -chassis:z() / 2 - 0.1)
+ attach:setZOffset(-1)
+ else
+ attach:getOffset():set(0, yOffset, chassis:z() / 2 + 0.1)
+ attach:setZOffset(1)
+ end
+ script:addAttachment(attach)
+ return true
+end
+
+local function isLinked(vehicleA, vehicleB)
+ if not vehicleA or not vehicleB then return false end
+ return vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA
+end
+
+local function reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
+ if TowBarMod.Utils and TowBarMod.Utils.updateAttachmentsForRigidTow then
+ TowBarMod.Utils.updateAttachmentsForRigidTow(vehicleA, vehicleB, attachmentA, attachmentB)
+ end
+
+ local towingMd = vehicleA:getModData()
+ local towedMd = vehicleB:getModData()
+ local currentScript = vehicleB:getScriptName()
+
+ if towingMd then
+ towingMd["isTowingByTowBar"] = true
+ vehicleA:transmitModData()
+ end
+ if towedMd then
+ if towedMd.towBarOriginalScriptName == nil and currentScript ~= "notTowingA_Trailer" then
+ towedMd.towBarOriginalScriptName = currentScript
+ end
+ if towedMd.towBarOriginalMass == nil then
+ towedMd.towBarOriginalMass = vehicleB:getMass()
+ end
+ if towedMd.towBarOriginalBrakingForce == nil then
+ towedMd.towBarOriginalBrakingForce = vehicleB:getBrakingForce()
+ end
+ towedMd["isTowingByTowBar"] = true
+ towedMd["towed"] = true
+ vehicleB:transmitModData()
+ end
+
+ vehicleB:setScriptName("notTowingA_Trailer")
+ if TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
+ pcall(TowBarMod.Hook.setVehiclePostAttach, nil, vehicleB)
+ end
+end
+
+local function applyAttachSync(args)
+ if not args then return end
+
+ local vehicleA = resolveVehicle(args.vehicleA)
+ local vehicleB = resolveVehicle(args.vehicleB)
+ if not vehicleA or not vehicleB then return end
+
+ local attachmentA = args.attachmentA or "trailer"
+ local attachmentB = args.attachmentB or "trailerfront"
+ if not ensureAttachment(vehicleA, attachmentA) or not ensureAttachment(vehicleB, attachmentB) then
+ return
+ end
+
+ if not isLinked(vehicleA, vehicleB) then
+ vehicleA:addPointConstraint(nil, vehicleB, attachmentA, attachmentB)
+ end
+
+ reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
+end
+
+local function safeBreak(vehicle)
+ if not vehicle then return end
+ if vehicle:getVehicleTowing() == nil and vehicle:getVehicleTowedBy() == nil then return end
+ vehicle:breakConstraint(true, true)
+end
+
+local function applyDetachSync(args)
+ if not args then return end
+ safeBreak(resolveVehicle(args.vehicleA))
+ safeBreak(resolveVehicle(args.vehicleB))
+end
+
+local function onServerCommand(module, command, args)
+ if module ~= "towbar" then return end
+
+ if command == "forceAttachSync" then
+ applyAttachSync(args)
+ elseif command == "forceDetachSync" then
+ applyDetachSync(args)
+ end
+end
+
+Events.OnServerCommand.Add(onServerCommand)
diff --git a/42.17/media/lua/client/TowBar/TowingHooking.lua b/42.17/media/lua/client/TowBar/TowingHooking.lua
new file mode 100644
index 0000000..44a008d
--- /dev/null
+++ b/42.17/media/lua/client/TowBar/TowingHooking.lua
@@ -0,0 +1,694 @@
+if not TowBarMod then TowBarMod = {} end
+if not TowBarMod.Hook then TowBarMod.Hook = {} end
+
+local TowBarTowMass = 200
+local AutoReattachCooldownHours = 1 / 7200 -- 0.5 seconds
+TowBarMod.Hook.lastAutoReattachAtByVehicle = TowBarMod.Hook.lastAutoReattachAtByVehicle or {}
+local AutoReattachPlayerCooldownHours = 1 / 14400 -- 0.25 seconds
+TowBarMod.Hook.lastAutoReattachAtByPlayer = TowBarMod.Hook.lastAutoReattachAtByPlayer or {}
+
+local function isTowBarTowPair(towingVehicle, towedVehicle)
+ if not towingVehicle or not towedVehicle then return false end
+
+ local towingModData = towingVehicle:getModData()
+ local towedModData = towedVehicle:getModData()
+ if not towingModData or not towedModData then return false end
+
+ if towingModData["isTowingByTowBar"] and towedModData["isTowingByTowBar"] and towedModData["towed"] then
+ return true
+ end
+
+ -- Rejoin fallback: original towbar state on the towed vehicle is enough to reapply rigid spacing.
+ if towedModData.towBarOriginalScriptName ~= nil then
+ return true
+ end
+
+ return false
+end
+
+local function getTowBarItem(playerObj)
+ if not playerObj then return nil end
+ local inventory = playerObj:getInventory()
+ if not inventory then return nil end
+ return inventory:getItemFromTypeRecurse("TowBar.TowBar")
+end
+
+local function sendTowAttachCommand(playerObj, args)
+ if not playerObj or not args then return end
+
+ -- MP-safe/server-authoritative attach path (Landtrain style).
+ if isClient() and isMultiplayer() then
+ sendClientCommand(playerObj, "towbar", "attachTowBar", args)
+ return
+ end
+
+ -- Keep vanilla attach path for SP/local behavior.
+ sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
+end
+
+local TowbarVariantSize = 24
+local TowbarNormalStart = 0
+local TowbarLargeStart = 24
+local TowbarMaxIndex = TowbarVariantSize - 1
+local VanillaScaleMin = 1.5
+local VanillaScaleMax = 2.0
+
+local function getVehicleModelScale(script)
+ if not script then return nil end
+
+ local ok, result = pcall(function()
+ return script:getModelScale()
+ end)
+ if ok and type(result) == "number" then
+ return result
+ end
+
+ ok, result = pcall(function()
+ local model = script:getModel()
+ if model then
+ return model:getScale()
+ end
+ return nil
+ end)
+ if ok and type(result) == "number" then
+ return result
+ end
+
+ return nil
+end
+
+local function isVanillaScale(script)
+ local modelScale = getVehicleModelScale(script)
+ if modelScale == nil then
+ return true
+ end
+
+ local configuredMin = TowBarMod.Config and tonumber(TowBarMod.Config.vanillaTowbarModelScaleMin)
+ local configuredMax = TowBarMod.Config and tonumber(TowBarMod.Config.vanillaTowbarModelScaleMax)
+ local minScale = configuredMin or VanillaScaleMin
+ local maxScale = configuredMax or VanillaScaleMax
+ return modelScale >= minScale and modelScale <= maxScale
+end
+
+local function getTowbarIndexVanilla(script)
+ local z = script:getPhysicsChassisShape():z() / 2 - 0.1
+ local index = math.floor((z * 2 / 3 - 1) * 10)
+ return math.max(0, math.min(TowbarMaxIndex, index))
+end
+
+local function getTowbarIndexSmallScale(script)
+ if not script then return nil end
+
+ local maxAbsTowZ = nil
+ local trailer = script:getAttachmentById("trailer")
+ if trailer then
+ maxAbsTowZ = math.abs(trailer:getOffset():z())
+ end
+ local trailerFront = script:getAttachmentById("trailerfront")
+ if trailerFront then
+ local frontAbsZ = math.abs(trailerFront:getOffset():z())
+ if not maxAbsTowZ or frontAbsZ > maxAbsTowZ then
+ maxAbsTowZ = frontAbsZ
+ end
+ end
+
+ if maxAbsTowZ ~= nil then
+ -- Match KI5-size vehicles by anchoring to tow attachment depth.
+ -- +0.1 keeps the bar slightly beyond the attachment point.
+ local index = math.floor((maxAbsTowZ + 0.1 - 1.0) * 10)
+ return math.max(0, math.min(TowbarMaxIndex, index))
+ end
+
+ return nil
+end
+
+local function getTowbarModelSlot(script)
+ local isVanilla = isVanillaScale(script)
+ local index = getTowbarIndexVanilla(script)
+ if not isVanilla then
+ local attachmentIndex = getTowbarIndexSmallScale(script)
+ if attachmentIndex ~= nil then
+ index = attachmentIndex
+ else
+ local offset = TowBarMod.Config and tonumber(TowBarMod.Config.smallScaleTowbarIndexOffset) or 2
+ index = math.max(0, math.min(TowbarMaxIndex, index + offset))
+ end
+ end
+ return index, isVanilla
+end
+
+local function setTowBarModelVisible(vehicle, isVisible)
+ if not vehicle then return end
+
+ local normalPart = vehicle:getPartById("towbar")
+ local largePart = vehicle:getPartById("towbarLarge")
+ if normalPart == nil and largePart == nil then return end
+
+ for j = 0, TowbarVariantSize - 1 do
+ if normalPart then normalPart:setModelVisible("towbar" .. j, false) end
+ if largePart then largePart:setModelVisible("towbar" .. j, false) end
+ end
+
+ if not isVisible then
+ vehicle:doDamageOverlay()
+ return
+ end
+
+ local script = vehicle:getScript()
+ if not script then
+ vehicle:doDamageOverlay()
+ return
+ end
+
+ local index, isVanilla = getTowbarModelSlot(script)
+ local part = isVanilla and normalPart or largePart
+ if part == nil then
+ part = normalPart or largePart
+ end
+ if part then
+ part:setModelVisible("towbar" .. index, true)
+ end
+
+ vehicle:doDamageOverlay()
+end
+
+local function resolveTowAttachmentsForPair(towingVehicle, towedVehicle, towedModData)
+ if not towingVehicle or not towedVehicle then
+ return nil, nil
+ end
+
+ local attachmentA = towingVehicle:getTowAttachmentSelf() or "trailer"
+ local attachmentB = towingVehicle:getTowAttachmentOther()
+ or (towedModData and towedModData["towBarChangedAttachmentId"])
+ or "trailerfront"
+
+ if not towingVehicle:canAttachTrailer(towedVehicle, attachmentA, attachmentB) then
+ if towingVehicle:canAttachTrailer(towedVehicle, "trailer", "trailerfront") then
+ attachmentA = "trailer"
+ attachmentB = "trailerfront"
+ elseif towingVehicle:canAttachTrailer(towedVehicle, "trailerfront", "trailer") then
+ attachmentA = "trailerfront"
+ attachmentB = "trailer"
+ end
+ end
+
+ return attachmentA, attachmentB
+end
+
+local function hasTowBarTowState(modData)
+ if not modData then
+ return false
+ end
+
+ if modData["isTowingByTowBar"] and modData["towed"] then
+ return true
+ end
+
+ -- Rejoin fallback: legacy saves may only have the original-script marker.
+ if modData.towBarOriginalScriptName ~= nil then
+ return true
+ end
+
+ return false
+end
+
+local function isActiveTowBarTowedVehicle(vehicle, modData)
+ if not vehicle or not modData then
+ return false
+ end
+
+ if modData["isTowingByTowBar"] and modData["towed"] then
+ return true
+ end
+
+ -- Rejoin fallback: if the tow link exists, original-script marker is enough.
+ if vehicle:getVehicleTowedBy() and modData.towBarOriginalScriptName ~= nil then
+ return true
+ end
+
+ return false
+end
+
+local function reattachTowBarPair(playerObj, towingVehicle, towedVehicle, requireDriver)
+ if not playerObj or not towingVehicle or not towedVehicle then
+ return false
+ end
+ if requireDriver and not towingVehicle:isDriver(playerObj) then
+ return false
+ end
+
+ local towingModData = towingVehicle:getModData()
+ local towedModData = towedVehicle:getModData()
+ if not towingModData or not towedModData then
+ return false
+ end
+ if requireDriver then
+ if not isTowBarTowPair(towingVehicle, towedVehicle) then
+ return false
+ end
+ else
+ if not isActiveTowBarTowedVehicle(towedVehicle, towedModData) then
+ return false
+ end
+ end
+
+ local attachmentA, attachmentB = resolveTowAttachmentsForPair(towingVehicle, towedVehicle, towedModData)
+ if not attachmentA or not attachmentB then
+ return false
+ end
+
+ local towingScript = towingVehicle:getScript()
+ local towedScript = towedVehicle:getScript()
+ if not towingScript or not towedScript then
+ return false
+ end
+ if not towingScript:getAttachmentById(attachmentA) or not towedScript:getAttachmentById(attachmentB) then
+ return false
+ end
+
+ TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
+
+ towedModData.towBarOriginalScriptName = towedModData.towBarOriginalScriptName or towedVehicle:getScriptName()
+ if towedModData.towBarOriginalMass == nil then
+ towedModData.towBarOriginalMass = towedVehicle:getMass()
+ end
+ if towedModData.towBarOriginalBrakingForce == nil then
+ towedModData.towBarOriginalBrakingForce = towedVehicle:getBrakingForce()
+ end
+
+ towingModData["isTowingByTowBar"] = true
+ towedModData["isTowingByTowBar"] = true
+ towedModData["towed"] = true
+ towingVehicle:transmitModData()
+ towedVehicle:transmitModData()
+
+ setTowBarModelVisible(towedVehicle, true)
+ towedVehicle:setScriptName("notTowingA_Trailer")
+
+ local args = {
+ vehicleA = towingVehicle:getId(),
+ vehicleB = towedVehicle:getId(),
+ attachmentA = attachmentA,
+ attachmentB = attachmentB
+ }
+ sendTowAttachCommand(playerObj, args)
+ ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
+ return true
+end
+
+local function reattachTowBarPairAfterCleanDetach(playerObj, towingVehicle, towedVehicle, requireDriver)
+ if not playerObj or not towingVehicle or not towedVehicle then
+ return false
+ end
+ if requireDriver and not towingVehicle:isDriver(playerObj) then
+ return false
+ end
+
+ local detachArgs = {
+ towingVehicle = towingVehicle:getId(),
+ vehicle = towedVehicle:getId()
+ }
+ sendClientCommand(playerObj, "towbar", "detachTowBar", detachArgs)
+
+ -- World load/spawn can restore constraints in a bad state. Reattach one
+ -- short tick later so the detach is fully applied first.
+ ISTimedActionQueue.add(TowBarScheduleAction:new(
+ playerObj,
+ 1,
+ reattachTowBarPair,
+ towingVehicle,
+ towedVehicle,
+ requireDriver
+ ))
+ return true
+end
+
+local function recoverTowBarVehicleAfterLoad(playerObj, vehicle, retriesLeft)
+ if not vehicle then return end
+
+ local modData = vehicle:getModData()
+ if not hasTowBarTowState(modData) then
+ return
+ end
+
+ local retries = tonumber(retriesLeft) or 0
+ local localPlayer = playerObj or getPlayer()
+ local towingVehicle = vehicle:getVehicleTowedBy()
+
+ if towingVehicle then
+ -- Apply rigid spacing as soon as the tow link exists to avoid a visible
+ -- bumper-to-bumper snap while waiting for reattach recovery.
+ TowBarMod.Hook.setVehiclePostAttach(nil, vehicle)
+ end
+
+ if localPlayer and towingVehicle then
+ if reattachTowBarPairAfterCleanDetach(localPlayer, towingVehicle, vehicle, false) then
+ return
+ end
+ end
+
+ if localPlayer and retries > 0 then
+ -- During world load, tow links can become available a few ticks later.
+ ISTimedActionQueue.add(TowBarScheduleAction:new(localPlayer, 10, recoverTowBarVehicleAfterLoad, vehicle, retries - 1))
+ return
+ end
+
+ -- Fallback: keep original post-attach restoration behavior.
+ setTowBarModelVisible(vehicle, true)
+ TowBarMod.Hook.setVehiclePostAttach(nil, vehicle)
+end
+
+function TowBarMod.Hook.setVehiclePostAttach(playerObj, towedVehicle, retriesLeft)
+ if not towedVehicle then return end
+
+ local towedModData = towedVehicle:getModData()
+ if not isActiveTowBarTowedVehicle(towedVehicle, towedModData) then return end
+
+ if towedModData and towedModData.towBarOriginalScriptName then
+ towedVehicle:setScriptName(towedModData.towBarOriginalScriptName)
+ end
+
+ local towingVehicle = towedVehicle:getVehicleTowedBy()
+ if towingVehicle then
+ local attachmentA, attachmentB = resolveTowAttachmentsForPair(towingVehicle, towedVehicle, towedModData)
+ if attachmentA and attachmentB then
+ TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
+ end
+ end
+
+ towedVehicle:setMass(TowBarTowMass)
+ towedVehicle:setBrakingForce(0)
+ towedVehicle:constraintChanged()
+ towedVehicle:updateTotalMass()
+
+ -- Re-show the towbar model after the script name has been restored.
+ -- setScriptName() resets model visibility, so we must set it again here.
+ setTowBarModelVisible(towedVehicle, true)
+end
+
+function TowBarMod.Hook.performAttachTowBar(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB)
+ if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end
+ if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end
+
+ local towBarItem = getTowBarItem(playerObj)
+ 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()
+
+ setTowBarModelVisible(towedVehicle, true)
+
+ -- Match the known-good rigid tow path: fake trailer + vanilla attach command.
+ towedVehicle:setScriptName("notTowingA_Trailer")
+
+ local args = {
+ vehicleA = towingVehicle:getId(),
+ vehicleB = towedVehicle:getId(),
+ attachmentA = attachmentA,
+ attachmentB = attachmentB
+ }
+ sendTowAttachCommand(playerObj, args)
+ ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
+end
+
+function TowBarMod.Hook.performDetachTowBar(playerObj, towingVehicle, towedVehicle)
+ if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end
+
+ TowBarMod.Utils.updateAttachmentsOnDefaultValues(towingVehicle, towedVehicle)
+
+ local args = { towingVehicle = towingVehicle:getId(), vehicle = towedVehicle:getId() }
+ sendClientCommand(playerObj, "towbar", "detachTowBar", 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 })
+
+ local towingModData = towingVehicle:getModData()
+ towingModData["isTowingByTowBar"] = false
+ towedModData["isTowingByTowBar"] = false
+ towedModData["towed"] = false
+ towedModData.towBarOriginalScriptName = nil
+ towedModData.towBarOriginalMass = nil
+ towedModData.towBarOriginalBrakingForce = nil
+ towingVehicle:transmitModData()
+ towedVehicle:transmitModData()
+
+ TowBarMod.Hook.lastAutoReattachAtByVehicle[towingVehicle:getId()] = nil
+
+ setTowBarModelVisible(towedVehicle, false)
+end
+
+function TowBarMod.Hook.reattachTowBarFromDriverSeat(playerObj, towingVehicle)
+ if not playerObj or not towingVehicle then return end
+
+ local towedVehicle = towingVehicle:getVehicleTowing()
+ if not towedVehicle then return end
+
+ reattachTowBarPair(playerObj, towingVehicle, towedVehicle, true)
+end
+
+local function tryAutoReattachFromCharacter(character)
+ if not character or not instanceof(character, "IsoPlayer") or not character:isLocalPlayer() then return end
+
+ local playerObj = character
+ local nowHours = getGameTime() and getGameTime():getWorldAgeHours() or 0
+ local playerNum = playerObj:getPlayerNum()
+ local lastPlayerHours = TowBarMod.Hook.lastAutoReattachAtByPlayer[playerNum]
+ if lastPlayerHours and (nowHours - lastPlayerHours) < AutoReattachPlayerCooldownHours then
+ return
+ end
+
+ local towingVehicle = playerObj:getVehicle()
+ if not towingVehicle then return end
+ if not towingVehicle:isDriver(playerObj) then return end
+ local towedVehicle = towingVehicle:getVehicleTowing()
+ if not towedVehicle then return end
+ if not isTowBarTowPair(towingVehicle, towedVehicle) then return end
+
+ local vehicleId = towingVehicle:getId()
+ local lastHours = TowBarMod.Hook.lastAutoReattachAtByVehicle[vehicleId]
+ if lastHours and (nowHours - lastHours) < AutoReattachCooldownHours then
+ return
+ end
+
+ TowBarMod.Hook.lastAutoReattachAtByPlayer[playerNum] = nowHours
+ TowBarMod.Hook.lastAutoReattachAtByVehicle[vehicleId] = nowHours
+ TowBarMod.Hook.reattachTowBarFromDriverSeat(playerObj, towingVehicle)
+end
+
+function TowBarMod.Hook.OnEnterVehicle(character)
+ tryAutoReattachFromCharacter(character)
+end
+
+function TowBarMod.Hook.OnSwitchVehicleSeat(character)
+ tryAutoReattachFromCharacter(character)
+end
+
+function TowBarMod.Hook.attachByTowBarAction(playerObj, towingVehicle, towedVehicle)
+ if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end
+
+ local item = getTowBarItem(playerObj)
+ 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.deattachTowBarAction(playerObj, vehicle)
+ local towingVehicle = vehicle
+ local towedVehicle = vehicle and vehicle:getVehicleTowing() or nil
+ if vehicle and 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.performDetachTowBar,
+ towingVehicle,
+ towedVehicle
+ ))
+end
+
+function TowBarMod.Hook.OnSpawnVehicle(vehicle)
+ recoverTowBarVehicleAfterLoad(nil, vehicle, 6)
+end
+
+function TowBarMod.Hook.OnGameStart()
+ local cell = getCell()
+ if not cell then return end
+
+ local vehicles = cell:getVehicles()
+ if not vehicles then return end
+
+ local playerObj = getPlayer()
+ for i = 0, vehicles:size() - 1 do
+ recoverTowBarVehicleAfterLoad(playerObj, vehicles:get(i), 6)
+ end
+end
+
+---------------------------------------------------------------------------
+--- Dev / debug helpers
+---------------------------------------------------------------------------
+
+function TowBarMod.Hook.devShowAllTowbarModels(playerObj, vehicle)
+ if not vehicle then return end
+ local normalPart = vehicle:getPartById("towbar")
+ local largePart = vehicle:getPartById("towbarLarge")
+ if normalPart == nil and largePart == nil then
+ print("[TowBar DEV] No 'towbar' or 'towbarLarge' part found on vehicle " .. tostring(vehicle:getScriptName()))
+ return
+ end
+ local script = vehicle:getScript()
+ local chassisZ = script and script:getPhysicsChassisShape():z() or 0
+ local halfZ = chassisZ / 2
+ local modelScale = script and getVehicleModelScale(script) or nil
+ local index, isVanilla = 0, true
+ if script then
+ index, isVanilla = getTowbarModelSlot(script)
+ end
+ local selectedPart = isVanilla and "towbar" or "towbarLarge"
+ print("[TowBar DEV] Vehicle: " .. tostring(vehicle:getScriptName()))
+ print("[TowBar DEV] chassisShape.z = " .. tostring(chassisZ) .. ", half = " .. tostring(halfZ))
+ print("[TowBar DEV] modelScale = " .. tostring(modelScale) .. ", part = " .. selectedPart)
+ print("[TowBar DEV] Formula picks index = " .. tostring(index) .. " (towbar" .. tostring(index) .. " at Z offset " .. tostring(1.0 + index * 0.1) .. ")")
+ print("[TowBar DEV] Showing towbar0..towbar23 on both parts")
+ for j = 0, TowbarVariantSize - 1 do
+ if normalPart then normalPart:setModelVisible("towbar" .. j, true) end
+ if largePart then largePart:setModelVisible("towbar" .. j, true) end
+ end
+ vehicle:doDamageOverlay()
+end
+
+function TowBarMod.Hook.devHideAllTowbarModels(playerObj, vehicle)
+ if not vehicle then return end
+ local normalPart = vehicle:getPartById("towbar")
+ local largePart = vehicle:getPartById("towbarLarge")
+ if normalPart == nil and largePart == nil then
+ print("[TowBar DEV] No 'towbar' or 'towbarLarge' part found on vehicle " .. tostring(vehicle:getScriptName()))
+ return
+ end
+ print("[TowBar DEV] Hiding ALL towbar models on " .. tostring(vehicle:getScriptName()))
+ for j = 0, TowbarVariantSize - 1 do
+ if normalPart then normalPart:setModelVisible("towbar" .. j, false) end
+ if largePart then largePart:setModelVisible("towbar" .. j, false) end
+ end
+ vehicle:doDamageOverlay()
+end
+
+function TowBarMod.Hook.devShowSingleTowbar(playerObj, vehicle, index)
+ if not vehicle then return end
+ local normalPart = vehicle:getPartById("towbar")
+ local largePart = vehicle:getPartById("towbarLarge")
+ if normalPart == nil and largePart == nil then
+ print("[TowBar DEV] No 'towbar' or 'towbarLarge' part found on vehicle " .. tostring(vehicle:getScriptName()))
+ return
+ end
+
+ local localIndex = math.max(0, math.min(TowbarMaxIndex, index % TowbarVariantSize))
+ local useLargePart = index >= TowbarVariantSize
+
+ for j = 0, TowbarVariantSize - 1 do
+ if normalPart then normalPart:setModelVisible("towbar" .. j, false) end
+ if largePart then largePart:setModelVisible("towbar" .. j, false) end
+ end
+
+ local part = useLargePart and largePart or normalPart
+ if part == nil then
+ part = normalPart or largePart
+ end
+ print("[TowBar DEV] Showing only towbar" .. tostring(localIndex) .. " on part " .. tostring(useLargePart and "towbarLarge" or "towbar") .. " (Z offset " .. tostring(1.0 + localIndex * 0.1) .. ") on " .. tostring(vehicle:getScriptName()))
+ if part then
+ part:setModelVisible("towbar" .. localIndex, true)
+ end
+ vehicle:doDamageOverlay()
+end
+
+Events.OnSpawnVehicleEnd.Add(TowBarMod.Hook.OnSpawnVehicle)
+if Events.OnGameStart then
+ Events.OnGameStart.Add(TowBarMod.Hook.OnGameStart)
+end
+Events.OnEnterVehicle.Add(TowBarMod.Hook.OnEnterVehicle)
+Events.OnSwitchVehicleSeat.Add(TowBarMod.Hook.OnSwitchVehicleSeat)
diff --git a/42.17/media/lua/client/TowBar/TowingUI.lua b/42.17/media/lua/client/TowBar/TowingUI.lua
new file mode 100644
index 0000000..351beba
--- /dev/null
+++ b/42.17/media/lua/client/TowBar/TowingUI.lua
@@ -0,0 +1,228 @@
+if not TowBarMod then TowBarMod = {} end
+if not TowBarMod.UI then TowBarMod.UI = {} end
+
+---------------------------------------------------------------------------
+--- UI functions
+---------------------------------------------------------------------------
+
+function TowBarMod.UI.removeDefaultDetachOption(playerObj)
+ local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
+ if menu == nil then return end
+
+ local tmpSlices = menu.slices
+ menu:clear()
+ for _, slice in ipairs(tmpSlices) do
+ local command = slice.command and slice.command[1]
+ local args = slice.command or {}
+ if command ~= ISVehicleMenu.onDetachTrailer then
+ menu:addSlice(
+ slice.text,
+ slice.texture,
+ args[1],
+ args[2],
+ args[3],
+ args[4],
+ args[5],
+ args[6],
+ args[7]
+ )
+ end
+ end
+end
+
+--- Show menu with available vehicles for tow bar hook.
+function TowBarMod.UI.showChooseVehicleMenu(playerObj, vehicle, vehicles, hasTowBar)
+ local playerIndex = playerObj:getPlayerNum()
+ local menu = getPlayerRadialMenu(playerIndex)
+ menu:clear()
+
+ local added = 0
+ for _, veh in ipairs(vehicles) do
+ local hookTypeVariants = TowBarMod.Utils.getHookTypeVariants(vehicle, veh, hasTowBar)
+ if #hookTypeVariants > 0 then
+ local hookType = hookTypeVariants[1]
+ menu:addSlice(
+ hookType.name,
+ getTexture("media/textures/tow_bar_attach.png"),
+ hookType.func,
+ playerObj,
+ hookType.towingVehicle,
+ hookType.towedVehicle,
+ hookType.towingPoint,
+ hookType.towedPoint
+ )
+ added = added + 1
+ end
+ end
+
+ if added == 0 then return end
+
+ menu:setX(getPlayerScreenLeft(playerIndex) + getPlayerScreenWidth(playerIndex) / 2 - menu:getWidth() / 2)
+ menu:setY(getPlayerScreenTop(playerIndex) + getPlayerScreenHeight(playerIndex) / 2 - menu:getHeight() / 2)
+ menu:addToUIManager()
+ if JoypadState.players[playerObj:getPlayerNum()+1] then
+ menu:setHideWhenButtonReleased(Joypad.DPadUp)
+ setJoypadFocus(playerObj:getPlayerNum(), menu)
+ playerObj:setJoypadIgnoreAimUntilCentered(true)
+ end
+end
+
+function TowBarMod.UI.addHookOptionToMenu(playerObj, vehicle)
+ local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
+ if menu == nil then return end
+
+ local hasTowBar = playerObj:getInventory():getItemFromTypeRecurse("TowBar.TowBar") ~= nil
+ if not hasTowBar then return end
+
+ local vehicles = TowBarMod.Utils.getAviableVehicles(vehicle, hasTowBar)
+
+ if #vehicles == 0 then
+ return
+ elseif #vehicles == 1 then
+ local hookTypeVariants = TowBarMod.Utils.getHookTypeVariants(vehicle, vehicles[1], hasTowBar)
+ if #hookTypeVariants > 0 then
+ local hookType = hookTypeVariants[1]
+ menu:addSlice(
+ hookType.name,
+ getTexture("media/textures/tow_bar_attach.png"),
+ hookType.func,
+ playerObj,
+ hookType.towingVehicle,
+ hookType.towedVehicle,
+ hookType.towingPoint,
+ hookType.towedPoint
+ )
+ end
+ else
+ menu:addSlice(
+ getText("UI_Text_Towing_attach") .. "...",
+ getTexture("media/textures/tow_bar_attach.png"),
+ TowBarMod.UI.showChooseVehicleMenu,
+ playerObj,
+ vehicle,
+ vehicles,
+ hasTowBar
+ )
+ end
+end
+
+function TowBarMod.UI.addUnhookOptionToMenu(playerObj, vehicle)
+ local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
+ if menu == nil then return end
+ if not vehicle:getModData()["isTowingByTowBar"] then return end
+ if not vehicle:getVehicleTowing() and not vehicle:getVehicleTowedBy() then return end
+
+ local towedVehicle = vehicle
+ if vehicle:getVehicleTowing() then
+ towedVehicle = vehicle:getVehicleTowing()
+ end
+
+ menu:addSlice(
+ getText("ContextMenu_Vehicle_DetachTrailer", ISVehicleMenu.getVehicleDisplayName(towedVehicle)),
+ getTexture("media/textures/tow_bar_detach.png"),
+ TowBarMod.Hook.deattachTowBarAction,
+ playerObj,
+ towedVehicle
+ )
+end
+
+
+---------------------------------------------------------------------------
+--- Dev menu
+---------------------------------------------------------------------------
+
+function TowBarMod.UI.showDevSingleTowbarMenu(playerObj, vehicle)
+ local playerIndex = playerObj:getPlayerNum()
+ local menu = getPlayerRadialMenu(playerIndex)
+ menu:clear()
+
+ for j = 0, 47 do
+ local zIndex = j % 24
+ local modelType = (j >= 24) and "large" or "normal"
+ menu:addSlice(
+ "towbar" .. j .. " [" .. modelType .. "] (Z=" .. tostring(1.0 + zIndex * 0.1) .. ")",
+ getTexture("media/textures/tow_bar_icon.png"),
+ TowBarMod.Hook.devShowSingleTowbar,
+ playerObj,
+ vehicle,
+ j
+ )
+ end
+
+ menu:setX(getPlayerScreenLeft(playerIndex) + getPlayerScreenWidth(playerIndex) / 2 - menu:getWidth() / 2)
+ menu:setY(getPlayerScreenTop(playerIndex) + getPlayerScreenHeight(playerIndex) / 2 - menu:getHeight() / 2)
+ menu:addToUIManager()
+ if JoypadState.players[playerObj:getPlayerNum()+1] then
+ menu:setHideWhenButtonReleased(Joypad.DPadUp)
+ setJoypadFocus(playerObj:getPlayerNum(), menu)
+ playerObj:setJoypadIgnoreAimUntilCentered(true)
+ end
+end
+
+function TowBarMod.UI.addDevOptionsToMenu(playerObj, vehicle)
+ local devModeEnabled = (TowBarMod.Config and TowBarMod.Config.devMode) or getDebug()
+ if not devModeEnabled then return end
+ if not vehicle then return end
+
+ local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
+ if menu == nil then return end
+
+ menu:addSlice(
+ "[DEV] Show ALL Towbars",
+ getTexture("media/textures/tow_bar_icon.png"),
+ TowBarMod.Hook.devShowAllTowbarModels,
+ playerObj,
+ vehicle
+ )
+
+ menu:addSlice(
+ "[DEV] Hide ALL Towbars",
+ getTexture("media/textures/tow_bar_icon.png"),
+ TowBarMod.Hook.devHideAllTowbarModels,
+ playerObj,
+ vehicle
+ )
+
+ menu:addSlice(
+ "[DEV] Pick Single Towbar...",
+ getTexture("media/textures/tow_bar_icon.png"),
+ TowBarMod.UI.showDevSingleTowbarMenu,
+ playerObj,
+ vehicle
+ )
+end
+
+---------------------------------------------------------------------------
+--- Mod compability
+---------------------------------------------------------------------------
+
+if getActivatedMods():contains("vehicle_additions") then
+ require("Vehicles/ISUI/Oven_Mattress_RadialMenu")
+ require("Vehicles/ISUI/FuelTruckTank_ISVehicleMenu_FillPartMenu")
+end
+
+---------------------------------------------------------------------------
+--- Attach to default menu method
+---------------------------------------------------------------------------
+
+if TowBarMod.UI.defaultShowRadialMenu == nil then
+ TowBarMod.UI.defaultShowRadialMenu = ISVehicleMenu.showRadialMenu
+end
+
+function ISVehicleMenu.showRadialMenu(playerObj)
+ TowBarMod.UI.defaultShowRadialMenu(playerObj)
+
+ if playerObj:getVehicle() then return end
+
+ local vehicle = ISVehicleMenu.getVehicleToInteractWith(playerObj)
+ if vehicle == nil then return end
+
+ if vehicle:getModData()["isTowingByTowBar"] then
+ TowBarMod.UI.removeDefaultDetachOption(playerObj)
+ TowBarMod.UI.addUnhookOptionToMenu(playerObj, vehicle)
+ elseif not vehicle:getVehicleTowing() and not vehicle:getVehicleTowedBy() then
+ TowBarMod.UI.addHookOptionToMenu(playerObj, vehicle)
+ end
+
+ TowBarMod.UI.addDevOptionsToMenu(playerObj, vehicle)
+end
diff --git a/42.17/media/lua/client/TowBar/TowingUtils.lua b/42.17/media/lua/client/TowBar/TowingUtils.lua
new file mode 100644
index 0000000..d757eef
--- /dev/null
+++ b/42.17/media/lua/client/TowBar/TowingUtils.lua
@@ -0,0 +1,252 @@
+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)
+
diff --git a/42.17/media/lua/server/BTTow.lua b/42.17/media/lua/server/BTTow.lua
new file mode 100644
index 0000000..c5e3e06
--- /dev/null
+++ b/42.17/media/lua/server/BTTow.lua
@@ -0,0 +1,117 @@
+BTtow = {}
+BTtow.Create = {}
+BTtow.Init = {}
+
+local TowbarVariantSize = 24
+local TowbarNormalStart = 0
+local TowbarLargeStart = 24
+local TowbarMaxIndex = TowbarVariantSize - 1
+local VanillaScaleMin = 1.5
+local VanillaScaleMax = 2.0
+
+local function getVehicleModelScale(script)
+ if not script then return nil end
+
+ local ok, result = pcall(function()
+ return script:getModelScale()
+ end)
+ if ok and type(result) == "number" then
+ return result
+ end
+
+ ok, result = pcall(function()
+ local model = script:getModel()
+ if model then
+ return model:getScale()
+ end
+ return nil
+ end)
+ if ok and type(result) == "number" then
+ return result
+ end
+
+ return nil
+end
+
+local function isVanillaScale(script)
+ local modelScale = getVehicleModelScale(script)
+ if modelScale == nil then
+ return true
+ end
+
+ local configuredMin = TowBarMod and TowBarMod.Config and tonumber(TowBarMod.Config.vanillaTowbarModelScaleMin)
+ local configuredMax = TowBarMod and TowBarMod.Config and tonumber(TowBarMod.Config.vanillaTowbarModelScaleMax)
+ local minScale = configuredMin or VanillaScaleMin
+ local maxScale = configuredMax or VanillaScaleMax
+ return modelScale >= minScale and modelScale <= maxScale
+end
+
+local function getTowbarIndexVanilla(script)
+ local z = script:getPhysicsChassisShape():z() / 2 - 0.1
+ local index = math.floor((z * 2 / 3 - 1) * 10)
+ return math.max(0, math.min(TowbarMaxIndex, index))
+end
+
+local function getTowbarIndexSmallScale(script)
+ if not script then return nil end
+
+ local maxAbsTowZ = nil
+ local trailer = script:getAttachmentById("trailer")
+ if trailer then
+ maxAbsTowZ = math.abs(trailer:getOffset():z())
+ end
+ local trailerFront = script:getAttachmentById("trailerfront")
+ if trailerFront then
+ local frontAbsZ = math.abs(trailerFront:getOffset():z())
+ if not maxAbsTowZ or frontAbsZ > maxAbsTowZ then
+ maxAbsTowZ = frontAbsZ
+ end
+ end
+
+ if maxAbsTowZ ~= nil then
+ local index = math.floor((maxAbsTowZ + 0.1 - 1.0) * 10)
+ return math.max(0, math.min(TowbarMaxIndex, index))
+ end
+
+ return nil
+end
+
+local function getTowbarModelSlot(script)
+ local isVanilla = isVanillaScale(script)
+ local index = getTowbarIndexVanilla(script)
+ if not isVanilla then
+ local attachmentIndex = getTowbarIndexSmallScale(script)
+ if attachmentIndex ~= nil then
+ index = attachmentIndex
+ else
+ local offset = TowBarMod and TowBarMod.Config and tonumber(TowBarMod.Config.smallScaleTowbarIndexOffset) or 2
+ index = math.max(0, math.min(TowbarMaxIndex, index + offset))
+ end
+ end
+ return index, isVanilla
+end
+
+function BTtow.Create.towbar(vehicle, part)
+ if part == nil then return end
+ for j=0, TowbarVariantSize - 1 do
+ part:setModelVisible("towbar" .. j, false)
+ end
+end
+
+function BTtow.Init.towbar(vehicle, part)
+ if part == nil then return end
+ for j=0, TowbarVariantSize - 1 do
+ part:setModelVisible("towbar" .. j, false)
+ end
+ if vehicle:getModData()["isTowingByTowBar"] and vehicle:getModData()["towed"] then
+ local script = vehicle:getScript()
+ if script then
+ local index, isVanilla = getTowbarModelSlot(script)
+ local partId = part:getId()
+ local shouldShowOnThisPart = (isVanilla and partId == "towbar") or ((not isVanilla) and partId == "towbarLarge")
+ if shouldShowOnThisPart then
+ part:setModelVisible("towbar" .. index, true)
+ end
+ end
+ end
+end
diff --git a/42.17/media/lua/server/Items/TowBarItem_Distributions.lua b/42.17/media/lua/server/Items/TowBarItem_Distributions.lua
new file mode 100644
index 0000000..0023a63
--- /dev/null
+++ b/42.17/media/lua/server/Items/TowBarItem_Distributions.lua
@@ -0,0 +1,71 @@
+require 'Items/ProceduralDistributions'
+require 'Items/SuburbsDistributions'
+require 'Items/Distributions'
+require 'Items/Distribution_BinJunk'
+require 'Items/Distribution_ClosetJunk'
+require 'Items/Distribution_DeskJunk'
+require 'Items/Distribution_ShelfJunk'
+require 'Items/Distribution_CounterJunk'
+require 'Items/Distribution_SideTableJunk'
+require 'Vehicles/VehicleDistributions'
+require 'Vehicles/VehicleDistribution_GloveBoxJunk'
+require 'Vehicles/VehicleDistribution_SeatJunk'
+require 'Vehicles/VehicleDistribution_TrunkJunk'
+
+----------------- TOW BAR -----------------------
+-- Mirror Jack spawn chance into TowBar in container distributions (world + vehicle containers).
+-- Intentionally excludes story-clutter floor placement tables (RandomizedWorldContent/StoryClutter).
+
+local TOWBAR_ITEM_TYPE = "TowBar.TowBar"
+local JACK_ITEM_TYPES = {
+ ["Jack"] = true,
+ ["Base.Jack"] = true,
+}
+
+local function addMissingTowBarsForJack(items)
+ if type(items) ~= "table" then return end
+
+ local jackCountByChance = {}
+ local towBarCountByChance = {}
+
+ for i = 1, #items, 2 do
+ local itemType = items[i]
+ local chance = tonumber(items[i + 1])
+ if type(itemType) == "string" and chance ~= nil then
+ if JACK_ITEM_TYPES[itemType] then
+ jackCountByChance[chance] = (jackCountByChance[chance] or 0) + 1
+ elseif itemType == TOWBAR_ITEM_TYPE then
+ towBarCountByChance[chance] = (towBarCountByChance[chance] or 0) + 1
+ end
+ end
+ end
+
+ for chance, jackCount in pairs(jackCountByChance) do
+ local missing = jackCount - (towBarCountByChance[chance] or 0)
+ for _ = 1, missing do
+ table.insert(items, TOWBAR_ITEM_TYPE)
+ table.insert(items, chance)
+ end
+ end
+end
+
+local function walkContainerDistributions(root, seen)
+ if type(root) ~= "table" or seen[root] then return end
+ seen[root] = true
+
+ for key, value in pairs(root) do
+ if key == "items" and type(value) == "table" then
+ addMissingTowBarsForJack(value)
+ elseif type(value) == "table" then
+ walkContainerDistributions(value, seen)
+ end
+ end
+end
+
+local seen = {}
+walkContainerDistributions(ProceduralDistributions, seen)
+walkContainerDistributions(SuburbsDistributions, seen)
+walkContainerDistributions(Distributions, seen)
+walkContainerDistributions(VehicleDistributions, seen)
+walkContainerDistributions(ClutterTables, seen)
+
diff --git a/42.17/media/lua/server/TowingCommands.lua b/42.17/media/lua/server/TowingCommands.lua
new file mode 100644
index 0000000..8565b02
--- /dev/null
+++ b/42.17/media/lua/server/TowingCommands.lua
@@ -0,0 +1,246 @@
+if isClient() then return end
+
+local TowingCommands = {}
+local Commands = {}
+local TowBarItemType = "TowBar.TowBar"
+local SyncDelayTicks = 2
+local SnapshotIntervalTicks = 120
+local pendingSync = {}
+local snapshotTickCounter = 0
+
+TowingCommands.wantNoise = getDebug() or false
+
+local noise = function(msg)
+ if TowingCommands.wantNoise then
+ print("TowBarCommands: " .. msg)
+ end
+end
+
+local function queueSync(kind, player, args)
+ if not args then return end
+ table.insert(pendingSync, {
+ kind = kind,
+ ticks = SyncDelayTicks,
+ player = player,
+ args = args
+ })
+end
+
+local function resolveAttachmentA(args, vehicleA)
+ if args and args.attachmentA then return args.attachmentA end
+ if vehicleA and vehicleA:getTowAttachmentSelf() then return vehicleA:getTowAttachmentSelf() end
+ return "trailer"
+end
+
+local function resolveAttachmentB(args, vehicleB)
+ if args and args.attachmentB then return args.attachmentB end
+ if vehicleB and vehicleB:getTowAttachmentSelf() then return vehicleB:getTowAttachmentSelf() end
+ return "trailerfront"
+end
+
+local function isLinked(vehicleA, vehicleB)
+ if not vehicleA or not vehicleB then return false end
+ return vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA
+end
+
+local function hasTowBarState(vehicle)
+ if not vehicle then return false end
+ local md = vehicle:getModData()
+ if not md then return false end
+ return md["isTowingByTowBar"] == true
+end
+
+local function broadcastAttach(vehicleA, vehicleB, attachmentA, attachmentB)
+ if not vehicleA or not vehicleB then return end
+ sendServerCommand("towbar", "forceAttachSync", {
+ vehicleA = vehicleA:getId(),
+ vehicleB = vehicleB:getId(),
+ attachmentA = attachmentA,
+ attachmentB = attachmentB
+ })
+end
+
+local function broadcastDetach(vehicleAId, vehicleBId)
+ sendServerCommand("towbar", "forceDetachSync", {
+ vehicleA = vehicleAId,
+ vehicleB = vehicleBId
+ })
+end
+
+local function processAttachSync(item)
+ local args = item.args or {}
+ local vehicleA = args.vehicleA and getVehicleById(args.vehicleA) or nil
+ local vehicleB = args.vehicleB and getVehicleById(args.vehicleB) or nil
+ if not vehicleA or not vehicleB then
+ noise("attach sync skipped missing vehicles A=" .. tostring(args.vehicleA) .. " B=" .. tostring(args.vehicleB))
+ return
+ end
+
+ local attachmentA = resolveAttachmentA(args, vehicleA)
+ local attachmentB = resolveAttachmentB(args, vehicleB)
+ if not isLinked(vehicleA, vehicleB) then
+ vehicleA:addPointConstraint(item.player, vehicleB, attachmentA, attachmentB)
+ end
+ if isLinked(vehicleA, vehicleB) then
+ broadcastAttach(vehicleA, vehicleB, attachmentA, attachmentB)
+ end
+end
+
+local function processDetachSync(item)
+ local args = item.args or {}
+ local vehicleAId = args.towingVehicle or args.vehicleA or args.vehicle
+ local vehicleBId = args.vehicleB
+ broadcastDetach(vehicleAId, vehicleBId)
+end
+
+local function snapshotActiveTowbarLinksServer()
+ local cell = getCell()
+ if not cell then return end
+ local list = cell:getVehicles()
+ if not list then return end
+
+ local it = list:iterator()
+ while it:hasNext() do
+ local towingVehicle = it:next()
+ local towedVehicle = towingVehicle and towingVehicle:getVehicleTowing() or nil
+ if towingVehicle and towedVehicle and towedVehicle:getVehicleTowedBy() == towingVehicle then
+ if hasTowBarState(towingVehicle) or hasTowBarState(towedVehicle) then
+ local attachmentA = resolveAttachmentA(nil, towingVehicle)
+ local towedMd = towedVehicle:getModData()
+ local attachmentB = (towedMd and towedMd["towBarChangedAttachmentId"]) or resolveAttachmentB(nil, towedVehicle)
+ if attachmentA == attachmentB then
+ attachmentA = "trailer"
+ attachmentB = "trailerfront"
+ end
+ broadcastAttach(towingVehicle, towedVehicle, attachmentA, attachmentB)
+ end
+ end
+ end
+end
+
+local function processPendingSync()
+ snapshotTickCounter = snapshotTickCounter + 1
+ if snapshotTickCounter >= SnapshotIntervalTicks then
+ snapshotTickCounter = 0
+ snapshotActiveTowbarLinksServer()
+ end
+
+ if #pendingSync == 0 then return end
+
+ local remaining = {}
+ for i = 1, #pendingSync do
+ local item = pendingSync[i]
+ item.ticks = item.ticks - 1
+ if item.ticks <= 0 then
+ if item.kind == "attach" then
+ processAttachSync(item)
+ elseif item.kind == "detach" then
+ processDetachSync(item)
+ end
+ else
+ table.insert(remaining, item)
+ end
+ end
+ pendingSync = remaining
+end
+
+function Commands.attachTowBar(player, args)
+ local vehicleA = getVehicleById(args.vehicleA)
+ local vehicleB = getVehicleById(args.vehicleB)
+ if not vehicleA then
+ noise("no such vehicle (A) id=" .. tostring(args.vehicleA))
+ return
+ end
+ if not vehicleB then
+ noise("no such vehicle (B) id=" .. tostring(args.vehicleB))
+ return
+ end
+
+ vehicleA:addPointConstraint(player, vehicleB, args.attachmentA, args.attachmentB)
+end
+
+function Commands.detachTowBar(player, args)
+ local towingVehicle = args.towingVehicle and getVehicleById(args.towingVehicle) or nil
+ local towedVehicle = args.vehicle and getVehicleById(args.vehicle) or nil
+
+ if not towingVehicle and towedVehicle then
+ towingVehicle = towedVehicle:getVehicleTowedBy()
+ end
+ if not towedVehicle and towingVehicle then
+ towedVehicle = towingVehicle:getVehicleTowing()
+ end
+
+ if towedVehicle then
+ towedVehicle:breakConstraint(true, false)
+ end
+ if towingVehicle and towingVehicle ~= towedVehicle then
+ towingVehicle:breakConstraint(true, false)
+ end
+end
+
+function Commands.consumeTowBar(player, args)
+ if not player then return end
+ local inventory = player:getInventory()
+ if not inventory then return end
+
+ local towBarItem = nil
+ local itemId = args and args.itemId
+ if itemId then
+ towBarItem = inventory:getItemWithID(itemId)
+ end
+ if not towBarItem then
+ towBarItem = inventory:getFirstTypeRecurse(TowBarItemType)
+ end
+ if not towBarItem then return end
+
+ local wasPrimary = player:isPrimaryHandItem(towBarItem)
+ local wasSecondary = player:isSecondaryHandItem(towBarItem)
+ player:removeFromHands(towBarItem)
+ inventory:Remove(towBarItem)
+ sendRemoveItemFromContainer(inventory, towBarItem)
+
+ if wasPrimary or wasSecondary then
+ sendEquip(player)
+ end
+end
+
+function Commands.giveTowBar(player, args)
+ if not player then return end
+ local inventory = player:getInventory()
+ if not inventory then return end
+
+ local towBarItem = inventory:AddItem(TowBarItemType)
+ if not towBarItem then return end
+ sendAddItemToContainer(inventory, towBarItem)
+
+ if args and args.equipPrimary then
+ player:setPrimaryHandItem(towBarItem)
+ sendEquip(player)
+ end
+end
+
+-- Compatibility aliases for older command names.
+Commands.attachConstraint = Commands.attachTowBar
+Commands.detachConstraint = Commands.detachTowBar
+
+TowingCommands.OnClientCommand = function(module, command, player, args)
+ -- Only sync explicit towbar commands so vanilla towing stays untouched.
+ if module == "towbar" and command == "attachTowBar" then
+ queueSync("attach", player, args)
+ elseif module == "towbar" and command == "detachTowBar" then
+ queueSync("detach", player, args)
+ end
+
+ if module == "towbar" and Commands[command] then
+ local argStr = ""
+ args = args or {}
+ for k, v in pairs(args) do
+ argStr = argStr .. " " .. tostring(k) .. "=" .. tostring(v)
+ end
+ noise("received " .. module .. " " .. command .. " " .. tostring(player) .. argStr)
+ Commands[command](player, args)
+ end
+end
+
+Events.OnClientCommand.Add(TowingCommands.OnClientCommand)
+Events.OnTick.Add(processPendingSync)
diff --git a/42.17/media/registries.lua b/42.17/media/registries.lua
new file mode 100644
index 0000000..ea87939
--- /dev/null
+++ b/42.17/media/registries.lua
@@ -0,0 +1,3 @@
+-- Build 42 registry file.
+-- This mod currently uses only base registries (for example ItemType = base:normal),
+-- so no custom identifier registrations are required here.
diff --git a/42.17/media/scripts/TowBar_items.txt b/42.17/media/scripts/TowBar_items.txt
new file mode 100644
index 0000000..554d51c
--- /dev/null
+++ b/42.17/media/scripts/TowBar_items.txt
@@ -0,0 +1,15 @@
+module TowBar
+{
+/*******************Towing Car*******************/
+ item TowBar
+ {
+ DisplayCategory = Tool,
+ Weight = 8.0,
+ ItemType = base:normal,
+ Icon = TowBar,
+ Tooltip = Tooltip_TowBar,
+ StaticModel = towbarModel,
+ WorldStaticModel = towbarModel,
+ }
+}
+
diff --git a/42.17/media/scripts/vehicles/burntvehicles.txt b/42.17/media/scripts/vehicles/burntvehicles.txt
new file mode 100644
index 0000000..4128178
--- /dev/null
+++ b/42.17/media/scripts/vehicles/burntvehicles.txt
@@ -0,0 +1,3265 @@
+module Base
+{
+ model Vehicles_PickUpBurnt
+ {
+ mesh = vehicles/Vehicles_PickUpBurnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle PickupBurnt
+ {
+ model
+ {
+ file = Vehicles_PickupBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3462 0.0000,
+ }
+
+ spawnOffsetY = 0.24999994,
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupburnt,
+ }
+
+ extents = 0.8022 0.7033 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3462 0.0000,
+ physicsChassisShape = 0.8022 0.7033 2.1868,
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3352 -1.1648,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3626 1.1374,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_PickUpVanBurnt
+ {
+ mesh = vehicles/Vehicles_PickUpVanBurnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle PickUpVanBurnt
+ {
+ model
+ {
+ file = Vehicles_PickUpVanBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3516 0.0000,
+ }
+
+ spawnOffsetY = 0.24999994,
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupvanburnt,
+ }
+
+ extents = 0.6813 0.6374 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3297 0.0000,
+ physicsChassisShape = 0.6813 0.6374 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3516 -1.1978,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3352 1.1758,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_CarNormal_Burnt
+ {
+ mesh = vehicles/Vehicles_CarNormal_Burnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle CarNormalBurnt
+ {
+ model
+ {
+ file = Vehicles_CarNormal_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3297 0.0000,
+ }
+
+ spawnOffsetY = 0.19999991,
+
+ skin
+ {
+ texture = Vehicles/vehicles_carnormal_burnt,
+ }
+
+ extents = 0.8022 0.6593 2.6044,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3242 0.0000,
+ physicsChassisShape = 0.8022 0.6593 2.6044,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.2637 -1.3242,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3352 1.3297,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ vehicle TaxiBurnt
+ {
+ model
+ {
+ file = Vehicles_CarNormal_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3297 0.0000,
+ }
+
+ spawnOffsetY = 0.19999991,
+
+ skin
+ {
+ texture = Vehicles/vehicles_taxi_burnt,
+ }
+
+ extents = 0.9011 0.6593 2.5055,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3297 0.0000,
+ physicsChassisShape = 0.9011 0.6593 2.5055,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.1923 -1.3736,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3242 1.3462,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_ModernCar02Burnt
+ {
+ mesh = vehicles/Vehicles_ModernCar02_Burnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle ModernCar02Burnt
+ {
+ model
+ {
+ file = Vehicles_ModernCarBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.2912 0.0000,
+ }
+
+ spawnOffsetY = 0.099999994,
+
+ skin
+ {
+ texture = Vehicles/vehicles_moderncar02_burnt,
+ }
+
+ extents = 0.6813 0.6374 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.2912 0.0000,
+ physicsChassisShape = 0.6813 0.6374 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.2802 -1.1538,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3242 1.1978,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_ModernCarBurnt
+ {
+ mesh = vehicles/Vehicles_ModernCarBurnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle ModernCarBurnt
+ {
+ model
+ {
+ file = Vehicles_ModernCarBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.2912 0.0000,
+ }
+
+ spawnOffsetY = 0.099999994,
+
+ skin
+ {
+ texture = Vehicles/vehicles_moderncar_burnt,
+ }
+
+ extents = 1.0000 0.5604 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.2912 0.0000,
+ physicsChassisShape = 1.0000 0.5604 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.2857 -1.1484,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3077 1.1758,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_SportsCar_Burnt
+ {
+ mesh = vehicles/Vehicles_SportsCar_Burnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle SportsCarBurnt
+ {
+ model
+ {
+ file = Vehicles_SportsCar_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.0000 0.0000,
+ }
+
+ spawnOffsetY = -0.20000005,
+
+ skin
+ {
+ texture = Vehicles/vehicles_sportscar_burnt,
+ }
+
+ extents = 0.8022 0.4725 2.1868,
+ mass = 400,
+ centerOfMassOffset = 0.0000 0.0000 0.0000,
+ physicsChassisShape = 0.8022 0.4725 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.1923 -1.0549,
+ rotate = 0.0000 0.0000 0.0000,
+ zoffset = -1,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.2198 1.0604,
+ rotate = 0.0000 0.0000 0.0000,
+ zoffset = 1,
+ }
+ }
+ model Vehicles_SmallCar02Burnt
+ {
+ mesh = vehicles/Vehicles_SmallCar02Burnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle SmallCar02Burnt
+ {
+ model
+ {
+ file = Vehicles_SmallCar02Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3352 0.0000,
+ }
+
+ spawnOffsetY = 0.19999991,
+
+ skin
+ {
+ texture = Vehicles/vehicles_smallcar02_burnt,
+ }
+
+ extents = 0.8022 0.6703 2.1868,
+ mass = 400,
+ centerOfMassOffset = 0.0000 0.3352 0.0000,
+ physicsChassisShape = 0.8022 0.6703 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3352 -1.0714,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.2967 1.1099,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_SmallCar_Burnt
+ {
+ mesh = vehicles/Vehicles_SmallCar_Burnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle SmallCarBurnt
+ {
+ model
+ {
+ file = Vehicles_SmallCar_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3022 0.0000,
+ }
+
+ spawnOffsetY = -0.10000008,
+
+ skin
+ {
+ texture = Vehicles/vehicles_smallcar_burnt,
+ }
+
+ extents = 0.7473 0.6044 1.8571,
+ mass = 400,
+ centerOfMassOffset = 0.0000 0.3022 0.0000,
+ physicsChassisShape = 0.7473 0.6044 1.8571,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.2418 0.6264,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.2088 -0.9231,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_VanSeats_Burnt
+ {
+ mesh = vehicles/Vehicles_VanSeats_Burnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle VanSeatsBurnt
+ {
+ model
+ {
+ file = Vehicles_VanSeats_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3626 0.0000,
+ }
+
+ spawnOffsetY = 0.5499999,
+
+ skin
+ {
+ texture = Vehicles/vehicles_vanseats_burnt,
+ }
+
+ physicsChassisShape = 0.9341 0.7253 2.3297,
+ centerOfMassOffset = 0.0000 0.3626 0.0000,
+ mass = 500,
+ extents = 0.9341 0.7253 2.3297,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3352 -1.2363,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3352 1.2253,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_Van_Burnt
+ {
+ mesh = vehicles/Vehicles_Van_Burnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle VanBurnt
+ {
+ model
+ {
+ file = Vehicles_Van_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3462 0.0000,
+ }
+
+ spawnOffsetY = 0.5499999,
+
+ skin
+ {
+ texture = Vehicles/vehicles_van_burnt,
+ }
+
+ physicsChassisShape = 0.9341 0.7253 2.3297,
+ centerOfMassOffset = 0.0000 0.3626 0.0000,
+ mass = 500,
+ extents = 0.9341 0.7253 2.3297,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3352 -1.2692,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3462 1.2198,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_SUV_Burnt
+ {
+ mesh = vehicles/Vehicles_SUV_Burnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle SUVBurnt
+ {
+ model
+ {
+ file = Vehicles_SUV_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3297 0.0000,
+ }
+
+ spawnOffsetY = 0.24999978,
+
+ skin
+ {
+ texture = Vehicles/vehicles_suv_burnt,
+ }
+
+ extents = 0.9011 0.6593 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3297 0.0000,
+ physicsChassisShape = 0.9011 0.6593 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3297 -1.0989,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3297 1.0989,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_OffRoadBurnt
+ {
+ mesh = vehicles/Vehicles_OffRoadBurnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle OffRoadBurnt
+ {
+ model
+ {
+ file = Vehicles_OffRoadBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3407 0.0000,
+ }
+
+ spawnOffsetY = 0.24999978,
+
+ skin
+ {
+ texture = Vehicles/vehicles_offroad_burnt,
+ }
+
+ extents = 0.8022 0.7143 1.7473,
+ mass = 400,
+ centerOfMassOffset = 0.0000 0.3571 0.0000,
+ physicsChassisShape = 0.8022 0.7143 1.7473,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3846 -0.8846,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3462 0.9286,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_LuxuryCarBurnt
+ {
+ mesh = vehicles/Vehicles_LuxuryCarBurnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle LuxuryCarBurnt
+ {
+ model
+ {
+ file = Vehicles_LuxuryCarBurnt,
+ scale = 1.6200,
+ offset = 0.0000 0.3889 0.0000,
+ }
+
+ spawnOffsetY = 0.19999991,
+
+ skin
+ {
+ texture = Vehicles/vehicles_luxurycar_burnt,
+ }
+
+ extents = 1.0988 0.8025 2.8025,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3889 0.0000,
+ physicsChassisShape = 1.0988 0.8025 2.8025,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3333 -1.4815,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3765 1.5185,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_PickUpVanLightsBurnt
+ {
+ mesh = vehicles/Vehicles_PickUpVanLightsBurnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle PickUpVanLightsBurnt
+ {
+ model
+ {
+ file = Vehicles_PickUpVanLightsBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3407 0.0000,
+ }
+
+ spawnOffsetY = 0.24999994,
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupvanlightsburnt,
+ }
+
+ extents = 0.6813 0.7033 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3407 0.0000,
+ physicsChassisShape = 0.6813 0.7033 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3297 -1.1758,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3242 1.1868,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_Ambulance_Burnt
+ {
+ mesh = vehicles/Vehicles_Ambulance_Burnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle AmbulanceBurnt
+ {
+ model
+ {
+ file = Vehicles_Ambulance_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3516 0.0000,
+ }
+
+ spawnOffsetY = 0.7999999,
+
+ skin
+ {
+ texture = Vehicles/vehicles_ambulance_burnt,
+ }
+
+ extents = 0.9011 0.8791 2.3077,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.4396 0.0000,
+ physicsChassisShape = 0.9011 0.8791 2.3077,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.4011 -1.2198,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3956 1.2033,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_VanRadio_Burnt
+ {
+ mesh = vehicles/Vehicles_VanRadio_Burnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle VanRadioBurnt
+ {
+ model
+ {
+ file = Vehicles_VanRadio_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3846 0.0000,
+ }
+
+ spawnOffsetY = 0.7999999,
+
+ skin
+ {
+ texture = Vehicles/vehicles_vanradio_burnt,
+ }
+
+ extents = 0.9011 0.9341 2.3077,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.4670 0.0000,
+ physicsChassisShape = 0.9011 0.9341 2.3077,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3626 -1.2857,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3242 1.2253,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ vehicle PickupSpecialBurnt
+ {
+ model
+ {
+ file = Vehicles_PickupBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3462 0.0000,
+ }
+
+ spawnOffsetY = 0.1499999,
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupburnt_fire,
+ }
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupburnt_fossoil,
+ }
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupburnt_police,
+ }
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupburnt_ranger,
+ }
+
+ extents = 0.8022 0.6593 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3462 0.0000,
+ physicsChassisShape = 0.8022 0.6593 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.2967 -1.1648,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3187 1.1374,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_NormalCar_BurntPolice
+ {
+ mesh = vehicles/Vehicles_NormalCar_BurntPolice,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle NormalCarBurntPolice
+ {
+ model
+ {
+ file = Vehicles_NormalCar_BurntPolice,
+ scale = 1.7000,
+ offset = 0.0000 0.3176 0.0000,
+ }
+
+ spawnOffsetY = 0.64999986,
+
+ skin
+ {
+ texture = Vehicles/vehicles_normalcar_burntpolice,
+ }
+
+ extents = 0.9059 0.6000 2.6000,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3176 0.0000,
+ physicsChassisShape = 0.9059 0.6000 2.6000,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3353 -1.3059,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3294 1.3529,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+}
diff --git a/42.17/media/scripts/vehicles/template_battery.txt b/42.17/media/scripts/vehicles/template_battery.txt
new file mode 100644
index 0000000..d5a679a
--- /dev/null
+++ b/42.17/media/scripts/vehicles/template_battery.txt
@@ -0,0 +1,315 @@
+module Base
+{
+ template vehicle Battery
+ {
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+ part towbarLarge
+ {
+ model towbar0
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+ part Battery
+ {
+ area = Engine,
+ itemType = Base.CarBattery,
+ mechanicRequireKey = true,
+ category = engine,
+ table install
+ {
+ items
+ {
+ 1
+ {
+ type = Base.Screwdriver,
+ count = 1,
+ keep = true,
+ equip = primary,
+ }
+ }
+ time = 100,
+ professions = ,
+ skills = ,
+ traits = ,
+ recipes = ,
+ test = Vehicles.InstallTest.Default,
+ door = EngineDoor,
+ }
+ table uninstall
+ {
+ items
+ {
+ 1
+ {
+ type = Base.Screwdriver,
+ count = 1,
+ keep = true,
+ equip = primary,
+ }
+ }
+ time = 100,
+ test = Vehicles.UninstallTest.Battery,
+ }
+ lua
+ {
+ create = Vehicles.Create.Battery,
+ update = Vehicles.Update.Battery,
+ }
+ }
+ }
+}
diff --git a/42.17/media/scripts/vehicles/template_towbar.txt b/42.17/media/scripts/vehicles/template_towbar.txt
new file mode 100644
index 0000000..68f1b24
--- /dev/null
+++ b/42.17/media/scripts/vehicles/template_towbar.txt
@@ -0,0 +1,284 @@
+module Base
+{
+ model towbarModel
+ {
+ mesh = vehicles/Towbar,
+ texture = Vehicles/Towbar_Texture,
+ scale = 0.01,
+ }
+
+ model towbarModelLarge
+ {
+ mesh = vehicles/Towbar,
+ texture = Vehicles/Towbar_Texture,
+ scale = 0.02022,
+ }
+
+ template vehicle Towbar
+ {
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ part towbarLarge
+ {
+ model towbar0
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ }
+}
diff --git a/42.17/mod.info b/42.17/mod.info
new file mode 100644
index 0000000..aa71ae9
--- /dev/null
+++ b/42.17/mod.info
@@ -0,0 +1,10 @@
+name=Towbars
+id=hrsys_towbars_testing
+poster=../common/media/textures/preview.png
+description=Towbar Towing Towed Towing Towbars. The thrid
+author=Riggs0
+category=vehicle
+icon=../common/media/textures/tow_bar_icon.png
+url=https://hudsonriggs.systems
+modversion=1.0.1
+versionMin=42.17.0
diff --git a/42.18/media/lua/client/TowBar/Config.lua b/42.18/media/lua/client/TowBar/Config.lua
new file mode 100644
index 0000000..f956d1f
--- /dev/null
+++ b/42.18/media/lua/client/TowBar/Config.lua
@@ -0,0 +1,9 @@
+if not TowBarMod then TowBarMod = {} end
+if not TowBarMod.Config then TowBarMod.Config = {} end
+
+TowBarMod.Config.lowLevelAnimation = "RemoveGrass"
+TowBarMod.Config.rigidTowbarDistance = 1.0
+TowBarMod.Config.devMode = false
+TowBarMod.Config.vanillaTowbarModelScaleMin = 1.5
+TowBarMod.Config.vanillaTowbarModelScaleMax = 2.0
+TowBarMod.Config.smallScaleTowbarIndexOffset = 2
diff --git a/42.18/media/lua/client/TowBar/CustomPathFindAction.lua b/42.18/media/lua/client/TowBar/CustomPathFindAction.lua
new file mode 100644
index 0000000..75cfbcd
--- /dev/null
+++ b/42.18/media/lua/client/TowBar/CustomPathFindAction.lua
@@ -0,0 +1,72 @@
+require "TimedActions/ISBaseTimedAction"
+
+TowBarCustomPathFind = ISBaseTimedAction:derive("TowBarCustomPathFind")
+
+function TowBarCustomPathFind:isValid()
+ return true
+end
+
+function TowBarCustomPathFind:update()
+ if instanceof(self.character, "IsoPlayer") and
+ (self.character:pressedMovement(false) or self.character:pressedCancelAction()) then
+ self:forceStop()
+ return
+ end
+
+ local result = self.character:getPathFindBehavior2():update()
+ if result == BehaviorResult.Succeeded then
+ self:forceComplete()
+ end
+
+ local x = self.character:getX()
+ local y = self.character:getY()
+
+ if x == self.lastX and y == self.lastY then
+ self.currentTimeInOnePosition = self.currentTimeInOnePosition + 1
+ else
+ self.currentTimeInOnePosition = 0
+ self.lastX = x
+ self.lastY = y
+ end
+
+ if self.currentTimeInOnePosition > self.maxTimeInOnePosition then
+ self:forceComplete()
+ end
+end
+
+function TowBarCustomPathFind:start()
+ self.character:facePosition(self.goal[2], self.goal[3])
+ self.character:getPathFindBehavior2():pathToLocationF(self.goal[2], self.goal[3], self.goal[4])
+end
+
+function TowBarCustomPathFind:stop()
+ ISBaseTimedAction.stop(self)
+ self.character:getPathFindBehavior2():cancel()
+ self.character:setPath2(nil)
+end
+
+function TowBarCustomPathFind:perform()
+ self.character:getPathFindBehavior2():cancel()
+ self.character:setPath2(nil)
+ ISBaseTimedAction.perform(self)
+end
+
+function TowBarCustomPathFind:pathToLocationF(character, targetX, targetY, targetZ)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.character = character
+ o.stopOnWalk = false
+ o.stopOnRun = false
+ o.maxTime = -1
+
+ o.maxTimeInOnePosition = 15
+ o.currentTimeInOnePosition = 0
+ o.lastX = -1
+ o.lastY = -1
+
+ o.goal = { 'LocationF', targetX, targetY, targetZ }
+ return o
+end
+
+
diff --git a/42.18/media/lua/client/TowBar/HookVehicleAction.lua b/42.18/media/lua/client/TowBar/HookVehicleAction.lua
new file mode 100644
index 0000000..4144d8f
--- /dev/null
+++ b/42.18/media/lua/client/TowBar/HookVehicleAction.lua
@@ -0,0 +1,56 @@
+require('TimedActions/ISBaseTimedAction')
+
+TowBarHookVehicle = ISBaseTimedAction:derive("TowBarHookVehicle")
+
+
+-- The condition which tells the timed action if it is still valid
+function TowBarHookVehicle:isValid()
+ return true;
+end
+
+-- Starts the Timed Action
+function TowBarHookVehicle:start()
+ self:setActionAnim(self.animation)
+ self.sound = getSoundManager():PlayWorldSound("towbar_hookingSound", false, self.character:getSquare(), 0, 5, 1, true)
+end
+
+-- Is called when the time has passed
+function TowBarHookVehicle:perform()
+ self.sound:stop();
+
+ if self.performFunc ~= nil then
+ self.performFunc(self.character, self.arg1, self.arg2, self.arg3, self.arg4)
+ end
+
+ ISBaseTimedAction.perform(self);
+end
+
+
+function TowBarHookVehicle:stop()
+ if self.sound then
+ self.sound:stop()
+ end
+
+ ISBaseTimedAction.stop(self)
+end
+
+function TowBarHookVehicle:new(character, time, animation, performFunc, arg1, arg2, arg3, arg4)
+ local o = {};
+ setmetatable(o, self)
+ self.__index = self
+ o.stopOnWalk = true
+ o.stopOnRun = true
+ o.maxTime = time
+
+ o.character = character;
+ o.animation = animation
+
+ o.performFunc = performFunc
+ o.arg1 = arg1
+ o.arg2 = arg2
+ o.arg3 = arg3
+ o.arg4 = arg4
+
+ return o;
+end
+
diff --git a/42.18/media/lua/client/TowBar/ScheduleAction.lua b/42.18/media/lua/client/TowBar/ScheduleAction.lua
new file mode 100644
index 0000000..9baec82
--- /dev/null
+++ b/42.18/media/lua/client/TowBar/ScheduleAction.lua
@@ -0,0 +1,42 @@
+require("TimedActions/ISBaseTimedAction")
+
+TowBarScheduleAction = ISBaseTimedAction:derive("TowBarScheduleAction")
+
+function TowBarScheduleAction:isValid()
+ return true
+end
+
+function TowBarScheduleAction:start()
+end
+
+function TowBarScheduleAction:perform()
+ if self.performFunc ~= nil then
+ self.performFunc(self.character, self.arg1, self.arg2, self.arg3, self.arg4)
+ end
+
+ ISBaseTimedAction.perform(self)
+end
+
+function TowBarScheduleAction:stop()
+ ISBaseTimedAction.stop(self)
+end
+
+function TowBarScheduleAction:new(character, time, performFunc, arg1, arg2, arg3, arg4)
+ local o = ISBaseTimedAction.new(self, character)
+
+ o.useProgressBar = false
+ o.stopOnWalk = false
+ o.stopOnRun = false
+
+ o.maxTime = time
+ o.character = character
+
+ o.performFunc = performFunc
+ o.arg1 = arg1
+ o.arg2 = arg2
+ o.arg3 = arg3
+ o.arg4 = arg4
+
+ return o
+end
+
diff --git a/42.18/media/lua/client/TowBar/TowSyncClient.lua b/42.18/media/lua/client/TowBar/TowSyncClient.lua
new file mode 100644
index 0000000..f3f7771
--- /dev/null
+++ b/42.18/media/lua/client/TowBar/TowSyncClient.lua
@@ -0,0 +1,125 @@
+if isServer() then return end
+
+if not TowBarMod then TowBarMod = {} end
+TowBarMod.Sync = TowBarMod.Sync or {}
+if TowBarMod.Sync._towSyncClientLoaded then return end
+TowBarMod.Sync._towSyncClientLoaded = true
+
+local function resolveVehicle(id)
+ if not id then return nil end
+ return getVehicleById(id)
+end
+
+local function ensureAttachment(vehicle, attachmentId)
+ if not vehicle or not attachmentId then return false end
+
+ local script = vehicle:getScript()
+ if not script then return false end
+ if script:getAttachmentById(attachmentId) ~= nil then return true end
+
+ local wheelCount = script:getWheelCount()
+ local yOffset = -0.5
+ if wheelCount > 0 then
+ local wheel = script:getWheel(0)
+ if wheel and wheel:getOffset() then
+ yOffset = wheel:getOffset():y() + 0.1
+ end
+ end
+
+ local chassis = script:getPhysicsChassisShape()
+ if not chassis then return false end
+
+ local attach = ModelAttachment.new(attachmentId)
+ if attachmentId == "trailer" then
+ attach:getOffset():set(0, yOffset, -chassis:z() / 2 - 0.1)
+ attach:setZOffset(-1)
+ else
+ attach:getOffset():set(0, yOffset, chassis:z() / 2 + 0.1)
+ attach:setZOffset(1)
+ end
+ script:addAttachment(attach)
+ return true
+end
+
+local function isLinked(vehicleA, vehicleB)
+ if not vehicleA or not vehicleB then return false end
+ return vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA
+end
+
+local function reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
+ if TowBarMod.Utils and TowBarMod.Utils.updateAttachmentsForRigidTow then
+ TowBarMod.Utils.updateAttachmentsForRigidTow(vehicleA, vehicleB, attachmentA, attachmentB)
+ end
+
+ local towingMd = vehicleA:getModData()
+ local towedMd = vehicleB:getModData()
+ local currentScript = vehicleB:getScriptName()
+
+ if towingMd then
+ towingMd["isTowingByTowBar"] = true
+ vehicleA:transmitModData()
+ end
+ if towedMd then
+ if towedMd.towBarOriginalScriptName == nil and currentScript ~= "notTowingA_Trailer" then
+ towedMd.towBarOriginalScriptName = currentScript
+ end
+ if towedMd.towBarOriginalMass == nil then
+ towedMd.towBarOriginalMass = vehicleB:getMass()
+ end
+ if towedMd.towBarOriginalBrakingForce == nil then
+ towedMd.towBarOriginalBrakingForce = vehicleB:getBrakingForce()
+ end
+ towedMd["isTowingByTowBar"] = true
+ towedMd["towed"] = true
+ vehicleB:transmitModData()
+ end
+
+ vehicleB:setScriptName("notTowingA_Trailer")
+ if TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
+ pcall(TowBarMod.Hook.setVehiclePostAttach, nil, vehicleB)
+ end
+end
+
+local function applyAttachSync(args)
+ if not args then return end
+
+ local vehicleA = resolveVehicle(args.vehicleA)
+ local vehicleB = resolveVehicle(args.vehicleB)
+ if not vehicleA or not vehicleB then return end
+
+ local attachmentA = args.attachmentA or "trailer"
+ local attachmentB = args.attachmentB or "trailerfront"
+ if not ensureAttachment(vehicleA, attachmentA) or not ensureAttachment(vehicleB, attachmentB) then
+ return
+ end
+
+ if not isLinked(vehicleA, vehicleB) then
+ vehicleA:addPointConstraint(nil, vehicleB, attachmentA, attachmentB)
+ end
+
+ reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
+end
+
+local function safeBreak(vehicle)
+ if not vehicle then return end
+ if vehicle:getVehicleTowing() == nil and vehicle:getVehicleTowedBy() == nil then return end
+ vehicle:breakConstraint(true, true)
+end
+
+local function applyDetachSync(args)
+ if not args then return end
+ safeBreak(resolveVehicle(args.vehicleA))
+ safeBreak(resolveVehicle(args.vehicleB))
+end
+
+local function onServerCommand(module, command, args)
+ if module ~= "towbar" then return end
+
+ if command == "forceAttachSync" then
+ applyAttachSync(args)
+ elseif command == "forceDetachSync" then
+ applyDetachSync(args)
+ end
+end
+
+Events.OnServerCommand.Add(onServerCommand)
diff --git a/42.18/media/lua/client/TowBar/TowingHooking.lua b/42.18/media/lua/client/TowBar/TowingHooking.lua
new file mode 100644
index 0000000..715e803
--- /dev/null
+++ b/42.18/media/lua/client/TowBar/TowingHooking.lua
@@ -0,0 +1,718 @@
+if not TowBarMod then TowBarMod = {} end
+if not TowBarMod.Hook then TowBarMod.Hook = {} end
+
+local TowBarTowMass = 200
+local AutoReattachCooldownHours = 1 / 7200 -- 0.5 seconds
+TowBarMod.Hook.lastAutoReattachAtByVehicle = TowBarMod.Hook.lastAutoReattachAtByVehicle or {}
+local AutoReattachPlayerCooldownHours = 1 / 14400 -- 0.25 seconds
+TowBarMod.Hook.lastAutoReattachAtByPlayer = TowBarMod.Hook.lastAutoReattachAtByPlayer or {}
+
+local function isTowBarTowPair(towingVehicle, towedVehicle)
+ if not towingVehicle or not towedVehicle then return false end
+
+ local towingModData = towingVehicle:getModData()
+ local towedModData = towedVehicle:getModData()
+ if not towingModData or not towedModData then return false end
+
+ if towingModData["isTowingByTowBar"] and towedModData["isTowingByTowBar"] and towedModData["towed"] then
+ return true
+ end
+
+ -- Rejoin fallback: original towbar state on the towed vehicle is enough to reapply rigid spacing.
+ if towedModData.towBarOriginalScriptName ~= nil then
+ return true
+ end
+
+ return false
+end
+
+local function getTowBarItem(playerObj)
+ if not playerObj then return nil end
+ local inventory = playerObj:getInventory()
+ if not inventory then return nil end
+ return inventory:getItemFromTypeRecurse("TowBar.TowBar")
+end
+
+local function sendTowAttachCommand(playerObj, args)
+ if not playerObj or not args then return end
+
+ -- MP-safe/server-authoritative attach path (Landtrain style).
+ if isClient() and isMultiplayer() then
+ sendClientCommand(playerObj, "towbar", "attachTowBar", args)
+ return
+ end
+
+ -- Keep vanilla attach path for SP/local behavior.
+ sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
+end
+
+local TowbarVariantSize = 24
+local TowbarNormalStart = 0
+local TowbarLargeStart = 24
+local TowbarMaxIndex = TowbarVariantSize - 1
+local VanillaScaleMin = 1.5
+local VanillaScaleMax = 2.0
+
+local function getVehicleModelScale(script)
+ if not script then return nil end
+
+ local ok, result = pcall(function()
+ return script:getModelScale()
+ end)
+ if ok and type(result) == "number" then
+ return result
+ end
+
+ ok, result = pcall(function()
+ local model = script:getModel()
+ if model then
+ return model:getScale()
+ end
+ return nil
+ end)
+ if ok and type(result) == "number" then
+ return result
+ end
+
+ return nil
+end
+
+local function isVanillaScale(script)
+ local modelScale = getVehicleModelScale(script)
+ if modelScale == nil then
+ return true
+ end
+
+ local configuredMin = TowBarMod.Config and tonumber(TowBarMod.Config.vanillaTowbarModelScaleMin)
+ local configuredMax = TowBarMod.Config and tonumber(TowBarMod.Config.vanillaTowbarModelScaleMax)
+ local minScale = configuredMin or VanillaScaleMin
+ local maxScale = configuredMax or VanillaScaleMax
+ return modelScale >= minScale and modelScale <= maxScale
+end
+
+local function getTowbarIndexVanilla(script)
+ local z = script:getPhysicsChassisShape():z() / 2 - 0.1
+ local index = math.floor((z * 2 / 3 - 1) * 10)
+ return math.max(0, math.min(TowbarMaxIndex, index))
+end
+
+local function getTowbarIndexSmallScale(script)
+ if not script then return nil end
+
+ local maxAbsTowZ = nil
+ local trailer = script:getAttachmentById("trailer")
+ if trailer then
+ maxAbsTowZ = math.abs(trailer:getOffset():z())
+ end
+ local trailerFront = script:getAttachmentById("trailerfront")
+ if trailerFront then
+ local frontAbsZ = math.abs(trailerFront:getOffset():z())
+ if not maxAbsTowZ or frontAbsZ > maxAbsTowZ then
+ maxAbsTowZ = frontAbsZ
+ end
+ end
+
+ if maxAbsTowZ ~= nil then
+ -- Match KI5-size vehicles by anchoring to tow attachment depth.
+ -- +0.1 keeps the bar slightly beyond the attachment point.
+ local index = math.floor((maxAbsTowZ + 0.1 - 1.0) * 10)
+ return math.max(0, math.min(TowbarMaxIndex, index))
+ end
+
+ return nil
+end
+
+local function getTowbarModelSlot(script)
+ local isVanilla = isVanillaScale(script)
+ local index = getTowbarIndexVanilla(script)
+ if not isVanilla then
+ local attachmentIndex = getTowbarIndexSmallScale(script)
+ if attachmentIndex ~= nil then
+ index = attachmentIndex
+ else
+ local offset = TowBarMod.Config and tonumber(TowBarMod.Config.smallScaleTowbarIndexOffset) or 2
+ index = math.max(0, math.min(TowbarMaxIndex, index + offset))
+ end
+ end
+ return index, isVanilla
+end
+
+local function setTowBarModelVisible(vehicle, isVisible)
+ if not vehicle then return end
+
+ local normalPart = vehicle:getPartById("towbar")
+ local largePart = vehicle:getPartById("towbarLarge")
+ if normalPart == nil and largePart == nil then return end
+
+ for j = 0, TowbarVariantSize - 1 do
+ if normalPart then normalPart:setModelVisible("towbar" .. j, false) end
+ if largePart then largePart:setModelVisible("towbar" .. j, false) end
+ end
+
+ if not isVisible then
+ vehicle:doDamageOverlay()
+ return
+ end
+
+ local script = vehicle:getScript()
+ if not script then
+ vehicle:doDamageOverlay()
+ return
+ end
+
+ local index, isVanilla = getTowbarModelSlot(script)
+ local part = isVanilla and normalPart or largePart
+ if part == nil then
+ part = normalPart or largePart
+ end
+ if part then
+ part:setModelVisible("towbar" .. index, true)
+ end
+
+ vehicle:doDamageOverlay()
+end
+
+local function resolveTowAttachmentsForPair(towingVehicle, towedVehicle, towedModData)
+ if not towingVehicle or not towedVehicle then
+ return nil, nil
+ end
+
+ local attachmentA = towingVehicle:getTowAttachmentSelf() or "trailer"
+ local attachmentB = towingVehicle:getTowAttachmentOther()
+ or (towedModData and towedModData["towBarChangedAttachmentId"])
+ or "trailerfront"
+
+ if not towingVehicle:canAttachTrailer(towedVehicle, attachmentA, attachmentB) then
+ if towingVehicle:canAttachTrailer(towedVehicle, "trailer", "trailerfront") then
+ attachmentA = "trailer"
+ attachmentB = "trailerfront"
+ elseif towingVehicle:canAttachTrailer(towedVehicle, "trailerfront", "trailer") then
+ attachmentA = "trailerfront"
+ attachmentB = "trailer"
+ end
+ end
+
+ return attachmentA, attachmentB
+end
+
+local function hasTowBarTowState(modData)
+ if not modData then
+ return false
+ end
+
+ if modData["isTowingByTowBar"] and modData["towed"] then
+ return true
+ end
+
+ -- Rejoin fallback: legacy saves may only have the original-script marker.
+ if modData.towBarOriginalScriptName ~= nil then
+ return true
+ end
+
+ return false
+end
+
+local function isActiveTowBarTowedVehicle(vehicle, modData)
+ if not vehicle or not modData then
+ return false
+ end
+
+ if modData["isTowingByTowBar"] and modData["towed"] then
+ return true
+ end
+
+ -- Rejoin fallback: if the tow link exists, original-script marker is enough.
+ if vehicle:getVehicleTowedBy() and modData.towBarOriginalScriptName ~= nil then
+ return true
+ end
+
+ return false
+end
+
+local function reattachTowBarPair(playerObj, towingVehicle, towedVehicle, requireDriver)
+ if not playerObj or not towingVehicle or not towedVehicle then
+ return false
+ end
+ if requireDriver and not towingVehicle:isDriver(playerObj) then
+ return false
+ end
+
+ local towingModData = towingVehicle:getModData()
+ local towedModData = towedVehicle:getModData()
+ if not towingModData or not towedModData then
+ return false
+ end
+ if requireDriver then
+ if not isTowBarTowPair(towingVehicle, towedVehicle) then
+ return false
+ end
+ else
+ if not isActiveTowBarTowedVehicle(towedVehicle, towedModData) then
+ return false
+ end
+ end
+
+ local attachmentA, attachmentB = resolveTowAttachmentsForPair(towingVehicle, towedVehicle, towedModData)
+ if not attachmentA or not attachmentB then
+ return false
+ end
+
+ local towingScript = towingVehicle:getScript()
+ local towedScript = towedVehicle:getScript()
+ if not towingScript or not towedScript then
+ return false
+ end
+ if not towingScript:getAttachmentById(attachmentA) or not towedScript:getAttachmentById(attachmentB) then
+ return false
+ end
+
+ TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
+
+ towedModData.towBarOriginalScriptName = towedModData.towBarOriginalScriptName or towedVehicle:getScriptName()
+ if towedModData.towBarOriginalMass == nil then
+ towedModData.towBarOriginalMass = towedVehicle:getMass()
+ end
+ if towedModData.towBarOriginalBrakingForce == nil then
+ towedModData.towBarOriginalBrakingForce = towedVehicle:getBrakingForce()
+ end
+
+ towingModData["isTowingByTowBar"] = true
+ towedModData["isTowingByTowBar"] = true
+ towedModData["towed"] = true
+ towingVehicle:transmitModData()
+ towedVehicle:transmitModData()
+
+ setTowBarModelVisible(towedVehicle, true)
+ towedVehicle:setScriptName("notTowingA_Trailer")
+
+ local args = {
+ vehicleA = towingVehicle:getId(),
+ vehicleB = towedVehicle:getId(),
+ attachmentA = attachmentA,
+ attachmentB = attachmentB
+ }
+ sendTowAttachCommand(playerObj, args)
+ ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
+ return true
+end
+
+local function reattachTowBarPairAfterCleanDetach(playerObj, towingVehicle, towedVehicle, requireDriver)
+ if not playerObj or not towingVehicle or not towedVehicle then
+ return false
+ end
+ if requireDriver and not towingVehicle:isDriver(playerObj) then
+ return false
+ end
+
+ local detachArgs = {
+ towingVehicle = towingVehicle:getId(),
+ vehicle = towedVehicle:getId()
+ }
+ sendClientCommand(playerObj, "towbar", "detachTowBar", detachArgs)
+
+ -- World load/spawn can restore constraints in a bad state. Reattach one
+ -- short tick later so the detach is fully applied first.
+ ISTimedActionQueue.add(TowBarScheduleAction:new(
+ playerObj,
+ 1,
+ reattachTowBarPair,
+ towingVehicle,
+ towedVehicle,
+ requireDriver
+ ))
+ return true
+end
+
+local function recoverTowBarVehicleAfterLoad(playerObj, vehicle, retriesLeft)
+ if not vehicle then return end
+
+ local modData = vehicle:getModData()
+ if not hasTowBarTowState(modData) then
+ return
+ end
+
+ local retries = tonumber(retriesLeft) or 0
+ local localPlayer = playerObj or getPlayer()
+ local towingVehicle = vehicle:getVehicleTowedBy()
+
+ if towingVehicle then
+ -- Apply rigid spacing as soon as the tow link exists to avoid a visible
+ -- bumper-to-bumper snap while waiting for reattach recovery.
+ TowBarMod.Hook.setVehiclePostAttach(nil, vehicle)
+ end
+
+ if localPlayer and towingVehicle then
+ if reattachTowBarPairAfterCleanDetach(localPlayer, towingVehicle, vehicle, false) then
+ return
+ end
+ end
+
+ if localPlayer and retries > 0 then
+ -- During world load, tow links can become available a few ticks later.
+ ISTimedActionQueue.add(TowBarScheduleAction:new(localPlayer, 10, recoverTowBarVehicleAfterLoad, vehicle, retries - 1))
+ return
+ end
+
+ -- Fallback: keep original post-attach restoration behavior.
+ setTowBarModelVisible(vehicle, true)
+ TowBarMod.Hook.setVehiclePostAttach(nil, vehicle)
+end
+
+function TowBarMod.Hook.setVehiclePostAttach(playerObj, towedVehicle, retriesLeft)
+ if not towedVehicle then return end
+
+ local towedModData = towedVehicle:getModData()
+ if not isActiveTowBarTowedVehicle(towedVehicle, towedModData) then return end
+
+ if towedModData and towedModData.towBarOriginalScriptName then
+ towedVehicle:setScriptName(towedModData.towBarOriginalScriptName)
+ end
+
+ local towingVehicle = towedVehicle:getVehicleTowedBy()
+ if towingVehicle then
+ local attachmentA, attachmentB = resolveTowAttachmentsForPair(towingVehicle, towedVehicle, towedModData)
+ if attachmentA and attachmentB then
+ TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
+ end
+ end
+
+ towedVehicle:setMass(TowBarTowMass)
+ towedVehicle:setBrakingForce(0)
+ towedVehicle:constraintChanged()
+ towedVehicle:updateTotalMass()
+
+ -- Re-show the towbar model after the script name has been restored.
+ -- setScriptName() resets model visibility, so we must set it again here.
+ setTowBarModelVisible(towedVehicle, true)
+end
+
+function TowBarMod.Hook.performAttachTowBar(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB)
+ if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end
+ if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end
+
+ local towBarItem = getTowBarItem(playerObj)
+ 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()
+
+ setTowBarModelVisible(towedVehicle, true)
+
+ -- Match the known-good rigid tow path: fake trailer + vanilla attach command.
+ towedVehicle:setScriptName("notTowingA_Trailer")
+
+ local args = {
+ vehicleA = towingVehicle:getId(),
+ vehicleB = towedVehicle:getId(),
+ attachmentA = attachmentA,
+ attachmentB = attachmentB
+ }
+ sendTowAttachCommand(playerObj, args)
+ ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
+end
+
+function TowBarMod.Hook.performDetachTowBar(playerObj, towingVehicle, towedVehicle)
+ if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end
+
+ TowBarMod.Utils.updateAttachmentsOnDefaultValues(towingVehicle, towedVehicle)
+
+ local args = { towingVehicle = towingVehicle:getId(), vehicle = towedVehicle:getId() }
+ sendClientCommand(playerObj, "towbar", "detachTowBar", 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 })
+
+ local towingModData = towingVehicle:getModData()
+ towingModData["isTowingByTowBar"] = false
+ towedModData["isTowingByTowBar"] = false
+ towedModData["towed"] = false
+ towedModData.towBarOriginalScriptName = nil
+ towedModData.towBarOriginalMass = nil
+ towedModData.towBarOriginalBrakingForce = nil
+ towingVehicle:transmitModData()
+ towedVehicle:transmitModData()
+
+ TowBarMod.Hook.lastAutoReattachAtByVehicle[towingVehicle:getId()] = nil
+
+ setTowBarModelVisible(towedVehicle, false)
+end
+
+function TowBarMod.Hook.reattachTowBarFromDriverSeat(playerObj, towingVehicle)
+ if not playerObj or not towingVehicle then return end
+
+ local towedVehicle = towingVehicle:getVehicleTowing()
+ if not towedVehicle then return end
+
+ reattachTowBarPair(playerObj, towingVehicle, towedVehicle, true)
+end
+
+local function tryAutoReattachFromCharacter(character)
+ if not character or not instanceof(character, "IsoPlayer") or not character:isLocalPlayer() then return end
+
+ local playerObj = character
+ local nowHours = getGameTime() and getGameTime():getWorldAgeHours() or 0
+ local playerNum = playerObj:getPlayerNum()
+ local lastPlayerHours = TowBarMod.Hook.lastAutoReattachAtByPlayer[playerNum]
+ if lastPlayerHours and (nowHours - lastPlayerHours) < AutoReattachPlayerCooldownHours then
+ return
+ end
+
+ local towingVehicle = playerObj:getVehicle()
+ if not towingVehicle then return end
+ if not towingVehicle:isDriver(playerObj) then return end
+ local towedVehicle = towingVehicle:getVehicleTowing()
+ if not towedVehicle then return end
+ if not isTowBarTowPair(towingVehicle, towedVehicle) then return end
+
+ local vehicleId = towingVehicle:getId()
+ local lastHours = TowBarMod.Hook.lastAutoReattachAtByVehicle[vehicleId]
+ if lastHours and (nowHours - lastHours) < AutoReattachCooldownHours then
+ return
+ end
+
+ TowBarMod.Hook.lastAutoReattachAtByPlayer[playerNum] = nowHours
+ TowBarMod.Hook.lastAutoReattachAtByVehicle[vehicleId] = nowHours
+ TowBarMod.Hook.reattachTowBarFromDriverSeat(playerObj, towingVehicle)
+end
+
+local function forEachCollectionItem(collection, callback)
+ if not collection then 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 size
+ ok, size = pcall(function()
+ return collection:size()
+ end)
+ if not ok or type(size) ~= "number" then return end
+
+ for i = 0, size - 1 do
+ callback(collection:get(i))
+ end
+end
+
+function TowBarMod.Hook.OnEnterVehicle(character)
+ tryAutoReattachFromCharacter(character)
+end
+
+function TowBarMod.Hook.OnSwitchVehicleSeat(character)
+ tryAutoReattachFromCharacter(character)
+end
+
+function TowBarMod.Hook.attachByTowBarAction(playerObj, towingVehicle, towedVehicle)
+ if playerObj == nil or towingVehicle == nil or towedVehicle == nil then return end
+
+ local item = getTowBarItem(playerObj)
+ 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.deattachTowBarAction(playerObj, vehicle)
+ local towingVehicle = vehicle
+ local towedVehicle = vehicle and vehicle:getVehicleTowing() or nil
+ if vehicle and 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.performDetachTowBar,
+ towingVehicle,
+ towedVehicle
+ ))
+end
+
+function TowBarMod.Hook.OnSpawnVehicle(vehicle)
+ recoverTowBarVehicleAfterLoad(nil, vehicle, 6)
+end
+
+function TowBarMod.Hook.OnGameStart()
+ local cell = getCell()
+ if not cell then return end
+
+ local vehicles = cell:getVehicles()
+ if not vehicles then return end
+
+ local playerObj = getPlayer()
+ forEachCollectionItem(vehicles, function(vehicle)
+ recoverTowBarVehicleAfterLoad(playerObj, vehicle, 6)
+ end)
+end
+
+---------------------------------------------------------------------------
+--- Dev / debug helpers
+---------------------------------------------------------------------------
+
+function TowBarMod.Hook.devShowAllTowbarModels(playerObj, vehicle)
+ if not vehicle then return end
+ local normalPart = vehicle:getPartById("towbar")
+ local largePart = vehicle:getPartById("towbarLarge")
+ if normalPart == nil and largePart == nil then
+ print("[TowBar DEV] No 'towbar' or 'towbarLarge' part found on vehicle " .. tostring(vehicle:getScriptName()))
+ return
+ end
+ local script = vehicle:getScript()
+ local chassisZ = script and script:getPhysicsChassisShape():z() or 0
+ local halfZ = chassisZ / 2
+ local modelScale = script and getVehicleModelScale(script) or nil
+ local index, isVanilla = 0, true
+ if script then
+ index, isVanilla = getTowbarModelSlot(script)
+ end
+ local selectedPart = isVanilla and "towbar" or "towbarLarge"
+ print("[TowBar DEV] Vehicle: " .. tostring(vehicle:getScriptName()))
+ print("[TowBar DEV] chassisShape.z = " .. tostring(chassisZ) .. ", half = " .. tostring(halfZ))
+ print("[TowBar DEV] modelScale = " .. tostring(modelScale) .. ", part = " .. selectedPart)
+ print("[TowBar DEV] Formula picks index = " .. tostring(index) .. " (towbar" .. tostring(index) .. " at Z offset " .. tostring(1.0 + index * 0.1) .. ")")
+ print("[TowBar DEV] Showing towbar0..towbar23 on both parts")
+ for j = 0, TowbarVariantSize - 1 do
+ if normalPart then normalPart:setModelVisible("towbar" .. j, true) end
+ if largePart then largePart:setModelVisible("towbar" .. j, true) end
+ end
+ vehicle:doDamageOverlay()
+end
+
+function TowBarMod.Hook.devHideAllTowbarModels(playerObj, vehicle)
+ if not vehicle then return end
+ local normalPart = vehicle:getPartById("towbar")
+ local largePart = vehicle:getPartById("towbarLarge")
+ if normalPart == nil and largePart == nil then
+ print("[TowBar DEV] No 'towbar' or 'towbarLarge' part found on vehicle " .. tostring(vehicle:getScriptName()))
+ return
+ end
+ print("[TowBar DEV] Hiding ALL towbar models on " .. tostring(vehicle:getScriptName()))
+ for j = 0, TowbarVariantSize - 1 do
+ if normalPart then normalPart:setModelVisible("towbar" .. j, false) end
+ if largePart then largePart:setModelVisible("towbar" .. j, false) end
+ end
+ vehicle:doDamageOverlay()
+end
+
+function TowBarMod.Hook.devShowSingleTowbar(playerObj, vehicle, index)
+ if not vehicle then return end
+ local normalPart = vehicle:getPartById("towbar")
+ local largePart = vehicle:getPartById("towbarLarge")
+ if normalPart == nil and largePart == nil then
+ print("[TowBar DEV] No 'towbar' or 'towbarLarge' part found on vehicle " .. tostring(vehicle:getScriptName()))
+ return
+ end
+
+ local localIndex = math.max(0, math.min(TowbarMaxIndex, index % TowbarVariantSize))
+ local useLargePart = index >= TowbarVariantSize
+
+ for j = 0, TowbarVariantSize - 1 do
+ if normalPart then normalPart:setModelVisible("towbar" .. j, false) end
+ if largePart then largePart:setModelVisible("towbar" .. j, false) end
+ end
+
+ local part = useLargePart and largePart or normalPart
+ if part == nil then
+ part = normalPart or largePart
+ end
+ print("[TowBar DEV] Showing only towbar" .. tostring(localIndex) .. " on part " .. tostring(useLargePart and "towbarLarge" or "towbar") .. " (Z offset " .. tostring(1.0 + localIndex * 0.1) .. ") on " .. tostring(vehicle:getScriptName()))
+ if part then
+ part:setModelVisible("towbar" .. localIndex, true)
+ end
+ vehicle:doDamageOverlay()
+end
+
+Events.OnSpawnVehicleEnd.Add(TowBarMod.Hook.OnSpawnVehicle)
+if Events.OnGameStart then
+ Events.OnGameStart.Add(TowBarMod.Hook.OnGameStart)
+end
+Events.OnEnterVehicle.Add(TowBarMod.Hook.OnEnterVehicle)
+Events.OnSwitchVehicleSeat.Add(TowBarMod.Hook.OnSwitchVehicleSeat)
diff --git a/42.18/media/lua/client/TowBar/TowingUI.lua b/42.18/media/lua/client/TowBar/TowingUI.lua
new file mode 100644
index 0000000..e80a87a
--- /dev/null
+++ b/42.18/media/lua/client/TowBar/TowingUI.lua
@@ -0,0 +1,231 @@
+if not TowBarMod then TowBarMod = {} end
+if not TowBarMod.UI then TowBarMod.UI = {} end
+
+---------------------------------------------------------------------------
+--- UI functions
+---------------------------------------------------------------------------
+
+function TowBarMod.UI.removeDefaultDetachOption(playerObj)
+ local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
+ if menu == nil then return end
+
+ local tmpSlices = {}
+ for i, slice in ipairs(menu.slices or {}) do
+ tmpSlices[i] = slice
+ end
+ menu:clear()
+ for _, slice in ipairs(tmpSlices) do
+ local command = slice.command and slice.command[1]
+ local args = slice.command or {}
+ if command ~= ISVehicleMenu.onDetachTrailer then
+ menu:addSlice(
+ slice.text,
+ slice.texture,
+ args[1],
+ args[2],
+ args[3],
+ args[4],
+ args[5],
+ args[6],
+ args[7]
+ )
+ end
+ end
+end
+
+--- Show menu with available vehicles for tow bar hook.
+function TowBarMod.UI.showChooseVehicleMenu(playerObj, vehicle, vehicles, hasTowBar)
+ local playerIndex = playerObj:getPlayerNum()
+ local menu = getPlayerRadialMenu(playerIndex)
+ menu:clear()
+
+ local added = 0
+ for _, veh in ipairs(vehicles) do
+ local hookTypeVariants = TowBarMod.Utils.getHookTypeVariants(vehicle, veh, hasTowBar)
+ if #hookTypeVariants > 0 then
+ local hookType = hookTypeVariants[1]
+ menu:addSlice(
+ hookType.name,
+ getTexture("media/textures/tow_bar_attach.png"),
+ hookType.func,
+ playerObj,
+ hookType.towingVehicle,
+ hookType.towedVehicle,
+ hookType.towingPoint,
+ hookType.towedPoint
+ )
+ added = added + 1
+ end
+ end
+
+ if added == 0 then return end
+
+ menu:setX(getPlayerScreenLeft(playerIndex) + getPlayerScreenWidth(playerIndex) / 2 - menu:getWidth() / 2)
+ menu:setY(getPlayerScreenTop(playerIndex) + getPlayerScreenHeight(playerIndex) / 2 - menu:getHeight() / 2)
+ menu:addToUIManager()
+ if JoypadState.players[playerObj:getPlayerNum()+1] then
+ menu:setHideWhenButtonReleased(Joypad.DPadUp)
+ setJoypadFocus(playerObj:getPlayerNum(), menu)
+ playerObj:setJoypadIgnoreAimUntilCentered(true)
+ end
+end
+
+function TowBarMod.UI.addHookOptionToMenu(playerObj, vehicle)
+ local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
+ if menu == nil then return end
+
+ local hasTowBar = playerObj:getInventory():getItemFromTypeRecurse("TowBar.TowBar") ~= nil
+ if not hasTowBar then return end
+
+ local vehicles = TowBarMod.Utils.getAviableVehicles(vehicle, hasTowBar)
+
+ if #vehicles == 0 then
+ return
+ elseif #vehicles == 1 then
+ local hookTypeVariants = TowBarMod.Utils.getHookTypeVariants(vehicle, vehicles[1], hasTowBar)
+ if #hookTypeVariants > 0 then
+ local hookType = hookTypeVariants[1]
+ menu:addSlice(
+ hookType.name,
+ getTexture("media/textures/tow_bar_attach.png"),
+ hookType.func,
+ playerObj,
+ hookType.towingVehicle,
+ hookType.towedVehicle,
+ hookType.towingPoint,
+ hookType.towedPoint
+ )
+ end
+ else
+ menu:addSlice(
+ getText("UI_Text_Towing_attach") .. "...",
+ getTexture("media/textures/tow_bar_attach.png"),
+ TowBarMod.UI.showChooseVehicleMenu,
+ playerObj,
+ vehicle,
+ vehicles,
+ hasTowBar
+ )
+ end
+end
+
+function TowBarMod.UI.addUnhookOptionToMenu(playerObj, vehicle)
+ local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
+ if menu == nil then return end
+ if not vehicle:getModData()["isTowingByTowBar"] then return end
+ if not vehicle:getVehicleTowing() and not vehicle:getVehicleTowedBy() then return end
+
+ local towedVehicle = vehicle
+ if vehicle:getVehicleTowing() then
+ towedVehicle = vehicle:getVehicleTowing()
+ end
+
+ menu:addSlice(
+ getText("ContextMenu_Vehicle_DetachTrailer", ISVehicleMenu.getVehicleDisplayName(towedVehicle)),
+ getTexture("media/textures/tow_bar_detach.png"),
+ TowBarMod.Hook.deattachTowBarAction,
+ playerObj,
+ towedVehicle
+ )
+end
+
+
+---------------------------------------------------------------------------
+--- Dev menu
+---------------------------------------------------------------------------
+
+function TowBarMod.UI.showDevSingleTowbarMenu(playerObj, vehicle)
+ local playerIndex = playerObj:getPlayerNum()
+ local menu = getPlayerRadialMenu(playerIndex)
+ menu:clear()
+
+ for j = 0, 47 do
+ local zIndex = j % 24
+ local modelType = (j >= 24) and "large" or "normal"
+ menu:addSlice(
+ "towbar" .. j .. " [" .. modelType .. "] (Z=" .. tostring(1.0 + zIndex * 0.1) .. ")",
+ getTexture("media/textures/tow_bar_icon.png"),
+ TowBarMod.Hook.devShowSingleTowbar,
+ playerObj,
+ vehicle,
+ j
+ )
+ end
+
+ menu:setX(getPlayerScreenLeft(playerIndex) + getPlayerScreenWidth(playerIndex) / 2 - menu:getWidth() / 2)
+ menu:setY(getPlayerScreenTop(playerIndex) + getPlayerScreenHeight(playerIndex) / 2 - menu:getHeight() / 2)
+ menu:addToUIManager()
+ if JoypadState.players[playerObj:getPlayerNum()+1] then
+ menu:setHideWhenButtonReleased(Joypad.DPadUp)
+ setJoypadFocus(playerObj:getPlayerNum(), menu)
+ playerObj:setJoypadIgnoreAimUntilCentered(true)
+ end
+end
+
+function TowBarMod.UI.addDevOptionsToMenu(playerObj, vehicle)
+ local devModeEnabled = (TowBarMod.Config and TowBarMod.Config.devMode) or getDebug()
+ if not devModeEnabled then return end
+ if not vehicle then return end
+
+ local menu = getPlayerRadialMenu(playerObj:getPlayerNum())
+ if menu == nil then return end
+
+ menu:addSlice(
+ "[DEV] Show ALL Towbars",
+ getTexture("media/textures/tow_bar_icon.png"),
+ TowBarMod.Hook.devShowAllTowbarModels,
+ playerObj,
+ vehicle
+ )
+
+ menu:addSlice(
+ "[DEV] Hide ALL Towbars",
+ getTexture("media/textures/tow_bar_icon.png"),
+ TowBarMod.Hook.devHideAllTowbarModels,
+ playerObj,
+ vehicle
+ )
+
+ menu:addSlice(
+ "[DEV] Pick Single Towbar...",
+ getTexture("media/textures/tow_bar_icon.png"),
+ TowBarMod.UI.showDevSingleTowbarMenu,
+ playerObj,
+ vehicle
+ )
+end
+
+---------------------------------------------------------------------------
+--- Mod compability
+---------------------------------------------------------------------------
+
+if getActivatedMods():contains("vehicle_additions") then
+ require("Vehicles/ISUI/Oven_Mattress_RadialMenu")
+ require("Vehicles/ISUI/FuelTruckTank_ISVehicleMenu_FillPartMenu")
+end
+
+---------------------------------------------------------------------------
+--- Attach to default menu method
+---------------------------------------------------------------------------
+
+if TowBarMod.UI.defaultShowRadialMenu == nil then
+ TowBarMod.UI.defaultShowRadialMenu = ISVehicleMenu.showRadialMenu
+end
+
+function ISVehicleMenu.showRadialMenu(playerObj)
+ TowBarMod.UI.defaultShowRadialMenu(playerObj)
+
+ if playerObj:getVehicle() then return end
+
+ local vehicle = ISVehicleMenu.getVehicleToInteractWith(playerObj)
+ if vehicle == nil then return end
+
+ if vehicle:getModData()["isTowingByTowBar"] then
+ TowBarMod.UI.removeDefaultDetachOption(playerObj)
+ TowBarMod.UI.addUnhookOptionToMenu(playerObj, vehicle)
+ elseif not vehicle:getVehicleTowing() and not vehicle:getVehicleTowedBy() then
+ TowBarMod.UI.addHookOptionToMenu(playerObj, vehicle)
+ end
+
+ TowBarMod.UI.addDevOptionsToMenu(playerObj, vehicle)
+end
diff --git a/42.18/media/lua/client/TowBar/TowingUtils.lua b/42.18/media/lua/client/TowBar/TowingUtils.lua
new file mode 100644
index 0000000..d757eef
--- /dev/null
+++ b/42.18/media/lua/client/TowBar/TowingUtils.lua
@@ -0,0 +1,252 @@
+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)
+
diff --git a/42.18/media/lua/server/BTTow.lua b/42.18/media/lua/server/BTTow.lua
new file mode 100644
index 0000000..c5e3e06
--- /dev/null
+++ b/42.18/media/lua/server/BTTow.lua
@@ -0,0 +1,117 @@
+BTtow = {}
+BTtow.Create = {}
+BTtow.Init = {}
+
+local TowbarVariantSize = 24
+local TowbarNormalStart = 0
+local TowbarLargeStart = 24
+local TowbarMaxIndex = TowbarVariantSize - 1
+local VanillaScaleMin = 1.5
+local VanillaScaleMax = 2.0
+
+local function getVehicleModelScale(script)
+ if not script then return nil end
+
+ local ok, result = pcall(function()
+ return script:getModelScale()
+ end)
+ if ok and type(result) == "number" then
+ return result
+ end
+
+ ok, result = pcall(function()
+ local model = script:getModel()
+ if model then
+ return model:getScale()
+ end
+ return nil
+ end)
+ if ok and type(result) == "number" then
+ return result
+ end
+
+ return nil
+end
+
+local function isVanillaScale(script)
+ local modelScale = getVehicleModelScale(script)
+ if modelScale == nil then
+ return true
+ end
+
+ local configuredMin = TowBarMod and TowBarMod.Config and tonumber(TowBarMod.Config.vanillaTowbarModelScaleMin)
+ local configuredMax = TowBarMod and TowBarMod.Config and tonumber(TowBarMod.Config.vanillaTowbarModelScaleMax)
+ local minScale = configuredMin or VanillaScaleMin
+ local maxScale = configuredMax or VanillaScaleMax
+ return modelScale >= minScale and modelScale <= maxScale
+end
+
+local function getTowbarIndexVanilla(script)
+ local z = script:getPhysicsChassisShape():z() / 2 - 0.1
+ local index = math.floor((z * 2 / 3 - 1) * 10)
+ return math.max(0, math.min(TowbarMaxIndex, index))
+end
+
+local function getTowbarIndexSmallScale(script)
+ if not script then return nil end
+
+ local maxAbsTowZ = nil
+ local trailer = script:getAttachmentById("trailer")
+ if trailer then
+ maxAbsTowZ = math.abs(trailer:getOffset():z())
+ end
+ local trailerFront = script:getAttachmentById("trailerfront")
+ if trailerFront then
+ local frontAbsZ = math.abs(trailerFront:getOffset():z())
+ if not maxAbsTowZ or frontAbsZ > maxAbsTowZ then
+ maxAbsTowZ = frontAbsZ
+ end
+ end
+
+ if maxAbsTowZ ~= nil then
+ local index = math.floor((maxAbsTowZ + 0.1 - 1.0) * 10)
+ return math.max(0, math.min(TowbarMaxIndex, index))
+ end
+
+ return nil
+end
+
+local function getTowbarModelSlot(script)
+ local isVanilla = isVanillaScale(script)
+ local index = getTowbarIndexVanilla(script)
+ if not isVanilla then
+ local attachmentIndex = getTowbarIndexSmallScale(script)
+ if attachmentIndex ~= nil then
+ index = attachmentIndex
+ else
+ local offset = TowBarMod and TowBarMod.Config and tonumber(TowBarMod.Config.smallScaleTowbarIndexOffset) or 2
+ index = math.max(0, math.min(TowbarMaxIndex, index + offset))
+ end
+ end
+ return index, isVanilla
+end
+
+function BTtow.Create.towbar(vehicle, part)
+ if part == nil then return end
+ for j=0, TowbarVariantSize - 1 do
+ part:setModelVisible("towbar" .. j, false)
+ end
+end
+
+function BTtow.Init.towbar(vehicle, part)
+ if part == nil then return end
+ for j=0, TowbarVariantSize - 1 do
+ part:setModelVisible("towbar" .. j, false)
+ end
+ if vehicle:getModData()["isTowingByTowBar"] and vehicle:getModData()["towed"] then
+ local script = vehicle:getScript()
+ if script then
+ local index, isVanilla = getTowbarModelSlot(script)
+ local partId = part:getId()
+ local shouldShowOnThisPart = (isVanilla and partId == "towbar") or ((not isVanilla) and partId == "towbarLarge")
+ if shouldShowOnThisPart then
+ part:setModelVisible("towbar" .. index, true)
+ end
+ end
+ end
+end
diff --git a/42.18/media/lua/server/Items/TowBarItem_Distributions.lua b/42.18/media/lua/server/Items/TowBarItem_Distributions.lua
new file mode 100644
index 0000000..0023a63
--- /dev/null
+++ b/42.18/media/lua/server/Items/TowBarItem_Distributions.lua
@@ -0,0 +1,71 @@
+require 'Items/ProceduralDistributions'
+require 'Items/SuburbsDistributions'
+require 'Items/Distributions'
+require 'Items/Distribution_BinJunk'
+require 'Items/Distribution_ClosetJunk'
+require 'Items/Distribution_DeskJunk'
+require 'Items/Distribution_ShelfJunk'
+require 'Items/Distribution_CounterJunk'
+require 'Items/Distribution_SideTableJunk'
+require 'Vehicles/VehicleDistributions'
+require 'Vehicles/VehicleDistribution_GloveBoxJunk'
+require 'Vehicles/VehicleDistribution_SeatJunk'
+require 'Vehicles/VehicleDistribution_TrunkJunk'
+
+----------------- TOW BAR -----------------------
+-- Mirror Jack spawn chance into TowBar in container distributions (world + vehicle containers).
+-- Intentionally excludes story-clutter floor placement tables (RandomizedWorldContent/StoryClutter).
+
+local TOWBAR_ITEM_TYPE = "TowBar.TowBar"
+local JACK_ITEM_TYPES = {
+ ["Jack"] = true,
+ ["Base.Jack"] = true,
+}
+
+local function addMissingTowBarsForJack(items)
+ if type(items) ~= "table" then return end
+
+ local jackCountByChance = {}
+ local towBarCountByChance = {}
+
+ for i = 1, #items, 2 do
+ local itemType = items[i]
+ local chance = tonumber(items[i + 1])
+ if type(itemType) == "string" and chance ~= nil then
+ if JACK_ITEM_TYPES[itemType] then
+ jackCountByChance[chance] = (jackCountByChance[chance] or 0) + 1
+ elseif itemType == TOWBAR_ITEM_TYPE then
+ towBarCountByChance[chance] = (towBarCountByChance[chance] or 0) + 1
+ end
+ end
+ end
+
+ for chance, jackCount in pairs(jackCountByChance) do
+ local missing = jackCount - (towBarCountByChance[chance] or 0)
+ for _ = 1, missing do
+ table.insert(items, TOWBAR_ITEM_TYPE)
+ table.insert(items, chance)
+ end
+ end
+end
+
+local function walkContainerDistributions(root, seen)
+ if type(root) ~= "table" or seen[root] then return end
+ seen[root] = true
+
+ for key, value in pairs(root) do
+ if key == "items" and type(value) == "table" then
+ addMissingTowBarsForJack(value)
+ elseif type(value) == "table" then
+ walkContainerDistributions(value, seen)
+ end
+ end
+end
+
+local seen = {}
+walkContainerDistributions(ProceduralDistributions, seen)
+walkContainerDistributions(SuburbsDistributions, seen)
+walkContainerDistributions(Distributions, seen)
+walkContainerDistributions(VehicleDistributions, seen)
+walkContainerDistributions(ClutterTables, seen)
+
diff --git a/42.18/media/lua/server/TowingCommands.lua b/42.18/media/lua/server/TowingCommands.lua
new file mode 100644
index 0000000..158b26e
--- /dev/null
+++ b/42.18/media/lua/server/TowingCommands.lua
@@ -0,0 +1,268 @@
+if isClient() then return end
+
+local TowingCommands = {}
+local Commands = {}
+local TowBarItemType = "TowBar.TowBar"
+local SyncDelayTicks = 2
+local SnapshotIntervalTicks = 120
+local pendingSync = {}
+local snapshotTickCounter = 0
+
+TowingCommands.wantNoise = getDebug() or false
+
+local noise = function(msg)
+ if TowingCommands.wantNoise then
+ print("TowBarCommands: " .. msg)
+ end
+end
+
+local function queueSync(kind, player, args)
+ if not args then return end
+ table.insert(pendingSync, {
+ kind = kind,
+ ticks = SyncDelayTicks,
+ player = player,
+ args = args
+ })
+end
+
+local function resolveAttachmentA(args, vehicleA)
+ if args and args.attachmentA then return args.attachmentA end
+ if vehicleA and vehicleA:getTowAttachmentSelf() then return vehicleA:getTowAttachmentSelf() end
+ return "trailer"
+end
+
+local function resolveAttachmentB(args, vehicleB)
+ if args and args.attachmentB then return args.attachmentB end
+ if vehicleB and vehicleB:getTowAttachmentSelf() then return vehicleB:getTowAttachmentSelf() end
+ return "trailerfront"
+end
+
+local function isLinked(vehicleA, vehicleB)
+ if not vehicleA or not vehicleB then return false end
+ return vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA
+end
+
+local function hasTowBarState(vehicle)
+ if not vehicle then return false end
+ local md = vehicle:getModData()
+ if not md then return false end
+ return md["isTowingByTowBar"] == true
+end
+
+local function forEachCollectionItem(collection, callback)
+ if not collection then 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 size
+ ok, size = pcall(function()
+ return collection:size()
+ end)
+ if not ok or type(size) ~= "number" then return end
+
+ for i = 0, size - 1 do
+ callback(collection:get(i))
+ end
+end
+
+local function broadcastAttach(vehicleA, vehicleB, attachmentA, attachmentB)
+ if not vehicleA or not vehicleB then return end
+ sendServerCommand("towbar", "forceAttachSync", {
+ vehicleA = vehicleA:getId(),
+ vehicleB = vehicleB:getId(),
+ attachmentA = attachmentA,
+ attachmentB = attachmentB
+ })
+end
+
+local function broadcastDetach(vehicleAId, vehicleBId)
+ sendServerCommand("towbar", "forceDetachSync", {
+ vehicleA = vehicleAId,
+ vehicleB = vehicleBId
+ })
+end
+
+local function processAttachSync(item)
+ local args = item.args or {}
+ local vehicleA = args.vehicleA and getVehicleById(args.vehicleA) or nil
+ local vehicleB = args.vehicleB and getVehicleById(args.vehicleB) or nil
+ if not vehicleA or not vehicleB then
+ noise("attach sync skipped missing vehicles A=" .. tostring(args.vehicleA) .. " B=" .. tostring(args.vehicleB))
+ return
+ end
+
+ local attachmentA = resolveAttachmentA(args, vehicleA)
+ local attachmentB = resolveAttachmentB(args, vehicleB)
+ if not isLinked(vehicleA, vehicleB) then
+ vehicleA:addPointConstraint(item.player, vehicleB, attachmentA, attachmentB)
+ end
+ if isLinked(vehicleA, vehicleB) then
+ broadcastAttach(vehicleA, vehicleB, attachmentA, attachmentB)
+ end
+end
+
+local function processDetachSync(item)
+ local args = item.args or {}
+ local vehicleAId = args.towingVehicle or args.vehicleA or args.vehicle
+ local vehicleBId = args.vehicleB
+ broadcastDetach(vehicleAId, vehicleBId)
+end
+
+local function snapshotActiveTowbarLinksServer()
+ local cell = getCell()
+ if not cell then return end
+ local list = cell:getVehicles()
+ if not list then return end
+
+ forEachCollectionItem(list, function(towingVehicle)
+ local towedVehicle = towingVehicle and towingVehicle:getVehicleTowing() or nil
+ if towingVehicle and towedVehicle and towedVehicle:getVehicleTowedBy() == towingVehicle then
+ if hasTowBarState(towingVehicle) or hasTowBarState(towedVehicle) then
+ local attachmentA = resolveAttachmentA(nil, towingVehicle)
+ local towedMd = towedVehicle:getModData()
+ local attachmentB = (towedMd and towedMd["towBarChangedAttachmentId"]) or resolveAttachmentB(nil, towedVehicle)
+ if attachmentA == attachmentB then
+ attachmentA = "trailer"
+ attachmentB = "trailerfront"
+ end
+ broadcastAttach(towingVehicle, towedVehicle, attachmentA, attachmentB)
+ end
+ end
+ end)
+end
+
+local function processPendingSync()
+ snapshotTickCounter = snapshotTickCounter + 1
+ if snapshotTickCounter >= SnapshotIntervalTicks then
+ snapshotTickCounter = 0
+ snapshotActiveTowbarLinksServer()
+ end
+
+ if #pendingSync == 0 then return end
+
+ local remaining = {}
+ for i = 1, #pendingSync do
+ local item = pendingSync[i]
+ item.ticks = item.ticks - 1
+ if item.ticks <= 0 then
+ if item.kind == "attach" then
+ processAttachSync(item)
+ elseif item.kind == "detach" then
+ processDetachSync(item)
+ end
+ else
+ table.insert(remaining, item)
+ end
+ end
+ pendingSync = remaining
+end
+
+function Commands.attachTowBar(player, args)
+ local vehicleA = getVehicleById(args.vehicleA)
+ local vehicleB = getVehicleById(args.vehicleB)
+ if not vehicleA then
+ noise("no such vehicle (A) id=" .. tostring(args.vehicleA))
+ return
+ end
+ if not vehicleB then
+ noise("no such vehicle (B) id=" .. tostring(args.vehicleB))
+ return
+ end
+
+ vehicleA:addPointConstraint(player, vehicleB, args.attachmentA, args.attachmentB)
+end
+
+function Commands.detachTowBar(player, args)
+ local towingVehicle = args.towingVehicle and getVehicleById(args.towingVehicle) or nil
+ local towedVehicle = args.vehicle and getVehicleById(args.vehicle) or nil
+
+ if not towingVehicle and towedVehicle then
+ towingVehicle = towedVehicle:getVehicleTowedBy()
+ end
+ if not towedVehicle and towingVehicle then
+ towedVehicle = towingVehicle:getVehicleTowing()
+ end
+
+ if towedVehicle then
+ towedVehicle:breakConstraint(true, false)
+ end
+ if towingVehicle and towingVehicle ~= towedVehicle then
+ towingVehicle:breakConstraint(true, false)
+ end
+end
+
+function Commands.consumeTowBar(player, args)
+ if not player then return end
+ local inventory = player:getInventory()
+ if not inventory then return end
+
+ local towBarItem = nil
+ local itemId = args and args.itemId
+ if itemId then
+ towBarItem = inventory:getItemWithID(itemId)
+ end
+ if not towBarItem then
+ towBarItem = inventory:getFirstTypeRecurse(TowBarItemType)
+ end
+ if not towBarItem then return end
+
+ local wasPrimary = player:isPrimaryHandItem(towBarItem)
+ local wasSecondary = player:isSecondaryHandItem(towBarItem)
+ player:removeFromHands(towBarItem)
+ inventory:Remove(towBarItem)
+ sendRemoveItemFromContainer(inventory, towBarItem)
+
+ if wasPrimary or wasSecondary then
+ sendEquip(player)
+ end
+end
+
+function Commands.giveTowBar(player, args)
+ if not player then return end
+ local inventory = player:getInventory()
+ if not inventory then return end
+
+ local towBarItem = inventory:AddItem(TowBarItemType)
+ if not towBarItem then return end
+ sendAddItemToContainer(inventory, towBarItem)
+
+ if args and args.equipPrimary then
+ player:setPrimaryHandItem(towBarItem)
+ sendEquip(player)
+ end
+end
+
+-- Compatibility aliases for older command names.
+Commands.attachConstraint = Commands.attachTowBar
+Commands.detachConstraint = Commands.detachTowBar
+
+TowingCommands.OnClientCommand = function(module, command, player, args)
+ -- Only sync explicit towbar commands so vanilla towing stays untouched.
+ if module == "towbar" and command == "attachTowBar" then
+ queueSync("attach", player, args)
+ elseif module == "towbar" and command == "detachTowBar" then
+ queueSync("detach", player, args)
+ end
+
+ if module == "towbar" and Commands[command] then
+ local argStr = ""
+ args = args or {}
+ for k, v in pairs(args) do
+ argStr = argStr .. " " .. tostring(k) .. "=" .. tostring(v)
+ end
+ noise("received " .. module .. " " .. command .. " " .. tostring(player) .. argStr)
+ Commands[command](player, args)
+ end
+end
+
+Events.OnClientCommand.Add(TowingCommands.OnClientCommand)
+Events.OnTick.Add(processPendingSync)
diff --git a/42.18/media/lua/shared/Translate/EN/IG_UI.json b/42.18/media/lua/shared/Translate/EN/IG_UI.json
new file mode 100644
index 0000000..c2decbb
--- /dev/null
+++ b/42.18/media/lua/shared/Translate/EN/IG_UI.json
@@ -0,0 +1,3 @@
+{
+ "IGUI_VehicleParttowbar": "Towbar Attachment"
+}
diff --git a/42.18/media/lua/shared/Translate/EN/ItemName.json b/42.18/media/lua/shared/Translate/EN/ItemName.json
new file mode 100644
index 0000000..c5abe93
--- /dev/null
+++ b/42.18/media/lua/shared/Translate/EN/ItemName.json
@@ -0,0 +1,3 @@
+{
+ "ItemName_TowBar.TowBar": "Tow Bar"
+}
diff --git a/42.18/media/lua/shared/Translate/EN/Tooltip.json b/42.18/media/lua/shared/Translate/EN/Tooltip.json
new file mode 100644
index 0000000..f0e670a
--- /dev/null
+++ b/42.18/media/lua/shared/Translate/EN/Tooltip.json
@@ -0,0 +1,3 @@
+{
+ "Tooltip_TowBar": "A tow bar can be used
to tow vehicles like a trailer"
+}
diff --git a/42.18/media/lua/shared/Translate/EN/UI.json b/42.18/media/lua/shared/Translate/EN/UI.json
new file mode 100644
index 0000000..2e33852
--- /dev/null
+++ b/42.18/media/lua/shared/Translate/EN/UI.json
@@ -0,0 +1,17 @@
+{
+ "UI_Text_Towing_turnOffParkingBrake": "Turn off parking brake",
+ "UI_Text_Towing_turnOnParkingBrake": "Turn on parking brake",
+ "UI_Text_Towing_noAviableVehicles": "No vehicles for attach
or can't attach",
+ "UI_Text_Towing_attach": "Attach",
+ "UI_Text_Towing_deattach": "Deattach",
+ "UI_Text_Towing_byRope": "by rope",
+ "UI_Text_Towing_byTowBar": "by tow bar",
+ "UI_Text_Towing_byHook": "by hook",
+ "UI_Text_Towing_flipUpright": "Flip upright",
+ "UI_Text_Towing_cannotDriveWhileTowed": "Cannot drive while being towed",
+ "UI_Text_PushByHands": "Push vehicle",
+ "UI_Text_PushByHands_Left": "Left",
+ "UI_Text_PushByHands_Right": "Right",
+ "UI_Text_PushByHands_Front": "Front",
+ "UI_Text_PushByHands_Behind": "Behind"
+}
diff --git a/42.18/media/registries.lua b/42.18/media/registries.lua
new file mode 100644
index 0000000..ea87939
--- /dev/null
+++ b/42.18/media/registries.lua
@@ -0,0 +1,3 @@
+-- Build 42 registry file.
+-- This mod currently uses only base registries (for example ItemType = base:normal),
+-- so no custom identifier registrations are required here.
diff --git a/42.18/media/scripts/TowBar_items.txt b/42.18/media/scripts/TowBar_items.txt
new file mode 100644
index 0000000..554d51c
--- /dev/null
+++ b/42.18/media/scripts/TowBar_items.txt
@@ -0,0 +1,15 @@
+module TowBar
+{
+/*******************Towing Car*******************/
+ item TowBar
+ {
+ DisplayCategory = Tool,
+ Weight = 8.0,
+ ItemType = base:normal,
+ Icon = TowBar,
+ Tooltip = Tooltip_TowBar,
+ StaticModel = towbarModel,
+ WorldStaticModel = towbarModel,
+ }
+}
+
diff --git a/42.18/media/scripts/vehicles/burntvehicles.txt b/42.18/media/scripts/vehicles/burntvehicles.txt
new file mode 100644
index 0000000..4128178
--- /dev/null
+++ b/42.18/media/scripts/vehicles/burntvehicles.txt
@@ -0,0 +1,3265 @@
+module Base
+{
+ model Vehicles_PickUpBurnt
+ {
+ mesh = vehicles/Vehicles_PickUpBurnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle PickupBurnt
+ {
+ model
+ {
+ file = Vehicles_PickupBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3462 0.0000,
+ }
+
+ spawnOffsetY = 0.24999994,
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupburnt,
+ }
+
+ extents = 0.8022 0.7033 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3462 0.0000,
+ physicsChassisShape = 0.8022 0.7033 2.1868,
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3352 -1.1648,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3626 1.1374,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_PickUpVanBurnt
+ {
+ mesh = vehicles/Vehicles_PickUpVanBurnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle PickUpVanBurnt
+ {
+ model
+ {
+ file = Vehicles_PickUpVanBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3516 0.0000,
+ }
+
+ spawnOffsetY = 0.24999994,
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupvanburnt,
+ }
+
+ extents = 0.6813 0.6374 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3297 0.0000,
+ physicsChassisShape = 0.6813 0.6374 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3516 -1.1978,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3352 1.1758,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_CarNormal_Burnt
+ {
+ mesh = vehicles/Vehicles_CarNormal_Burnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle CarNormalBurnt
+ {
+ model
+ {
+ file = Vehicles_CarNormal_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3297 0.0000,
+ }
+
+ spawnOffsetY = 0.19999991,
+
+ skin
+ {
+ texture = Vehicles/vehicles_carnormal_burnt,
+ }
+
+ extents = 0.8022 0.6593 2.6044,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3242 0.0000,
+ physicsChassisShape = 0.8022 0.6593 2.6044,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.2637 -1.3242,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3352 1.3297,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ vehicle TaxiBurnt
+ {
+ model
+ {
+ file = Vehicles_CarNormal_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3297 0.0000,
+ }
+
+ spawnOffsetY = 0.19999991,
+
+ skin
+ {
+ texture = Vehicles/vehicles_taxi_burnt,
+ }
+
+ extents = 0.9011 0.6593 2.5055,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3297 0.0000,
+ physicsChassisShape = 0.9011 0.6593 2.5055,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.1923 -1.3736,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3242 1.3462,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_ModernCar02Burnt
+ {
+ mesh = vehicles/Vehicles_ModernCar02_Burnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle ModernCar02Burnt
+ {
+ model
+ {
+ file = Vehicles_ModernCarBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.2912 0.0000,
+ }
+
+ spawnOffsetY = 0.099999994,
+
+ skin
+ {
+ texture = Vehicles/vehicles_moderncar02_burnt,
+ }
+
+ extents = 0.6813 0.6374 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.2912 0.0000,
+ physicsChassisShape = 0.6813 0.6374 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.2802 -1.1538,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3242 1.1978,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_ModernCarBurnt
+ {
+ mesh = vehicles/Vehicles_ModernCarBurnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle ModernCarBurnt
+ {
+ model
+ {
+ file = Vehicles_ModernCarBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.2912 0.0000,
+ }
+
+ spawnOffsetY = 0.099999994,
+
+ skin
+ {
+ texture = Vehicles/vehicles_moderncar_burnt,
+ }
+
+ extents = 1.0000 0.5604 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.2912 0.0000,
+ physicsChassisShape = 1.0000 0.5604 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.2857 -1.1484,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3077 1.1758,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_SportsCar_Burnt
+ {
+ mesh = vehicles/Vehicles_SportsCar_Burnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle SportsCarBurnt
+ {
+ model
+ {
+ file = Vehicles_SportsCar_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.0000 0.0000,
+ }
+
+ spawnOffsetY = -0.20000005,
+
+ skin
+ {
+ texture = Vehicles/vehicles_sportscar_burnt,
+ }
+
+ extents = 0.8022 0.4725 2.1868,
+ mass = 400,
+ centerOfMassOffset = 0.0000 0.0000 0.0000,
+ physicsChassisShape = 0.8022 0.4725 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.1923 -1.0549,
+ rotate = 0.0000 0.0000 0.0000,
+ zoffset = -1,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.2198 1.0604,
+ rotate = 0.0000 0.0000 0.0000,
+ zoffset = 1,
+ }
+ }
+ model Vehicles_SmallCar02Burnt
+ {
+ mesh = vehicles/Vehicles_SmallCar02Burnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle SmallCar02Burnt
+ {
+ model
+ {
+ file = Vehicles_SmallCar02Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3352 0.0000,
+ }
+
+ spawnOffsetY = 0.19999991,
+
+ skin
+ {
+ texture = Vehicles/vehicles_smallcar02_burnt,
+ }
+
+ extents = 0.8022 0.6703 2.1868,
+ mass = 400,
+ centerOfMassOffset = 0.0000 0.3352 0.0000,
+ physicsChassisShape = 0.8022 0.6703 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3352 -1.0714,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.2967 1.1099,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_SmallCar_Burnt
+ {
+ mesh = vehicles/Vehicles_SmallCar_Burnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle SmallCarBurnt
+ {
+ model
+ {
+ file = Vehicles_SmallCar_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3022 0.0000,
+ }
+
+ spawnOffsetY = -0.10000008,
+
+ skin
+ {
+ texture = Vehicles/vehicles_smallcar_burnt,
+ }
+
+ extents = 0.7473 0.6044 1.8571,
+ mass = 400,
+ centerOfMassOffset = 0.0000 0.3022 0.0000,
+ physicsChassisShape = 0.7473 0.6044 1.8571,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.2418 0.6264,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.2088 -0.9231,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_VanSeats_Burnt
+ {
+ mesh = vehicles/Vehicles_VanSeats_Burnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle VanSeatsBurnt
+ {
+ model
+ {
+ file = Vehicles_VanSeats_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3626 0.0000,
+ }
+
+ spawnOffsetY = 0.5499999,
+
+ skin
+ {
+ texture = Vehicles/vehicles_vanseats_burnt,
+ }
+
+ physicsChassisShape = 0.9341 0.7253 2.3297,
+ centerOfMassOffset = 0.0000 0.3626 0.0000,
+ mass = 500,
+ extents = 0.9341 0.7253 2.3297,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3352 -1.2363,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3352 1.2253,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_Van_Burnt
+ {
+ mesh = vehicles/Vehicles_Van_Burnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle VanBurnt
+ {
+ model
+ {
+ file = Vehicles_Van_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3462 0.0000,
+ }
+
+ spawnOffsetY = 0.5499999,
+
+ skin
+ {
+ texture = Vehicles/vehicles_van_burnt,
+ }
+
+ physicsChassisShape = 0.9341 0.7253 2.3297,
+ centerOfMassOffset = 0.0000 0.3626 0.0000,
+ mass = 500,
+ extents = 0.9341 0.7253 2.3297,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3352 -1.2692,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3462 1.2198,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_SUV_Burnt
+ {
+ mesh = vehicles/Vehicles_SUV_Burnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle SUVBurnt
+ {
+ model
+ {
+ file = Vehicles_SUV_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3297 0.0000,
+ }
+
+ spawnOffsetY = 0.24999978,
+
+ skin
+ {
+ texture = Vehicles/vehicles_suv_burnt,
+ }
+
+ extents = 0.9011 0.6593 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3297 0.0000,
+ physicsChassisShape = 0.9011 0.6593 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3297 -1.0989,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3297 1.0989,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_OffRoadBurnt
+ {
+ mesh = vehicles/Vehicles_OffRoadBurnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle OffRoadBurnt
+ {
+ model
+ {
+ file = Vehicles_OffRoadBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3407 0.0000,
+ }
+
+ spawnOffsetY = 0.24999978,
+
+ skin
+ {
+ texture = Vehicles/vehicles_offroad_burnt,
+ }
+
+ extents = 0.8022 0.7143 1.7473,
+ mass = 400,
+ centerOfMassOffset = 0.0000 0.3571 0.0000,
+ physicsChassisShape = 0.8022 0.7143 1.7473,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3846 -0.8846,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3462 0.9286,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_LuxuryCarBurnt
+ {
+ mesh = vehicles/Vehicles_LuxuryCarBurnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle LuxuryCarBurnt
+ {
+ model
+ {
+ file = Vehicles_LuxuryCarBurnt,
+ scale = 1.6200,
+ offset = 0.0000 0.3889 0.0000,
+ }
+
+ spawnOffsetY = 0.19999991,
+
+ skin
+ {
+ texture = Vehicles/vehicles_luxurycar_burnt,
+ }
+
+ extents = 1.0988 0.8025 2.8025,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3889 0.0000,
+ physicsChassisShape = 1.0988 0.8025 2.8025,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3333 -1.4815,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3765 1.5185,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_PickUpVanLightsBurnt
+ {
+ mesh = vehicles/Vehicles_PickUpVanLightsBurnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle PickUpVanLightsBurnt
+ {
+ model
+ {
+ file = Vehicles_PickUpVanLightsBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3407 0.0000,
+ }
+
+ spawnOffsetY = 0.24999994,
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupvanlightsburnt,
+ }
+
+ extents = 0.6813 0.7033 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3407 0.0000,
+ physicsChassisShape = 0.6813 0.7033 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3297 -1.1758,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3242 1.1868,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_Ambulance_Burnt
+ {
+ mesh = vehicles/Vehicles_Ambulance_Burnt,
+ shader = vehicle,
+ scale = 0.01,
+ }
+ vehicle AmbulanceBurnt
+ {
+ model
+ {
+ file = Vehicles_Ambulance_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3516 0.0000,
+ }
+
+ spawnOffsetY = 0.7999999,
+
+ skin
+ {
+ texture = Vehicles/vehicles_ambulance_burnt,
+ }
+
+ extents = 0.9011 0.8791 2.3077,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.4396 0.0000,
+ physicsChassisShape = 0.9011 0.8791 2.3077,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.4011 -1.2198,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3956 1.2033,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_VanRadio_Burnt
+ {
+ mesh = vehicles/Vehicles_VanRadio_Burnt,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle VanRadioBurnt
+ {
+ model
+ {
+ file = Vehicles_VanRadio_Burnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3846 0.0000,
+ }
+
+ spawnOffsetY = 0.7999999,
+
+ skin
+ {
+ texture = Vehicles/vehicles_vanradio_burnt,
+ }
+
+ extents = 0.9011 0.9341 2.3077,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.4670 0.0000,
+ physicsChassisShape = 0.9011 0.9341 2.3077,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3626 -1.2857,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3242 1.2253,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ vehicle PickupSpecialBurnt
+ {
+ model
+ {
+ file = Vehicles_PickupBurnt,
+ scale = 1.8200,
+ offset = 0.0000 0.3462 0.0000,
+ }
+
+ spawnOffsetY = 0.1499999,
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupburnt_fire,
+ }
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupburnt_fossoil,
+ }
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupburnt_police,
+ }
+
+ skin
+ {
+ texture = Vehicles/vehicles_pickupburnt_ranger,
+ }
+
+ extents = 0.8022 0.6593 2.1868,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3462 0.0000,
+ physicsChassisShape = 0.8022 0.6593 2.1868,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.2967 -1.1648,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3187 1.1374,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+ model Vehicles_NormalCar_BurntPolice
+ {
+ mesh = vehicles/Vehicles_NormalCar_BurntPolice,
+ shader = vehicle,
+ static = TRUE,
+ scale = 0.01,
+ }
+ vehicle NormalCarBurntPolice
+ {
+ model
+ {
+ file = Vehicles_NormalCar_BurntPolice,
+ scale = 1.7000,
+ offset = 0.0000 0.3176 0.0000,
+ }
+
+ spawnOffsetY = 0.64999986,
+
+ skin
+ {
+ texture = Vehicles/vehicles_normalcar_burntpolice,
+ }
+
+ extents = 0.9059 0.6000 2.6000,
+ mass = 500,
+ centerOfMassOffset = 0.0000 0.3176 0.0000,
+ physicsChassisShape = 0.9059 0.6000 2.6000,
+
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ attachment trailer
+ {
+ offset = 0.0000 -0.3353 -1.3059,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+
+ attachment trailerfront
+ {
+ offset = 0.0000 -0.3294 1.3529,
+ rotate = 0.0000 0.0000 0.0000,
+ }
+ }
+}
diff --git a/42.18/media/scripts/vehicles/template_battery.txt b/42.18/media/scripts/vehicles/template_battery.txt
new file mode 100644
index 0000000..d5a679a
--- /dev/null
+++ b/42.18/media/scripts/vehicles/template_battery.txt
@@ -0,0 +1,315 @@
+module Base
+{
+ template vehicle Battery
+ {
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+ part towbarLarge
+ {
+ model towbar0
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+ part Battery
+ {
+ area = Engine,
+ itemType = Base.CarBattery,
+ mechanicRequireKey = true,
+ category = engine,
+ table install
+ {
+ items
+ {
+ 1
+ {
+ type = Base.Screwdriver,
+ count = 1,
+ keep = true,
+ equip = primary,
+ }
+ }
+ time = 100,
+ professions = ,
+ skills = ,
+ traits = ,
+ recipes = ,
+ test = Vehicles.InstallTest.Default,
+ door = EngineDoor,
+ }
+ table uninstall
+ {
+ items
+ {
+ 1
+ {
+ type = Base.Screwdriver,
+ count = 1,
+ keep = true,
+ equip = primary,
+ }
+ }
+ time = 100,
+ test = Vehicles.UninstallTest.Battery,
+ }
+ lua
+ {
+ create = Vehicles.Create.Battery,
+ update = Vehicles.Update.Battery,
+ }
+ }
+ }
+}
diff --git a/42.18/media/scripts/vehicles/template_towbar.txt b/42.18/media/scripts/vehicles/template_towbar.txt
new file mode 100644
index 0000000..68f1b24
--- /dev/null
+++ b/42.18/media/scripts/vehicles/template_towbar.txt
@@ -0,0 +1,284 @@
+module Base
+{
+ model towbarModel
+ {
+ mesh = vehicles/Towbar,
+ texture = Vehicles/Towbar_Texture,
+ scale = 0.01,
+ }
+
+ model towbarModelLarge
+ {
+ mesh = vehicles/Towbar,
+ texture = Vehicles/Towbar_Texture,
+ scale = 0.02022,
+ }
+
+ template vehicle Towbar
+ {
+ part towbar
+ {
+ model towbar0
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModel,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModel,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModel,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ part towbarLarge
+ {
+ model towbar0
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.0,
+ }
+ model towbar1
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.1,
+ }
+ model towbar2
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.2,
+ }
+ model towbar3
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.3,
+ }
+ model towbar4
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.4,
+ }
+ model towbar5
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.5,
+ }
+ model towbar6
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.6,
+ }
+ model towbar7
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.7,
+ }
+ model towbar8
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.8,
+ }
+ model towbar9
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 1.9,
+ }
+ model towbar10
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.0,
+ }
+ model towbar11
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.1,
+ }
+ model towbar12
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.2,
+ }
+ model towbar13
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.3,
+ }
+ model towbar14
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.4,
+ }
+ model towbar15
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.5,
+ }
+ model towbar16
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.6,
+ }
+ model towbar17
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.7,
+ }
+ model towbar18
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.8,
+ }
+ model towbar19
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 2.9,
+ }
+ model towbar20
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.0,
+ }
+ model towbar21
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.1,
+ }
+ model towbar22
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.2,
+ }
+ model towbar23
+ {
+ file = towbarModelLarge,
+ offset = 0 -0.3 3.3,
+ }
+
+ area = Engine,
+ mechanicRequireKey = false,
+ lua
+ {
+ create = BTtow.Create.towbar,
+ init = BTtow.Init.towbar,
+ }
+ }
+
+ }
+}
diff --git a/42.18/mod.info b/42.18/mod.info
new file mode 100644
index 0000000..58ed90d
--- /dev/null
+++ b/42.18/mod.info
@@ -0,0 +1,10 @@
+name=Towbars
+id=hrsys_towbars_testing
+poster=../common/media/textures/preview.png
+description=Tow bars for vehicle-to-vehicle towing.
+author=Riggs0
+category=vehicle
+icon=../common/media/textures/tow_bar_icon.png
+url=https://hudsonriggs.systems
+modversion=1.0.2
+versionMin=42.18.0
diff --git a/Migration Guide.md b/Migration Guide.md
deleted file mode 100644
index 34eb6f9..0000000
--- a/Migration Guide.md
+++ /dev/null
@@ -1,111 +0,0 @@
-# Migration Guide
-
-Auto-converted from PDF using `uv run --with pypdf`.
-
-## Page 1
-
-Migration Guide
-Registries
-In version 42.13, the way to add some identifiers has been changed. The IDs are
-used in scripts and recipes.
-Identifiers:
-CharacterTrait
-CharacterProfession
-ItemTag
-Brochure
-Flier
-ItemBodyLocation
-ItemType
-MoodleType
-WeaponCategory
-Newspaper
-AmmoType
-To add these identifiers and use them in scripts, you need to add them using Lua in
-the registries.lua file. This file must be stored in the media folder. IT MUST
-HAVE THIS EXACT NAME, and it is loaded before scripts and before any other Lua
-files.
-Example of adding IDs:
-CharacterTrait.register("testmod:nimblefingers")
-CharacterProfession.register("testmod:thief")
-ItemTag.register("testmod:bobbypin")
-Brochure.register("testmod:Village")
-Flier.register("testmod:BirdMilk")
-ItemBodyLocation.register("testmod:MiddleFinger")
-ItemType.register("testmod:gamedev")
-MoodleType.register("testmod:Happy")
-WeaponCategory.register("testmod:birb")
-Newspaper.register("testmod:BirdNews", List.of("BirdKnews_July30",
-"BirdKnews_July2"))
-local item_key = ItemKey.new("bullets_666", ItemType.NORMAL)
-AmmoType.register("testmod:duck_bullets", item_key)
-Example of usage in scripts:
-
-## Page 2
-
-character_trait_definition testmod:nimblefingers
-{
-IsProfessionTrait = false,
-DisabledInMultiplayer = false,
-CharacterTrait = testmod:nimblefingers,
-Cost = 3,
-UIName = UI_trait_nimblefingers,
-UIDescription = UI_trait_nimblefingersDesc,
-XPBoosts = Lockpicking=2,
-GrantedRecipes =
-Lockpicking;AlarmCheck;CreateBobbyPin;CreateBobbyPin2,
-}
-craftRecipe CreateBobbyPin
-{
-timedAction = Making,
-Time = 40,
-Tags = InHandCraft;CanBeDoneInDark,
-needTobeLearn = true,
-inputs
-{
-item 1 tags[base:screwdriver] mode:keep
-flags[MayDegradeLight;Prop1],
-item 1 [Base.Paperclip],
-}
-outputs
-{
-item 1 TestMod.HandmadeBobbyPin,
-}
-}
-character_profession_definition testmod:thief
-{
-CharacterProfession = testmod:thief,
-Cost = 2,
-UIName = UI_prof_Thief,
-IconPathName = profession_burglar2,
-XPBoosts = Nimble=3;Sneak=2;Lightfoot=1;Lockpicking=2,
-GrantedTraits = testmod:nimblefingers,
-}
-item HandmadeBobbyPin
-{
-Weight = 0.01,
-ItemType = base:normal,
-Icon = HandmadeBobbyPin,
-Tags = testmod:bobbypin,
-Tooltip = Tooltip_TestMod_BobbyPin,
-WorldStaticModel = Paperclip,
-
-## Page 3
-
-Lua
-Scripts
-P.S.
-Future content patches will include modding changes based on your reports and
-requests, and new API documentation will gradually become available.
-}
-More details check in mod example
-Some Lua API has been modified. If something has stopped working for you, check
-the decompiled Java code.
-There will be more API changes in upcoming unstable patches.
-Item Script: DisplayName has been removed. Now translation is taken only
-from Module.ItemId.
-Item Script: Type has been renamed to ItemType and requires
-the ItemType registry.
-Tags now require the ItemTag registry.
-It will also be useful to study script examples from the base game. They are now
-generated from Java code and are read by game as before.
-
diff --git a/Migration Guide.pdf b/Migration Guide.pdf
deleted file mode 100644
index 5d10120..0000000
Binary files a/Migration Guide.pdf and /dev/null differ
diff --git a/Project Zomboid_ API for Inventory Items.md b/Project Zomboid_ API for Inventory Items.md
deleted file mode 100644
index bc5cb06..0000000
--- a/Project Zomboid_ API for Inventory Items.md
+++ /dev/null
@@ -1,1375 +0,0 @@
-# Project Zomboid_ API for Inventory Items
-
-Auto-converted from PDF using `uv run --with pypdf`.
-
-## Page 1
-
-Project Zomboid API for Inventory Items
-Document version 1.0
-
-## Page 2
-
-Contents
-Contents .................................................................................................................................. 1 Introduction ............................................................................................................................ 2 1. General description ........................................................................................................... 3 2. Timed Action Architecture ................................................................................................ 4 2.1 Modification of the ‘new’ function ................................................................................. 5 2.2 Realisation of the “getDuration” function ...................................................................... 8 2.3 Split of the “perform” function into “perform” and “complete” ....................................... 9 3. Realisation features of the long Timed Actions ............................................................ 12 4. The use of the sendClientCommand .............................................................................. 14 5. Synchronisation of the creation, deletion and modification of items and objects .... 16
-1
-
-## Page 3
-
-Introduction
-This document is for the modders for Project Zomboid and it has the new
-system
-of
-Inventory
-Items
-manipulation
-described,
-which
-was
-developed
-to
-prevent
-cheating.
-All inventory Items processing in multiplayer are transferred to the server side.
-That
-means,
-that
-while
-it’s
-still
-possible
-for
-the
-modification
-on
-the
-client
-side
-to
-create
-an
-Inventory
-Item
-and
-put
-it
-to
-the
-inventory,
-such
-an
-item
-won’t
-be
-available
-for
-interaction
-and
-will
-be
-deleted
-from
-the
-inventory
-after
-relogin.
-All items should be created on the server side, and then transferred to the
-client.
-Thus
-an
-Inventory
-Item
-will
-be
-in
-the
-player’s
-inventory
-on
-both
-client
-and
-server
-sides.
-2
-
-## Page 4
-
-1. General description
-The main way of an Inventory Item creation, deletion and manipulation is to
-create
-it
-in
-a
-Timed
-Action.
-The
-Inventory
-Item
-creation,
-deletion
-and
-modification
-should
-be
-done
-on
-the
-server
-side,
-modifying
-the
-player’s
-inventory
-on
-the
-server
-side.
-The
-player’s
-inventory
-on
-the
-client
-side
-is
-modified
-subsequently
-to
-be
-identical
-to
-the
-one
-on
-the
-server
-side.
-An alternative way of an Inventory Item creation, deletion and manipulation is
-to
-send
-a
-command
-using
-“
-sendCommand
-“
-or
-“
-sendClientCommand
-“
-functions.
-To
-do
-that
-you
-have
-to
-implement
-your
-command
-processor,
-that
-will
-receive
-the
-necessary
-data,
-perform
-cheating
-checks,
-create/delete/modify
-the
-Inventory
-Item
-on
-the
-server
-side,
-and
-send
-the
-corresponding
-packets
-to
-clients
-for
-synchronisation.
-This
-way
-could
-come
-in
-handy
-while
-dealing
-with
-the
-Inventory
-Item
-manipulation
-that
-doesn’t
-come
-from
-the
-user's
-actions,
-e.g.
-admin
-powers.
-3
-
-## Page 5
-
-2. Timed Action Architecture
-The new implementation of Timed Action allows to execute Timed Action on
-both
-the
-server
-and
-the
-client.
-To adapt the existing Timed Action to the new architecture the following
-should
-be
-done:
-1) Move the Timed Action file from media/lua/client folder to
-media/lua/shared
-one.
-This
-will
-allow
-the
-script
-to
-be
-loaded
-by
-the
-client
-as
-well
-as
-the
-server.
-2) Make sure there is a variable of the same name with the same value for
-each
-new
-function
-argument.
-3) Create the getDuration function, which returns the execution time
-of
-the
-Timed
-Action.
-4) Move some code from the ‘perform’ function to the new ‘complete’
-function.
-a) ‘perform’ function: i) performs the actions that are only needed on the client, e.g.
-animation
-and
-sound
-management;
-ii) doesn’t contain any manipulations with any items or
-objects;
-iii) is performed on the client as well as in singleplayer mode; b) ‘complete’ function: i) contains manipulations with items and objects only; ii) is performed on the server as well as in single-player mode; iii) is executed after ‘perform’ in single-player mode; 5) Add the call of the function sending changes to clients to the ‘complete’
-function
-(see
-section
-4).
-Let’s take a closer look at the required changes.
-4
-
-## Page 6
-
-2.1 Modification of the ‘new’ function
-When running a Timed Action, the game sends it to the server side and
-restores
-the
-Timed
-Action
-Lua
-object.
-This
-is
-necessary
-for
-execution
-of
-‘getDuration’
-and
-‘complete’
-functions
-on
-the
-server
-side.
-The
-server
-receives
-the
-list
-of
-the
-‘new’
-function
-arguments
-and
-searches
-for
-their
-values
-in
-the
-variables
-(fields)
-of
-the
-object.
-Let’s assume there is a following code:
-function ISPlaceTrap:new(playerObj, trap, damage, maxTime) local o = ISBaseTimedAction.new(self, playerObj); o.square = character:getCurrentSquare(); o.weapon = trap; o.damage = damage / 20; o.maxTime = maxTime; return o; end
-Code piece 1 - Example of the incorrect ‘new’ function The game won’t be able to restore such an object on the server side, because
-the
-argument
-names
-aren’t
-the
-same
-as
-the
-names
-of
-the
-variables
-in
-the
-object.
-The following changes should be made in order for the server to be able to
-send
-a
-Timed
-Action
-to
-the
-server:
-1) Rename playerObj to character . There is a realisation of the
-ISBaseTimedAction.new
-function
-In
-Code
-piece
-2,
-and
-we
-could
-see
-that
-this
-function
-saves
-the
-argument
-into
-the
-variable
-named
-‘character’.
-2) Rename the “ trap ” argument to “ weapon ”, because the value of the
-“
-trap
-”
-argument
-is
-saved
-into
-the
-“
-weapon
-”
-variable.
-3) It is necessary to get rid of saving the changed “ damage ” value into a
-variable
-with
-the
-same
-name.
-Otherwise,
-if
-we
-run
-such
-a
-timed
-action
-with
-“
-damage
-”
-equal
-to
-1000,
-then
-an
-object
-will
-be
-created
-with
-the
-“
-damage
-”
-variable
-equal
-to
-50.
-When
-this
-object
-is
-transferred
-to
-the
-server
-side,
-this
-constructor
-is
-called
-again,
-and
-it’ll
-get
-the
-value
-of
-50
-as
-an
-argument
-of
-damage.
-As
-a
-result,
-the
-server
-will
-get
-an
-object
-with
-5
-
-## Page 7
-
-damage value equal to 2. To prevent this from happening, the incoming
-values
-should
-be
-kept
-unchanged.
-4) The execution time of the Timed Action also shouldn’t be transferred as
-an
-argument,
-as
-it
-created
-a
-vulnerability
-where
-a
-cheater
-can
-perform
-Timed
-Actions
-instantly
-changing
-the
-time
-argument.
-To
-avoid
-this,
-the
-game
-uses
-the
-“
-getDuration
-”
-function
-to
-calculate
-the
-Timed
-Action
-execution
-time
-on
-the
-server
-side.
-function ISBaseTimedAction:new (character) local o = {} setmetatable(o, self) self.__index = self o.character = character; o.stopOnWalk = true; o.stopOnRun = true; o.stopOnAim = true; o.caloriesModifier = 1; o.maxTime = -1; return o end
-Code piece 2 - realisation of the ISBaseTimedAction:new function After following all the suggestions you’ll get the code given in Code piece 3.
-function ISPlaceTrap:new(character, weapon) local o = ISBaseTimedAction.new(self, character); o.square = character:getCurrentSquare(); o.weapon = weapon; o.maxTime = o:getDuration(); return o; end
-Code piece 3 - An example of the correct ‘new’ function implementation Currently the game supports the following data types for the arguments of the
-‘new’
-function:
-1. BaseVehicle 2. BloodBodyPartType 3. BodyPart 4. Boolean 5. CraftRecipe 6. Double 7. EvolvedRecipe
-6
-
-## Page 8
-
-8. FluidContainer 9. Integer 10. InventoryItem 11. IsoAnimal 12. IsoDeadBody 13. IsoGridSquare 14. IsoHutch.NestBox 15. IsoObject 16. IsoPlayer 17. ItemContainer 18. KahluaTableImpl 19. MultiStageBuilding.Stage 20. PZNetKahluaTableImpl 21. Recipe 22. Resource 23. SpriteConfigManager.ObjectInfo 24. String 25. VehiclePart 26. VehicleWindow 27. null Serialisation realisation is in the PZNetKahluaTableImpl class.
-It
-should
-also
-be
-noted
-that
-while
-passing
-an
-object,
-the
-client
-sends
-information
-to
-the
-server
-to
-search
-for
-the
-corresponding
-object
-on
-the
-server
-side.
-After
-that
-the
-server
-searches
-for
-the
-corresponding
-object
-and
-uses
-it
-as
-an
-argument.
-For
-this
-reason,
-the
-game
-cannot
-transfer
-as
-an
-argument
-an
-object
-created
-on
-the
-client
-side.
-7
-
-## Page 9
-
-2.2 Realisation of the “getDuration” function
-The “ getDuration ” function is used by the server to fetch the time needed
-to
-execute
-the
-Timed
-Action.
-However,
-the
-client
-can
-also
-use
-this
-function,
-using
-the
-following
-code
-in
-the
-‘new’
-function.
-o.maxTime = o:getDuration();
-Code piece 4 - the usage of the getDuration function in the ‘new’ function to
-receive
-the
-execution
-time
-of
-a
-Timed
-Action
-The “ getDuration ” function returns the execution time of the Timed Action
-in
-cycles.
-To
-convert
-the
-time
-in
-seconds
-to
-the
-value
-that
-the
-“
-getDuration
-”
-function
-should
-return,
-divide
-the
-time
-by
-0.02.
-That
-is,
-for
-a
-Timed
-Action
-that
-should
-last
-1
-second,
-the
-“
-getDuration
-”
-function
-should
-return
-a
-value
-of
-50.
-For
-an
-action
-that
-is
-executed
-instantaneously,
-it
-is
-recommended
-to
-return
-the
-value
-‘1’.
-For infinite Timed Actions the “ getDuration ” function should return the
-value
-‘-1’.
-Also this function should return the value 1 if the TimedActionInstant cheat is
-enabled.
-Here’s an example usage of this function for Timed Action that lasts 1 second.
-function ISPlaceTrap:getDuration() if self.character:isTimedActionInstant() then return 1; end return 50 end
-Code piece 5 - Typical realisation of the getDuration function, returning the 1
-second
-time
-8
-
-## Page 10
-
-2.3 Split of the “perform” function into “perform” and “complete” It is necessary to split the “ perform ” function into 2 functions: “ perform ”
-and
-“
-complete
-”.
-Both
-of
-these
-functions
-are
-executed
-at
-the
-end
-of
-Timed
-Action
-execution.
-The
-client
-executes
-only
-the
-“
-perform
-”
-function,
-and
-the
-server
-executes
-only
-the
-“
-complete
-”
-one.
-Singleplayer
-first
-executes
-“
-perform
-”
-and
-then
-“
-complete
-”.
-The “ perform ” function must contain code that is not related to modifying
-items
-and
-objects.
-This
-can
-be
-code
-to
-control
-sounds,
-animation,
-interaction
-with
-UI,
-etc.
-The “ complete ” function must contain code for items and objects
-modification.
-This
-function
-cannot
-contain
-code
-that
-cannot
-be
-called
-on
-the
-server
-side.
-Let’s take a look at such a split, using ISAddFuelAction as an example.
-function ISAddFuelAction:perform() self.character:stopOrTriggerSound(self.sound) self.item:setJobDelta(0.0); if self.item:IsDrainable() then self.item:Use() else self.character:removeFromHands(self.item) self.character:getInventory():Remove(self.item) end local cf = self.campfire local args = { x = cf.x, y = cf.y, z = cf.z, fuelAmt = self.fuelAmt } CCampfireSystem.instance:sendCommand(self.character, 'addFuel', args) -- needed to remove from queue / start next. ISBaseTimedAction.perform(self); end
-Code piece 6 - The obsolete realisation of the ‘perform’ function for the
-ISAddFuelAction
-timed
-action.
-This function contains the code to control the interface and sound, manipulate
-objects,
-and
-the
-synchronisation
-code
-using
-the
-command.
-9
-
-## Page 11
-
-Let’s keep in this function only the code for interface and sound control.
-function ISAddFuelAction:perform() self.character:stopOrTriggerSound(self.sound) self.item:setJobDelta(0.0); -- needed to remove from queue / start next. ISBaseTimedAction.perform(self); end
-Code piece 7 - The new realisation of the “perform” function of the
-ISAddFuelAction
-timed
-action
-We’ll create a new function called “ complete ” and put the code there to
-manipulate
-the
-objects.
-We also have to perform an action that was previously executed by sending the
-“
-addFuel
-”
-command.
-If
-we
-look
-at
-the
-“
-addFuel
-”
-command
-realisation,
-we’ll
-find
-the
-following
-code:
-function SCampfireSystemCommand(command, player, args) if command == 'addFuel' then local campfire = campfireAt(args.x, args.y, args.z) if campfire then campfire:addFuel(args.fuelAmt) end …
-Code piece 8 - Implementation of the “addFuel” command handler Apparently, the coordinates of the campfire were sent to the server. Then the
-server
-was
-finding
-the
-campfire
-on
-the
-map
-and
-performed
-the
-“
-addFuel
-”
-function.
-As
-the
-“
-complete
-”
-function
-is
-going
-to
-be
-performed
-at
-the
-server
-side,
-we
-don’t
-have
-to
-send
-the
-command
-anymore.
-We
-can
-directly
-get
-an
-object
-and
-perform
-the
-required
-function.
-So we change the Code piece 9 to the code from the command handler you can
-see
-in
-Code
-piece
-10:
-CCampfireSystem.instance:sendCommand(self.character, 'addFuel', args)
-Code piece 9 - Sending of the addFuel command from client to server
-local campfire = campfireAt(args.x, args.y, args.z)
-10
-
-## Page 12
-
-if campfire then campfire:addFuel(args.fuelAmt) end
-Code piece 10 - Search and modifying of the object We also change the call of the local “ campfireAt ” function to its realisation,
-which
-is
-given
-above.
-local function campfireAt(x, y, z) return SCampfireSystem.instance:getLuaObjectAt(x, y, z) end
-Code piece 11 - Realisation of the campfireAt function As a result we get the following function:
-function ISAddFuelAction:complete() if self.item:IsDrainable() then self.item:UseAndSync() else self.character:removeFromHands(self.item) self.character:getInventory():Remove(self.item) sendRemoveItemFromContainer(self.character:getInventory(),self.item) end local campfire = SCampfireSystem.instance:getLuaObjectAt(self.campfire.x, self.campfire.y, self.campfire.z) if campfire then campfire:addFuel(self.fuelAmt) end return true end
-Code piece 12 - The new realisation of the ‘complete’ function of the
-ISAddFuelAction
-timed
-action
-11
-
-## Page 13
-
-3. Realisation features of the long
-Timed
-Actions
-To implement Timed Actions that perform actions during execution, such as
-pouring
-liquids
-or
-reading
-books,
-it
-is
-necessary
-to
-use
-an
-additional
-Anim
-Event
-emulation
-API.
-To implement such an action the implementation of the “ serverStart “
-function
-is
-needed,
-which
-is
-executed
-on
-the
-server
-side
-when
-the
-Timed
-Action
-starts.
-In
-this
-function,
-call
-the
-“
-emulateAnimEvent
-”
-function
-to
-set
-up
-the
-AnimEvent
-emulator.
-After
-that,
-you
-need
-to
-write
-an
-implementation
-of
-the
-“animEvent”
-function,
-which
-will
-be
-called
-periodically
-to
-execute
-a
-part
-of
-the
-whole
-action.
-To
-stop
-such
-an
-action,
-the
-server-side
-function
-self.netAction:forceComplete()
-should
-be
-called.
-You
-could
-use
-the
-“
-self.netAction:getProgress()
-”
-function
-to
-get
-the
-progress
-of
-the
-action
-in
-the
-animEvent
-function
-on
-the
-server
-side.
-Let’s take a look at the examples. First here’s the realisation of the serverStart
-function:
-function ISChopTreeAction:serverStart() self.axe = self.character:getPrimaryHandItem() emulateAnimEvent(self.netAction, 1500, "ChopTree", nil) end
-Code piece 13 - Example of realisation of the serverStart function The “ emulateAnimEvent ” function takes the following arguments: 1) “NetTimedAction” - always equals to self.netAction ; 2) “duration” - a period in milliseconds; 3) “event” - a name, that will be sent to “ animEvent ” function; 4) “parameter” - a stock parameter for the “ animEvent ” function. An example of implementation of the “ animEvent ” function is shown in the
-code
-piece
-14.
-function ISChopTreeAction:animEvent(event, parameter)
-12
-
-## Page 14
-
-if not isClient() then if event == 'ChopTree' then self.tree:WeaponHit(self.character, self.axe) self:useEndurance() if self.tree:getObjectIndex() == -1 then if isServer() then self.netAction:forceComplete() else self:forceComplete() end end end else if event == 'ChopTree' then self.tree:WeaponHitEffects(self.character, self.axe) end end end
-Code piece 14 - Example of realisation of the “animEvent” function This function on the client only reproduces tree chopping effects using the
-“
-WeaponHitEffects
-”
-function.
-But
-in
-singleplayer,
-and
-on
-the
-server,
-this
-function
-calculates
-the
-damage
-the
-tree
-takes
-from
-axe
-hit
-using
-the
-self.tree:WeaponHit(self.character,
-self.axe)
-function.
-Finally, when the tree is chopped down - it stops the infinite Timed Action:
-if isServer() then self.netAction:forceComplete() else self:forceComplete() end Code piece 15 - Realisation of the end of the Timed Action
-13
-
-## Page 15
-
-4. The use of the
-sendClientCommand
-The “ sendClientCommand ” function is executed on the client and sends a
-command
-whose
-handler
-is
-on
-the
-server.
-If
-this
-function
-is
-called
-in
-a
-singleplayer
-game
-the
-handler
-of
-this
-command
-will
-also
-be
-called
-by
-the
-game.
-The “ sendClientCommand ” function has the following arguments: 1) IsoPlayer “player” - optional argument, player’s object; 2) String “module” - the name of the module for which the command is
-sent;
-3) String “command” - the command name; 4) KahluaTable “args” - table with arguments.
-sendClientCommand(self.player, "vehicle", "getKey", { vehicle = self.vehicle:getId() })
-Code piece 16 - Sending the getKey command for the ‘vehicle’ module with an
-argument
-vehicle
-=
-self.vehicle:getId()
-Upon receiving the client command the server calls an OnClientCommand
-event
-with
-the
-following
-arguments:
-module,
-command,
-player,
-args.
-local VehicleCommands = {} local Commands = {} function Commands.getKey(player, args) local vehicle = getVehicleById(args.vehicle) if vehicle and checkPermissions(player, Capability.UseMechanicsCheat) then local item = vehicle:createVehicleKey() if item then player:getInventory():AddItem(item); sendAddItemToContainer(player:getInventory(), item); end else noise('no such vehicle id='..tostring(args.vehicle)) end end VehicleCommands.OnClientCommand = function(module, command, player, args) if module == 'vehicle' and Commands[command] then
-14
-
-## Page 16
-
-Commands[command](player, args) end end Events.OnClientCommand.Add(VehicleCommands.OnClientCommand)
-Code piece 17 - Example of implementation of the getKey command handler on
-the
-server
-side
-The Events.OnClientCommand.Add() function call sets the
-OnClientCommand
-event
-handler.
-After
-executing
-this
-function,
-receiving
-any
-command
-will
-result
-in
-calling
-the
-function
-indicated
-as
-an
-argument.
-In
-this
-case
-the
-VehicleCommands.OnClientCommand
-function
-is
-the
-handler
-for
-the
-OnClientCommand
-event.
-The VehicleCommands.OnClientCommand function checks if the
-module
-is
-set
-equal
-to
-“vehicle”
-and
-if
-there
-is
-a
-function
-with
-the
-name
-corresponding
-to
-the
-command
-name
-in
-the
-Commands
-table.
-Then
-the
-function
-calls
-the
-corresponding
-function
-by
-sending
-it
-the
-“player”
-and
-“args”
-arguments.
-The Commands.getKey function is a handler of the “ getKey ” command.
-This
-function
-works
-only
-if
-the
-player
-has
-the
-UseMechanicsCheat
-permission.
-In
-case
-if
-the
-player
-has
-the
-appropriate
-permission
-and
-the
-required
-vehicle
-is
-found
-on
-the
-server
-side
-then
-it
-creates
-the
-key
-InventoryItem
-on
-the
-server
-side
-using
-the
-“
-createVehicleKey
-”
-function,
-and
-after
-that
-adds
-it
-to
-the
-player’s
-inventory
-and
-sends
-it
-back
-to
-the
-client
-side.
-15
-
-## Page 17
-
-5. Synchronisation of the creation,
-deletion
-and
-modification
-of
-items
-and
-objects
-All object changes made in the ‘complete’ function should be sent to clients.
-The
-following
-functions
-are
-needed
-for
-that:
-1. sendAddItemToContainer - adds the InventoryItem to the container; 2. sendRemoveItemFromContainer - deletes the InventoryItem from the
-container;
-3. syncItemFields - synchronises the following variables: condition,
-remoteControlID,
-uses,
-currentAmmoCount,
-haveBeenRepaired,
-taintedWater,
-wetness,
-dirtyness,
-bloodLevel,
-hungChange,
-weight,
-alreadyReadPages,
-customPages,
-customName,
-attachedSlot,
-attachedSlotType,
-attachedToModel,
-fluidContainer,
-moddata;
-4. syncItemModData - synchronises the moddata; 5. syncHandWeaponFields - synchronises the following variables:
-currentAmmoCount,
-roundChambered,
-containsClip,
-spentRoundCount,
-spentRoundChambered,
-isJammed,
-maxRange,
-minRangeRanged,
-clipSize,
-reloadTime,
-recoilDelay,
-aimingTime,
-hitChance,
-minAngle,
-minDamage,
-maxDamage,
-attachments,
-moddata;
-6. sendItemStats - synchronises the following variables: uses, usedDelta,
-isFood,
-frozen,
-heat,
-cookingTime,
-minutesToCook,
-minutesToBurn,
-hungChange,
-calories,
-carbohydrates,
-lipids,
-proteins,
-thirstChange,
-fluReduction,
-painReduction,
-endChange,
-reduceFoodSickness,
-stressChange,
-fatigueChange,
-unhappyChange,
-boredomChange,
-poisonPower,
-poisonDetectionLevel,
-extraItems,
-alcoholic,
-baseHunger,
-customName,
-tainted,
-fluidAmount,
-isFluidContainer,
-isCooked,
-isBurnt,
-freezingTime,
-name;
-7. transmitCompleteItemToClients - add an object to the map; 8. transmitRemoveItemFromSquare - delete an object from the map; 9. sync - synchronise the object change on the map; 10. transmitUpdatedSpriteToClients - synchronise the changed sprite.
-16
-
-## Page 18
-
-Here are some examples of the code for how to create, delete and manipulate
-items:
-local candle = instanceItem("Base.Candle") self.character:getInventory():AddItem(candle); sendAddItemToContainer(self.character:getInventory(), candle);
-Code piece 18 - Adding InventoryItem to the inventory
-self.character:removeFromHands(self.weapon) self.character:getInventory():Remove(self.weapon); sendRemoveItemFromContainer(self.character:getInventory(), self.weapon);
-Code piece 19 - Deleting the InventoryItem from the inventory Here are some examples of the code for how to create, delete and manipulate
-objects.
-local trap = IsoTrap.new(self.weapon, self.square:getCell(), self.square); self.square:AddTileObject(trap); trap:transmitCompleteItemToClients();
-Code piece 20 - Adding the IsoObject to the map
-self.trap:getSquare():transmitRemoveItemFromSquare(self.trap); self.trap:removeFromWorld(); self.trap:removeFromSquare();
-Code piece 21 - Deleting the IsoObject from the map
-self.generator:setActivated(self.activate) self.generator:sync()
-Code piece 22 - IsoObject synchronisation on the map
-17
-
diff --git a/Project Zomboid_ API for Inventory Items.pdf b/Project Zomboid_ API for Inventory Items.pdf
deleted file mode 100644
index c52f6d6..0000000
Binary files a/Project Zomboid_ API for Inventory Items.pdf and /dev/null differ
diff --git a/mod.info b/mod.info
index 66d2d14..09039f1 100644
--- a/mod.info
+++ b/mod.info
@@ -1,10 +1,10 @@
name=Towbars
id=hrsys_towbars
poster=common/media/textures/preview.png
-description=Towbar Towing Towed Towing Towbars. The thrid
+description=Tow bars for vehicle-to-vehicle towing.
author=Riggs0
category=vehicle
versionMin=42.13.0
url=https://hudsonriggs.systems
-modversion=1.0.0
+modversion=1.0.2
icon=common/media/textures/tow_bar_icon.png