Saving and Loading Fixed
This commit is contained in:
@@ -6,17 +6,28 @@ local LANDTRAIN_DEBUG = true
|
||||
local LANDTRAIN_FALLBACK_MAX_DIST_SQ = 3.25 -- ~1.8 tiles
|
||||
local LANDTRAIN_FALLBACK_MAX_DZ = 0.9
|
||||
local LANDTRAIN_FRONT_SQL_ID_KEY = "landtrainTowbarFrontSqlId"
|
||||
local LANDTRAIN_FRONT_ID_KEY_LEGACY = "landtrainTowbarFrontVehicleId"
|
||||
local LANDTRAIN_FRONT_ATTACHMENT_A_KEY = "landtrainTowbarAttachmentA"
|
||||
local LANDTRAIN_FRONT_ATTACHMENT_B_KEY = "landtrainTowbarAttachmentB"
|
||||
local LANDTRAIN_REAR_SQL_ID_KEY = "landtrainTowbarRearSqlId"
|
||||
local LANDTRAIN_SAVED_POS_X_KEY = "landtrainSavedPosX"
|
||||
local LANDTRAIN_SAVED_POS_Y_KEY = "landtrainSavedPosY"
|
||||
local LANDTRAIN_SAVED_POS_Z_KEY = "landtrainSavedPosZ"
|
||||
local LANDTRAIN_SAVED_DIR_KEY = "landtrainSavedDir"
|
||||
|
||||
local function emitLandtrainLog(line)
|
||||
print(line)
|
||||
if type(writeLog) == "function" then
|
||||
pcall(writeLog, "Landtrain", line)
|
||||
end
|
||||
end
|
||||
|
||||
local function ltLog(msg)
|
||||
if not LANDTRAIN_DEBUG then return end
|
||||
print("[Landtrain] " .. tostring(msg))
|
||||
emitLandtrainLog("[Landtrain] " .. tostring(msg))
|
||||
end
|
||||
|
||||
local function ltInfo(msg)
|
||||
print("[Landtrain][Info] " .. tostring(msg))
|
||||
emitLandtrainLog("[Landtrain][Info] " .. tostring(msg))
|
||||
end
|
||||
|
||||
ltInfo("UnlimitedTowbarChains loaded. debug=" .. tostring(LANDTRAIN_DEBUG))
|
||||
@@ -27,6 +38,115 @@ local function vehLabel(vehicle)
|
||||
return tostring(vehicle:getId()) .. ":" .. tostring(name)
|
||||
end
|
||||
|
||||
local _landtrainPairPickPosA = Vector3f.new()
|
||||
local _landtrainPairPickPosB = Vector3f.new()
|
||||
|
||||
local function hasAttachmentById(vehicle, attachmentId)
|
||||
if vehicle == nil or attachmentId == nil then return false end
|
||||
local script = vehicle:getScript()
|
||||
if script == nil then return false end
|
||||
return script:getAttachmentById(attachmentId) ~= nil
|
||||
end
|
||||
|
||||
local function getTowbarStyleAttachmentPair(towingVehicle, towedVehicle)
|
||||
local attachmentA = nil
|
||||
local attachmentB = nil
|
||||
|
||||
if hasAttachmentById(towingVehicle, "trailer") then
|
||||
attachmentA = "trailer"
|
||||
elseif towingVehicle ~= nil then
|
||||
attachmentA = towingVehicle:getTowAttachmentSelf()
|
||||
end
|
||||
|
||||
if hasAttachmentById(towedVehicle, "trailerfront") then
|
||||
attachmentB = "trailerfront"
|
||||
elseif towedVehicle ~= nil then
|
||||
attachmentB = towedVehicle:getTowAttachmentSelf()
|
||||
end
|
||||
|
||||
if attachmentA == attachmentB then
|
||||
if attachmentA == "trailer" and hasAttachmentById(towedVehicle, "trailerfront") then
|
||||
attachmentB = "trailerfront"
|
||||
elseif attachmentA == "trailerfront" and hasAttachmentById(towingVehicle, "trailer") then
|
||||
attachmentA = "trailer"
|
||||
end
|
||||
end
|
||||
|
||||
if attachmentA == nil then attachmentA = "trailer" end
|
||||
if attachmentB == nil then attachmentB = "trailerfront" end
|
||||
return attachmentA, attachmentB
|
||||
end
|
||||
|
||||
local function chooseLandtrainAttachmentPair(towingVehicle, towedVehicle, preferredA, preferredB)
|
||||
local seen = {}
|
||||
local candidates = {}
|
||||
local function addCandidate(a, b)
|
||||
if a == nil or b == nil or a == b then return end
|
||||
local key = tostring(a) .. "|" .. tostring(b)
|
||||
if seen[key] then return end
|
||||
seen[key] = true
|
||||
table.insert(candidates, { a = a, b = b })
|
||||
end
|
||||
|
||||
addCandidate(preferredA, preferredB)
|
||||
addCandidate(towingVehicle and towingVehicle:getTowAttachmentSelf() or nil, towedVehicle and towedVehicle:getTowAttachmentSelf() or nil)
|
||||
addCandidate("trailer", "trailerfront")
|
||||
addCandidate("trailerfront", "trailer")
|
||||
|
||||
local bestPair = nil
|
||||
local bestDistSq = nil
|
||||
local bestDz = nil
|
||||
for _, pair in ipairs(candidates) do
|
||||
if hasAttachmentById(towingVehicle, pair.a) and hasAttachmentById(towedVehicle, pair.b) then
|
||||
local posA = towingVehicle:getAttachmentWorldPos(pair.a, _landtrainPairPickPosA)
|
||||
local posB = towedVehicle:getAttachmentWorldPos(pair.b, _landtrainPairPickPosB)
|
||||
if posA ~= nil and posB ~= nil then
|
||||
local dx = posA:x() - posB:x()
|
||||
local dy = posA:y() - posB:y()
|
||||
local dz = math.abs(posA:z() - posB:z())
|
||||
local distSq = dx * dx + dy * dy + (posA:z() - posB:z()) * (posA:z() - posB:z())
|
||||
if bestPair == nil or distSq < bestDistSq or (distSq == bestDistSq and dz < bestDz) then
|
||||
bestPair = pair
|
||||
bestDistSq = distSq
|
||||
bestDz = dz
|
||||
end
|
||||
elseif bestPair == nil then
|
||||
bestPair = pair
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if bestPair ~= nil then
|
||||
return bestPair.a, bestPair.b
|
||||
end
|
||||
|
||||
local fallbackA = preferredA or (towingVehicle and towingVehicle:getTowAttachmentSelf()) or "trailer"
|
||||
local fallbackB = preferredB or (towedVehicle and towedVehicle:getTowAttachmentSelf()) or "trailerfront"
|
||||
if fallbackA == fallbackB then
|
||||
if fallbackA == "trailerfront" then
|
||||
fallbackA = "trailer"
|
||||
else
|
||||
fallbackB = "trailerfront"
|
||||
end
|
||||
end
|
||||
return fallbackA, fallbackB
|
||||
end
|
||||
|
||||
local function chooseLandtrainTowbarPair(towingVehicle, towedVehicle, preferredA, preferredB)
|
||||
local towbarA, towbarB = getTowbarStyleAttachmentPair(towingVehicle, towedVehicle)
|
||||
if towbarA ~= towbarB and hasAttachmentById(towingVehicle, towbarA) and hasAttachmentById(towedVehicle, towbarB) then
|
||||
return towbarA, towbarB
|
||||
end
|
||||
|
||||
if preferredA ~= nil and preferredB ~= nil and preferredA ~= preferredB
|
||||
and hasAttachmentById(towingVehicle, preferredA)
|
||||
and hasAttachmentById(towedVehicle, preferredB) then
|
||||
return preferredA, preferredB
|
||||
end
|
||||
|
||||
return chooseLandtrainAttachmentPair(towingVehicle, towedVehicle, preferredA, preferredB)
|
||||
end
|
||||
|
||||
local function dumpTowState(prefix, vehicle)
|
||||
if not LANDTRAIN_DEBUG then return end
|
||||
if vehicle == nil then
|
||||
@@ -131,7 +251,7 @@ local function getVehicleBySqlIdSafe(sqlId)
|
||||
|
||||
for i = 0, vehicles:size() - 1 do
|
||||
local vehicle = vehicles:get(i)
|
||||
if vehicle ~= nil and vehicle:getSqlId and vehicle:getSqlId() == targetSqlId then
|
||||
if vehicle ~= nil and vehicle.getSqlId and vehicle:getSqlId() == targetSqlId then
|
||||
return vehicle
|
||||
end
|
||||
end
|
||||
@@ -140,11 +260,13 @@ end
|
||||
|
||||
local function storeLandtrainFrontLinkData(towingVehicle, towedVehicle, attachmentA, attachmentB)
|
||||
if towingVehicle == nil or towedVehicle == nil then return end
|
||||
local towingModData = towingVehicle:getModData()
|
||||
local towedModData = towedVehicle:getModData()
|
||||
if towedModData == nil then return end
|
||||
if towingModData == nil or towedModData == nil then return end
|
||||
if towingVehicle == towedVehicle or towingVehicle:getId() == towedVehicle:getId() then
|
||||
towingModData[LANDTRAIN_REAR_SQL_ID_KEY] = nil
|
||||
towingVehicle:transmitModData()
|
||||
towedModData[LANDTRAIN_FRONT_SQL_ID_KEY] = nil
|
||||
towedModData[LANDTRAIN_FRONT_ID_KEY_LEGACY] = nil
|
||||
towedModData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] = nil
|
||||
towedModData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] = nil
|
||||
towedVehicle:transmitModData()
|
||||
@@ -152,15 +274,22 @@ local function storeLandtrainFrontLinkData(towingVehicle, towedVehicle, attachme
|
||||
return
|
||||
end
|
||||
|
||||
local towingSqlId = towingVehicle:getSqlId and towingVehicle:getSqlId() or -1
|
||||
local towingSqlId = towingVehicle.getSqlId and towingVehicle:getSqlId() or -1
|
||||
if towingSqlId ~= nil and towingSqlId >= 0 then
|
||||
towedModData[LANDTRAIN_FRONT_SQL_ID_KEY] = towingSqlId
|
||||
else
|
||||
towedModData[LANDTRAIN_FRONT_SQL_ID_KEY] = nil
|
||||
end
|
||||
towedModData[LANDTRAIN_FRONT_ID_KEY_LEGACY] = nil
|
||||
towedModData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] = attachmentA or towingVehicle:getTowAttachmentSelf() or "trailer"
|
||||
towedModData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] = attachmentB or towedVehicle:getTowAttachmentSelf() or "trailerfront"
|
||||
local towedSqlId = towedVehicle.getSqlId and towedVehicle:getSqlId() or -1
|
||||
if towedSqlId ~= nil and towedSqlId >= 0 then
|
||||
towingModData[LANDTRAIN_REAR_SQL_ID_KEY] = towedSqlId
|
||||
else
|
||||
towingModData[LANDTRAIN_REAR_SQL_ID_KEY] = nil
|
||||
end
|
||||
towingVehicle:transmitModData()
|
||||
local resolvedA, resolvedB = chooseLandtrainTowbarPair(towingVehicle, towedVehicle, attachmentA, attachmentB)
|
||||
towedModData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] = resolvedA
|
||||
towedModData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] = resolvedB
|
||||
towedVehicle:transmitModData()
|
||||
end
|
||||
|
||||
@@ -168,8 +297,19 @@ local function clearLandtrainFrontLinkData(vehicle)
|
||||
if vehicle == nil then return end
|
||||
local modData = vehicle:getModData()
|
||||
if modData == nil then return end
|
||||
local frontSqlId = tonumber(modData[LANDTRAIN_FRONT_SQL_ID_KEY])
|
||||
local vehicleSqlId = vehicle.getSqlId and vehicle:getSqlId() or nil
|
||||
if frontSqlId ~= nil and vehicleSqlId ~= nil and vehicleSqlId >= 0 then
|
||||
local frontVehicle = getVehicleBySqlIdSafe(frontSqlId)
|
||||
if frontVehicle ~= nil then
|
||||
local frontModData = frontVehicle:getModData()
|
||||
if frontModData ~= nil and tonumber(frontModData[LANDTRAIN_REAR_SQL_ID_KEY]) == vehicleSqlId then
|
||||
frontModData[LANDTRAIN_REAR_SQL_ID_KEY] = nil
|
||||
frontVehicle:transmitModData()
|
||||
end
|
||||
end
|
||||
end
|
||||
modData[LANDTRAIN_FRONT_SQL_ID_KEY] = nil
|
||||
modData[LANDTRAIN_FRONT_ID_KEY_LEGACY] = nil
|
||||
modData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] = nil
|
||||
modData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] = nil
|
||||
vehicle:transmitModData()
|
||||
@@ -187,6 +327,7 @@ local function isTowbarManagedVehicle(vehicle)
|
||||
or modData["isChangedTowedAttachment"] == true
|
||||
or modData["towBarChangedAttachmentId"] ~= nil
|
||||
or modData[LANDTRAIN_FRONT_SQL_ID_KEY] ~= nil
|
||||
or modData[LANDTRAIN_REAR_SQL_ID_KEY] ~= nil
|
||||
end
|
||||
|
||||
local function restoreVehicleTowbarDefaults(vehicle)
|
||||
@@ -244,9 +385,9 @@ local function restoreVehicleTowbarDefaults(vehicle)
|
||||
modData.towBarOriginalMass = nil
|
||||
modData.towBarOriginalBrakingForce = nil
|
||||
modData[LANDTRAIN_FRONT_SQL_ID_KEY] = nil
|
||||
modData[LANDTRAIN_FRONT_ID_KEY_LEGACY] = nil
|
||||
modData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] = nil
|
||||
modData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] = nil
|
||||
modData[LANDTRAIN_REAR_SQL_ID_KEY] = nil
|
||||
|
||||
if TowBarMod and TowBarMod.Hook and TowBarMod.Hook.clearReapplied then
|
||||
TowBarMod.Hook.clearReapplied(vehicle)
|
||||
@@ -277,7 +418,7 @@ local function reconcileTowbarSplitVehicle(vehicle)
|
||||
end
|
||||
|
||||
modData["towed"] = (frontVehicle ~= nil)
|
||||
modData["isTowingByTowBar"] = (frontVehicle ~= nil) or (hasTowbarRear == true)
|
||||
modData["isTowingByTowBar"] = (modData["towed"] == true) or (hasTowbarRear == true)
|
||||
vehicle:transmitModData()
|
||||
end
|
||||
|
||||
@@ -344,7 +485,7 @@ end
|
||||
|
||||
local function sanitizeLoadedTowLinks()
|
||||
local vehicles = getLoadedVehicles()
|
||||
if #vehicles == 0 then return end
|
||||
if #vehicles == 0 then return vehicles end
|
||||
|
||||
for _, vehicle in ipairs(vehicles) do
|
||||
local front = vehicle:getVehicleTowedBy()
|
||||
@@ -357,252 +498,382 @@ local function sanitizeLoadedTowLinks()
|
||||
end
|
||||
end
|
||||
|
||||
for _, vehicle in ipairs(vehicles) do
|
||||
reconcileTowbarSplitAround(vehicle)
|
||||
refreshTowBarState(vehicle)
|
||||
local frontVehicle = vehicle:getVehicleTowedBy()
|
||||
if frontVehicle ~= nil and isTowbarManagedVehicle(vehicle) then
|
||||
storeLandtrainFrontLinkData(frontVehicle, vehicle)
|
||||
end
|
||||
end
|
||||
return vehicles
|
||||
end
|
||||
|
||||
local _landtrainLoadSanityTicks = 0
|
||||
local function onLandtrainLoadSanityTick()
|
||||
if _landtrainLoadSanityTicks <= 0 then
|
||||
Events.OnTick.Remove(onLandtrainLoadSanityTick)
|
||||
return
|
||||
local function setModDataNumberIfChanged(modData, key, value)
|
||||
if modData == nil or key == nil or value == nil then return false end
|
||||
local current = tonumber(modData[key])
|
||||
if current ~= nil and math.abs(current - value) < 0.0001 then
|
||||
return false
|
||||
end
|
||||
modData[key] = value
|
||||
return true
|
||||
end
|
||||
|
||||
_landtrainLoadSanityTicks = _landtrainLoadSanityTicks - 1
|
||||
if (_landtrainLoadSanityTicks % 30) ~= 0 then
|
||||
return
|
||||
end
|
||||
sanitizeLoadedTowLinks()
|
||||
end
|
||||
|
||||
local function scheduleLandtrainLoadSanity()
|
||||
_landtrainLoadSanityTicks = 300 -- run for ~5 seconds
|
||||
Events.OnTick.Remove(onLandtrainLoadSanityTick)
|
||||
Events.OnTick.Add(onLandtrainLoadSanityTick)
|
||||
sanitizeLoadedTowLinks()
|
||||
end
|
||||
|
||||
local landtrainPendingRestoreVehicleIds = {}
|
||||
local landtrainReapplyTickCounter = 0
|
||||
|
||||
local function hasLandtrainFrontLinkData(vehicle)
|
||||
local function saveLandtrainVehiclePosition(vehicle)
|
||||
if vehicle == nil then return false end
|
||||
local modData = vehicle:getModData()
|
||||
if modData == nil then return false end
|
||||
return modData[LANDTRAIN_FRONT_SQL_ID_KEY] ~= nil
|
||||
|
||||
local changed = false
|
||||
changed = setModDataNumberIfChanged(modData, LANDTRAIN_SAVED_POS_X_KEY, vehicle:getX()) or changed
|
||||
changed = setModDataNumberIfChanged(modData, LANDTRAIN_SAVED_POS_Y_KEY, vehicle:getY()) or changed
|
||||
changed = setModDataNumberIfChanged(modData, LANDTRAIN_SAVED_POS_Z_KEY, vehicle:getZ()) or changed
|
||||
if vehicle.getDirectionAngle then
|
||||
local ok, angle = pcall(function()
|
||||
return vehicle:getDirectionAngle()
|
||||
end)
|
||||
if ok and angle ~= nil then
|
||||
changed = setModDataNumberIfChanged(modData, LANDTRAIN_SAVED_DIR_KEY, angle) or changed
|
||||
end
|
||||
end
|
||||
|
||||
local function queueLandtrainTowbarRestore(vehicle)
|
||||
if vehicle == nil then return end
|
||||
if changed then
|
||||
vehicle:transmitModData()
|
||||
end
|
||||
return changed
|
||||
end
|
||||
|
||||
local function isLandtrainTowbarLink(frontVehicle, rearVehicle)
|
||||
if frontVehicle == nil or rearVehicle == nil then return false end
|
||||
if frontVehicle == rearVehicle or frontVehicle:getId() == rearVehicle:getId() then return false end
|
||||
if frontVehicle:getVehicleTowing() ~= rearVehicle or rearVehicle:getVehicleTowedBy() ~= frontVehicle then
|
||||
return false
|
||||
end
|
||||
|
||||
local frontModData = frontVehicle:getModData()
|
||||
local rearModData = rearVehicle:getModData()
|
||||
if frontModData == nil or rearModData == nil then return false end
|
||||
return isTowbarManagedVehicle(frontVehicle)
|
||||
or isTowbarManagedVehicle(rearVehicle)
|
||||
or tonumber(rearModData[LANDTRAIN_FRONT_SQL_ID_KEY]) ~= nil
|
||||
or tonumber(frontModData[LANDTRAIN_REAR_SQL_ID_KEY]) ~= nil
|
||||
end
|
||||
|
||||
local function saveLandtrainTowbarLink(frontVehicle, rearVehicle, attachmentA, attachmentB)
|
||||
if not isLandtrainTowbarLink(frontVehicle, rearVehicle) then return false end
|
||||
storeLandtrainFrontLinkData(frontVehicle, rearVehicle, attachmentA, attachmentB)
|
||||
local changed = false
|
||||
changed = saveLandtrainVehiclePosition(frontVehicle) or changed
|
||||
changed = saveLandtrainVehiclePosition(rearVehicle) or changed
|
||||
return changed
|
||||
end
|
||||
|
||||
local function saveActiveLandtrainTowbarSnapshot(vehicles)
|
||||
local loaded = vehicles or getLoadedVehicles()
|
||||
local savedLinks = 0
|
||||
for _, rearVehicle in ipairs(loaded) do
|
||||
local frontVehicle = rearVehicle:getVehicleTowedBy()
|
||||
if isLandtrainTowbarLink(frontVehicle, rearVehicle) then
|
||||
local rearModData = rearVehicle:getModData()
|
||||
local attachmentA, attachmentB = chooseLandtrainTowbarPair(
|
||||
frontVehicle,
|
||||
rearVehicle,
|
||||
rearModData and rearModData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] or nil,
|
||||
rearModData and rearModData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] or nil
|
||||
)
|
||||
if saveLandtrainTowbarLink(frontVehicle, rearVehicle, attachmentA, attachmentB) then
|
||||
savedLinks = savedLinks + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
return savedLinks
|
||||
end
|
||||
|
||||
local function callVehicleMethodSafe(vehicle, methodName, ...)
|
||||
if vehicle == nil then return false end
|
||||
local method = vehicle[methodName]
|
||||
if type(method) ~= "function" then return false end
|
||||
local ok = pcall(method, vehicle, ...)
|
||||
return ok
|
||||
end
|
||||
|
||||
local function restoreSavedVehiclePosition(vehicle)
|
||||
if vehicle == nil then return false end
|
||||
local modData = vehicle:getModData()
|
||||
if modData == nil then return end
|
||||
if modData == nil then return false end
|
||||
|
||||
-- Drop legacy runtime-id metadata; it is not stable across save/load sessions.
|
||||
if modData[LANDTRAIN_FRONT_ID_KEY_LEGACY] ~= nil then
|
||||
modData[LANDTRAIN_FRONT_ID_KEY_LEGACY] = nil
|
||||
vehicle:transmitModData()
|
||||
end
|
||||
|
||||
local vehicleSqlId = vehicle.getSqlId and vehicle:getSqlId() or nil
|
||||
local frontVehicleSqlId = tonumber(modData[LANDTRAIN_FRONT_SQL_ID_KEY])
|
||||
if frontVehicleSqlId ~= nil and vehicleSqlId ~= nil and vehicleSqlId >= 0 and frontVehicleSqlId == vehicleSqlId then
|
||||
modData[LANDTRAIN_FRONT_SQL_ID_KEY] = nil
|
||||
modData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] = nil
|
||||
modData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] = nil
|
||||
modData["towed"] = false
|
||||
vehicle:transmitModData()
|
||||
ltLog("queueLandtrainTowbarRestore cleared invalid self front sqlId for " .. vehLabel(vehicle))
|
||||
end
|
||||
|
||||
if modData["towed"] == true
|
||||
or modData["isTowingByTowBar"] == true
|
||||
or modData[LANDTRAIN_FRONT_SQL_ID_KEY] ~= nil
|
||||
or modData.towBarOriginalScriptName ~= nil
|
||||
or modData.towBarOriginalMass ~= nil
|
||||
or modData.towBarOriginalBrakingForce ~= nil then
|
||||
landtrainPendingRestoreVehicleIds[vehicle:getId()] = true
|
||||
end
|
||||
end
|
||||
|
||||
local function tryLandtrainTowbarRestore(vehicle, playerObj)
|
||||
if vehicle == nil then return true end
|
||||
local modData = vehicle:getModData()
|
||||
if modData == nil then return true end
|
||||
|
||||
local frontVehicle = vehicle:getVehicleTowedBy()
|
||||
if frontVehicle == vehicle then
|
||||
ltLog("tryLandtrainTowbarRestore cleared self front link for " .. vehLabel(vehicle))
|
||||
clearLandtrainFrontLinkData(vehicle)
|
||||
modData["towed"] = false
|
||||
vehicle:transmitModData()
|
||||
return true
|
||||
end
|
||||
if frontVehicle ~= nil then
|
||||
if modData["isTowingByTowBar"] == true or modData["towed"] == true then
|
||||
storeLandtrainFrontLinkData(frontVehicle, vehicle)
|
||||
if TowBarMod and TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
|
||||
TowBarMod.Hook.setVehiclePostAttach(playerObj, vehicle)
|
||||
end
|
||||
if TowBarMod and TowBarMod.Hook and TowBarMod.Hook.markReapplied then
|
||||
TowBarMod.Hook.markReapplied(vehicle)
|
||||
end
|
||||
setTowBarModelVisibleForVehicle(vehicle, true)
|
||||
refreshTowBarState(frontVehicle)
|
||||
refreshTowBarState(vehicle)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if modData["towed"] ~= true and not hasLandtrainFrontLinkData(vehicle) then
|
||||
clearLandtrainFrontLinkData(vehicle)
|
||||
return true
|
||||
end
|
||||
|
||||
local frontVehicleSqlId = tonumber(modData[LANDTRAIN_FRONT_SQL_ID_KEY])
|
||||
if frontVehicleSqlId == nil then
|
||||
clearLandtrainFrontLinkData(vehicle)
|
||||
if modData["towed"] == true then
|
||||
modData["towed"] = false
|
||||
vehicle:transmitModData()
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local vehicleSqlId = vehicle.getSqlId and vehicle:getSqlId() or nil
|
||||
if vehicleSqlId ~= nil and vehicleSqlId >= 0 and frontVehicleSqlId == vehicleSqlId then
|
||||
ltLog("tryLandtrainTowbarRestore dropped self metadata sqlId for " .. vehLabel(vehicle))
|
||||
clearLandtrainFrontLinkData(vehicle)
|
||||
modData["towed"] = false
|
||||
vehicle:transmitModData()
|
||||
return true
|
||||
end
|
||||
|
||||
local savedFrontVehicle = getVehicleBySqlIdSafe(frontVehicleSqlId)
|
||||
if savedFrontVehicle == nil then
|
||||
return false
|
||||
end
|
||||
if savedFrontVehicle == vehicle or savedFrontVehicle:getId() == vehicle:getId() then
|
||||
ltLog("tryLandtrainTowbarRestore dropped self resolved front for " .. vehLabel(vehicle))
|
||||
clearLandtrainFrontLinkData(vehicle)
|
||||
modData["towed"] = false
|
||||
vehicle:transmitModData()
|
||||
return true
|
||||
end
|
||||
|
||||
local currentRear = savedFrontVehicle:getVehicleTowing()
|
||||
if currentRear ~= nil and currentRear ~= vehicle then
|
||||
local x = tonumber(modData[LANDTRAIN_SAVED_POS_X_KEY])
|
||||
local y = tonumber(modData[LANDTRAIN_SAVED_POS_Y_KEY])
|
||||
local z = tonumber(modData[LANDTRAIN_SAVED_POS_Z_KEY])
|
||||
if x == nil or y == nil or z == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
local attachmentA = modData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] or savedFrontVehicle:getTowAttachmentSelf() or "trailer"
|
||||
local attachmentB = modData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] or vehicle:getTowAttachmentSelf() or "trailerfront"
|
||||
local scriptA = savedFrontVehicle:getScript()
|
||||
local scriptB = vehicle:getScript()
|
||||
if scriptA == nil or scriptB == nil then
|
||||
return false
|
||||
end
|
||||
if scriptA:getAttachmentById(attachmentA) == nil then
|
||||
attachmentA = savedFrontVehicle:getTowAttachmentSelf() or "trailer"
|
||||
end
|
||||
if scriptB:getAttachmentById(attachmentB) == nil then
|
||||
attachmentB = vehicle:getTowAttachmentSelf() or "trailerfront"
|
||||
end
|
||||
if scriptA:getAttachmentById(attachmentA) == nil or scriptB:getAttachmentById(attachmentB) == nil then
|
||||
return false
|
||||
local moved = false
|
||||
moved = callVehicleMethodSafe(vehicle, "setX", x) or moved
|
||||
moved = callVehicleMethodSafe(vehicle, "setY", y) or moved
|
||||
moved = callVehicleMethodSafe(vehicle, "setZ", z) or moved
|
||||
callVehicleMethodSafe(vehicle, "setLx", x)
|
||||
callVehicleMethodSafe(vehicle, "setLy", y)
|
||||
callVehicleMethodSafe(vehicle, "setLz", z)
|
||||
|
||||
local dir = tonumber(modData[LANDTRAIN_SAVED_DIR_KEY])
|
||||
if dir ~= nil then
|
||||
callVehicleMethodSafe(vehicle, "setDirectionAngle", dir)
|
||||
end
|
||||
|
||||
playerObj = playerObj or getPlayer()
|
||||
if playerObj == nil then
|
||||
return false
|
||||
local cell = getCell()
|
||||
if cell ~= nil then
|
||||
local square = cell:getGridSquare(math.floor(x), math.floor(y), math.floor(z))
|
||||
if square ~= nil then
|
||||
callVehicleMethodSafe(vehicle, "setCurrent", square)
|
||||
end
|
||||
end
|
||||
|
||||
callVehicleMethodSafe(vehicle, "transmitPosition")
|
||||
return moved
|
||||
end
|
||||
|
||||
local function collectSavedLandtrainLinks(vehicles)
|
||||
local links = {}
|
||||
local seenRear = {}
|
||||
local frontSqlByRearSql = {}
|
||||
for _, vehicle in ipairs(vehicles) do
|
||||
local modData = vehicle and vehicle:getModData() or nil
|
||||
local rearSqlId = vehicle and vehicle.getSqlId and vehicle:getSqlId() or nil
|
||||
local frontSqlId = modData and tonumber(modData[LANDTRAIN_FRONT_SQL_ID_KEY]) or nil
|
||||
if rearSqlId ~= nil and rearSqlId >= 0 and frontSqlId ~= nil and frontSqlId >= 0 and frontSqlId ~= rearSqlId then
|
||||
frontSqlByRearSql[rearSqlId] = frontSqlId
|
||||
end
|
||||
end
|
||||
|
||||
local function getSavedDepthForRearSql(rearSqlId)
|
||||
if rearSqlId == nil or rearSqlId < 0 then return 0 end
|
||||
local depth = 0
|
||||
local seen = {}
|
||||
local cursorSqlId = rearSqlId
|
||||
while cursorSqlId ~= nil do
|
||||
local frontSqlId = frontSqlByRearSql[cursorSqlId]
|
||||
if frontSqlId == nil or frontSqlId < 0 then
|
||||
break
|
||||
end
|
||||
if seen[frontSqlId] then
|
||||
break
|
||||
end
|
||||
seen[frontSqlId] = true
|
||||
depth = depth + 1
|
||||
cursorSqlId = frontSqlId
|
||||
end
|
||||
return depth
|
||||
end
|
||||
|
||||
for _, rearVehicle in ipairs(vehicles) do
|
||||
if rearVehicle ~= nil then
|
||||
local rearId = rearVehicle:getId()
|
||||
if not seenRear[rearId] then
|
||||
local rearModData = rearVehicle:getModData()
|
||||
local frontSqlId = rearModData and tonumber(rearModData[LANDTRAIN_FRONT_SQL_ID_KEY]) or nil
|
||||
local frontVehicle = nil
|
||||
if frontSqlId ~= nil and frontSqlId >= 0 then
|
||||
frontVehicle = getVehicleBySqlIdSafe(frontSqlId)
|
||||
end
|
||||
if frontVehicle == nil then
|
||||
frontVehicle = rearVehicle:getVehicleTowedBy()
|
||||
end
|
||||
if frontVehicle ~= nil and frontVehicle ~= rearVehicle and frontVehicle:getId() ~= rearVehicle:getId() then
|
||||
local attachmentA, attachmentB = chooseLandtrainTowbarPair(
|
||||
frontVehicle,
|
||||
rearVehicle,
|
||||
rearModData and rearModData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] or nil,
|
||||
rearModData and rearModData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] or nil
|
||||
)
|
||||
table.insert(links, {
|
||||
frontVehicle = frontVehicle,
|
||||
rearVehicle = rearVehicle,
|
||||
attachmentA = attachmentA,
|
||||
attachmentB = attachmentB,
|
||||
depth = getSavedDepthForRearSql(rearVehicle.getSqlId and rearVehicle:getSqlId() or nil)
|
||||
})
|
||||
seenRear[rearId] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(links, function(a, b)
|
||||
if a.depth == b.depth then
|
||||
return a.rearVehicle:getId() < b.rearVehicle:getId()
|
||||
end
|
||||
return a.depth < b.depth
|
||||
end)
|
||||
return links
|
||||
end
|
||||
|
||||
local function detachSavedLandtrainLinks(links)
|
||||
local detached = 0
|
||||
for i = #links, 1, -1 do
|
||||
local link = links[i]
|
||||
local frontVehicle = link.frontVehicle
|
||||
local rearVehicle = link.rearVehicle
|
||||
if frontVehicle ~= nil and rearVehicle ~= nil
|
||||
and (frontVehicle:getVehicleTowing() == rearVehicle or rearVehicle:getVehicleTowedBy() == frontVehicle) then
|
||||
if breakConstraintSafe(frontVehicle, "load-rebuild-detach") then
|
||||
detached = detached + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
return detached
|
||||
end
|
||||
|
||||
local function attachSavedLandtrainLink(playerObj, link, delayTicks)
|
||||
local frontVehicle = link.frontVehicle
|
||||
local rearVehicle = link.rearVehicle
|
||||
if playerObj == nil or frontVehicle == nil or rearVehicle == nil then return false end
|
||||
if TowBarMod == nil or TowBarMod.Utils == nil or TowBarMod.Utils.updateAttachmentsForRigidTow == nil then
|
||||
return false
|
||||
end
|
||||
if frontVehicle == rearVehicle or frontVehicle:getId() == rearVehicle:getId() then
|
||||
return false
|
||||
end
|
||||
|
||||
TowBarMod.Utils.updateAttachmentsForRigidTow(savedFrontVehicle, vehicle, attachmentA, attachmentB)
|
||||
vehicle:setScriptName("notTowingA_Trailer")
|
||||
local scriptA = frontVehicle:getScript()
|
||||
local scriptB = rearVehicle:getScript()
|
||||
if scriptA == nil or scriptB == nil then
|
||||
return false
|
||||
end
|
||||
if scriptA:getAttachmentById(link.attachmentA) == nil or scriptB:getAttachmentById(link.attachmentB) == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
local originalScriptName = rearVehicle:getScriptName()
|
||||
TowBarMod.Utils.updateAttachmentsForRigidTow(frontVehicle, rearVehicle, link.attachmentA, link.attachmentB)
|
||||
rearVehicle:setScriptName("notTowingA_Trailer")
|
||||
local args = {
|
||||
vehicleA = savedFrontVehicle:getId(),
|
||||
vehicleB = vehicle:getId(),
|
||||
attachmentA = attachmentA,
|
||||
attachmentB = attachmentB
|
||||
vehicleA = frontVehicle:getId(),
|
||||
vehicleB = rearVehicle:getId(),
|
||||
attachmentA = link.attachmentA,
|
||||
attachmentB = link.attachmentB
|
||||
}
|
||||
if args.vehicleA == args.vehicleB then
|
||||
ltLog("tryLandtrainTowbarRestore blocked self attach args for " .. vehLabel(vehicle))
|
||||
clearLandtrainFrontLinkData(vehicle)
|
||||
modData["towed"] = false
|
||||
vehicle:transmitModData()
|
||||
return true
|
||||
return false
|
||||
end
|
||||
sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
|
||||
|
||||
local frontModData = frontVehicle:getModData()
|
||||
local rearModData = rearVehicle:getModData()
|
||||
if frontModData == nil or rearModData == nil then return false end
|
||||
frontModData["isTowingByTowBar"] = true
|
||||
rearModData["isTowingByTowBar"] = true
|
||||
rearModData["towed"] = true
|
||||
if rearModData.towBarOriginalScriptName == nil and originalScriptName ~= "notTowingA_Trailer" then
|
||||
rearModData.towBarOriginalScriptName = originalScriptName
|
||||
end
|
||||
if rearModData.towBarOriginalMass == nil then
|
||||
rearModData.towBarOriginalMass = rearVehicle:getMass()
|
||||
end
|
||||
if rearModData.towBarOriginalBrakingForce == nil then
|
||||
rearModData.towBarOriginalBrakingForce = rearVehicle:getBrakingForce()
|
||||
end
|
||||
frontVehicle:transmitModData()
|
||||
rearVehicle:transmitModData()
|
||||
storeLandtrainFrontLinkData(frontVehicle, rearVehicle, link.attachmentA, link.attachmentB)
|
||||
|
||||
if TowBarScheduleAction and TowBarMod and TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
|
||||
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, vehicle))
|
||||
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, delayTicks, TowBarMod.Hook.setVehiclePostAttach, rearVehicle))
|
||||
elseif TowBarMod and TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
|
||||
TowBarMod.Hook.setVehiclePostAttach(playerObj, vehicle)
|
||||
TowBarMod.Hook.setVehiclePostAttach(playerObj, rearVehicle)
|
||||
end
|
||||
if TowBarMod and TowBarMod.Hook and TowBarMod.Hook.markReapplied then
|
||||
TowBarMod.Hook.markReapplied(rearVehicle)
|
||||
end
|
||||
|
||||
savedFrontVehicle:getModData()["isTowingByTowBar"] = true
|
||||
modData["isTowingByTowBar"] = true
|
||||
modData["towed"] = true
|
||||
savedFrontVehicle:transmitModData()
|
||||
vehicle:transmitModData()
|
||||
storeLandtrainFrontLinkData(savedFrontVehicle, vehicle, attachmentA, attachmentB)
|
||||
setTowBarModelVisibleForVehicle(vehicle, true)
|
||||
refreshTowBarState(savedFrontVehicle)
|
||||
refreshTowBarState(vehicle)
|
||||
if TowBarMod and TowBarMod.Hook and TowBarMod.Hook.markReapplied then
|
||||
TowBarMod.Hook.markReapplied(vehicle)
|
||||
end
|
||||
setTowBarModelVisibleForVehicle(rearVehicle, true)
|
||||
refreshTowBarState(frontVehicle)
|
||||
refreshTowBarState(rearVehicle)
|
||||
return true
|
||||
end
|
||||
|
||||
local function processLandtrainPendingRestores()
|
||||
landtrainReapplyTickCounter = landtrainReapplyTickCounter + 1
|
||||
if landtrainReapplyTickCounter < 15 then
|
||||
local function runLandtrainSingleLoadRestorePass()
|
||||
local vehicles = sanitizeLoadedTowLinks()
|
||||
if vehicles == nil or #vehicles == 0 then
|
||||
ltInfo("Landtrain load rebuild: no loaded vehicles")
|
||||
return
|
||||
end
|
||||
landtrainReapplyTickCounter = 0
|
||||
|
||||
local resolvedVehicleIds = {}
|
||||
local playerObj = getPlayer()
|
||||
for vehicleId, _ in pairs(landtrainPendingRestoreVehicleIds) do
|
||||
local vehicle = getVehicleByIdSafe(vehicleId)
|
||||
if vehicle == nil or tryLandtrainTowbarRestore(vehicle, playerObj) then
|
||||
table.insert(resolvedVehicleIds, vehicleId)
|
||||
local links = collectSavedLandtrainLinks(vehicles)
|
||||
if #links == 0 then
|
||||
ltInfo("Landtrain load rebuild: no saved towbar links")
|
||||
return
|
||||
end
|
||||
|
||||
local playerObj = getPlayer() or getSpecificPlayer(0)
|
||||
if playerObj == nil then
|
||||
ltInfo("Landtrain load rebuild skipped: no player")
|
||||
return
|
||||
end
|
||||
if TowBarMod == nil or TowBarMod.Utils == nil or TowBarMod.Utils.updateAttachmentsForRigidTow == nil then
|
||||
ltInfo("Landtrain load rebuild skipped: TowBar utilities unavailable")
|
||||
return
|
||||
end
|
||||
|
||||
local movedVehicles = 0
|
||||
local movedSeen = {}
|
||||
for _, link in ipairs(links) do
|
||||
local frontId = link.frontVehicle:getId()
|
||||
local rearId = link.rearVehicle:getId()
|
||||
if not movedSeen[frontId] then
|
||||
if restoreSavedVehiclePosition(link.frontVehicle) then
|
||||
movedVehicles = movedVehicles + 1
|
||||
end
|
||||
movedSeen[frontId] = true
|
||||
end
|
||||
if not movedSeen[rearId] then
|
||||
if restoreSavedVehiclePosition(link.rearVehicle) then
|
||||
movedVehicles = movedVehicles + 1
|
||||
end
|
||||
movedSeen[rearId] = true
|
||||
end
|
||||
end
|
||||
|
||||
for _, vehicleId in ipairs(resolvedVehicleIds) do
|
||||
landtrainPendingRestoreVehicleIds[vehicleId] = nil
|
||||
local detachedCount = detachSavedLandtrainLinks(links)
|
||||
local attachedCount = 0
|
||||
for index, link in ipairs(links) do
|
||||
local delay = 10 + ((index - 1) * 5)
|
||||
if attachSavedLandtrainLink(playerObj, link, delay) then
|
||||
attachedCount = attachedCount + 1
|
||||
end
|
||||
end
|
||||
|
||||
local function queueLandtrainLoadedTowbarRestores()
|
||||
local vehicles = getLoadedVehicles()
|
||||
for _, vehicle in ipairs(vehicles) do
|
||||
local frontVehicle = vehicle:getVehicleTowedBy()
|
||||
if frontVehicle ~= nil and isTowbarManagedVehicle(vehicle) then
|
||||
storeLandtrainFrontLinkData(frontVehicle, vehicle)
|
||||
end
|
||||
queueLandtrainTowbarRestore(vehicle)
|
||||
end
|
||||
saveActiveLandtrainTowbarSnapshot(vehicles)
|
||||
ltInfo("Landtrain load rebuild complete moved=" .. tostring(movedVehicles)
|
||||
.. " detached=" .. tostring(detachedCount)
|
||||
.. " attached=" .. tostring(attachedCount)
|
||||
.. " links=" .. tostring(#links))
|
||||
end
|
||||
|
||||
local function onLandtrainSpawnVehicleEnd(vehicle)
|
||||
if vehicle == nil then return end
|
||||
local frontVehicle = vehicle:getVehicleTowedBy()
|
||||
if frontVehicle ~= nil and isTowbarManagedVehicle(vehicle) then
|
||||
storeLandtrainFrontLinkData(frontVehicle, vehicle)
|
||||
local landtrainLoadRebuildDone = false
|
||||
local landtrainSaveSnapshotTickCounter = 0
|
||||
local function onLandtrainSaveSnapshotTick()
|
||||
if not landtrainLoadRebuildDone then
|
||||
return
|
||||
end
|
||||
queueLandtrainTowbarRestore(vehicle)
|
||||
queueLandtrainTowbarRestore(vehicle:getVehicleTowedBy())
|
||||
queueLandtrainTowbarRestore(vehicle:getVehicleTowing())
|
||||
landtrainSaveSnapshotTickCounter = landtrainSaveSnapshotTickCounter + 1
|
||||
if landtrainSaveSnapshotTickCounter < 60 then
|
||||
return
|
||||
end
|
||||
landtrainSaveSnapshotTickCounter = 0
|
||||
saveActiveLandtrainTowbarSnapshot()
|
||||
end
|
||||
|
||||
local landtrainLoadRestoreDelayTicks = 0
|
||||
local function onLandtrainSingleLoadRestoreTick()
|
||||
if landtrainLoadRestoreDelayTicks > 0 then
|
||||
landtrainLoadRestoreDelayTicks = landtrainLoadRestoreDelayTicks - 1
|
||||
return
|
||||
end
|
||||
Events.OnTick.Remove(onLandtrainSingleLoadRestoreTick)
|
||||
runLandtrainSingleLoadRestorePass()
|
||||
landtrainLoadRebuildDone = true
|
||||
end
|
||||
|
||||
local function scheduleLandtrainSingleLoadRestore()
|
||||
-- Single delayed load pass: restore saved positions, detach all saved links, reattach in chain order.
|
||||
landtrainLoadRebuildDone = false
|
||||
landtrainSaveSnapshotTickCounter = 0
|
||||
landtrainLoadRestoreDelayTicks = 90
|
||||
Events.OnTick.Remove(onLandtrainSingleLoadRestoreTick)
|
||||
Events.OnTick.Add(onLandtrainSingleLoadRestoreTick)
|
||||
end
|
||||
|
||||
local _landtrainHookPosA = Vector3f.new()
|
||||
@@ -700,11 +971,12 @@ local function captureTowbarFrontLink(towingVehicle)
|
||||
return nil
|
||||
end
|
||||
|
||||
local attachmentA, attachmentB = chooseLandtrainTowbarPair(frontVehicle, towingVehicle, nil, nil)
|
||||
local link = {
|
||||
frontVehicle = frontVehicle,
|
||||
towingVehicle = towingVehicle,
|
||||
attachmentA = frontVehicle:getTowAttachmentSelf() or "trailer",
|
||||
attachmentB = towingVehicle:getTowAttachmentSelf() or "trailerfront"
|
||||
attachmentA = attachmentA,
|
||||
attachmentB = attachmentB
|
||||
}
|
||||
ltLog("captureTowbarFrontLink captured front=" .. vehLabel(frontVehicle) .. " middle=" .. vehLabel(towingVehicle)
|
||||
.. " attA=" .. tostring(link.attachmentA) .. " attB=" .. tostring(link.attachmentB))
|
||||
@@ -809,9 +1081,16 @@ end
|
||||
|
||||
local function menuHasTowbarAttachSlice(menu)
|
||||
if menu == nil or menu.slices == nil then return false end
|
||||
local attachAction = TowBarMod and TowBarMod.Hook and TowBarMod.Hook.attachByTowBarAction or nil
|
||||
local chooseAction = TowBarMod and TowBarMod.UI and TowBarMod.UI.showChooseVehicleMenu or nil
|
||||
if attachAction == nil and chooseAction == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, slice in ipairs(menu.slices) do
|
||||
local command = slice.command and slice.command[1]
|
||||
if command == TowBarMod.Hook.attachByTowBarAction or command == TowBarMod.UI.showChooseVehicleMenu then
|
||||
if (attachAction ~= nil and command == attachAction)
|
||||
or (chooseAction ~= nil and command == chooseAction) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
@@ -820,9 +1099,13 @@ end
|
||||
|
||||
local function menuHasTowbarDetachSlice(menu)
|
||||
if menu == nil or menu.slices == nil then return false end
|
||||
local detachAction = TowBarMod and TowBarMod.Hook and TowBarMod.Hook.deattachTowBarAction or nil
|
||||
if detachAction == nil then
|
||||
return false
|
||||
end
|
||||
for _, slice in ipairs(menu.slices) do
|
||||
local command = slice.command and slice.command[1]
|
||||
if command == TowBarMod.Hook.deattachTowBarAction then
|
||||
if command == detachAction then
|
||||
return true
|
||||
end
|
||||
end
|
||||
@@ -971,7 +1254,18 @@ end
|
||||
|
||||
local function addLandtrainHookOptionToMenu(playerObj, vehicle)
|
||||
if playerObj == nil or vehicle == nil then return end
|
||||
if not playerObj:getInventory():getItemFromTypeRecurse("TowBar.TowBar") then
|
||||
local hasTowBarItem = false
|
||||
if playerObj.getInventory and playerObj:getInventory() ~= nil then
|
||||
hasTowBarItem = playerObj:getInventory():getItemFromTypeRecurse("TowBar.TowBar") ~= nil
|
||||
end
|
||||
if not hasTowBarItem and TowBarMod and TowBarMod.Hook and TowBarMod.Hook.getTowBarInventoryItem then
|
||||
hasTowBarItem = TowBarMod.Hook.getTowBarInventoryItem(playerObj) ~= nil
|
||||
end
|
||||
if not hasTowBarItem and TowBarMod and TowBarMod.Utils and TowBarMod.Utils.getTowBarInventoryItem then
|
||||
hasTowBarItem = TowBarMod.Utils.getTowBarInventoryItem(playerObj) ~= nil
|
||||
end
|
||||
|
||||
if not hasTowBarItem then
|
||||
ltLog("addLandtrainHookOptionToMenu no TowBar item for " .. vehLabel(vehicle))
|
||||
return
|
||||
end
|
||||
@@ -1093,7 +1387,7 @@ local function installLandtrainTowbarPatch()
|
||||
if towedVehicle then
|
||||
refreshTowBarState(towedVehicle:getVehicleTowing())
|
||||
end
|
||||
queueLandtrainTowbarRestore(towedVehicle)
|
||||
saveActiveLandtrainTowbarSnapshot()
|
||||
end
|
||||
|
||||
TowBarMod.Hook.performAttachTowBar = performAttachWrapper
|
||||
@@ -1166,8 +1460,7 @@ local function installLandtrainTowbarPatch()
|
||||
refreshTowBarState(resolvedTowingVehicle:getVehicleTowing())
|
||||
refreshTowBarState(resolvedTowedVehicle:getVehicleTowedBy())
|
||||
refreshTowBarState(resolvedTowedVehicle:getVehicleTowing())
|
||||
queueLandtrainTowbarRestore(resolvedTowingVehicle)
|
||||
queueLandtrainTowbarRestore(resolvedTowedVehicle)
|
||||
saveActiveLandtrainTowbarSnapshot()
|
||||
|
||||
dumpTowState("performDeattachTowBar post-reconcile towing", resolvedTowingVehicle)
|
||||
dumpTowState("performDeattachTowBar post-reconcile towed", resolvedTowedVehicle)
|
||||
@@ -1202,7 +1495,7 @@ local function installLandtrainTowbarPatch()
|
||||
ltLog("installLandtrainTowbarPatch patched ISVehicleMenu.onDetachTrailer")
|
||||
end
|
||||
|
||||
-- Towbar UI only adds attach when fully unlinked; add attach option for linked vehicles too.
|
||||
-- Towbar UI only adds attach when fully unlinked; add attach for linked/chain-related interactions too.
|
||||
if ISVehicleMenu and ISVehicleMenu.showRadialMenu and ISVehicleMenu.showRadialMenu ~= TowBarMod.UI._landtrainShowRadialWrapper then
|
||||
local originalShowRadialMenu = ISVehicleMenu.showRadialMenu
|
||||
local showRadialWrapper = function(playerObj)
|
||||
@@ -1228,7 +1521,15 @@ local function installLandtrainTowbarPatch()
|
||||
return
|
||||
end
|
||||
|
||||
if (vehicle:getVehicleTowing() or vehicle:getVehicleTowedBy()) then
|
||||
local vehicleLinked = vehicle:getVehicleTowing() ~= nil or vehicle:getVehicleTowedBy() ~= nil
|
||||
local chainRelated = isTowbarChainVehicleOrNeighbor(vehicle)
|
||||
local shouldInjectAttach = vehicleLinked or chainRelated
|
||||
ltLog("showRadial attach check vehicle=" .. vehLabel(vehicle)
|
||||
.. " linked=" .. tostring(vehicleLinked)
|
||||
.. " chainRelated=" .. tostring(chainRelated)
|
||||
.. " shouldInject=" .. tostring(shouldInjectAttach))
|
||||
|
||||
if shouldInjectAttach then
|
||||
addLandtrainHookOptionToMenu(playerObj, vehicle)
|
||||
else
|
||||
ltLog("showRadial vehicle not linked, Landtrain attach not injected for " .. vehLabel(vehicle))
|
||||
@@ -1299,7 +1600,5 @@ end
|
||||
Events.OnGameBoot.Add(ensureTowAttachmentsForTrailers)
|
||||
Events.OnGameBoot.Add(startLandtrainInstallWatchdog)
|
||||
Events.OnGameStart.Add(startLandtrainInstallWatchdog)
|
||||
Events.OnGameStart.Add(scheduleLandtrainLoadSanity)
|
||||
Events.OnGameStart.Add(queueLandtrainLoadedTowbarRestores)
|
||||
Events.OnSpawnVehicleEnd.Add(onLandtrainSpawnVehicleEnd)
|
||||
Events.OnTick.Add(processLandtrainPendingRestores)
|
||||
Events.OnGameStart.Add(scheduleLandtrainSingleLoadRestore)
|
||||
Events.OnTick.Add(onLandtrainSaveSnapshotTick)
|
||||
|
||||
Reference in New Issue
Block a user