fix: 装备属性面板避让逻辑 + 多项功能更新

- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
This commit is contained in:
shen
2026-09-21 16:38:59 -07:00
parent 1522a8a1a1
commit c93894313a
5498 changed files with 4558004 additions and 1442 deletions
+52 -8
View File
@@ -12,10 +12,10 @@ func _init() -> void:
var fails: int = 0
# 1. 验证 40250 FLY 常量与编号映射
if FlyObject.FLY_EXP != 0:
printerr("FAIL: FLY_EXP 应为 0,实际为: ", FlyObject.FLY_EXP)
if FlyObject.FLY_EXP != 1:
printerr("FAIL: FLY_EXP 应为 1,实际为: ", FlyObject.FLY_EXP)
fails += 1
if FlyObject.FLY_HP_SMALL != 1 or FlyObject.FLY_SP_SMALL != 4:
if FlyObject.FLY_HP_SMALL != 15 or FlyObject.FLY_SP_SMALL != 4:
printerr("FAIL: FLY_HP/SP 常量映射错误")
fails += 1
@@ -68,7 +68,7 @@ func _init() -> void:
printerr("FAIL: 经验球命中后未从活跃队列销毁!")
fails += 1
# 3. 验证 net_world._on_fly(FLY_EXP) 生成 3 颗聚能光球
# 3. 验证 net_world._on_fly(FLY_EXP) 按 40250 CreateIndexedFly 生成 1 颗聚能光球
var nw := NetWorld.new()
mount.add_child(nw)
nw.parent = mount
@@ -90,11 +90,15 @@ func _init() -> void:
mount.add_child(player_node)
nw.set_local_node(player_node)
nw.set_local_vid(1)
# The local actor is owned by GameScene rather than _by_vid. During the
# first GC_CREATE_FLY after a phase transition the EntityStore row may not
# have been mirrored yet; 40250 still resolves the main instance directly.
fake_client.ents.erase(1)
# 触发 40250 官方 _on_fly(0, 100, 1)
# 触发 40250 官方 _on_fly(FLY_EXP, 100, 1)
nw._on_fly(FlyObject.FLY_EXP, 100, 1)
if mgr.active_count() != 3:
printerr("FAIL: _on_fly(FLY_EXP) 应发射 3 颗经验聚能光球,实际: ", mgr.active_count())
if mgr.active_count() != 1:
printerr("FAIL: _on_fly(FLY_EXP) 应按一次 GC_CREATE_FLY 发射 1 颗经验聚能光球,实际: ", mgr.active_count())
fails += 1
for orb in mgr._instances:
if not orb._is_object or orb._target_obj != player_node:
@@ -102,11 +106,35 @@ func _init() -> void:
fails += 1
break
# 40250 RecvCreateFlyPacket requires both CInstanceBase pointers. A stale
# EntityStore row is not enough to create a visual projectile when the
# remote scene node has not been built yet.
mgr.clear_for_map_change()
fake_client.ents[2] = {"vid": 2, "pos": Vector3(6, 0, 0)}
nw._on_fly(FlyObject.FLY_EXP, 100, 2)
if mgr.active_count() != 0:
printerr("FAIL: 远端终点节点缺失时不应仅凭 EntityStore 坐标创建飞行物")
fails += 1
fake_client.ents.erase(2)
var saved_mob_node: Node3D = nw._by_vid[100]
nw._by_vid.erase(100)
nw._on_fly(FlyObject.FLY_EXP, 100, 1)
if mgr.active_count() != 0:
printerr("FAIL: 远端起点节点缺失时不应创建飞行物")
fails += 1
nw._by_vid[100] = saved_mob_node
# 清空飞行物
for i in range(50):
mgr.step(0.02)
if mgr.active_count() == 0:
break
# The indexed EXP resource intentionally starts with a spreading phase and
# only enables homing at one second; isolate the offline helper assertion
# from that server-created instance instead of assuming the old synthetic
# speed reaches the target in 1s.
mgr.clear_for_map_change()
# 4. 验证 net_world.spawn_exp_fly 接口
nw.spawn_exp_fly(Vector3(2, 0, 2), player_node, 3)
@@ -139,7 +167,23 @@ func _init() -> void:
printerr("FAIL: POINT_EXP 不应重复生成经验光球,实际数量: ", mgr.active_count())
fails += 1
mount.queue_free()
# All flying instances are now test-owned and no longer needed. Break the
# manager's handler/target cycles before synchronously destroying the fixture;
# this mirrors CFlyingManager::DeleteAllInstances before application exit.
mgr.clear_for_map_change()
if is_instance_valid(pc):
pc.free()
mount.free()
# Do not keep RefCounted fixtures alive on the _init stack until quit().
d = null
inst = null
player_target = null
nw = null
np = null
pc = null
mgr = null
mount = null
await process_frame
if fails == 0:
print("PASS: test_exp_fly_parity (6/6 全部通过)")