Files
mtgodot-poc/project/test_consumables_parity.gd
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight
  - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程
  - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题
  - 新增 test_bridge_height_parity.gd 自动化对拍测试
- 40250 怪物击杀经验动效:
  - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附
- 40250 客户端全系统功能对齐(Batches 1-31):
  - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试
- 文档沉淀:
  - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

127 lines
6.4 KiB
GDScript

# test_consumables_parity.gd —— 验证方向 4:日月神水自充能、龙神特药与传送卷轴 40250 1:1 对齐
extends SceneTree
const ConsumableSystem = preload("res://consumable_system.gd")
var _passed := 0
var _failed := 0
func _assert(cond: bool, msg: String) -> void:
if cond:
_passed += 1
print(" [PASS] %s" % msg)
else:
_failed += 1
printerr(" [FAIL] %s" % msg)
func _init() -> void:
print("\n=== Running test_consumables_parity (Direction 4: Consumables & Scrolls 1:1) ===")
test_elixir_identification()
test_elixir_toggle_active()
test_elixir_recovery_and_exhaustion()
test_dragon_god_potions()
test_town_and_return_scrolls()
print("\n=== SUMMARY: %d passed, %d failed ===" % [_passed, _failed])
if _failed > 0:
printerr("TEST FAILED!")
quit(1)
else:
print("ALL TESTS PASSED!")
quit(0)
func test_elixir_identification() -> void:
print("\n--- Test 1: Sun & Moon Elixirs Identification ---")
_assert(ConsumableSystem.is_elixir(72723), "72723 (Sun Elixir S) is an elixir")
_assert(ConsumableSystem.is_hp_elixir(72723), "72723 is an HP elixir")
_assert(not ConsumableSystem.is_sp_elixir(72723), "72723 is not an SP elixir")
_assert(ConsumableSystem.is_elixir(72728), "72728 (Moon Elixir M) is an elixir")
_assert(ConsumableSystem.is_sp_elixir(72728), "72728 is an SP elixir")
_assert(not ConsumableSystem.is_hp_elixir(72728), "72728 is not an HP elixir")
_assert(not ConsumableSystem.is_elixir(27001), "27001 Red Potion is not an elixir")
_assert(ConsumableSystem.is_dragon_god_potion(71027), "71027 is Dragon God Potion")
_assert(not ConsumableSystem.is_dragon_god_potion(72723), "Elixir is not Dragon God Potion")
func test_elixir_toggle_active() -> void:
print("\n--- Test 2: Sun/Moon Elixir Toggle Active (40250 char_item.cpp:3950) ---")
var elixir_hp := {"vnum": 72723, "sockets": [0, 0, 0]}
# Initially inactive
var toggle1 := ConsumableSystem.toggle_elixir(elixir_hp)
_assert(toggle1.ok and toggle1.active, "1st click activates HP elixir")
_assert(elixir_hp.sockets[0] == 1, "Socket 0 is set to 1 (Active)")
_assert(toggle1.affect_type == ConsumableSystem.AFFECT_AUTO_HP_RECOVERY, "Affect type is AFFECT_AUTO_HP_RECOVERY (534)")
_assert(elixir_hp.sockets[2] == 1000000, "Initialized capacity to 1,000,000 HP")
# 2nd click deactivates
var toggle2 := ConsumableSystem.toggle_elixir(elixir_hp)
_assert(toggle2.ok and not toggle2.active, "2nd click deactivates HP elixir")
_assert(elixir_hp.sockets[0] == 0, "Socket 0 is set to 0 (Inactive)")
func test_elixir_recovery_and_exhaustion() -> void:
print("\n--- Test 3: Auto Recovery Tick & Capacity Depletion (40250 char_item.cpp:7326) ---")
# Elixir with 500 capacity, initially active
var small_elixir := {"vnum": 72723, "sockets": [1, 0, 500]}
# Player at 800/1000 HP (needs 200)
var tick1 := ConsumableSystem.process_elixir_recovery(small_elixir, 800, 1000)
_assert(tick1.recovered == 200, "Recovered 200 HP to full")
_assert(not tick1.exhausted, "Elixir not exhausted yet")
_assert(small_elixir.sockets[1] == 200, "Used amount increased to 200")
_assert(tick1.remaining == 300, "Remaining capacity is 300")
# Player at 1000/1000 HP (full HP)
var tick_full := ConsumableSystem.process_elixir_recovery(small_elixir, 1000, 1000)
_assert(tick_full.recovered == 0, "No recovery when already at full HP")
_assert(small_elixir.sockets[1] == 200, "Used amount unchanged")
# Player at 600/1000 HP (needs 400, but only 300 left in elixir)
var tick_deplete := ConsumableSystem.process_elixir_recovery(small_elixir, 600, 1000)
_assert(tick_deplete.recovered == 300, "Recovered all remaining 300 HP")
_assert(tick_deplete.exhausted, "Elixir is now exhausted (used >= capacity)")
_assert(small_elixir.sockets[1] == 500, "Used amount reached full 500")
func test_dragon_god_potions() -> void:
print("\n--- Test 4: Dragon God Potions Buff Specs (71027..71030) ---")
var p_att := ConsumableSystem.use_dragon_god_potion(ConsumableSystem.VNUM_DRAGON_GOD_ATTACK)
_assert(p_att.ok and p_att.bonus_type == "POINT_ATT_BONUS" and p_att.bonus_value == 15,
"Dragon God Attack gives +15% attack bonus")
_assert(p_att.duration == 1800.0, "Dragon God Attack lasts 1800 seconds (30 minutes)")
var p_def := ConsumableSystem.use_dragon_god_potion(ConsumableSystem.VNUM_DRAGON_GOD_DEFENSE)
_assert(p_def.ok and p_def.bonus_type == "POINT_DEF_BONUS" and p_def.bonus_value == 15,
"Dragon God Defense gives +15% defense bonus")
var p_life := ConsumableSystem.use_dragon_god_potion(ConsumableSystem.VNUM_DRAGON_GOD_LIFE)
_assert(p_life.ok and p_life.bonus_type == "POINT_MAX_HP_PCT" and p_life.bonus_value == 20,
"Dragon God Life gives +20% HP bonus")
var p_mana := ConsumableSystem.use_dragon_god_potion(ConsumableSystem.VNUM_DRAGON_GOD_MANA)
_assert(p_mana.ok and p_mana.bonus_type == "POINT_MAX_SP_PCT" and p_mana.bonus_value == 20,
"Dragon God Mana gives +20% SP bonus")
func test_town_and_return_scrolls() -> void:
print("\n--- Test 5: Town Scroll (22001) & Return Scroll (22010) ---")
# Town Scroll 22001
var dummy_item := {"vnum": ConsumableSystem.VNUM_TOWN_SCROLL}
var res_town := ConsumableSystem.use_warp_scroll(ConsumableSystem.VNUM_TOWN_SCROLL, dummy_item, 100.0, 200.0)
_assert(res_town.ok and res_town.action == "TOWN_WARP" and res_town.consumed,
"Town scroll triggers town warp and is consumed")
# Return Scroll 22010 (Empty) -> Records current coordinate (1234, 5678)
var return_scroll := {"vnum": ConsumableSystem.VNUM_RETURN_SCROLL, "sockets": [0, 0, 0]}
var res_rec := ConsumableSystem.use_warp_scroll(ConsumableSystem.VNUM_RETURN_SCROLL, return_scroll, 1234.0, 5678.0, 2)
_assert(res_rec.ok and res_rec.action == "RECORDED", "Empty return scroll records coordinate")
_assert(not res_rec.consumed, "Recording coordinate does not consume the scroll")
_assert(return_scroll.sockets[0] == 1234 and return_scroll.sockets[1] == 5678 and return_scroll.sockets[2] == 2,
"Recorded coordinates saved into sockets [1234, 5678, map 2]")
# Return Scroll 22010 (Recorded) -> Warps and is consumed
var res_warp := ConsumableSystem.use_warp_scroll(ConsumableSystem.VNUM_RETURN_SCROLL, return_scroll, 999.0, 888.0, 1)
_assert(res_warp.ok and res_warp.action == "WARP_TO_COORDS", "Recorded return scroll warps to saved location")
_assert(res_warp.consumed, "Using recorded return scroll consumes the scroll")
_assert(res_warp.target_x == 1234 and res_warp.target_y == 5678 and res_warp.target_map == 2,
"Target coordinates match recorded values")