fix(multiplayer): replicate per-player avatar loadout and preserve original materials
checkpoint / checkpoint (push) Successful in 5m57s
checkpoint / checkpoint (push) Successful in 5m57s
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
extends SceneTree
|
||||
## Client process for the multiplayer avatar replication test
|
||||
## (scripts/run_multiplayer_avatar_test.sh).
|
||||
##
|
||||
## Role "b": Uncustomized character (no hair, no attachments).
|
||||
## Observes Client A. Verifies Client A's remote model HAS hair/attachments
|
||||
## and does NOT have a solid material_override tint.
|
||||
##
|
||||
## Role "a": Customized character (with hair + chest).
|
||||
## Observes Client B. Verifies Client B's remote model has NO hair/attachments
|
||||
## and does NOT have a solid material_override tint.
|
||||
|
||||
var role := "b"
|
||||
var port := 7789
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
for i in args.size():
|
||||
if args[i] == "--role" and i + 1 < args.size():
|
||||
role = args[i + 1]
|
||||
elif args[i] == "--port" and i + 1 < args.size():
|
||||
port = int(args[i + 1])
|
||||
_run()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
await process_frame
|
||||
|
||||
var avatars = root.get_node_or_null("AvatarSystem")
|
||||
if avatars == null:
|
||||
print("RESULT %s avatar_system_missing" % role)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
await avatars.ensure_catalog()
|
||||
var bases: Array = avatars.get_assets_for_slot("base_body")
|
||||
if bases.is_empty():
|
||||
print("RESULT %s no_base_bodies" % role)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
if role == "a":
|
||||
# Machine A: customized character (hair + chest)
|
||||
avatars.set_loadout_from_dict({
|
||||
"base_body": bases[0],
|
||||
"slots": {
|
||||
"hair": "hair_ponytail_01",
|
||||
"chest": "chest_bandit_01",
|
||||
}
|
||||
})
|
||||
else:
|
||||
# Machine B: uncustomized character (no attachments)
|
||||
avatars.set_loadout_from_dict({
|
||||
"base_body": bases[0],
|
||||
"slots": {}
|
||||
})
|
||||
|
||||
var session = root.get_node("Session")
|
||||
session.begin(1, "127.0.0.1", port)
|
||||
|
||||
change_scene_to_file("res://scenes/game.tscn")
|
||||
|
||||
for i in 120:
|
||||
await process_frame
|
||||
|
||||
var game := current_scene
|
||||
var netc = game.get_node_or_null("NetworkClient") if game else null
|
||||
if netc == null or not netc.is_connected_to_server():
|
||||
print("RESULT %s not_connected" % role)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
print("RESULT %s in_world" % role)
|
||||
|
||||
# Search for remote player
|
||||
var remote_node: Node3D = null
|
||||
for i in 1200:
|
||||
await process_frame
|
||||
for c in game.get_children():
|
||||
if c is Node3D and c.name.begins_with("Remote_"):
|
||||
var vis = c.get_node_or_null("Visual")
|
||||
if vis != null:
|
||||
remote_node = c as Node3D
|
||||
break
|
||||
if remote_node != null:
|
||||
# Give it 10 frames to ensure loadout replication snapshot has arrived and applied
|
||||
for j in 10:
|
||||
await process_frame
|
||||
break
|
||||
|
||||
if remote_node == null:
|
||||
print("RESULT %s remote_not_found" % role)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var visual: Node = remote_node.get_node_or_null("Visual")
|
||||
if visual == null:
|
||||
print("RESULT %s visual_not_found" % role)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var skel: Skeleton3D = visual.find_child("Skeleton3D", true, false) as Skeleton3D
|
||||
if skel == null:
|
||||
print("RESULT %s skeleton_not_found" % role)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var mount_count := 0
|
||||
for c in skel.get_children():
|
||||
if c is BoneAttachment3D:
|
||||
mount_count += 1
|
||||
|
||||
var has_override := false
|
||||
var meshes: Array[MeshInstance3D] = []
|
||||
_gather_meshes(visual, meshes)
|
||||
for m in meshes:
|
||||
if m.material_override != null:
|
||||
has_override = true
|
||||
break
|
||||
|
||||
if role == "b":
|
||||
# Observer B observing A: A must have customizations (mounts >= 2) and no tint
|
||||
var a_customized := mount_count >= 2
|
||||
var tinted := has_override
|
||||
print("RESULT b mounts=%d tinted=%s ok=%s" % [mount_count, str(tinted), str(a_customized and not tinted)])
|
||||
# Stay connected long enough for A to finish its verification
|
||||
for i in 300:
|
||||
await process_frame
|
||||
quit(0 if (a_customized and not tinted) else 1)
|
||||
else:
|
||||
# Observer A observing B: B must NOT have customizations (mounts == 0) and no tint
|
||||
var b_uncustomized := mount_count == 0
|
||||
var tinted := has_override
|
||||
print("RESULT a mounts=%d tinted=%s ok=%s" % [mount_count, str(tinted), str(b_uncustomized and not tinted)])
|
||||
quit(0 if (b_uncustomized and not tinted) else 1)
|
||||
|
||||
|
||||
func _gather_meshes(node: Node, out: Array[MeshInstance3D]) -> void:
|
||||
if node is MeshInstance3D:
|
||||
out.append(node as MeshInstance3D)
|
||||
for c in node.get_children():
|
||||
_gather_meshes(c, out)
|
||||
Reference in New Issue
Block a user