Working 42.20 MP

This commit is contained in:
2026-08-19 12:12:12 -04:00
parent e403323017
commit e99f3bbff9
22 changed files with 2262 additions and 825 deletions
+128 -285
View File
@@ -1,11 +1,9 @@
if not TowBarMod then TowBarMod = {} end
if not TowBarMod.Hook then TowBarMod.Hook = {} end
require("TowBar/VehicleCompatibility")
local DefaultTowBarTowMass = 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 FreeRollTickInterval = 15
local freeRollTickCounter = 0
@@ -44,7 +42,7 @@ local function applyFreeRollingTowState(vehicle)
modData.towBarOriginalBrakingForce = vehicle:getBrakingForce()
end
storeOriginalVehicleCall(vehicle, modData, "towBarOriginalParkingBrakeOn", "isParkingBrakeOn")
storeOriginalVehicleCall(vehicle, modData, "towBarOriginalParkingBrake", "isParkingBrake")
storeOriginalVehicleCall(vehicle, modData, "towBarOriginalParkingBrake", "getParkingBrake")
storeOriginalVehicleCall(vehicle, modData, "towBarOriginalHandbrake", "isHandbrake")
local configuredTowMass = TowBarMod.Config and tonumber(TowBarMod.Config.towedVehicleRollingMass)
@@ -61,10 +59,13 @@ local function applyFreeRollingTowState(vehicle)
tryVehicleCall(vehicle, "setHandbrake", false)
end
vehicle:constraintChanged()
vehicle:updateTotalMass()
-- Match the working wrecker path. Recalculating total mass here would
-- immediately replace the temporary towing mass in multiplayer; creating
-- the rigid constraint below notifies Bullet of the changed vehicle state.
end
TowBarMod.Hook.applyFreeRollingTowState = applyFreeRollingTowState
local function restoreFreeRollingTowState(vehicle, modData)
if not vehicle or not modData then return end
@@ -88,25 +89,6 @@ local function restoreFreeRollingTowState(vehicle, modData)
vehicle:updateTotalMass()
end
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()
@@ -116,15 +98,8 @@ 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)
-- SP and MP now share the same authoritative attach lifecycle.
sendClientCommand(playerObj, "towbar", "attachTowBar", args)
end
local TowbarVariantSize = 24
@@ -138,6 +113,8 @@ local TowbarVisualScale = 2.5
local TowbarModelLength = 0.9714089036
local TowbarScaledModelLength = TowbarModelLength * TowbarVisualScale
local TowbarModelHalfLength = TowbarScaledModelLength / 2
local VanillaScaleMin = 1.5
local VanillaScaleMax = 2.0
local function getTowbarFrontEdgeZ(script)
if not script then return nil end
@@ -178,16 +155,87 @@ local function getTowbarModelSlot(script)
return math.max(0, math.min(TowbarMaxIndex, index))
end
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()
return model and model:getScale() or 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)
return modelScale >= (configuredMin or VanillaScaleMin)
and modelScale <= (configuredMax or VanillaScaleMax)
end
local function getTowbarIndexVanilla(script)
if not script then return nil end
local ok, shape = pcall(function() return script:getPhysicsChassisShape() end)
if not ok or not shape then return nil end
local zOk, shapeZ = pcall(function() return shape:z() end)
if not zOk or type(shapeZ) ~= "number" then return nil end
local z = shapeZ / 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 return nil end
local index = math.floor((maxAbsTowZ + 0.1 - 1.0) * 10)
return math.max(0, math.min(TowbarMaxIndex, index))
end
local function getLegacyTowbarModelSlot(script)
local useNormalPart = isVanillaScale(script)
local index = getTowbarIndexVanilla(script)
if not useNormalPart then
index = getTowbarIndexSmallScale(script) or index
if index == nil then
local offset = TowBarMod.Config and tonumber(TowBarMod.Config.smallScaleTowbarIndexOffset) or 2
index = math.max(0, math.min(TowbarMaxIndex, offset))
end
end
return index, useNormalPart
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
local ki5Part = vehicle:getPartById("towbarKI5")
if normalPart == nil and largePart == nil and ki5Part == 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
if ki5Part then ki5Part:setModelVisible("towbar" .. j, false) end
end
if not isVisible then
@@ -201,8 +249,16 @@ local function setTowBarModelVisible(vehicle, isVisible)
return
end
local index = getTowbarModelSlot(script)
local part = normalPart
local isKi5 = TowBarMod.Compatibility.isKi5Vehicle(vehicle)
local index, useNormalPart
if isKi5 then
index = getTowbarModelSlot(script)
else
index, useNormalPart = getLegacyTowbarModelSlot(script)
end
local part = isKi5 and ki5Part or (useNormalPart and normalPart or largePart)
if part == nil and not isKi5 then part = normalPart or largePart end
if part and index ~= nil then
part:setModelVisible("towbar" .. index, true)
end
@@ -259,23 +315,6 @@ local function resolveTowAttachmentsForPair(towingVehicle, towedVehicle, towedMo
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
@@ -293,142 +332,22 @@ local function isActiveTowBarTowedVehicle(vehicle, modData)
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()
applyFreeRollingTowState(towedVehicle)
towingModData["isTowingByTowBar"] = true
towingModData["towed"] = false
towingModData["towBarTowedVehicleId"] = towedVehicle:getId()
towingModData["towBarTowingVehicleId"] = nil
towingModData["towBarExpectedAttachment"] = attachmentA
towedModData["isTowingByTowBar"] = true
towedModData["towed"] = true
towedModData["towBarTowedVehicleId"] = nil
towedModData["towBarTowingVehicleId"] = towingVehicle:getId()
towedModData["towBarExpectedAttachment"] = attachmentB
towingVehicle:transmitModData()
towedVehicle:transmitModData()
TowBarMod.Hook.setVehicleScriptWithTowBarHidden(towedVehicle, "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. Server/SP reconciliation owns recovery and
-- never refunds or consumes an item while loading a save.
TowBarMod.Hook.setVehiclePostAttach(nil, vehicle)
return
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
-- Leave reciprocal saved state untouched. The authoritative audit will
-- reconnect it when both vehicle chunks are loaded.
end
function TowBarMod.Hook.setVehiclePostAttach(playerObj, towedVehicle, retriesLeft)
function TowBarMod.Hook.setVehiclePostAttach(playerObj, towedVehicle, knownTowingVehicle)
if not towedVehicle then return end
local towedModData = towedVehicle:getModData()
if not isActiveTowBarTowedVehicle(towedVehicle, towedModData) then return end
if towedModData and towedModData.towBarOriginalScriptName then
local towingVehicle = knownTowingVehicle or towedVehicle:getVehicleTowedBy()
if not towingVehicle then return end
-- The rigid primitive passes the authoritative towing vehicle because MP
-- reciprocal getters can lag behind accepted local constraint submission.
if towedModData and towedModData.towBarOriginalScriptName
and towedVehicle:getScriptName() ~= towedModData.towBarOriginalScriptName then
TowBarMod.Hook.setVehicleScriptWithTowBarHidden(towedVehicle, towedModData.towBarOriginalScriptName)
end
local towingVehicle = towedVehicle:getVehicleTowedBy()
if towingVehicle then
local attachmentA, attachmentB = resolveTowAttachmentsForPair(towingVehicle, towedVehicle, towedModData)
if attachmentA and attachmentB then
@@ -443,7 +362,6 @@ function TowBarMod.Hook.setVehiclePostAttach(playerObj, towedVehicle, retriesLef
towingVehicle:transmitModData()
towedVehicle:transmitModData()
end
TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
end
end
@@ -459,35 +377,9 @@ function TowBarMod.Hook.performAttachTowBar(playerObj, towingVehicle, towedVehic
if #(TowBarMod.Utils.getHookTypeVariants(towingVehicle, towedVehicle, true)) == 0 then return end
local towBarItem = getTowBarItem(playerObj)
if towBarItem ~= nil and not (isClient() and isMultiplayer()) then
sendClientCommand(playerObj, "towbar", "consumeTowBar", { itemId = towBarItem:getID() })
end
if towBarItem == nil then return end
playerObj:setPrimaryHandItem(nil)
TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
local towingModData = towingVehicle:getModData()
local towedModData = towedVehicle:getModData()
towedModData.towBarOriginalScriptName = towedVehicle:getScriptName()
applyFreeRollingTowState(towedVehicle)
towingModData["isTowingByTowBar"] = true
towingModData["towed"] = false
towingModData["towBarTowedVehicleId"] = towedVehicle:getId()
towingModData["towBarTowingVehicleId"] = nil
towingModData["towBarExpectedAttachment"] = attachmentA
towedModData["isTowingByTowBar"] = true
towedModData["towed"] = true
towedModData["towBarTowedVehicleId"] = nil
towedModData["towBarTowingVehicleId"] = towingVehicle:getId()
towedModData["towBarExpectedAttachment"] = attachmentB
towingVehicle:transmitModData()
towedVehicle:transmitModData()
-- Match the known-good rigid tow path: fake trailer + vanilla attach command.
TowBarMod.Hook.setVehicleScriptWithTowBarHidden(towedVehicle, "notTowingA_Trailer")
local args = {
vehicleA = towingVehicle:getId(),
vehicleB = towedVehicle:getId(),
@@ -496,7 +388,6 @@ function TowBarMod.Hook.performAttachTowBar(playerObj, towingVehicle, towedVehic
itemId = towBarItem and towBarItem:getID() or nil
}
sendTowAttachCommand(playerObj, args)
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle))
end
function TowBarMod.Hook.cleanupDetachedTowBar(towingVehicle, towedVehicle)
@@ -538,7 +429,6 @@ function TowBarMod.Hook.cleanupDetachedTowBar(towingVehicle, towedVehicle)
towingVehicle:transmitModData()
towedVehicle:transmitModData()
TowBarMod.Hook.lastAutoReattachAtByVehicle[towingVehicle:getId()] = nil
setTowBarModelVisible(towedVehicle, false)
end
@@ -547,46 +437,6 @@ function TowBarMod.Hook.performDetachTowBar(playerObj, towingVehicle, towedVehic
local args = { towingVehicle = towingVehicle:getId(), vehicle = towedVehicle:getId() }
sendClientCommand(playerObj, "towbar", "detachTowBar", args)
TowBarMod.Hook.cleanupDetachedTowBar(towingVehicle, towedVehicle)
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)
@@ -634,14 +484,6 @@ local function keepTowBarVehiclesFreeRolling()
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
@@ -725,20 +567,11 @@ function TowBarMod.Hook.deattachTowBarAction(playerObj, vehicle)
end
function TowBarMod.Hook.OnSpawnVehicle(vehicle)
recoverTowBarVehicleAfterLoad(nil, vehicle, 6)
-- Server persistence snapshots own attach recovery after vehicle streaming.
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)
-- Server persistence broadcasts the same attach snapshot used by a fresh pair.
end
---------------------------------------------------------------------------
@@ -749,8 +582,9 @@ 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()))
local ki5Part = vehicle:getPartById("towbarKI5")
if normalPart == nil and largePart == nil and ki5Part == nil then
print("[TowBar DEV] No towbar model part found on vehicle " .. tostring(vehicle:getScriptName()))
return
end
local script = vehicle:getScript()
@@ -765,10 +599,11 @@ function TowBarMod.Hook.devShowAllTowbarModels(playerObj, vehicle)
print("[TowBar DEV] chassisShape.z = " .. tostring(chassisZ) .. ", half = " .. tostring(halfZ))
print("[TowBar DEV] frontEdgeZ = " .. tostring(script and getTowbarFrontEdgeZ(script) or nil) .. ", 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")
print("[TowBar DEV] Showing towbar0..towbar23 on all 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
if ki5Part then ki5Part:setModelVisible("towbar" .. j, true) end
end
vehicle:doDamageOverlay()
end
@@ -777,14 +612,16 @@ 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()))
local ki5Part = vehicle:getPartById("towbarKI5")
if normalPart == nil and largePart == nil and ki5Part == nil then
print("[TowBar DEV] No towbar model 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
if ki5Part then ki5Part:setModelVisible("towbar" .. j, false) end
end
vehicle:doDamageOverlay()
end
@@ -793,24 +630,32 @@ 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()))
local ki5Part = vehicle:getPartById("towbarKI5")
if normalPart == nil and largePart == nil and ki5Part == nil then
print("[TowBar DEV] No towbar model part found on vehicle " .. tostring(vehicle:getScriptName()))
return
end
local localIndex = math.max(0, math.min(TowbarMaxIndex, index % TowbarVariantSize))
local useLargePart = index >= TowbarVariantSize
local selectedPartId = "towbar"
if index >= TowbarVariantSize * 2 then
selectedPartId = "towbarKI5"
elseif index >= TowbarVariantSize then
selectedPartId = "towbarLarge"
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
if ki5Part then ki5Part:setModelVisible("towbar" .. j, false) end
end
local part = useLargePart and largePart or normalPart
local part = selectedPartId == "towbarKI5" and ki5Part
or (selectedPartId == "towbarLarge" and largePart or normalPart)
if part == nil then
part = normalPart or largePart
part = normalPart or largePart or ki5Part
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()))
print("[TowBar DEV] Showing only towbar" .. tostring(localIndex) .. " on part " .. selectedPartId .. " (Z offset " .. tostring(1.0 + localIndex * 0.1) .. ") on " .. tostring(vehicle:getScriptName()))
if part then
part:setModelVisible("towbar" .. localIndex, true)
end
@@ -821,6 +666,4 @@ 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)
Events.OnTick.Add(keepTowBarVehiclesFreeRolling)