Working 42.20 MP
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
local failures = 0
|
||||
|
||||
local function expect(condition, message)
|
||||
if not condition then
|
||||
failures = failures + 1
|
||||
io.stderr:write("FAIL: " .. message .. "\n")
|
||||
end
|
||||
end
|
||||
|
||||
package.path = table.concat({
|
||||
"42.20/media/lua/client/?.lua",
|
||||
"42.20/media/lua/shared/?.lua",
|
||||
package.path
|
||||
}, ";")
|
||||
|
||||
isServer = function() return false end
|
||||
isClient = function() return true end
|
||||
|
||||
local serverCommandCallbacks = {}
|
||||
local enterVehicleCallbacks = {}
|
||||
local spawnVehicleCallbacks = {}
|
||||
local tickCallbacks = {}
|
||||
Events = {
|
||||
OnServerCommand = {
|
||||
Add = function(callback)
|
||||
serverCommandCallbacks[#serverCommandCallbacks + 1] = callback
|
||||
end
|
||||
},
|
||||
OnEnterVehicle = {
|
||||
Add = function(callback)
|
||||
enterVehicleCallbacks[#enterVehicleCallbacks + 1] = callback
|
||||
end
|
||||
},
|
||||
OnSwitchVehicleSeat = { Add = function() end },
|
||||
OnSpawnVehicleEnd = {
|
||||
Add = function(callback)
|
||||
spawnVehicleCallbacks[#spawnVehicleCallbacks + 1] = callback
|
||||
end
|
||||
},
|
||||
OnTick = {
|
||||
Add = function(callback)
|
||||
tickCallbacks[#tickCallbacks + 1] = callback
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
local localPlayer = { vehicle = nil }
|
||||
function localPlayer:getVehicle() return self.vehicle end
|
||||
|
||||
local attachmentIds = {
|
||||
trailer = true,
|
||||
trailerfront = true,
|
||||
towbarWreckerHookLow = true,
|
||||
towbarWreckerHookMid = true,
|
||||
towbarWreckerHookHigh = true
|
||||
}
|
||||
|
||||
local function unlink(vehicleA, vehicleB)
|
||||
vehicleA.towing, vehicleA.towedBy = nil, nil
|
||||
vehicleB.towing, vehicleB.towedBy = nil, nil
|
||||
end
|
||||
|
||||
local function link(vehicleA, vehicleB)
|
||||
vehicleA.towing, vehicleA.towedBy = vehicleB, nil
|
||||
vehicleB.towing, vehicleB.towedBy = nil, vehicleA
|
||||
end
|
||||
|
||||
local function newVehicle(id, sqlId)
|
||||
local value = {
|
||||
id = id,
|
||||
sqlId = sqlId,
|
||||
modData = {},
|
||||
scriptName = "Base.DriverEntry" .. tostring(id),
|
||||
mass = 1000,
|
||||
brakingForce = 20,
|
||||
towing = nil,
|
||||
towedBy = nil,
|
||||
driver = nil,
|
||||
addCalls = 0,
|
||||
breakCalls = 0
|
||||
}
|
||||
|
||||
local script = {
|
||||
getAttachmentById = function(_, attachmentId)
|
||||
return attachmentIds[attachmentId] and {} or nil
|
||||
end
|
||||
}
|
||||
|
||||
function value:getId() return self.id end
|
||||
function value:getSqlId() return self.sqlId end
|
||||
function value:getModData() return self.modData end
|
||||
function value:transmitModData() end
|
||||
function value:getScript() return script end
|
||||
function value:getScriptName() return self.scriptName end
|
||||
function value:setScriptName(scriptName) self.scriptName = scriptName end
|
||||
function value:getMass() return self.mass end
|
||||
function value:setMass(mass) self.mass = mass end
|
||||
function value:getBrakingForce() return self.brakingForce end
|
||||
function value:setBrakingForce(force) self.brakingForce = force end
|
||||
function value:isParkingBrakeOn() return false end
|
||||
function value:setParkingBrakeOn() end
|
||||
function value:getParkingBrake() return false end
|
||||
function value:setParkingBrake() end
|
||||
function value:isHandbrake() return false end
|
||||
function value:setHandbrake() end
|
||||
function value:getVehicleTowing() return self.towing end
|
||||
function value:getVehicleTowedBy() return self.towedBy end
|
||||
function value:isDriver(player) return self.driver == player end
|
||||
function value:attachmentExist(attachmentId)
|
||||
return attachmentIds[attachmentId] == true
|
||||
end
|
||||
function value:addPointConstraint(_, other)
|
||||
self.addCalls = self.addCalls + 1
|
||||
link(self, other)
|
||||
end
|
||||
function value:breakConstraint()
|
||||
self.breakCalls = self.breakCalls + 1
|
||||
local other = self.towing or self.towedBy
|
||||
if other then unlink(self, other) end
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
local portableTowing = newVehicle(101, 1001)
|
||||
local portableTowed = newVehicle(102, 1002)
|
||||
local wrecker = newVehicle(201, 2001)
|
||||
local wreckerTarget = newVehicle(202, 2002)
|
||||
local unrelated = newVehicle(301, 3001)
|
||||
local allVehicles = {
|
||||
portableTowing,
|
||||
portableTowed,
|
||||
wrecker,
|
||||
wreckerTarget,
|
||||
unrelated
|
||||
}
|
||||
local vehiclesById = {}
|
||||
for _, vehicle in ipairs(allVehicles) do vehiclesById[vehicle:getId()] = vehicle end
|
||||
|
||||
getPlayer = function() return localPlayer end
|
||||
getVehicleById = function(id) return vehiclesById[tonumber(id)] end
|
||||
getCell = function()
|
||||
return { getVehicles = function() return allVehicles end }
|
||||
end
|
||||
|
||||
TowBarMod = {
|
||||
Utils = { updateAttachmentsForRigidTow = function() end },
|
||||
Hook = {
|
||||
applyFreeRollingTowState = function() end,
|
||||
setVehiclePostAttach = function() end,
|
||||
setVehicleScriptWithTowBarHidden = function(vehicle, scriptName)
|
||||
vehicle:setScriptName(scriptName)
|
||||
return true
|
||||
end,
|
||||
cleanupDetachedTowBar = function() end
|
||||
},
|
||||
Wrecker = {
|
||||
getWorldVehicles = function() return allVehicles end,
|
||||
getHeightAttachmentId = function(level)
|
||||
return ({
|
||||
[0] = "towbarWreckerHookLow",
|
||||
[1] = "towbarWreckerHookMid",
|
||||
[2] = "towbarWreckerHookHigh"
|
||||
})[tonumber(level) or 0]
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
-- Model a fully persisted portable pair whose transient sync cache was lost
|
||||
-- during reload. The exact native relation is deliberately present: entry
|
||||
-- must replace it with one fresh client-owned rigid constraint.
|
||||
portableTowing.modData = {
|
||||
isTowingByTowBar = true,
|
||||
towed = false,
|
||||
towBarTowedVehicleId = portableTowed:getId(),
|
||||
towBarTowedVehicleSqlId = portableTowed:getSqlId(),
|
||||
towBarExpectedAttachment = "trailer"
|
||||
}
|
||||
portableTowed.modData = {
|
||||
isTowingByTowBar = true,
|
||||
towed = true,
|
||||
towBarTowingVehicleId = portableTowing:getId(),
|
||||
towBarTowingVehicleSqlId = portableTowing:getSqlId(),
|
||||
towBarExpectedAttachment = "trailerfront",
|
||||
towBarOriginalScriptName = portableTowed:getScriptName()
|
||||
}
|
||||
link(portableTowing, portableTowed)
|
||||
|
||||
-- Model the equivalent persisted wrecker pair, also without applied-level
|
||||
-- cache state. Both members carry the reciprocal identity written by the
|
||||
-- authoritative wrecker attach path.
|
||||
wrecker.modData = {
|
||||
wreckerTowActive = true,
|
||||
wreckerTowedVehicleId = wreckerTarget:getId(),
|
||||
wreckerTowedVehicleSqlId = wreckerTarget:getSqlId(),
|
||||
wreckerTargetAttachment = "trailerfront",
|
||||
wreckerHeightLevel = 2
|
||||
}
|
||||
wreckerTarget.modData = {
|
||||
wreckerTowingVehicleId = wrecker:getId(),
|
||||
wreckerTowingVehicleSqlId = wrecker:getSqlId(),
|
||||
wreckerOriginalScriptName = wreckerTarget:getScriptName()
|
||||
}
|
||||
link(wrecker, wreckerTarget)
|
||||
|
||||
dofile("42.20/media/lua/client/TowBar/TowSyncClient.lua")
|
||||
dofile("42.20/media/lua/client/TowBar/WreckerSyncClient.lua")
|
||||
|
||||
expect(next(TowBarMod.Sync.desiredPairs) == nil,
|
||||
"portable regression setup must not depend on transient desired-pair sync")
|
||||
expect(next(TowBarMod.Sync.appliedPairs) == nil,
|
||||
"portable regression setup must begin without an applied-pair cache")
|
||||
expect(next(TowBarMod.WreckerSync.appliedLevels) == nil,
|
||||
"wrecker regression setup must begin without an applied-level cache")
|
||||
|
||||
local function enterAsDriver(vehicle)
|
||||
for _, candidate in ipairs(allVehicles) do candidate.driver = nil end
|
||||
vehicle.driver = localPlayer
|
||||
localPlayer.vehicle = vehicle
|
||||
for i = 1, #enterVehicleCallbacks do
|
||||
enterVehicleCallbacks[i](localPlayer)
|
||||
end
|
||||
end
|
||||
|
||||
enterAsDriver(portableTowing)
|
||||
expect(portableTowing.breakCalls == 1 and portableTowing.addCalls == 1,
|
||||
"portable driver entry must force exactly one rebuild from persisted modData")
|
||||
enterAsDriver(portableTowing)
|
||||
expect(portableTowing.breakCalls == 2 and portableTowing.addCalls == 2,
|
||||
"each later portable driver entry must force one new rebuild, not remain cache-idempotent")
|
||||
|
||||
enterAsDriver(wrecker)
|
||||
expect(wrecker.breakCalls == 1 and wrecker.addCalls == 1,
|
||||
"wrecker driver entry must force exactly one rebuild from persisted modData")
|
||||
enterAsDriver(wrecker)
|
||||
expect(wrecker.breakCalls == 2 and wrecker.addCalls == 2,
|
||||
"each later wrecker driver entry must force one new rebuild, not remain cache-idempotent")
|
||||
|
||||
local portableCounts = portableTowing.breakCalls + portableTowing.addCalls
|
||||
local wreckerCounts = wrecker.breakCalls + wrecker.addCalls
|
||||
enterAsDriver(unrelated)
|
||||
expect(portableTowing.breakCalls + portableTowing.addCalls == portableCounts
|
||||
and wrecker.breakCalls + wrecker.addCalls == wreckerCounts,
|
||||
"entering an unrelated vehicle must not rebuild any persisted tow pair")
|
||||
expect(#tickCallbacks == 0,
|
||||
"driver-entry recovery must remain event-driven and must not install a tick retry loop")
|
||||
|
||||
if failures > 0 then os.exit(1) end
|
||||
print("PASS: driver entry force-reattaches persisted portable and wrecker pairs once")
|
||||
Reference in New Issue
Block a user