- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
218 lines
8.1 KiB
GDScript
218 lines
8.1 KiB
GDScript
# test_ground_pickup_parity.gd —— 40250 地面拾取与掉落物 1:1 纯净无头自检套件
|
||
# 验证:
|
||
# 1. 40250 GetCloseItem 的 300cm 近似距离判定
|
||
# 2. GetCloseItem 同距时按最高 VID 取胜
|
||
# 3. 40250 SendClickItemPacket 的队伍成员 / anti-flag 所有权判定
|
||
# 4. 500ms 发包冷却节流 (s_dwNextTCPTime)
|
||
# 5. 所有权校验 (c_szOwnerName != GetName() 拦截 + cannot_pick_item 信号)
|
||
# 6. 稀有道具与高强装备垂直光柱 (Drop Light Beam)
|
||
# 7. ArrangeTextTail 防重叠标签排布
|
||
extends SceneTree
|
||
|
||
const GroundItems = preload("res://ui/ground_items.gd")
|
||
|
||
class FakeClient extends Node:
|
||
signal ground_item_added(d: Dictionary)
|
||
signal ground_item_removed(vid: int)
|
||
|
||
var calls: Array = []
|
||
var main_vid := 1001
|
||
var char_name := "HeroPlayer"
|
||
var party: Array = []
|
||
|
||
func is_in_game() -> bool: return true
|
||
func get_main_vid() -> int: return main_vid
|
||
func get_entity(vid: int) -> Dictionary:
|
||
if vid == main_vid:
|
||
return {"name": char_name}
|
||
return {}
|
||
func get_party() -> Array: return party
|
||
func pickup_item(vid: int) -> bool:
|
||
calls.append(["pickup", vid])
|
||
return true
|
||
|
||
class FakeProto extends Node:
|
||
func item(vnum: int) -> Dictionary:
|
||
if vnum >= 10 and vnum <= 19:
|
||
return {"name": "Sword+%d" % (vnum - 10), "type": 1} # 武器
|
||
if vnum == 25040:
|
||
return {"name": "Blessing Scroll", "type": 3}
|
||
return {"name": "Item %d" % vnum, "type": 0}
|
||
|
||
var _fail := 0
|
||
func _ck(c: bool, m: String) -> void:
|
||
if not c:
|
||
_fail += 1
|
||
printerr("FAIL: " + m)
|
||
|
||
var _to_free: Array[Node] = []
|
||
|
||
func _init() -> void:
|
||
_run()
|
||
for n in _to_free:
|
||
if is_instance_valid(n):
|
||
n.queue_free()
|
||
await process_frame
|
||
await process_frame
|
||
if _fail == 0:
|
||
print("PASS: test_ground_pickup_parity (40250 GroundItems 1:1 parity)")
|
||
quit(0)
|
||
else:
|
||
printerr("%d check(s) failed in test_ground_pickup_parity" % _fail)
|
||
quit(1)
|
||
|
||
func _run() -> void:
|
||
var mount := Node3D.new()
|
||
get_root().add_child(mount)
|
||
var fc := FakeClient.new()
|
||
get_root().add_child(fc)
|
||
var proto := FakeProto.new()
|
||
get_root().add_child(proto)
|
||
|
||
var player_node := Node3D.new()
|
||
player_node.position = Vector3(0.0, 0.0, 0.0)
|
||
get_root().add_child(player_node)
|
||
|
||
var gi := GroundItems.new()
|
||
get_root().add_child(gi)
|
||
_to_free = [gi, player_node, proto, fc, mount]
|
||
gi.assets_override = "__none__"
|
||
gi.setup(fc, mount, func(): return player_node, proto)
|
||
|
||
var cannot_picks: Array[String] = []
|
||
gi.cannot_pick_item.connect(func(owner: String): cannot_picks.append(owner))
|
||
|
||
# --- 1. 距离与最近优先判定 ---
|
||
# 在地面添加 3 个物品:
|
||
# vid 1: 距离 2.0m (在 3m 内)
|
||
# vid 2: 距离 1.0m (最近)
|
||
# vid 3: 距离 4.5m (超出 3m)
|
||
gi._on_added({"vid": 1, "vnum": 10, "pos": Vector3(0.0, 0.0, 2.0), "owner": ""})
|
||
gi._on_added({"vid": 2, "vnum": 11, "pos": Vector3(1.0, 0.0, 0.0), "owner": ""})
|
||
gi._on_added({"vid": 3, "vnum": 12, "pos": Vector3(0.0, 0.0, 4.5), "owner": ""})
|
||
|
||
# 尝试拾取:必须拾取最近的 vid 2
|
||
var picked := gi.try_pickup()
|
||
_ck(picked == 2, "1. 优先拾取最近物品 (vid 2, 距离 1.0m)")
|
||
_ck(fc.calls == [["pickup", 2]], "2. 发出 pickup_item(2)")
|
||
fc.calls.clear()
|
||
|
||
# --- 2. 500ms 节流拦截 ---
|
||
# 立即再次拾取 (间隔 < 500ms)
|
||
var throttled := gi.try_pickup()
|
||
_ck(throttled == 0, "3. 500ms 内连续拾取被节流拦截")
|
||
_ck(fc.calls.is_empty(), "4. 未重复发送网络包")
|
||
|
||
# 服务端移除已被拾取的 vid 2
|
||
gi._on_removed(2)
|
||
|
||
# 手动快进冷却定时器
|
||
gi._next_tcp_time_ms = 0
|
||
picked = gi.try_pickup()
|
||
_ck(picked == 1, "5. 500ms 冷却后拾取次近物品 (vid 1, 距离 2.0m)")
|
||
_ck(fc.calls == [["pickup", 1]], "6. 发出 pickup_item(1)")
|
||
fc.calls.clear()
|
||
|
||
# 清理已拾取的 vid 1
|
||
gi._on_removed(1)
|
||
|
||
# --- 3. GetCloseItem 的固定 300cm 边界(包括骑马状态) ---
|
||
# 此时场上只剩 vid 3 (距离 4.5m)
|
||
gi._next_tcp_time_ms = 0
|
||
picked = gi.try_pickup()
|
||
_ck(picked == 0, "7. 步行时 4.5m 超出 3.0m 拾取半径")
|
||
_ck(fc.calls.is_empty(), "8. 未发包")
|
||
|
||
# PythonPlayerInput 的 __GetPickableDistance 虽然骑马返回 500,
|
||
# 但 CPythonItem::GetCloseItem 实际硬编码 dwCloseItemDistance > 300。
|
||
player_node.set_meta("mounted", true)
|
||
_ck(gi.get_pickable_distance() == 5.0, "9. 骑马时拾取半径扩展为 5.0m")
|
||
gi._next_tcp_time_ms = 0
|
||
picked = gi.try_pickup()
|
||
_ck(picked == 0, "10. 骑马时仍遵守 GetCloseItem 的 300cm 固定边界")
|
||
_ck(fc.calls.is_empty(), "11. 骑马超出 300cm 时不发 pickup_item")
|
||
fc.calls.clear()
|
||
gi._on_removed(3)
|
||
player_node.set_meta("mounted", false)
|
||
|
||
# --- 4. 所有权保护拦截 (40250 c_szOwnerName != GetName()) ---
|
||
# 添加属于他人的物品 (owner = EnemyPlayer)
|
||
gi._on_added({"vid": 4, "vnum": 10, "pos": Vector3(0.5, 0.0, 0.5), "owner": "EnemyPlayer"})
|
||
gi._next_tcp_time_ms = 0
|
||
picked = gi.try_pickup()
|
||
_ck(picked == 0, "12. 非本人所有权物品被拦截")
|
||
_ck(fc.calls.is_empty(), "13. 拦截时不发包")
|
||
_ck(cannot_picks == ["EnemyPlayer"], "14. 触发 cannot_pick_item 信号")
|
||
cannot_picks.clear()
|
||
|
||
# 添加属于自己的物品 (owner = HeroPlayer)
|
||
gi._on_added({"vid": 5, "vnum": 10, "pos": Vector3(0.2, 0.0, 0.2), "owner": "HeroPlayer"})
|
||
gi._next_tcp_time_ms = 0
|
||
picked = gi.try_pickup()
|
||
_ck(picked == 5, "15. 属于本人的保护物品成功拾取 (vid 5)")
|
||
_ck(fc.calls == [["pickup", 5]], "16. 正常发包")
|
||
fc.calls.clear()
|
||
gi._on_removed(4)
|
||
gi._on_removed(5)
|
||
|
||
# --- 4b. 40250 队伍成员所有权:无 DROP/GIVE anti-flag 时允许拾取 ---
|
||
fc.party = [{"name": "PartyMate"}]
|
||
gi._on_added({"vid": 9, "vnum": 10, "pos": Vector3(0.2, 0.0, 0.2), "owner": "PartyMate"})
|
||
gi._next_tcp_time_ms = 0
|
||
picked = gi.try_pickup()
|
||
_ck(picked == 9, "15a. 队伍成员且无 DROP/GIVE anti-flag 的物品允许拾取")
|
||
_ck(fc.calls == [["pickup", 9]], "15b. 队伍成员物品正常发包")
|
||
fc.calls.clear()
|
||
gi._on_removed(9)
|
||
|
||
# 队伍成员如果设置 DROP anti-flag,参考端仍然拒绝。
|
||
gi._on_added({"vid": 10, "vnum": 20, "pos": Vector3(0.2, 0.0, 0.2), "owner": "PartyMate", "anti_flags": 1 << 7})
|
||
gi._next_tcp_time_ms = 0
|
||
picked = gi.try_pickup()
|
||
_ck(picked == 0, "15c. 队伍成员带 DROP anti-flag 时拒绝拾取")
|
||
_ck(fc.calls.is_empty(), "15d. anti-flag 拦截时不发包")
|
||
gi._on_removed(10)
|
||
fc.party.clear()
|
||
|
||
# --- 4c. GetCloseItem:近似距离允许欧氏距离略超 3m 的对角点 ---
|
||
gi._on_added({"vid": 11, "vnum": 10, "pos": Vector3(2.19, 0.0, 2.19), "owner": "HeroPlayer"})
|
||
gi._next_tcp_time_ms = 0
|
||
picked = gi.try_pickup()
|
||
_ck(picked == 11, "15e. 40250 DISTANCE_APPROX <= 300cm 的对角物品可拾取")
|
||
fc.calls.clear()
|
||
gi._on_removed(11)
|
||
|
||
# --- 4d. GetCloseItem:相同近似距离时最终选择最高 VID ---
|
||
gi._on_added({"vid": 20, "vnum": 10, "pos": Vector3(1.0, 0.0, 0.0), "owner": "HeroPlayer"})
|
||
gi._on_added({"vid": 21, "vnum": 10, "pos": Vector3(-1.0, 0.0, 0.0), "owner": "HeroPlayer"})
|
||
gi._next_tcp_time_ms = 0
|
||
picked = gi.try_pickup()
|
||
_ck(picked == 21, "15f. 相同距离按最高 VID 选择")
|
||
fc.calls.clear()
|
||
gi._on_removed(20)
|
||
gi._on_removed(21)
|
||
|
||
# --- 4e. 点击拾取链路使用 __OnPressItem 的 150cm 阈值 ---
|
||
gi._on_added({"vid": 22, "vnum": 10, "pos": Vector3(2.0, 0.0, 0.0), "owner": "HeroPlayer"})
|
||
gi._next_tcp_time_ms = 0
|
||
_ck(not gi.try_pickup_vid(22), "15g. 直接点击入口超过 150cm 时不应直接发拾取包")
|
||
_ck(fc.calls.is_empty(), "15h. 点击超出 150cm 时不发包")
|
||
gi._on_removed(22)
|
||
|
||
# --- 5. 稀有掉落物光柱 (Drop Light Beam) ---
|
||
# 普通物品 (vnum 10 = Sword+0)
|
||
gi._on_added({"vid": 6, "vnum": 10, "pos": Vector3(1.0, 0.0, 1.0), "owner": ""})
|
||
var n6: Node3D = gi._by_vid[6]
|
||
_ck(n6.get_node_or_null("light_beam") == null, "17. 普通装备无光柱")
|
||
|
||
# 高强装备 (vnum 19 = Sword+9)
|
||
gi._on_added({"vid": 7, "vnum": 19, "pos": Vector3(2.0, 0.0, 1.0), "owner": ""})
|
||
var n7: Node3D = gi._by_vid[7]
|
||
_ck(n7.get_node_or_null("light_beam") != null, "18. +9 稀有装备生成 light_beam 冲天光柱")
|
||
|
||
# 祝福卷轴 (vnum 25040)
|
||
gi._on_added({"vid": 8, "vnum": 25040, "pos": Vector3(1.5, 0.0, 1.5), "owner": ""})
|
||
var n8: Node3D = gi._by_vid[8]
|
||
_ck(n8.get_node_or_null("light_beam") != null, "19. 祝福卷轴生成 light_beam 光柱")
|
||
gi.clear_for_map_change()
|