246 lines
8.0 KiB
Lua
246 lines
8.0 KiB
Lua
if isClient() then return end
|
|
|
|
local TowingCommands = {}
|
|
local Commands = {}
|
|
local TowBarItemType = "TowBar.TowBar"
|
|
local SyncDelayTicks = 2
|
|
local SnapshotIntervalTicks = 120
|
|
local pendingSync = {}
|
|
local snapshotTickCounter = 0
|
|
|
|
TowingCommands.wantNoise = getDebug() or false
|
|
|
|
local noise = function(msg)
|
|
if TowingCommands.wantNoise then
|
|
print("TowBarCommands: " .. msg)
|
|
end
|
|
end
|
|
|
|
local function queueSync(kind, player, args)
|
|
if not args then return end
|
|
table.insert(pendingSync, {
|
|
kind = kind,
|
|
ticks = SyncDelayTicks,
|
|
player = player,
|
|
args = args
|
|
})
|
|
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 isLinked(vehicleA, vehicleB)
|
|
if not vehicleA or not vehicleB then return false end
|
|
return vehicleA:getVehicleTowing() == vehicleB and vehicleB:getVehicleTowedBy() == vehicleA
|
|
end
|
|
|
|
local function hasTowBarState(vehicle)
|
|
if not vehicle then return false end
|
|
local md = vehicle:getModData()
|
|
if not md then return false end
|
|
return md["isTowingByTowBar"] == true
|
|
end
|
|
|
|
local function broadcastAttach(vehicleA, vehicleB, attachmentA, attachmentB)
|
|
if not vehicleA or not vehicleB then return end
|
|
sendServerCommand("towbar", "forceAttachSync", {
|
|
vehicleA = vehicleA:getId(),
|
|
vehicleB = vehicleB:getId(),
|
|
attachmentA = attachmentA,
|
|
attachmentB = attachmentB
|
|
})
|
|
end
|
|
|
|
local function broadcastDetach(vehicleAId, vehicleBId)
|
|
sendServerCommand("towbar", "forceDetachSync", {
|
|
vehicleA = vehicleAId,
|
|
vehicleB = vehicleBId
|
|
})
|
|
end
|
|
|
|
local function processAttachSync(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
|
|
noise("attach sync skipped missing vehicles A=" .. tostring(args.vehicleA) .. " B=" .. tostring(args.vehicleB))
|
|
return
|
|
end
|
|
|
|
local attachmentA = resolveAttachmentA(args, vehicleA)
|
|
local attachmentB = resolveAttachmentB(args, vehicleB)
|
|
if not isLinked(vehicleA, vehicleB) then
|
|
vehicleA:addPointConstraint(item.player, vehicleB, attachmentA, attachmentB)
|
|
end
|
|
if isLinked(vehicleA, vehicleB) then
|
|
broadcastAttach(vehicleA, vehicleB, attachmentA, attachmentB)
|
|
end
|
|
end
|
|
|
|
local function processDetachSync(item)
|
|
local args = item.args or {}
|
|
local vehicleAId = args.towingVehicle or args.vehicleA or args.vehicle
|
|
local vehicleBId = args.vehicleB
|
|
broadcastDetach(vehicleAId, vehicleBId)
|
|
end
|
|
|
|
local function snapshotActiveTowbarLinksServer()
|
|
local cell = getCell()
|
|
if not cell then return end
|
|
local list = cell:getVehicles()
|
|
if not list then return end
|
|
|
|
for i = 0, list:size() - 1 do
|
|
local towingVehicle = list:get(i)
|
|
local towedVehicle = towingVehicle and towingVehicle:getVehicleTowing() or nil
|
|
if towingVehicle and towedVehicle and towedVehicle:getVehicleTowedBy() == towingVehicle then
|
|
if hasTowBarState(towingVehicle) or hasTowBarState(towedVehicle) then
|
|
local attachmentA = resolveAttachmentA(nil, towingVehicle)
|
|
local towedMd = towedVehicle:getModData()
|
|
local attachmentB = (towedMd and towedMd["towBarChangedAttachmentId"]) or resolveAttachmentB(nil, towedVehicle)
|
|
if attachmentA == attachmentB then
|
|
attachmentA = "trailer"
|
|
attachmentB = "trailerfront"
|
|
end
|
|
broadcastAttach(towingVehicle, towedVehicle, attachmentA, attachmentB)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function processPendingSync()
|
|
snapshotTickCounter = snapshotTickCounter + 1
|
|
if snapshotTickCounter >= SnapshotIntervalTicks then
|
|
snapshotTickCounter = 0
|
|
snapshotActiveTowbarLinksServer()
|
|
end
|
|
|
|
if #pendingSync == 0 then return end
|
|
|
|
local remaining = {}
|
|
for i = 1, #pendingSync do
|
|
local item = pendingSync[i]
|
|
item.ticks = item.ticks - 1
|
|
if item.ticks <= 0 then
|
|
if item.kind == "attach" then
|
|
processAttachSync(item)
|
|
elseif item.kind == "detach" then
|
|
processDetachSync(item)
|
|
end
|
|
else
|
|
table.insert(remaining, item)
|
|
end
|
|
end
|
|
pendingSync = remaining
|
|
end
|
|
|
|
function Commands.attachTowBar(player, args)
|
|
local vehicleA = getVehicleById(args.vehicleA)
|
|
local vehicleB = getVehicleById(args.vehicleB)
|
|
if not vehicleA then
|
|
noise("no such vehicle (A) id=" .. tostring(args.vehicleA))
|
|
return
|
|
end
|
|
if not vehicleB then
|
|
noise("no such vehicle (B) id=" .. tostring(args.vehicleB))
|
|
return
|
|
end
|
|
|
|
vehicleA:addPointConstraint(player, vehicleB, args.attachmentA, args.attachmentB)
|
|
end
|
|
|
|
function Commands.detachTowBar(player, args)
|
|
local towingVehicle = args.towingVehicle and getVehicleById(args.towingVehicle) or nil
|
|
local towedVehicle = args.vehicle and getVehicleById(args.vehicle) or nil
|
|
|
|
if not towingVehicle and towedVehicle then
|
|
towingVehicle = towedVehicle:getVehicleTowedBy()
|
|
end
|
|
if not towedVehicle and towingVehicle then
|
|
towedVehicle = towingVehicle:getVehicleTowing()
|
|
end
|
|
|
|
if towedVehicle then
|
|
towedVehicle:breakConstraint(true, false)
|
|
end
|
|
if towingVehicle and towingVehicle ~= towedVehicle then
|
|
towingVehicle:breakConstraint(true, false)
|
|
end
|
|
end
|
|
|
|
function Commands.consumeTowBar(player, args)
|
|
if not player then return end
|
|
local inventory = player:getInventory()
|
|
if not inventory then return end
|
|
|
|
local towBarItem = nil
|
|
local itemId = args and args.itemId
|
|
if itemId then
|
|
towBarItem = inventory:getItemWithID(itemId)
|
|
end
|
|
if not towBarItem then
|
|
towBarItem = inventory:getFirstTypeRecurse(TowBarItemType)
|
|
end
|
|
if not towBarItem then return end
|
|
|
|
local wasPrimary = player:isPrimaryHandItem(towBarItem)
|
|
local wasSecondary = player:isSecondaryHandItem(towBarItem)
|
|
player:removeFromHands(towBarItem)
|
|
inventory:Remove(towBarItem)
|
|
sendRemoveItemFromContainer(inventory, towBarItem)
|
|
|
|
if wasPrimary or wasSecondary then
|
|
sendEquip(player)
|
|
end
|
|
end
|
|
|
|
function Commands.giveTowBar(player, args)
|
|
if not player then return end
|
|
local inventory = player:getInventory()
|
|
if not inventory then return end
|
|
|
|
local towBarItem = inventory:AddItem(TowBarItemType)
|
|
if not towBarItem then return end
|
|
sendAddItemToContainer(inventory, towBarItem)
|
|
|
|
if args and args.equipPrimary then
|
|
player:setPrimaryHandItem(towBarItem)
|
|
sendEquip(player)
|
|
end
|
|
end
|
|
|
|
-- Compatibility aliases for older command names.
|
|
Commands.attachConstraint = Commands.attachTowBar
|
|
Commands.detachConstraint = Commands.detachTowBar
|
|
|
|
TowingCommands.OnClientCommand = function(module, command, player, args)
|
|
-- Only sync explicit towbar commands so vanilla towing stays untouched.
|
|
if module == "towbar" and command == "attachTowBar" then
|
|
queueSync("attach", player, args)
|
|
elseif module == "towbar" and command == "detachTowBar" then
|
|
queueSync("detach", player, args)
|
|
end
|
|
|
|
if module == "towbar" and Commands[command] then
|
|
local argStr = ""
|
|
args = args or {}
|
|
for k, v in pairs(args) do
|
|
argStr = argStr .. " " .. tostring(k) .. "=" .. tostring(v)
|
|
end
|
|
noise("received " .. module .. " " .. command .. " " .. tostring(player) .. argStr)
|
|
Commands[command](player, args)
|
|
end
|
|
end
|
|
|
|
Events.OnClientCommand.Add(TowingCommands.OnClientCommand)
|
|
Events.OnTick.Add(processPendingSync)
|