42.20 and Towtrucks
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
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")
|
||||
Reference in New Issue
Block a user