Working 42.20 MP
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
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
|
||||
}, ";")
|
||||
|
||||
local runtime = "client"
|
||||
isServer = function() return runtime == "server" end
|
||||
isClient = function() return runtime == "client" end
|
||||
getDebug = function() return false end
|
||||
getTimestampMs = function() return 1000 end
|
||||
|
||||
local serverCommandCallbacks = {}
|
||||
local clientPlayer = { onlineId = 17 }
|
||||
Events = {
|
||||
OnServerCommand = {
|
||||
Add = function(callback)
|
||||
serverCommandCallbacks[#serverCommandCallbacks + 1] = callback
|
||||
end
|
||||
},
|
||||
OnSpawnVehicleEnd = { Add = function() end }
|
||||
}
|
||||
|
||||
local function attachmentScript()
|
||||
local attachments = { trailer = {}, trailerfront = {} }
|
||||
return {
|
||||
getAttachmentById = function(_, attachmentId)
|
||||
return attachments[attachmentId]
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
local function vehicle(id, sqlId, acknowledgeConstraint)
|
||||
local value = {
|
||||
id = id,
|
||||
sqlId = sqlId,
|
||||
script = attachmentScript(),
|
||||
scriptName = "Base.MultiplayerRegression" .. tostring(id),
|
||||
modData = {},
|
||||
towing = nil,
|
||||
towedBy = nil,
|
||||
constraintKind = nil,
|
||||
acknowledgeConstraint = acknowledgeConstraint,
|
||||
constraintCalls = 0
|
||||
}
|
||||
|
||||
function value:getId() return self.id end
|
||||
function value:getSqlId() return self.sqlId end
|
||||
function value:getScript() return self.script end
|
||||
function value:getScriptName() return self.scriptName end
|
||||
function value:setScriptName(scriptName) self.scriptName = scriptName end
|
||||
function value:getModData() return self.modData end
|
||||
function value:transmitModData() end
|
||||
function value:getMass() return 1000 end
|
||||
function value:getBrakingForce() return 20 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:getTowAttachmentSelf() return nil end
|
||||
function value:attachmentExist(attachmentId)
|
||||
return self.script:getAttachmentById(attachmentId) ~= nil
|
||||
end
|
||||
function value:getX() return 0 end
|
||||
function value:getY() return 0 end
|
||||
function value:addPointConstraint(_, other)
|
||||
self.constraintCalls = self.constraintCalls + 1
|
||||
if self.acknowledgeConstraint then
|
||||
self.towing = other
|
||||
other.towedBy = self
|
||||
self.constraintKind = runtime == "client" and "rigid" or "native"
|
||||
other.constraintKind = self.constraintKind
|
||||
end
|
||||
end
|
||||
function value:breakConstraint()
|
||||
local other = self.towing or self.towedBy
|
||||
self.towing, self.towedBy = nil, nil
|
||||
self.constraintKind = nil
|
||||
if other then
|
||||
other.towing, other.towedBy = nil, nil
|
||||
other.constraintKind = nil
|
||||
end
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
local clientVehicles = {
|
||||
[101] = vehicle(101, 1001, true),
|
||||
[102] = vehicle(102, 1002, true),
|
||||
[201] = vehicle(201, 2001, false),
|
||||
[202] = vehicle(202, 2002, false)
|
||||
}
|
||||
clientVehicles[101].driver = clientPlayer
|
||||
clientVehicles[201].driver = clientPlayer
|
||||
local serverVehicles = {
|
||||
-- This models the dedicated-server behavior behind the live regression:
|
||||
-- addPointConstraint is accepted, but the server getters do not expose the
|
||||
-- relation before the authoritative client sync must be sent.
|
||||
[101] = vehicle(101, 1001, false),
|
||||
[102] = vehicle(102, 1002, false)
|
||||
}
|
||||
|
||||
getVehicleById = function(id)
|
||||
local vehicles = runtime == "server" and serverVehicles or clientVehicles
|
||||
return vehicles[tonumber(id)]
|
||||
end
|
||||
getPlayer = function() return clientPlayer end
|
||||
|
||||
TowBarMod = {
|
||||
Utils = {
|
||||
updateAttachmentsForRigidTow = function() end
|
||||
},
|
||||
Hook = {
|
||||
applyFreeRollingTowState = function() end,
|
||||
setVehicleScriptWithTowBarHidden = function(target, scriptName)
|
||||
target:setScriptName(scriptName)
|
||||
return true
|
||||
end,
|
||||
setVehiclePostAttach = function() end,
|
||||
cleanupDetachedTowBar = function() end
|
||||
}
|
||||
}
|
||||
|
||||
-- Load the real portable client sync and rigid-constraint implementation.
|
||||
dofile("42.20/media/lua/client/TowBar/TowSyncClient.lua")
|
||||
|
||||
local globalData = {}
|
||||
ModData = {
|
||||
getOrCreate = function(name)
|
||||
globalData[name] = globalData[name] or {}
|
||||
return globalData[name]
|
||||
end,
|
||||
transmit = function() end
|
||||
}
|
||||
|
||||
local serverCallbacks = {}
|
||||
Events = {
|
||||
OnClientCommand = { Add = function(callback) serverCallbacks.clientCommand = callback end },
|
||||
OnTick = { Add = function(callback) serverCallbacks.tick = callback end }
|
||||
}
|
||||
|
||||
runtime = "server"
|
||||
|
||||
local item = { id = 7001 }
|
||||
function item:getID() return self.id end
|
||||
function item:getFullType() return "TowBar.TowBar" end
|
||||
|
||||
local inventory = { items = { item } }
|
||||
function inventory:getItemWithID(id)
|
||||
return self.items[1] and self.items[1]:getID() == id and self.items[1] or nil
|
||||
end
|
||||
function inventory:getFirstTypeRecurse(fullType)
|
||||
return self.items[1] and self.items[1]:getFullType() == fullType and self.items[1] or nil
|
||||
end
|
||||
function inventory:contains(candidate) return self.items[1] == candidate end
|
||||
function inventory:Remove(candidate)
|
||||
if self.items[1] == candidate then self.items[1] = nil end
|
||||
end
|
||||
function inventory:AddItem() return nil end
|
||||
|
||||
local player = { vehicle = serverVehicles[101] }
|
||||
function player:getInventory() return inventory end
|
||||
function player:getVehicle() return self.vehicle end
|
||||
function player:getX() return 0 end
|
||||
function player:getY() return 0 end
|
||||
function player:isPrimaryHandItem() return false end
|
||||
function player:isSecondaryHandItem() return false end
|
||||
function player:removeFromHands() end
|
||||
function player:setPrimaryHandItem() end
|
||||
|
||||
sendRemoveItemFromContainer = function() end
|
||||
sendAddItemToContainer = function() end
|
||||
sendEquip = function() end
|
||||
|
||||
local attachSyncCount = 0
|
||||
local attachSyncPhases = {}
|
||||
sendServerCommand = function(module, command, args)
|
||||
if module == "towbar" and command == "forceAttachSync" then
|
||||
attachSyncCount = attachSyncCount + 1
|
||||
attachSyncPhases[#attachSyncPhases + 1] = args.phase
|
||||
end
|
||||
|
||||
local previousRuntime = runtime
|
||||
runtime = "client"
|
||||
for i = 1, #serverCommandCallbacks do
|
||||
serverCommandCallbacks[i](module, command, args)
|
||||
end
|
||||
runtime = previousRuntime
|
||||
end
|
||||
|
||||
getCell = function()
|
||||
return { getVehicles = function() return nil end }
|
||||
end
|
||||
|
||||
-- Load the real server command handler after the client event handler has been
|
||||
-- captured. Both halves now communicate through sendServerCommand above.
|
||||
dofile("42.20/media/lua/server/TowingCommands.lua")
|
||||
|
||||
serverCallbacks.clientCommand("towbar", "attachTowBar", player, {
|
||||
vehicleA = 101,
|
||||
vehicleB = 102,
|
||||
attachmentA = "trailer",
|
||||
attachmentB = "trailerfront",
|
||||
itemId = item:getID()
|
||||
})
|
||||
|
||||
expect(serverVehicles[101].modData.isTowingByTowBar == true,
|
||||
"accepted MP attach must publish towbar state for its visual")
|
||||
expect(serverVehicles[101].constraintCalls == 1,
|
||||
"accepted MP attach must still request the server-side logical constraint")
|
||||
expect(attachSyncCount == 1,
|
||||
"accepted MP attach must immediately send one forceAttachSync even when server towing getters lag")
|
||||
expect(attachSyncPhases[1] == nil,
|
||||
"portable MP attach must use the same single unphased sync as the working wrecker")
|
||||
expect(clientVehicles[101]:getVehicleTowing() == clientVehicles[102]
|
||||
and clientVehicles[102]:getVehicleTowedBy() == clientVehicles[101],
|
||||
"the same command that shows the MP towbar visual must create the client rigid constraint")
|
||||
expect(clientVehicles[101].constraintKind == "rigid",
|
||||
"the one-shot sync must create rigid rather than native towing physics")
|
||||
|
||||
-- Like the working wrecker, server acknowledgement is bookkeeping only. It
|
||||
-- must not introduce a second portable-only attach protocol or rebuild.
|
||||
serverVehicles[101].towing = serverVehicles[102]
|
||||
serverVehicles[102].towedBy = serverVehicles[101]
|
||||
serverCallbacks.tick()
|
||||
serverCallbacks.tick()
|
||||
|
||||
expect(attachSyncCount == 1,
|
||||
"server acknowledgement must not send a second portable-only attach phase")
|
||||
expect(clientVehicles[101].constraintCalls == 1,
|
||||
"the wrecker-style attach must submit one local rigid constraint")
|
||||
expect(clientVehicles[101].constraintKind == "rigid",
|
||||
"the single attach sync must leave the client using rigid towing physics")
|
||||
|
||||
sendServerCommand("towbar", "forceAttachSync", {
|
||||
vehicleA = 101,
|
||||
vehicleB = 102,
|
||||
attachmentA = "trailer",
|
||||
attachmentB = "trailerfront"
|
||||
})
|
||||
expect(clientVehicles[101].constraintCalls == 1,
|
||||
"duplicate snapshots must not repeatedly rebuild rigid physics")
|
||||
|
||||
-- The local add itself can also precede the engine's towing-getter update.
|
||||
-- Reconciliation must adopt the now-visible exact pair without duplicating
|
||||
-- the working wrecker-style single attach.
|
||||
runtime = "client"
|
||||
local delayedProvisional = {
|
||||
vehicleA = 201,
|
||||
vehicleB = 202,
|
||||
attachmentA = "trailer",
|
||||
attachmentB = "trailerfront"
|
||||
}
|
||||
for i = 1, #serverCommandCallbacks do
|
||||
serverCommandCallbacks[i]("towbar", "forceAttachSync", delayedProvisional)
|
||||
end
|
||||
expect(clientVehicles[201].constraintCalls == 1
|
||||
and clientVehicles[201]:getVehicleTowing() == nil,
|
||||
"a rigid add may be accepted before its local getters update")
|
||||
|
||||
clientVehicles[201].towing = clientVehicles[202]
|
||||
clientVehicles[202].towedBy = clientVehicles[201]
|
||||
clientVehicles[201].constraintKind = "rigid"
|
||||
clientVehicles[202].constraintKind = "rigid"
|
||||
for i = 1, #serverCommandCallbacks do
|
||||
serverCommandCallbacks[i]("towbar", "forceAttachSync", delayedProvisional)
|
||||
end
|
||||
expect(clientVehicles[201].constraintCalls == 1,
|
||||
"delayed local getter acknowledgement must make the next snapshot idempotent")
|
||||
|
||||
clientVehicles[201].acknowledgeConstraint = true
|
||||
for i = 1, #serverCommandCallbacks do
|
||||
serverCommandCallbacks[i]("towbar", "forceAttachSync", delayedProvisional)
|
||||
end
|
||||
expect(clientVehicles[201].constraintCalls == 1
|
||||
and clientVehicles[201].constraintKind == "rigid",
|
||||
"duplicate sync after delayed local acceptance must remain idempotent")
|
||||
runtime = "server"
|
||||
|
||||
if failures > 0 then os.exit(1) end
|
||||
print("PASS: multiplayer portable towbar immediately synchronizes its rigid client constraint")
|
||||
Reference in New Issue
Block a user