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_DIST_SQ = 3.25 -- ~1.8 tiles
|
||||||
local LANDTRAIN_FALLBACK_MAX_DZ = 0.9
|
local LANDTRAIN_FALLBACK_MAX_DZ = 0.9
|
||||||
local LANDTRAIN_FRONT_SQL_ID_KEY = "landtrainTowbarFrontSqlId"
|
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_A_KEY = "landtrainTowbarAttachmentA"
|
||||||
local LANDTRAIN_FRONT_ATTACHMENT_B_KEY = "landtrainTowbarAttachmentB"
|
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)
|
local function ltLog(msg)
|
||||||
if not LANDTRAIN_DEBUG then return end
|
if not LANDTRAIN_DEBUG then return end
|
||||||
print("[Landtrain] " .. tostring(msg))
|
emitLandtrainLog("[Landtrain] " .. tostring(msg))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ltInfo(msg)
|
local function ltInfo(msg)
|
||||||
print("[Landtrain][Info] " .. tostring(msg))
|
emitLandtrainLog("[Landtrain][Info] " .. tostring(msg))
|
||||||
end
|
end
|
||||||
|
|
||||||
ltInfo("UnlimitedTowbarChains loaded. debug=" .. tostring(LANDTRAIN_DEBUG))
|
ltInfo("UnlimitedTowbarChains loaded. debug=" .. tostring(LANDTRAIN_DEBUG))
|
||||||
@@ -27,6 +38,115 @@ local function vehLabel(vehicle)
|
|||||||
return tostring(vehicle:getId()) .. ":" .. tostring(name)
|
return tostring(vehicle:getId()) .. ":" .. tostring(name)
|
||||||
end
|
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)
|
local function dumpTowState(prefix, vehicle)
|
||||||
if not LANDTRAIN_DEBUG then return end
|
if not LANDTRAIN_DEBUG then return end
|
||||||
if vehicle == nil then
|
if vehicle == nil then
|
||||||
@@ -131,7 +251,7 @@ local function getVehicleBySqlIdSafe(sqlId)
|
|||||||
|
|
||||||
for i = 0, vehicles:size() - 1 do
|
for i = 0, vehicles:size() - 1 do
|
||||||
local vehicle = vehicles:get(i)
|
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
|
return vehicle
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -140,11 +260,13 @@ end
|
|||||||
|
|
||||||
local function storeLandtrainFrontLinkData(towingVehicle, towedVehicle, attachmentA, attachmentB)
|
local function storeLandtrainFrontLinkData(towingVehicle, towedVehicle, attachmentA, attachmentB)
|
||||||
if towingVehicle == nil or towedVehicle == nil then return end
|
if towingVehicle == nil or towedVehicle == nil then return end
|
||||||
|
local towingModData = towingVehicle:getModData()
|
||||||
local towedModData = towedVehicle: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
|
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_SQL_ID_KEY] = nil
|
||||||
towedModData[LANDTRAIN_FRONT_ID_KEY_LEGACY] = nil
|
|
||||||
towedModData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] = nil
|
towedModData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] = nil
|
||||||
towedModData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] = nil
|
towedModData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] = nil
|
||||||
towedVehicle:transmitModData()
|
towedVehicle:transmitModData()
|
||||||
@@ -152,15 +274,22 @@ local function storeLandtrainFrontLinkData(towingVehicle, towedVehicle, attachme
|
|||||||
return
|
return
|
||||||
end
|
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
|
if towingSqlId ~= nil and towingSqlId >= 0 then
|
||||||
towedModData[LANDTRAIN_FRONT_SQL_ID_KEY] = towingSqlId
|
towedModData[LANDTRAIN_FRONT_SQL_ID_KEY] = towingSqlId
|
||||||
else
|
else
|
||||||
towedModData[LANDTRAIN_FRONT_SQL_ID_KEY] = nil
|
towedModData[LANDTRAIN_FRONT_SQL_ID_KEY] = nil
|
||||||
end
|
end
|
||||||
towedModData[LANDTRAIN_FRONT_ID_KEY_LEGACY] = nil
|
local towedSqlId = towedVehicle.getSqlId and towedVehicle:getSqlId() or -1
|
||||||
towedModData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] = attachmentA or towingVehicle:getTowAttachmentSelf() or "trailer"
|
if towedSqlId ~= nil and towedSqlId >= 0 then
|
||||||
towedModData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] = attachmentB or towedVehicle:getTowAttachmentSelf() or "trailerfront"
|
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()
|
towedVehicle:transmitModData()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -168,8 +297,19 @@ local function clearLandtrainFrontLinkData(vehicle)
|
|||||||
if vehicle == nil then return end
|
if vehicle == nil then return end
|
||||||
local modData = vehicle:getModData()
|
local modData = vehicle:getModData()
|
||||||
if modData == nil then return end
|
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_SQL_ID_KEY] = nil
|
||||||
modData[LANDTRAIN_FRONT_ID_KEY_LEGACY] = nil
|
|
||||||
modData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] = nil
|
modData[LANDTRAIN_FRONT_ATTACHMENT_A_KEY] = nil
|
||||||
modData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] = nil
|
modData[LANDTRAIN_FRONT_ATTACHMENT_B_KEY] = nil
|
||||||
vehicle:transmitModData()
|
vehicle:transmitModData()
|
||||||
@@ -187,6 +327,7 @@ local function isTowbarManagedVehicle(vehicle)
|
|||||||
or modData["isChangedTowedAttachment"] == true
|
or modData["isChangedTowedAttachment"] == true
|
||||||
or modData["towBarChangedAttachmentId"] ~= nil
|
or modData["towBarChangedAttachmentId"] ~= nil
|
||||||
or modData[LANDTRAIN_FRONT_SQL_ID_KEY] ~= nil
|
or modData[LANDTRAIN_FRONT_SQL_ID_KEY] ~= nil
|
||||||
|
or modData[LANDTRAIN_REAR_SQL_ID_KEY] ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function restoreVehicleTowbarDefaults(vehicle)
|
local function restoreVehicleTowbarDefaults(vehicle)
|
||||||
@@ -244,9 +385,9 @@ local function restoreVehicleTowbarDefaults(vehicle)
|
|||||||
modData.towBarOriginalMass = nil
|
modData.towBarOriginalMass = nil
|
||||||
modData.towBarOriginalBrakingForce = nil
|
modData.towBarOriginalBrakingForce = nil
|
||||||
modData[LANDTRAIN_FRONT_SQL_ID_KEY] = 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_A_KEY] = nil
|
||||||
modData[LANDTRAIN_FRONT_ATTACHMENT_B_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
|
if TowBarMod and TowBarMod.Hook and TowBarMod.Hook.clearReapplied then
|
||||||
TowBarMod.Hook.clearReapplied(vehicle)
|
TowBarMod.Hook.clearReapplied(vehicle)
|
||||||
@@ -277,7 +418,7 @@ local function reconcileTowbarSplitVehicle(vehicle)
|
|||||||
end
|
end
|
||||||
|
|
||||||
modData["towed"] = (frontVehicle ~= nil)
|
modData["towed"] = (frontVehicle ~= nil)
|
||||||
modData["isTowingByTowBar"] = (frontVehicle ~= nil) or (hasTowbarRear == true)
|
modData["isTowingByTowBar"] = (modData["towed"] == true) or (hasTowbarRear == true)
|
||||||
vehicle:transmitModData()
|
vehicle:transmitModData()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -344,7 +485,7 @@ end
|
|||||||
|
|
||||||
local function sanitizeLoadedTowLinks()
|
local function sanitizeLoadedTowLinks()
|
||||||
local vehicles = getLoadedVehicles()
|
local vehicles = getLoadedVehicles()
|
||||||
if #vehicles == 0 then return end
|
if #vehicles == 0 then return vehicles end
|
||||||
|
|
||||||
for _, vehicle in ipairs(vehicles) do
|
for _, vehicle in ipairs(vehicles) do
|
||||||
local front = vehicle:getVehicleTowedBy()
|
local front = vehicle:getVehicleTowedBy()
|
||||||
@@ -357,252 +498,382 @@ local function sanitizeLoadedTowLinks()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, vehicle in ipairs(vehicles) do
|
return vehicles
|
||||||
reconcileTowbarSplitAround(vehicle)
|
|
||||||
refreshTowBarState(vehicle)
|
|
||||||
local frontVehicle = vehicle:getVehicleTowedBy()
|
|
||||||
if frontVehicle ~= nil and isTowbarManagedVehicle(vehicle) then
|
|
||||||
storeLandtrainFrontLinkData(frontVehicle, vehicle)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local _landtrainLoadSanityTicks = 0
|
local function setModDataNumberIfChanged(modData, key, value)
|
||||||
local function onLandtrainLoadSanityTick()
|
if modData == nil or key == nil or value == nil then return false end
|
||||||
if _landtrainLoadSanityTicks <= 0 then
|
local current = tonumber(modData[key])
|
||||||
Events.OnTick.Remove(onLandtrainLoadSanityTick)
|
if current ~= nil and math.abs(current - value) < 0.0001 then
|
||||||
return
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
end
|
|
||||||
|
|
||||||
local function queueLandtrainTowbarRestore(vehicle)
|
|
||||||
if vehicle == nil then return end
|
|
||||||
local modData = vehicle:getModData()
|
|
||||||
if modData == nil then return 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
|
return false
|
||||||
end
|
end
|
||||||
if savedFrontVehicle == vehicle or savedFrontVehicle:getId() == vehicle:getId() then
|
modData[key] = value
|
||||||
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
|
|
||||||
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
|
|
||||||
end
|
|
||||||
|
|
||||||
playerObj = playerObj or getPlayer()
|
|
||||||
if playerObj == nil then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
if TowBarMod == nil or TowBarMod.Utils == nil or TowBarMod.Utils.updateAttachmentsForRigidTow == nil then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
TowBarMod.Utils.updateAttachmentsForRigidTow(savedFrontVehicle, vehicle, attachmentA, attachmentB)
|
|
||||||
vehicle:setScriptName("notTowingA_Trailer")
|
|
||||||
local args = {
|
|
||||||
vehicleA = savedFrontVehicle:getId(),
|
|
||||||
vehicleB = vehicle:getId(),
|
|
||||||
attachmentA = attachmentA,
|
|
||||||
attachmentB = 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
|
|
||||||
end
|
|
||||||
sendClientCommand(playerObj, "vehicle", "attachTrailer", args)
|
|
||||||
if TowBarScheduleAction and TowBarMod and TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
|
|
||||||
ISTimedActionQueue.add(TowBarScheduleAction:new(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, vehicle))
|
|
||||||
elseif TowBarMod and TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
|
|
||||||
TowBarMod.Hook.setVehiclePostAttach(playerObj, vehicle)
|
|
||||||
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
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local function processLandtrainPendingRestores()
|
local function saveLandtrainVehiclePosition(vehicle)
|
||||||
landtrainReapplyTickCounter = landtrainReapplyTickCounter + 1
|
if vehicle == nil then return false end
|
||||||
if landtrainReapplyTickCounter < 15 then
|
local modData = vehicle:getModData()
|
||||||
|
if modData == nil then return false end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 false end
|
||||||
|
|
||||||
|
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 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 = frontVehicle:getId(),
|
||||||
|
vehicleB = rearVehicle:getId(),
|
||||||
|
attachmentA = link.attachmentA,
|
||||||
|
attachmentB = link.attachmentB
|
||||||
|
}
|
||||||
|
if args.vehicleA == args.vehicleB then
|
||||||
|
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, delayTicks, TowBarMod.Hook.setVehiclePostAttach, rearVehicle))
|
||||||
|
elseif TowBarMod and TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
|
||||||
|
TowBarMod.Hook.setVehiclePostAttach(playerObj, rearVehicle)
|
||||||
|
end
|
||||||
|
if TowBarMod and TowBarMod.Hook and TowBarMod.Hook.markReapplied then
|
||||||
|
TowBarMod.Hook.markReapplied(rearVehicle)
|
||||||
|
end
|
||||||
|
|
||||||
|
setTowBarModelVisibleForVehicle(rearVehicle, true)
|
||||||
|
refreshTowBarState(frontVehicle)
|
||||||
|
refreshTowBarState(rearVehicle)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function runLandtrainSingleLoadRestorePass()
|
||||||
|
local vehicles = sanitizeLoadedTowLinks()
|
||||||
|
if vehicles == nil or #vehicles == 0 then
|
||||||
|
ltInfo("Landtrain load rebuild: no loaded vehicles")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
landtrainReapplyTickCounter = 0
|
|
||||||
|
|
||||||
local resolvedVehicleIds = {}
|
local links = collectSavedLandtrainLinks(vehicles)
|
||||||
local playerObj = getPlayer()
|
if #links == 0 then
|
||||||
for vehicleId, _ in pairs(landtrainPendingRestoreVehicleIds) do
|
ltInfo("Landtrain load rebuild: no saved towbar links")
|
||||||
local vehicle = getVehicleByIdSafe(vehicleId)
|
return
|
||||||
if vehicle == nil or tryLandtrainTowbarRestore(vehicle, playerObj) then
|
end
|
||||||
table.insert(resolvedVehicleIds, vehicleId)
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, vehicleId in ipairs(resolvedVehicleIds) do
|
local detachedCount = detachSavedLandtrainLinks(links)
|
||||||
landtrainPendingRestoreVehicleIds[vehicleId] = nil
|
local attachedCount = 0
|
||||||
end
|
for index, link in ipairs(links) do
|
||||||
end
|
local delay = 10 + ((index - 1) * 5)
|
||||||
|
if attachSavedLandtrainLink(playerObj, link, delay) then
|
||||||
local function queueLandtrainLoadedTowbarRestores()
|
attachedCount = attachedCount + 1
|
||||||
local vehicles = getLoadedVehicles()
|
|
||||||
for _, vehicle in ipairs(vehicles) do
|
|
||||||
local frontVehicle = vehicle:getVehicleTowedBy()
|
|
||||||
if frontVehicle ~= nil and isTowbarManagedVehicle(vehicle) then
|
|
||||||
storeLandtrainFrontLinkData(frontVehicle, vehicle)
|
|
||||||
end
|
end
|
||||||
queueLandtrainTowbarRestore(vehicle)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
saveActiveLandtrainTowbarSnapshot(vehicles)
|
||||||
|
ltInfo("Landtrain load rebuild complete moved=" .. tostring(movedVehicles)
|
||||||
|
.. " detached=" .. tostring(detachedCount)
|
||||||
|
.. " attached=" .. tostring(attachedCount)
|
||||||
|
.. " links=" .. tostring(#links))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function onLandtrainSpawnVehicleEnd(vehicle)
|
local landtrainLoadRebuildDone = false
|
||||||
if vehicle == nil then return end
|
local landtrainSaveSnapshotTickCounter = 0
|
||||||
local frontVehicle = vehicle:getVehicleTowedBy()
|
local function onLandtrainSaveSnapshotTick()
|
||||||
if frontVehicle ~= nil and isTowbarManagedVehicle(vehicle) then
|
if not landtrainLoadRebuildDone then
|
||||||
storeLandtrainFrontLinkData(frontVehicle, vehicle)
|
return
|
||||||
end
|
end
|
||||||
queueLandtrainTowbarRestore(vehicle)
|
landtrainSaveSnapshotTickCounter = landtrainSaveSnapshotTickCounter + 1
|
||||||
queueLandtrainTowbarRestore(vehicle:getVehicleTowedBy())
|
if landtrainSaveSnapshotTickCounter < 60 then
|
||||||
queueLandtrainTowbarRestore(vehicle:getVehicleTowing())
|
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
|
end
|
||||||
|
|
||||||
local _landtrainHookPosA = Vector3f.new()
|
local _landtrainHookPosA = Vector3f.new()
|
||||||
@@ -700,11 +971,12 @@ local function captureTowbarFrontLink(towingVehicle)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local attachmentA, attachmentB = chooseLandtrainTowbarPair(frontVehicle, towingVehicle, nil, nil)
|
||||||
local link = {
|
local link = {
|
||||||
frontVehicle = frontVehicle,
|
frontVehicle = frontVehicle,
|
||||||
towingVehicle = towingVehicle,
|
towingVehicle = towingVehicle,
|
||||||
attachmentA = frontVehicle:getTowAttachmentSelf() or "trailer",
|
attachmentA = attachmentA,
|
||||||
attachmentB = towingVehicle:getTowAttachmentSelf() or "trailerfront"
|
attachmentB = attachmentB
|
||||||
}
|
}
|
||||||
ltLog("captureTowbarFrontLink captured front=" .. vehLabel(frontVehicle) .. " middle=" .. vehLabel(towingVehicle)
|
ltLog("captureTowbarFrontLink captured front=" .. vehLabel(frontVehicle) .. " middle=" .. vehLabel(towingVehicle)
|
||||||
.. " attA=" .. tostring(link.attachmentA) .. " attB=" .. tostring(link.attachmentB))
|
.. " attA=" .. tostring(link.attachmentA) .. " attB=" .. tostring(link.attachmentB))
|
||||||
@@ -809,9 +1081,16 @@ end
|
|||||||
|
|
||||||
local function menuHasTowbarAttachSlice(menu)
|
local function menuHasTowbarAttachSlice(menu)
|
||||||
if menu == nil or menu.slices == nil then return false end
|
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
|
for _, slice in ipairs(menu.slices) do
|
||||||
local command = slice.command and slice.command[1]
|
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
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -820,9 +1099,13 @@ end
|
|||||||
|
|
||||||
local function menuHasTowbarDetachSlice(menu)
|
local function menuHasTowbarDetachSlice(menu)
|
||||||
if menu == nil or menu.slices == nil then return false end
|
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
|
for _, slice in ipairs(menu.slices) do
|
||||||
local command = slice.command and slice.command[1]
|
local command = slice.command and slice.command[1]
|
||||||
if command == TowBarMod.Hook.deattachTowBarAction then
|
if command == detachAction then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -971,7 +1254,18 @@ end
|
|||||||
|
|
||||||
local function addLandtrainHookOptionToMenu(playerObj, vehicle)
|
local function addLandtrainHookOptionToMenu(playerObj, vehicle)
|
||||||
if playerObj == nil or vehicle == nil then return end
|
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))
|
ltLog("addLandtrainHookOptionToMenu no TowBar item for " .. vehLabel(vehicle))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -1093,7 +1387,7 @@ local function installLandtrainTowbarPatch()
|
|||||||
if towedVehicle then
|
if towedVehicle then
|
||||||
refreshTowBarState(towedVehicle:getVehicleTowing())
|
refreshTowBarState(towedVehicle:getVehicleTowing())
|
||||||
end
|
end
|
||||||
queueLandtrainTowbarRestore(towedVehicle)
|
saveActiveLandtrainTowbarSnapshot()
|
||||||
end
|
end
|
||||||
|
|
||||||
TowBarMod.Hook.performAttachTowBar = performAttachWrapper
|
TowBarMod.Hook.performAttachTowBar = performAttachWrapper
|
||||||
@@ -1166,8 +1460,7 @@ local function installLandtrainTowbarPatch()
|
|||||||
refreshTowBarState(resolvedTowingVehicle:getVehicleTowing())
|
refreshTowBarState(resolvedTowingVehicle:getVehicleTowing())
|
||||||
refreshTowBarState(resolvedTowedVehicle:getVehicleTowedBy())
|
refreshTowBarState(resolvedTowedVehicle:getVehicleTowedBy())
|
||||||
refreshTowBarState(resolvedTowedVehicle:getVehicleTowing())
|
refreshTowBarState(resolvedTowedVehicle:getVehicleTowing())
|
||||||
queueLandtrainTowbarRestore(resolvedTowingVehicle)
|
saveActiveLandtrainTowbarSnapshot()
|
||||||
queueLandtrainTowbarRestore(resolvedTowedVehicle)
|
|
||||||
|
|
||||||
dumpTowState("performDeattachTowBar post-reconcile towing", resolvedTowingVehicle)
|
dumpTowState("performDeattachTowBar post-reconcile towing", resolvedTowingVehicle)
|
||||||
dumpTowState("performDeattachTowBar post-reconcile towed", resolvedTowedVehicle)
|
dumpTowState("performDeattachTowBar post-reconcile towed", resolvedTowedVehicle)
|
||||||
@@ -1202,7 +1495,7 @@ local function installLandtrainTowbarPatch()
|
|||||||
ltLog("installLandtrainTowbarPatch patched ISVehicleMenu.onDetachTrailer")
|
ltLog("installLandtrainTowbarPatch patched ISVehicleMenu.onDetachTrailer")
|
||||||
end
|
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
|
if ISVehicleMenu and ISVehicleMenu.showRadialMenu and ISVehicleMenu.showRadialMenu ~= TowBarMod.UI._landtrainShowRadialWrapper then
|
||||||
local originalShowRadialMenu = ISVehicleMenu.showRadialMenu
|
local originalShowRadialMenu = ISVehicleMenu.showRadialMenu
|
||||||
local showRadialWrapper = function(playerObj)
|
local showRadialWrapper = function(playerObj)
|
||||||
@@ -1228,7 +1521,15 @@ local function installLandtrainTowbarPatch()
|
|||||||
return
|
return
|
||||||
end
|
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)
|
addLandtrainHookOptionToMenu(playerObj, vehicle)
|
||||||
else
|
else
|
||||||
ltLog("showRadial vehicle not linked, Landtrain attach not injected for " .. vehLabel(vehicle))
|
ltLog("showRadial vehicle not linked, Landtrain attach not injected for " .. vehLabel(vehicle))
|
||||||
@@ -1299,7 +1600,5 @@ end
|
|||||||
Events.OnGameBoot.Add(ensureTowAttachmentsForTrailers)
|
Events.OnGameBoot.Add(ensureTowAttachmentsForTrailers)
|
||||||
Events.OnGameBoot.Add(startLandtrainInstallWatchdog)
|
Events.OnGameBoot.Add(startLandtrainInstallWatchdog)
|
||||||
Events.OnGameStart.Add(startLandtrainInstallWatchdog)
|
Events.OnGameStart.Add(startLandtrainInstallWatchdog)
|
||||||
Events.OnGameStart.Add(scheduleLandtrainLoadSanity)
|
Events.OnGameStart.Add(scheduleLandtrainSingleLoadRestore)
|
||||||
Events.OnGameStart.Add(queueLandtrainLoadedTowbarRestores)
|
Events.OnTick.Add(onLandtrainSaveSnapshotTick)
|
||||||
Events.OnSpawnVehicleEnd.Add(onLandtrainSpawnVehicleEnd)
|
|
||||||
Events.OnTick.Add(processLandtrainPendingRestores)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user