184 lines
6.5 KiB
Lua
184 lines
6.5 KiB
Lua
local failures = 0
|
|
|
|
local function expect(condition, message)
|
|
if not condition then
|
|
failures = failures + 1
|
|
io.stderr:write("FAIL: " .. message .. "\n")
|
|
end
|
|
end
|
|
|
|
local serverCommand
|
|
local vehicleSpawned
|
|
Events = {
|
|
OnServerCommand = {
|
|
Add = function(callback) serverCommand = callback end
|
|
},
|
|
OnSpawnVehicleEnd = {
|
|
Add = function(callback) vehicleSpawned = callback end
|
|
}
|
|
}
|
|
|
|
isServer = function() return false end
|
|
|
|
local trace = {}
|
|
local function record(value) trace[#trace + 1] = value end
|
|
|
|
local function script()
|
|
return {
|
|
getAttachmentById = function(_, id)
|
|
if id == "trailer" or id == "trailerfront" then return {} end
|
|
return nil
|
|
end,
|
|
getWheelCount = function() return 0 end,
|
|
getPhysicsChassisShape = function()
|
|
return { z = function() return 4 end }
|
|
end,
|
|
addAttachment = function() end
|
|
}
|
|
end
|
|
|
|
local rigidAdds = 0
|
|
local breaks = 0
|
|
local towing
|
|
local towed
|
|
|
|
local function vehicle(id)
|
|
local value = {
|
|
id = id,
|
|
modData = {},
|
|
script = script(),
|
|
towing = nil,
|
|
towedBy = nil
|
|
}
|
|
|
|
function value:getId() return self.id end
|
|
function value:getScript() return self.script end
|
|
function value:getScriptName() return "Base.TestVehicle" end
|
|
function value:getModData() return self.modData end
|
|
function value:transmitModData() end
|
|
function value:getMass() return 1200 end
|
|
function value:getBrakingForce() return 25 end
|
|
function value:getVehicleTowing() return self.towing end
|
|
function value:getVehicleTowedBy() return self.towedBy end
|
|
function value:addPointConstraint(_, other, attachmentA, attachmentB, localOnly)
|
|
rigidAdds = rigidAdds + 1
|
|
record("rigid-add")
|
|
expect(self == towing and other == towed, "rigid rebuild must use the original pair")
|
|
expect(attachmentA == "trailer" and attachmentB == "trailerfront", "rigid rebuild must preserve endpoints")
|
|
expect(localOnly == true, "rigid rebuild must be local-only to avoid an attach/detach command race")
|
|
self.towing = other
|
|
other.towedBy = self
|
|
end
|
|
function value:breakConstraint()
|
|
breaks = breaks + 1
|
|
record("break-native")
|
|
if self == towing or self == towed then
|
|
towing.towing = nil
|
|
towing.towedBy = nil
|
|
towed.towing = nil
|
|
towed.towedBy = nil
|
|
end
|
|
end
|
|
|
|
return value
|
|
end
|
|
|
|
towing = vehicle(101)
|
|
towed = vehicle(202)
|
|
local vehicles = { [101] = towing, [202] = towed }
|
|
getVehicleById = function(id) return vehicles[id] end
|
|
|
|
TowBarMod = {
|
|
Utils = {
|
|
updateAttachmentsForRigidTow = function()
|
|
record("rigid-offsets")
|
|
end
|
|
},
|
|
Hook = {
|
|
cleanupDetachedTowBar = function()
|
|
record("cleanup")
|
|
towing.modData = {}
|
|
towed.modData = {}
|
|
end,
|
|
setVehicleScriptWithTowBarHidden = function(_, scriptName)
|
|
record("script:" .. tostring(scriptName))
|
|
return true
|
|
end,
|
|
setVehiclePostAttach = function()
|
|
record("post-attach")
|
|
end
|
|
}
|
|
}
|
|
|
|
dofile("42.20/media/lua/client/TowBar/TowSyncClient.lua")
|
|
expect(type(serverCommand) == "function", "client sync must register its server-command handler")
|
|
|
|
-- Begin with the old physical pair so spontaneous cleanup exercises the same
|
|
-- path as a real Build 42 break notification.
|
|
towing.towing = towed
|
|
towed.towedBy = towing
|
|
towing.modData.towBarTowedVehicleId = towed:getId()
|
|
towed.modData.towBarTowingVehicleId = towing:getId()
|
|
serverCommand("towbar", "spontaneousDetachSync", {
|
|
vehicleA = towing:getId(),
|
|
vehicleB = towed:getId()
|
|
})
|
|
expect(breaks > 0, "spontaneous cleanup must remove the old native relation")
|
|
expect(towing:getVehicleTowing() == nil and towed:getVehicleTowedBy() == nil, "spontaneous cleanup must leave the pair unlinked")
|
|
|
|
-- A direct reattach can arrive from the server as a native B42 relationship
|
|
-- before forceAttachSync reaches the client. That native relationship is the
|
|
-- rope-like constraint; forceAttachSync must replace it with the rigid path.
|
|
towing.towing = towed
|
|
towed.towedBy = towing
|
|
local breaksBeforeReattach = breaks
|
|
serverCommand("towbar", "forceAttachSync", {
|
|
vehicleA = towing:getId(),
|
|
vehicleB = towed:getId(),
|
|
attachmentA = "trailer",
|
|
attachmentB = "trailerfront"
|
|
})
|
|
|
|
expect(breaks == breaksBeforeReattach + 1, "direct reattach must replace the native B42 rope relation")
|
|
expect(rigidAdds == 1, "direct reattach must create exactly one rigid local constraint")
|
|
|
|
local fakeScriptIndex
|
|
local rigidAddIndex
|
|
for index, value in ipairs(trace) do
|
|
if value == "script:notTowingA_Trailer" and not fakeScriptIndex then fakeScriptIndex = index end
|
|
if value == "rigid-add" and not rigidAddIndex then rigidAddIndex = index end
|
|
end
|
|
expect(fakeScriptIndex ~= nil, "rigid reattach must select the fake-trailer script")
|
|
expect(rigidAddIndex ~= nil and fakeScriptIndex < rigidAddIndex, "fake-trailer selection must precede rigid constraint creation")
|
|
expect(towing:getVehicleTowing() == towed and towed:getVehicleTowedBy() == towing, "rigid rebuild must finish with a reciprocal tow relation")
|
|
|
|
local rigidAddsAfterFirstSync = rigidAdds
|
|
local breaksAfterFirstSync = breaks
|
|
serverCommand("towbar", "forceAttachSync", {
|
|
vehicleA = towing:getId(),
|
|
vehicleB = towed:getId(),
|
|
attachmentA = "trailer",
|
|
attachmentB = "trailerfront"
|
|
})
|
|
expect(rigidAdds == rigidAddsAfterFirstSync, "a repeated attach snapshot must not layer a second rigid constraint")
|
|
expect(breaks == breaksAfterFirstSync, "a repeated attach snapshot must adopt the already-rigid pair")
|
|
|
|
-- Streaming can recreate a native relationship with the same runtime IDs
|
|
-- while the Lua module and its applied cache survive. The spawn notification
|
|
-- must invalidate that stale cache so the next snapshot replaces the rope.
|
|
expect(type(vehicleSpawned) == "function", "client sync must observe vehicle streaming")
|
|
vehicleSpawned(towed)
|
|
local rigidAddsBeforeStreamRestore = rigidAdds
|
|
local breaksBeforeStreamRestore = breaks
|
|
serverCommand("towbar", "forceAttachSync", {
|
|
vehicleA = towing:getId(),
|
|
vehicleB = towed:getId(),
|
|
attachmentA = "trailer",
|
|
attachmentB = "trailerfront"
|
|
})
|
|
expect(rigidAdds == rigidAddsBeforeStreamRestore + 1, "stream-restored native rope must be rebuilt rigid")
|
|
expect(breaks == breaksBeforeStreamRestore + 1, "stream-restored native rope must be removed exactly once")
|
|
|
|
if failures > 0 then os.exit(1) end
|
|
print("PASS: spontaneous break followed by direct attach rebuilds a rigid towbar")
|