Working
This commit is contained in:
@@ -45,14 +45,51 @@ local function resolveVehicle(id)
|
||||
return getVehicleById(id)
|
||||
end
|
||||
|
||||
local function reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
|
||||
if not vehicleA or not vehicleB then return end
|
||||
|
||||
if TowBarMod.Utils and TowBarMod.Utils.updateAttachmentsForRigidTow then
|
||||
TowBarMod.Utils.updateAttachmentsForRigidTow(vehicleA, vehicleB, attachmentA, attachmentB)
|
||||
end
|
||||
|
||||
local towingMd = vehicleA:getModData()
|
||||
local towedMd = vehicleB:getModData()
|
||||
local currentScript = vehicleB:getScriptName()
|
||||
|
||||
if towingMd then
|
||||
towingMd["isTowingByTowBar"] = true
|
||||
vehicleA:transmitModData()
|
||||
end
|
||||
if towedMd then
|
||||
if towedMd.towBarOriginalScriptName == nil and currentScript ~= "notTowingA_Trailer" then
|
||||
towedMd.towBarOriginalScriptName = currentScript
|
||||
end
|
||||
if towedMd.towBarOriginalMass == nil then
|
||||
towedMd.towBarOriginalMass = vehicleB:getMass()
|
||||
end
|
||||
if towedMd.towBarOriginalBrakingForce == nil then
|
||||
towedMd.towBarOriginalBrakingForce = vehicleB:getBrakingForce()
|
||||
end
|
||||
towedMd["isTowingByTowBar"] = true
|
||||
towedMd["towed"] = true
|
||||
vehicleB:transmitModData()
|
||||
end
|
||||
|
||||
vehicleB:setScriptName("notTowingA_Trailer")
|
||||
if TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
|
||||
pcall(TowBarMod.Hook.setVehiclePostAttach, nil, vehicleB)
|
||||
end
|
||||
log("reconcilePairState A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId())
|
||||
.. " attachmentA=" .. tostring(attachmentA) .. " attachmentB=" .. tostring(attachmentB))
|
||||
end
|
||||
|
||||
local function applyAttachSync(args)
|
||||
if not args then return end
|
||||
|
||||
local vehicleA = resolveVehicle(args.vehicleA)
|
||||
local vehicleB = resolveVehicle(args.vehicleB)
|
||||
if not vehicleA or not vehicleB then return end
|
||||
|
||||
if vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA then
|
||||
if not vehicleA or not vehicleB then
|
||||
log("applyAttachSync skipped: missing vehicle A=" .. tostring(args.vehicleA) .. " B=" .. tostring(args.vehicleB))
|
||||
return
|
||||
end
|
||||
|
||||
@@ -63,7 +100,15 @@ local function applyAttachSync(args)
|
||||
return
|
||||
end
|
||||
|
||||
vehicleA:addPointConstraint(nil, vehicleB, attachmentA, attachmentB)
|
||||
if vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA then
|
||||
log("applyAttachSync already linked A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId()))
|
||||
else
|
||||
log("applyAttachSync addPointConstraint A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId())
|
||||
.. " attachmentA=" .. tostring(attachmentA) .. " attachmentB=" .. tostring(attachmentB))
|
||||
vehicleA:addPointConstraint(nil, vehicleB, attachmentA, attachmentB)
|
||||
end
|
||||
|
||||
reconcilePairState(vehicleA, vehicleB, attachmentA, attachmentB)
|
||||
end
|
||||
|
||||
local function safeBreak(vehicle)
|
||||
|
||||
@@ -20,6 +20,10 @@ local function log(msg)
|
||||
end
|
||||
end
|
||||
|
||||
local function vehicleId(vehicle)
|
||||
return vehicle and vehicle:getId() or "nil"
|
||||
end
|
||||
|
||||
local function hasAttachment(vehicle, attachmentId)
|
||||
if vehicle == nil or attachmentId == nil then return false end
|
||||
local script = vehicle:getScript()
|
||||
@@ -180,6 +184,18 @@ local function restoreDefaultsIfLeadLost(vehicle)
|
||||
clearChangedOffset(vehicle)
|
||||
restoreTowBarDefaults(vehicle)
|
||||
|
||||
local md = vehicle:getModData()
|
||||
if md then
|
||||
md["towed"] = false
|
||||
if vehicle:getVehicleTowing() == nil then
|
||||
md["isTowingByTowBar"] = false
|
||||
md.towBarOriginalScriptName = nil
|
||||
md.towBarOriginalMass = nil
|
||||
md.towBarOriginalBrakingForce = nil
|
||||
end
|
||||
vehicle:transmitModData()
|
||||
end
|
||||
|
||||
setTowBarModelVisible(vehicle, false)
|
||||
end
|
||||
|
||||
@@ -208,12 +224,104 @@ local function sendAttach(playerObj, towingVehicle, towedVehicle, attachmentA, a
|
||||
end
|
||||
end
|
||||
|
||||
local function captureFrontLink(middleVehicle)
|
||||
local function isLinkedPair(towingVehicle, towedVehicle)
|
||||
if not towingVehicle or not towedVehicle then return false end
|
||||
return towingVehicle:getVehicleTowing() == towedVehicle and towedVehicle:getVehicleTowedBy() == towingVehicle
|
||||
end
|
||||
|
||||
local function markTowBarPair(towingVehicle, towedVehicle)
|
||||
local towingMd = towingVehicle and towingVehicle:getModData() or nil
|
||||
local towedMd = towedVehicle and towedVehicle:getModData() or nil
|
||||
|
||||
if towingMd then
|
||||
towingMd["isTowingByTowBar"] = true
|
||||
towingVehicle:transmitModData()
|
||||
end
|
||||
if towedMd then
|
||||
local currentScript = towedVehicle and towedVehicle:getScriptName() or nil
|
||||
if towedMd.towBarOriginalScriptName == nil and currentScript ~= "notTowingA_Trailer" then
|
||||
towedMd.towBarOriginalScriptName = currentScript
|
||||
end
|
||||
if towedMd.towBarOriginalMass == nil and towedVehicle then
|
||||
towedMd.towBarOriginalMass = towedVehicle:getMass()
|
||||
end
|
||||
if towedMd.towBarOriginalBrakingForce == nil and towedVehicle then
|
||||
towedMd.towBarOriginalBrakingForce = towedVehicle:getBrakingForce()
|
||||
end
|
||||
towedMd["isTowingByTowBar"] = true
|
||||
towedMd["towed"] = true
|
||||
towedVehicle:transmitModData()
|
||||
end
|
||||
end
|
||||
|
||||
local function ensurePairAttached(playerObj, towingVehicle, towedVehicle, preferredA, preferredB, alwaysSend)
|
||||
if not playerObj or not towingVehicle or not towedVehicle then
|
||||
log("ensurePairAttached skipped: missing refs A=" .. tostring(vehicleId(towingVehicle)) .. " B=" .. tostring(vehicleId(towedVehicle)))
|
||||
return false
|
||||
end
|
||||
if towingVehicle == towedVehicle or towingVehicle:getId() == towedVehicle:getId() then
|
||||
log("ensurePairAttached skipped: identical vehicles id=" .. tostring(vehicleId(towingVehicle)))
|
||||
return false
|
||||
end
|
||||
if wouldCreateTowLoop(towingVehicle, towedVehicle) then
|
||||
log("ensurePairAttached skipped: loop A=" .. tostring(vehicleId(towingVehicle)) .. " B=" .. tostring(vehicleId(towedVehicle)))
|
||||
return false
|
||||
end
|
||||
|
||||
local attachmentA, attachmentB = resolvePair(towingVehicle, towedVehicle, preferredA, preferredB)
|
||||
if not hasAttachment(towingVehicle, attachmentA) or not hasAttachment(towedVehicle, attachmentB) then
|
||||
log("ensurePairAttached skipped: missing attachment A=" .. tostring(attachmentA) .. " B=" .. tostring(attachmentB)
|
||||
.. " ids=" .. tostring(vehicleId(towingVehicle)) .. "->" .. tostring(vehicleId(towedVehicle)))
|
||||
return false
|
||||
end
|
||||
|
||||
applyRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
|
||||
towedVehicle:setScriptName("notTowingA_Trailer")
|
||||
markTowBarPair(towingVehicle, towedVehicle)
|
||||
|
||||
local linked = isLinkedPair(towingVehicle, towedVehicle)
|
||||
log("ensurePairAttached pair=" .. tostring(vehicleId(towingVehicle)) .. "->" .. tostring(vehicleId(towedVehicle))
|
||||
.. " linked=" .. tostring(linked) .. " alwaysSend=" .. tostring(alwaysSend)
|
||||
.. " attachmentA=" .. tostring(attachmentA) .. " attachmentB=" .. tostring(attachmentB))
|
||||
|
||||
if linked and TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
|
||||
pcall(TowBarMod.Hook.setVehiclePostAttach, nil, towedVehicle)
|
||||
end
|
||||
|
||||
if alwaysSend or not linked then
|
||||
sendAttach(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB)
|
||||
elseif TowBarMod.Hook and TowBarMod.Hook.setVehiclePostAttach then
|
||||
queueAction(playerObj, 10, TowBarMod.Hook.setVehiclePostAttach, towedVehicle)
|
||||
end
|
||||
|
||||
setTowBarModelVisible(towedVehicle, true)
|
||||
refreshAround(towingVehicle)
|
||||
refreshAround(towedVehicle)
|
||||
return true
|
||||
end
|
||||
|
||||
local function captureFrontLinks(middleVehicle)
|
||||
if not middleVehicle then return nil end
|
||||
local frontVehicle = middleVehicle:getVehicleTowedBy()
|
||||
if not frontVehicle or frontVehicle == middleVehicle then return nil end
|
||||
local a, b = resolvePair(frontVehicle, middleVehicle)
|
||||
return { front = frontVehicle, middle = middleVehicle, a = a, b = b }
|
||||
|
||||
local links = {}
|
||||
local cursor = middleVehicle
|
||||
local visited = {}
|
||||
|
||||
while cursor do
|
||||
if visited[cursor] then break end
|
||||
visited[cursor] = true
|
||||
|
||||
local frontVehicle = cursor:getVehicleTowedBy()
|
||||
if not frontVehicle then break end
|
||||
if frontVehicle == cursor or visited[frontVehicle] then break end
|
||||
|
||||
local a, b = resolvePair(frontVehicle, cursor)
|
||||
table.insert(links, { front = frontVehicle, middle = cursor, a = a, b = b })
|
||||
cursor = frontVehicle
|
||||
end
|
||||
|
||||
if #links == 0 then return nil end
|
||||
return links
|
||||
end
|
||||
|
||||
local function restoreFrontLink(playerObj, link)
|
||||
@@ -225,23 +333,21 @@ local function restoreFrontLink(playerObj, link)
|
||||
if front == middle or front:getId() == middle:getId() then return end
|
||||
if wouldCreateTowLoop(front, middle) then return end
|
||||
|
||||
local a, b = resolvePair(front, middle, link.a, link.b)
|
||||
if not hasAttachment(front, a) or not hasAttachment(middle, b) then return end
|
||||
|
||||
applyRigidTow(front, middle, a, b)
|
||||
middle:setScriptName("notTowingA_Trailer")
|
||||
sendAttach(playerObj, front, middle, a, b)
|
||||
|
||||
setTowBarModelVisible(middle, true)
|
||||
refreshAround(front)
|
||||
refreshAround(middle)
|
||||
ensurePairAttached(playerObj, front, middle, link.a, link.b, false)
|
||||
end
|
||||
|
||||
local function queueRestoreFront(playerObj, link, delay)
|
||||
if not playerObj or not link then return end
|
||||
queueAction(playerObj, delay or 12, function(character, linkArg)
|
||||
restoreFrontLink(character, linkArg)
|
||||
end, link)
|
||||
local function restoreFrontLinks(playerObj, links)
|
||||
if not links then return end
|
||||
for _, link in ipairs(links) do
|
||||
restoreFrontLink(playerObj, link)
|
||||
end
|
||||
end
|
||||
|
||||
local function queueRestoreFrontLinks(playerObj, links, delay)
|
||||
if not playerObj or not links then return end
|
||||
queueAction(playerObj, delay or 12, function(character, linksArg)
|
||||
restoreFrontLinks(character, linksArg)
|
||||
end, links)
|
||||
end
|
||||
|
||||
local function resolveDetachPair(towingVehicle, towedVehicle)
|
||||
@@ -411,16 +517,31 @@ LT.performAttachWrapper = function(playerObj, towingVehicle, towedVehicle, attac
|
||||
if towingVehicle == towedVehicle or towingVehicle:getId() == towedVehicle:getId() then return end
|
||||
if wouldCreateTowLoop(towingVehicle, towedVehicle) then return end
|
||||
|
||||
local frontLink = captureFrontLink(towingVehicle)
|
||||
local frontLinks = captureFrontLinks(towingVehicle)
|
||||
log("performAttachWrapper begin pair=" .. tostring(vehicleId(towingVehicle)) .. "->" .. tostring(vehicleId(towedVehicle))
|
||||
.. " frontLinks=" .. tostring(frontLinks and #frontLinks or 0))
|
||||
original(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB)
|
||||
|
||||
restoreFrontLink(playerObj, frontLink)
|
||||
queueRestoreFront(playerObj, frontLink, 12)
|
||||
queueRestoreFront(playerObj, frontLink, 30)
|
||||
restoreFrontLinks(playerObj, frontLinks)
|
||||
queueRestoreFrontLinks(playerObj, frontLinks, 12)
|
||||
queueRestoreFrontLinks(playerObj, frontLinks, 30)
|
||||
|
||||
setTowBarModelVisible(towedVehicle, true)
|
||||
refreshAround(towingVehicle)
|
||||
refreshAround(towedVehicle)
|
||||
ensurePairAttached(playerObj, towingVehicle, towedVehicle, attachmentA, attachmentB, false)
|
||||
|
||||
local attachState = {
|
||||
towing = towingVehicle,
|
||||
towed = towedVehicle,
|
||||
attachmentA = attachmentA,
|
||||
attachmentB = attachmentB
|
||||
}
|
||||
queueAction(playerObj, 14, function(character, state)
|
||||
if not state then return end
|
||||
ensurePairAttached(character, state.towing, state.towed, state.attachmentA, state.attachmentB, true)
|
||||
end, attachState)
|
||||
queueAction(playerObj, 34, function(character, state)
|
||||
if not state then return end
|
||||
ensurePairAttached(character, state.towing, state.towed, state.attachmentA, state.attachmentB, true)
|
||||
end, attachState)
|
||||
end
|
||||
|
||||
LT.performDetachWrapper = function(playerObj, towingVehicle, towedVehicle)
|
||||
@@ -430,12 +551,12 @@ LT.performDetachWrapper = function(playerObj, towingVehicle, towedVehicle)
|
||||
local resolvedTowing, resolvedTowed = resolveDetachPair(towingVehicle, towedVehicle)
|
||||
if resolvedTowing == nil or resolvedTowed == nil then return end
|
||||
|
||||
local frontLink = captureFrontLink(resolvedTowing)
|
||||
local frontLinks = captureFrontLinks(resolvedTowing)
|
||||
original(playerObj, resolvedTowing, resolvedTowed)
|
||||
|
||||
restoreFrontLink(playerObj, frontLink)
|
||||
queueRestoreFront(playerObj, frontLink, 12)
|
||||
queueRestoreFront(playerObj, frontLink, 30)
|
||||
restoreFrontLinks(playerObj, frontLinks)
|
||||
queueRestoreFrontLinks(playerObj, frontLinks, 12)
|
||||
queueRestoreFrontLinks(playerObj, frontLinks, 30)
|
||||
|
||||
restoreDefaultsIfLeadLost(resolvedTowing)
|
||||
restoreDefaultsIfLeadLost(resolvedTowed)
|
||||
|
||||
@@ -70,8 +70,12 @@ local function processAttach(item)
|
||||
log("attach sync failed: server link not established A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId()))
|
||||
return
|
||||
end
|
||||
else
|
||||
log("attach sync already linked A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId()))
|
||||
end
|
||||
|
||||
log("attach sync broadcast A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId())
|
||||
.. " attachmentA=" .. tostring(attachmentA) .. " attachmentB=" .. tostring(attachmentB))
|
||||
sendServerCommand("landtrain", "forceAttachSync", {
|
||||
vehicleA = vehicleA:getId(),
|
||||
vehicleB = vehicleB:getId(),
|
||||
@@ -94,7 +98,8 @@ end
|
||||
local function processPending()
|
||||
if #pending == 0 then return end
|
||||
|
||||
for i = #pending, 1, -1 do
|
||||
local remaining = {}
|
||||
for i = 1, #pending do
|
||||
local item = pending[i]
|
||||
item.ticks = item.ticks - 1
|
||||
if item.ticks <= 0 then
|
||||
@@ -103,17 +108,24 @@ local function processPending()
|
||||
elseif item.kind == "detach" then
|
||||
processDetach(item)
|
||||
end
|
||||
table.remove(pending, i)
|
||||
else
|
||||
table.insert(remaining, item)
|
||||
end
|
||||
end
|
||||
pending = remaining
|
||||
end
|
||||
|
||||
local function onClientCommand(module, command, player, args)
|
||||
if module == "vehicle" and command == "attachTrailer" then
|
||||
log("queue attach from " .. tostring(player and player:getUsername() or "unknown")
|
||||
.. " A=" .. tostring(args and args.vehicleA) .. " B=" .. tostring(args and args.vehicleB)
|
||||
.. " attachmentA=" .. tostring(args and args.attachmentA) .. " attachmentB=" .. tostring(args and args.attachmentB))
|
||||
queueSync("attach", player, args)
|
||||
elseif module == "vehicle" and command == "detachTrailer" then
|
||||
log("queue detach(vehicle) from " .. tostring(player and player:getUsername() or "unknown"))
|
||||
queueSync("detach", player, args)
|
||||
elseif module == "towbar" and command == "detachTowBar" then
|
||||
log("queue detach(towbar) from " .. tostring(player and player:getUsername() or "unknown"))
|
||||
queueSync("detach", player, args)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user