179 lines
7.8 KiB
Lua
179 lines
7.8 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
|
|
|
|
isServer = function() return false end
|
|
|
|
local metrics = {
|
|
offsets = 0,
|
|
breaks = 0,
|
|
adds = 0,
|
|
hides = 0,
|
|
postAttach = 0,
|
|
scriptSwaps = 0,
|
|
freeRolling = 0
|
|
}
|
|
local trace = {}
|
|
local function record(event) trace[#trace + 1] = event end
|
|
|
|
local function script()
|
|
return {
|
|
getAttachmentById = function(_, id)
|
|
if id == "trailer" or id == "trailerfront" then return {} end
|
|
return nil
|
|
end
|
|
}
|
|
end
|
|
|
|
local acknowledgeAdd = true
|
|
local function unlink(a, b)
|
|
a.towing, a.towedBy = nil, nil
|
|
b.towing, b.towedBy = nil, nil
|
|
end
|
|
|
|
local function link(a, b)
|
|
a.towing, a.towedBy = b, nil
|
|
b.towing, b.towedBy = nil, a
|
|
end
|
|
|
|
local function vehicle(id)
|
|
local value = {
|
|
id = id,
|
|
towing = nil,
|
|
towedBy = nil,
|
|
script = script(),
|
|
scriptName = "Base.TestVehicle",
|
|
modData = {}
|
|
}
|
|
function value:getId() return self.id end
|
|
function value:getScript() return self.script end
|
|
function value:getScriptName() return self.scriptName end
|
|
function value:getModData() return self.modData end
|
|
function value:getVehicleTowing() return self.towing end
|
|
function value:getVehicleTowedBy() return self.towedBy end
|
|
function value:breakConstraint()
|
|
metrics.breaks = metrics.breaks + 1
|
|
record("break")
|
|
local other = self.towing or self.towedBy
|
|
if other then unlink(self, other) end
|
|
end
|
|
function value:addPointConstraint(player, other, attachmentA, attachmentB, localOnly)
|
|
metrics.adds = metrics.adds + 1
|
|
record("add")
|
|
expect(player == nil, "rigid primitive must not impersonate a player")
|
|
expect(attachmentA == "trailer" and attachmentB == "trailerfront",
|
|
"rigid primitive must preserve selected attachment IDs")
|
|
expect(localOnly == true, "rigid primitive must create the client-local rigid constraint")
|
|
if acknowledgeAdd then link(self, other) end
|
|
end
|
|
return value
|
|
end
|
|
|
|
TowBarMod = {
|
|
Utils = {
|
|
updateAttachmentsForRigidTow = function(a, b, attachmentA, attachmentB)
|
|
metrics.offsets = metrics.offsets + 1
|
|
expect(a ~= nil and b ~= nil, "rigid offset update must receive both vehicles")
|
|
expect(attachmentA == "trailer" and attachmentB == "trailerfront",
|
|
"rigid offset update must receive the selected endpoints")
|
|
end
|
|
},
|
|
Hook = {
|
|
applyFreeRollingTowState = function(vehicle)
|
|
metrics.freeRolling = metrics.freeRolling + 1
|
|
record("free")
|
|
expect(vehicle ~= nil, "rigid primitive must receive the towed vehicle for free-roll setup")
|
|
end,
|
|
setVehicleScriptWithTowBarHidden = function(vehicle, scriptName)
|
|
metrics.hides = metrics.hides + 1
|
|
metrics.scriptSwaps = metrics.scriptSwaps + 1
|
|
vehicle.scriptName = scriptName
|
|
record("script:" .. tostring(scriptName))
|
|
expect(vehicle ~= nil,
|
|
"rigid primitive must receive a vehicle for each hidden script swap")
|
|
end,
|
|
setVehiclePostAttach = function(player, vehicle, towingVehicle)
|
|
metrics.postAttach = metrics.postAttach + 1
|
|
record("post")
|
|
expect(player == nil and vehicle ~= nil and towingVehicle ~= nil,
|
|
"rigid primitive must restore post-attach state after acknowledgement")
|
|
expect(vehicle:getScriptName() == "Base.TestVehicle",
|
|
"rigid primitive must restore the real script before post-attach finalization")
|
|
end
|
|
}
|
|
}
|
|
|
|
dofile("42.20/media/lua/client/TowBar/RigidTow.lua")
|
|
|
|
expect(TowBarMod.RigidTow ~= nil and type(TowBarMod.RigidTow.attach) == "function",
|
|
"production rigid module must expose attach")
|
|
|
|
-- First attach with no physical relation uses the exact rigid path once.
|
|
local a, b = vehicle(101), vehicle(102)
|
|
trace = {}
|
|
local attached = TowBarMod.RigidTow.attach(a, b, "trailer", "trailerfront")
|
|
expect(attached == true, "acknowledged initial rigid add must succeed")
|
|
expect(a:getVehicleTowing() == b and b:getVehicleTowedBy() == a,
|
|
"acknowledged rigid add must leave an exact reciprocal relation")
|
|
expect(metrics.offsets == 1 and metrics.adds == 1 and metrics.breaks == 0,
|
|
"initial rigid add must update offsets and add once without a needless break")
|
|
expect(metrics.scriptSwaps == 2 and metrics.postAttach == 1,
|
|
"successful rigid add must select the fake script and restore the real script exactly once")
|
|
expect(table.concat(trace, ",") == "free,script:notTowingA_Trailer,add,script:Base.TestVehicle,post",
|
|
"initial rigid sequence must be free-roll, fake script, local add, real script, then post-attach")
|
|
|
|
-- A restored native relation is replaced inside this primitive. The lifecycle
|
|
-- controller must not grow its own second copy of this physics sequence.
|
|
local restoredA, restoredB = vehicle(201), vehicle(202)
|
|
link(restoredA, restoredB)
|
|
local beforeOffsets, beforeAdds = metrics.offsets, metrics.adds
|
|
local beforeBreaks, beforePost = metrics.breaks, metrics.postAttach
|
|
trace = {}
|
|
attached = TowBarMod.RigidTow.attach(restoredA, restoredB, "trailer", "trailerfront")
|
|
expect(attached == true, "restored native relation must be replaced by rigid mechanics")
|
|
expect(metrics.offsets == beforeOffsets + 1 and metrics.adds == beforeAdds + 1,
|
|
"native replacement must update offsets and add one rigid constraint")
|
|
expect(metrics.breaks == beforeBreaks + 1,
|
|
"native replacement must break the exact old pair once")
|
|
expect(metrics.postAttach == beforePost + 1,
|
|
"native replacement must run post-attach only after the new link exists")
|
|
expect(table.concat(trace, ",") == "break,free,script:notTowingA_Trailer,add,script:Base.TestVehicle,post",
|
|
"native replacement sequence must break, free-roll, use the fake script, add, restore the real script, then finalize")
|
|
|
|
-- A link to a different vehicle is never collateral damage.
|
|
local conflictA, expectedB, unrelated = vehicle(301), vehicle(302), vehicle(399)
|
|
link(conflictA, unrelated)
|
|
local beforeConflictBreaks, beforeConflictAdds = metrics.breaks, metrics.adds
|
|
attached = TowBarMod.RigidTow.attach(conflictA, expectedB, "trailer", "trailerfront")
|
|
expect(attached == false, "conflicting physical link must reject rigid attachment")
|
|
expect(metrics.breaks == beforeConflictBreaks and metrics.adds == beforeConflictAdds,
|
|
"conflicting physical link must not break or layer another constraint")
|
|
|
|
-- Invalid endpoints are rejected before modifying a valid existing pair.
|
|
local missingA, missingB = vehicle(351), vehicle(352)
|
|
link(missingA, missingB)
|
|
local beforeMissingBreaks, beforeMissingAdds = metrics.breaks, metrics.adds
|
|
attached = TowBarMod.RigidTow.attach(missingA, missingB, "missing", "trailerfront")
|
|
expect(attached == false, "missing attachment must reject rigid attachment")
|
|
expect(metrics.breaks == beforeMissingBreaks and metrics.adds == beforeMissingAdds,
|
|
"missing attachment must not break the existing pair or attempt an add")
|
|
|
|
-- In multiplayer Java/Bullet may accept the local constraint before the Lua
|
|
-- reciprocal getters update. Submission is success; waiting for those getters
|
|
-- here strands an already-streamed pair with only its visual state applied.
|
|
local delayedA, delayedB = vehicle(401), vehicle(402)
|
|
acknowledgeAdd = false
|
|
local beforeDelayedPost = metrics.postAttach
|
|
attached = TowBarMod.RigidTow.attach(delayedA, delayedB, "trailer", "trailerfront")
|
|
expect(attached == true, "submitted rigid add must tolerate delayed multiplayer getters")
|
|
expect(metrics.postAttach == beforeDelayedPost + 1,
|
|
"delayed multiplayer getters must not block post-attach restoration")
|
|
|
|
if failures > 0 then os.exit(1) end
|
|
print("PASS: single rigid-tow contract preserves exact-pair mechanics and delayed MP submission")
|