This commit is contained in:
2026-02-12 09:02:55 -05:00
parent c01fe187d0
commit d4114eb6c6
13 changed files with 1249 additions and 1800 deletions

View File

@@ -0,0 +1,77 @@
if isClient() then return end
if not TowBarMod then TowBarMod = {} end
TowBarMod.Landtrain = TowBarMod.Landtrain or {}
if TowBarMod.Landtrain._serverTowAttachmentsLoaded then return end
TowBarMod.Landtrain._serverTowAttachmentsLoaded = true
local function log(msg)
print("[Landtrain][Server] " .. tostring(msg))
end
local function addMissingTowAttachments(script)
if not script then return 0 end
local wheelCount = script:getWheelCount()
local yOffset = -0.5
if wheelCount > 0 then
local wheel = script:getWheel(0)
if wheel and wheel:getOffset() then
yOffset = wheel:getOffset():y() + 0.1
end
end
local chassis = script:getPhysicsChassisShape()
if not chassis then return 0 end
local changed = 0
if script:getAttachmentById("trailer") == nil then
local rearAttach = ModelAttachment.new("trailer")
rearAttach:getOffset():set(0, yOffset, -chassis:z() / 2 - 0.1)
rearAttach:setZOffset(-1)
script:addAttachment(rearAttach)
changed = changed + 1
end
if script:getAttachmentById("trailerfront") == nil then
local frontAttach = ModelAttachment.new("trailerfront")
frontAttach:getOffset():set(0, yOffset, chassis:z() / 2 + 0.1)
frontAttach:setZOffset(1)
script:addAttachment(frontAttach)
changed = changed + 1
end
return changed
end
local function ensureTowAttachmentsServer()
local sm = getScriptManager()
if sm == nil then
log("ScriptManager unavailable; skipping tow attachment bootstrap")
return
end
local scripts = sm:getAllVehicleScripts()
if scripts == nil then
log("Vehicle script list unavailable; skipping tow attachment bootstrap")
return
end
local patchedScripts = 0
local addedAttachments = 0
for i = 0, scripts:size() - 1 do
local script = scripts:get(i)
local added = addMissingTowAttachments(script)
if added > 0 then
patchedScripts = patchedScripts + 1
addedAttachments = addedAttachments + added
end
end
log("Tow attachment bootstrap complete: scripts=" .. tostring(patchedScripts) .. ", attachments=" .. tostring(addedAttachments))
end
Events.OnGameBoot.Add(ensureTowAttachmentsServer)
log("LandtrainTowAttachmentsServer loaded")

View File

@@ -0,0 +1,124 @@
if isClient() then return end
if not TowBarMod then TowBarMod = {} end
TowBarMod.Landtrain = TowBarMod.Landtrain or {}
if TowBarMod.Landtrain._towSyncServerLoaded then return end
TowBarMod.Landtrain._towSyncServerLoaded = true
local SYNC_DELAY_TICKS = 2
local pending = {}
local function log(msg)
print("[Landtrain][TowSyncServer] " .. tostring(msg))
end
local function queueSync(kind, player, args)
if not args then return end
table.insert(pending, {
kind = kind,
ticks = SYNC_DELAY_TICKS,
player = player,
args = args
})
end
local function vehicleHasAttachment(vehicle, attachmentId)
if not vehicle or not attachmentId then return false end
local script = vehicle:getScript()
return script ~= nil and script:getAttachmentById(attachmentId) ~= nil
end
local function isLinked(vehicleA, vehicleB)
if not vehicleA or not vehicleB then return false end
return vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA
end
local function resolveAttachmentA(args, vehicleA)
if args and args.attachmentA then return args.attachmentA end
if vehicleA and vehicleA:getTowAttachmentSelf() then return vehicleA:getTowAttachmentSelf() end
return "trailer"
end
local function resolveAttachmentB(args, vehicleB)
if args and args.attachmentB then return args.attachmentB end
if vehicleB and vehicleB:getTowAttachmentSelf() then return vehicleB:getTowAttachmentSelf() end
return "trailerfront"
end
local function processAttach(item)
local args = item.args or {}
local vehicleA = args.vehicleA and getVehicleById(args.vehicleA) or nil
local vehicleB = args.vehicleB and getVehicleById(args.vehicleB) or nil
if not vehicleA or not vehicleB then
log("attach sync skipped: missing vehicle A=" .. tostring(args.vehicleA) .. " B=" .. tostring(args.vehicleB))
return
end
local attachmentA = resolveAttachmentA(args, vehicleA)
local attachmentB = resolveAttachmentB(args, vehicleB)
local linked = isLinked(vehicleA, vehicleB)
if not linked then
if not vehicleHasAttachment(vehicleA, attachmentA) or not vehicleHasAttachment(vehicleB, attachmentB) then
log("attach sync failed: missing attachment A=" .. tostring(attachmentA) .. " B=" .. tostring(attachmentB))
return
end
vehicleA:addPointConstraint(item.player, vehicleB, attachmentA, attachmentB)
linked = isLinked(vehicleA, vehicleB)
if not linked then
log("attach sync failed: server link not established A=" .. tostring(vehicleA:getId()) .. " B=" .. tostring(vehicleB:getId()))
return
end
end
sendServerCommand("landtrain", "forceAttachSync", {
vehicleA = vehicleA:getId(),
vehicleB = vehicleB:getId(),
attachmentA = attachmentA,
attachmentB = attachmentB
})
end
local function processDetach(item)
local args = item.args or {}
local vehicleAId = args.towingVehicle or args.vehicleA or args.vehicle
local vehicleBId = args.vehicleB
sendServerCommand("landtrain", "forceDetachSync", {
vehicleA = vehicleAId,
vehicleB = vehicleBId
})
end
local function processPending()
if #pending == 0 then return end
for i = #pending, 1, -1 do
local item = pending[i]
item.ticks = item.ticks - 1
if item.ticks <= 0 then
if item.kind == "attach" then
processAttach(item)
elseif item.kind == "detach" then
processDetach(item)
end
table.remove(pending, i)
end
end
end
local function onClientCommand(module, command, player, args)
if module == "vehicle" and command == "attachTrailer" then
queueSync("attach", player, args)
elseif module == "vehicle" and command == "detachTrailer" then
queueSync("detach", player, args)
elseif module == "towbar" and command == "detachTowBar" then
queueSync("detach", player, args)
end
end
Events.OnClientCommand.Add(onClientCommand)
Events.OnTick.Add(processPending)
log("LandtrainTowSyncServer loaded")