- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
121 lines
4.8 KiB
GDScript
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
|