Files
mtgodot-poc/project/net_world_respawn_test.gd
T
shenandshen c93894313a fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
2026-09-21 16:38:59 -07:00

121 lines
4.8 KiB
GDScript

# net_world_respawn_test —— 40250 NetworkActorManager::__AppendCharacterManagerActor
# 重建边界:普通同 VID 重建使用新包坐标;只有上下马切换保留旧实例坐标。
extends SceneTree
const NetWorld = preload("res://net_world.gd")
const FakeClient = preload("res://net_client_fake.gd")
var _fail := 0
func _ck(ok: bool, message: String) -> void:
if not ok:
_fail += 1
printerr("FAIL: " + message)
func _init() -> void:
await _run()
if _fail == 0:
print("PASS: net_world_respawn_test (spawn overwrite / mount transition)")
quit(0)
else:
printerr("%d check(s) failed" % _fail)
quit(1)
func _run() -> void:
var mount := Node3D.new()
get_root().add_child(mount)
var client := FakeClient.new()
get_root().add_child(client)
var world: Node = NetWorld.new()
get_root().add_child(world)
world.setup(client, mount)
world.set_process(false)
await process_frame
var first := {
"vid": 42, "name": "mob", "race": 101, "kind": 2, "ch_type": 2,
"pos": Vector3(1.0, 0.0, -2.0), "pos_cm": Vector3(100.0, 200.0, 0.0),
"angle_deg": 0.0, "moving": false, "func": 0, "walk_mode": 0,
"dead": false, "mount_vnum": 0, "affect_flags": 0,
"hp": 100, "max_hp": 100, "parts": [0, 0, 0, 0]
}
client.entities[42] = first
client.entity_spawned.emit(first)
var node: Node3D = world.node_for(42)
_ck(node != null, "first spawn creates the actor node")
if node == null:
return
_ck(node.position.is_equal_approx(Vector3(first.pos.x, first.pos.y, -first.pos.z)),
"first spawn uses incoming network position")
# Reference __AppendCharacterManagerActor uses the new SCreateData position
# when the mount state is unchanged. The old node position must not win.
var normal_readd := first.duplicate(true)
normal_readd["pos"] = Vector3(7.0, 0.0, -8.0)
normal_readd["pos_cm"] = Vector3(700.0, 800.0, 0.0)
normal_readd["angle_deg"] = 90.0
client.entities[42] = normal_readd
client.entity_spawned.emit(normal_readd)
var rebuilt: Node3D = world.node_for(42)
_ck(rebuilt != null and rebuilt != node, "same VID re-add recreates the scene instance")
if rebuilt == null:
return
_ck(rebuilt.position.is_equal_approx(Vector3(normal_readd.pos.x, normal_readd.pos.y, -normal_readd.pos.z)),
"ordinary re-add uses the new authoritative position")
_ck(is_equal_approx(rebuilt.rotation.y, deg_to_rad(90.0)),
"ordinary re-add uses the new authoritative rotation")
# 40250 preserves the old pixel position only when switching mount state.
var before_mount: Vector3 = rebuilt.position
var mount_readd := normal_readd.duplicate(true)
mount_readd["pos"] = Vector3(12.0, 0.0, -13.0)
mount_readd["pos_cm"] = Vector3(1200.0, 1300.0, 0.0)
mount_readd["mount_vnum"] = 20030
client.entities[42] = mount_readd
client.entity_spawned.emit(mount_readd)
var mounted: Node3D = world.node_for(42)
_ck(mounted != null, "mount transition recreates the scene instance")
if mounted != null:
_ck(mounted.position.is_equal_approx(before_mount),
"mount transition preserves old position while rebuilding")
_ck(is_equal_approx(mounted.rotation.y, deg_to_rad(90.0)),
"mount transition still uses the incoming rotation")
# 40250 RemoveActor uses DeleteInstanceByFade, but a later AppendActor for
# the same VID sees no alive instance and creates the replacement immediately;
# the old dead-list blend-out must not suppress the new actor.
client.entity_despawned.emit(42)
_ck(world.node_for(42) == null, "despawn removes the live node while it fades")
var reused := mount_readd.duplicate(true)
reused["pos"] = Vector3(20.0, 0.0, -21.0)
reused["pos_cm"] = Vector3(2000.0, 2100.0, 0.0)
reused["mount_vnum"] = 0
client.entities[42] = reused
client.entity_spawned.emit(reused)
var replacement: Node3D = world.node_for(42)
_ck(replacement != null, "same VID spawn is rebuilt during the old fade")
if replacement != null:
_ck(replacement.position.is_equal_approx(Vector3(reused.pos.x, reused.pos.y, -reused.pos.z)),
"same VID replacement uses its new authoritative position")
await get_root().get_tree().create_timer(0.5).timeout
_ck(world.node_for(42) == replacement, "old fade completion does not remove the replacement")
# 40250 removes the selected/main actor through DeleteInstance, not the
# delayed dead-list blend-out path used for ordinary remote actors.
var main_data := reused.duplicate(true)
main_data["vid"] = 7
main_data["name"] = "main"
client.entities[7] = main_data
world._main_vid = 7
client.entity_spawned.emit(main_data)
var main_node: Node3D = world.node_for(7)
_ck(main_node != null, "main actor spawn creates the selected node")
client.entity_despawned.emit(7)
_ck(not world._fading.has(7), "main actor despawn skips DeleteInstanceByFade")
_ck(world.node_for(7) == null, "main actor despawn removes the node immediately")
world.free()
client.free()
mount.free()
await process_frame