- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
159 lines
5.7 KiB
GDScript
159 lines
5.7 KiB
GDScript
# test_cube_crafting_parity.gd —— Metin2 40250 官方魔方炼金合成台 1:1 测试套件
|
|
extends SceneTree
|
|
|
|
const CubeCraftingSystem = preload("res://cube_crafting_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_cube_crafting_parity (Direction 3: Cube Crafting System 1:1) ===")
|
|
|
|
test_baek_go_herb_crafting()
|
|
test_huahn_so_dew_crafting()
|
|
test_jae_seon_kim_triton_sword()
|
|
test_recipe_and_npc_mismatch()
|
|
test_history_and_serialization()
|
|
|
|
print("\n=======================================================")
|
|
print("Cube Crafting Parity Tests: %d Passed, %d Failed" % [_passed, _failed])
|
|
print("=======================================================\n")
|
|
|
|
if _failed > 0:
|
|
quit(1)
|
|
else:
|
|
quit(0)
|
|
|
|
func test_baek_go_herb_crafting() -> void:
|
|
print("\n--- Test 1: Baek-Go Herb Juice Crafting (40250 1:1) ---")
|
|
var system = CubeCraftingSystem.new()
|
|
|
|
var opened_emitted := []
|
|
system.cube_opened.connect(func(npc, name):
|
|
opened_emitted.append({"npc": npc, "name": name})
|
|
)
|
|
|
|
# 打开白高魔方界面
|
|
_assert(system.open_cube(20018) == true, "Opened cube at Baek-Go (20018)")
|
|
_assert(opened_emitted.size() == 1, "cube_opened emitted")
|
|
|
|
# 放入 1 朵桃花 (50721)
|
|
var slots: Array = [{"vnum": 50721, "count": 1}]
|
|
var inv: Array = []
|
|
var res = system.craft_item(slots, inv, 0)
|
|
_assert(res["ok"] == true and res["success"] == true, "Crafted Peach Flower Juice")
|
|
_assert(res["reward_vnum"] == 50801, "Reward vnum 50801")
|
|
_assert(slots[0] == null, "Cube slot material consumed")
|
|
_assert(inv.size() == 1 and inv[0]["vnum"] == 50801, "Inventory received juice")
|
|
|
|
# 放入 1 朵紫丁香 (50723) -> 批量产出 100 瓶药水 (50803)
|
|
slots = [{"vnum": 50723, "count": 1}]
|
|
res = system.craft_item(slots, inv, 0)
|
|
_assert(res["ok"] == true, "Crafted Lilac Juice batch")
|
|
_assert(res["reward_count"] == 100, "Yielded 100 count")
|
|
system.close_cube()
|
|
_assert(system.is_cube_open == false, "Closed cube")
|
|
|
|
func test_huahn_so_dew_crafting() -> void:
|
|
print("\n--- Test 2: Huahn-So Dew Crafting & Gold Fee (40250 1:1) ---")
|
|
var system = CubeCraftingSystem.new()
|
|
system.open_cube(20022) # 幻素
|
|
|
|
# 材料: 桃花汁 x10 (50801) + 空瓶 x10 (70031)
|
|
var slots: Array = [
|
|
{"vnum": 50801, "count": 10},
|
|
{"vnum": 70031, "count": 10}
|
|
]
|
|
var inv: Array = []
|
|
|
|
# 金币不足拦截 (需要 10,000 金币,玩家只有 5,000)
|
|
var res = system.craft_item(slots, inv, 5000)
|
|
_assert(res["ok"] == false, "Rejected due to insufficient gold")
|
|
_assert(res["reason"] == "NOT_ENOUGH_GOLD", "Reason is NOT_ENOUGH_GOLD")
|
|
_assert(slots[0]["count"] == 10, "Materials not consumed on gold error")
|
|
|
|
# 金币充足,成功合成
|
|
res = system.craft_item(slots, inv, 20000)
|
|
_assert(res["ok"] == true and res["success"] == true, "Crafted Peach Flower Dew (50821)")
|
|
_assert(res["remaining_gold"] == 10000, "Deducted 10,000 gold fee")
|
|
_assert(res["reward_vnum"] == 50821, "Received Dew item 50821")
|
|
|
|
func test_jae_seon_kim_triton_sword() -> void:
|
|
print("\n--- Test 3: Jae-Seon Kim Triton Sword Crafting (40250 1:1) ---")
|
|
var system = CubeCraftingSystem.new()
|
|
system.open_cube(20383) # 金在善
|
|
|
|
# 1. 战神之剑+5 (145) + 30500 x10 + 30505 x10 (成功率 5%)
|
|
var slots: Array = [
|
|
{"vnum": 145, "count": 1},
|
|
{"vnum": 30500, "count": 10},
|
|
{"vnum": 30505, "count": 10}
|
|
]
|
|
var inv: Array = []
|
|
|
|
# 模拟失败 (roll = 50 > 5)
|
|
var res = system.craft_item(slots, inv, 150000, 50)
|
|
_assert(res["ok"] == true and res["success"] == false, "Low tier Triton craft failed")
|
|
_assert(res["remaining_gold"] == 50000, "Fee deducted even on failure")
|
|
_assert(inv.is_empty(), "No reward given on failure")
|
|
|
|
# 2. 战神之剑+9 (149) + 30500 x10 + 30505 x10 (保底 100% 成功)
|
|
slots = [
|
|
{"vnum": 149, "count": 1},
|
|
{"vnum": 30500, "count": 10},
|
|
{"vnum": 30505, "count": 10}
|
|
]
|
|
res = system.craft_item(slots, inv, 100000, 10)
|
|
_assert(res["ok"] == true and res["success"] == true, "Master tier +9 Triton craft succeeded 100%")
|
|
_assert(res["reward_vnum"] == 460, "Reward is Triton Sword+0 (460)")
|
|
_assert(inv.size() == 1 and inv[0]["vnum"] == 460, "Triton Sword added to inventory")
|
|
|
|
func test_recipe_and_npc_mismatch() -> void:
|
|
print("\n--- Test 4: Recipe & NPC Mismatch Handling ---")
|
|
var system = CubeCraftingSystem.new()
|
|
system.open_cube(20018) # 白高
|
|
|
|
# 放入海神之剑的材料到白高面前 -> 配方不匹配
|
|
var slots: Array = [
|
|
{"vnum": 149, "count": 1},
|
|
{"vnum": 30500, "count": 10},
|
|
{"vnum": 30505, "count": 10}
|
|
]
|
|
var inv: Array = []
|
|
var res = system.craft_item(slots, inv, 100000)
|
|
_assert(res["ok"] == false, "Baek-Go cannot craft Triton Sword")
|
|
_assert(res["reason"] == "NO_MATCHING_RECIPE", "Reason is NO_MATCHING_RECIPE")
|
|
|
|
# 窗口关闭时调用 -> 拦截
|
|
system.close_cube()
|
|
res = system.craft_item(slots, inv, 100000)
|
|
_assert(res["ok"] == false, "Rejected when cube closed")
|
|
_assert(res["reason"] == "CUBE_NOT_OPEN", "Reason is CUBE_NOT_OPEN")
|
|
|
|
func test_history_and_serialization() -> void:
|
|
print("\n--- Test 5: Crafting History & Serialization ---")
|
|
var system = CubeCraftingSystem.new()
|
|
system.open_cube(20018)
|
|
var slots: Array = [{"vnum": 50721, "count": 1}]
|
|
var inv: Array = []
|
|
system.craft_item(slots, inv, 0)
|
|
|
|
var hist = system.get_history()
|
|
_assert(hist.size() == 1, "Recorded 1 crafting history")
|
|
_assert(hist[0]["recipe_id"] == "herb_peach_flower", "Recorded recipe herb_peach_flower")
|
|
|
|
var data = system.serialize()
|
|
var restored = CubeCraftingSystem.new()
|
|
restored.deserialize(data)
|
|
_assert(restored.get_history().size() == 1, "Restored 1 history record")
|
|
system.clear_history()
|
|
_assert(system.get_history().is_empty(), "Cleared history")
|