- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
208 lines
7.7 KiB
GDScript
208 lines
7.7 KiB
GDScript
# test_fish_clam_roast_parity.gd —— Metin2 40250 活鱼宰杀、珍珠蚌开珠与篝火炙烤盛宴 1:1 测试套件
|
||
extends SceneTree
|
||
|
||
const FishClamRoastSystem = preload("res://fish_clam_roast_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_fish_clam_roast_parity (Direction 1: Fish Gutting & Clam Pearls 1:1) ===")
|
||
|
||
test_live_fish_gutting()
|
||
test_clam_pearl_opening()
|
||
test_campfire_grilling()
|
||
test_cooked_fish_buffs()
|
||
test_serialization()
|
||
|
||
print("\n=======================================================")
|
||
print("Fish & Clam Parity Tests: %d Passed, %d Failed" % [_passed, _failed])
|
||
print("=======================================================\n")
|
||
|
||
if _failed > 0:
|
||
quit(1)
|
||
else:
|
||
quit(0)
|
||
|
||
func test_live_fish_gutting() -> void:
|
||
print("\n--- Test 1: Live Fish Gutting (40250 1:1) ---")
|
||
var system = FishClamRoastSystem.new()
|
||
|
||
var inv: Array = [
|
||
{"vnum": 27806, "count": 1}, # 活鲤鱼 (Sazan)
|
||
{"vnum": 27808, "count": 1}, # 活草鱼 (Ot Sazanı)
|
||
{"vnum": 27803, "count": 1}, # 活鲈鱼
|
||
{"vnum": 27803, "count": 1}, # 活鲈鱼
|
||
{"vnum": 27803, "count": 1} # 活鲈鱼
|
||
]
|
||
|
||
# 1. 产生死鱼 (roll >= 4001)
|
||
var res = system.gut_live_fish(inv, 0, 5000)
|
||
_assert(res["ok"] == true, "Gut fish succeeded")
|
||
_assert(res["result"] == "dead_fish", "Result is dead_fish")
|
||
_assert(res["vnum"] == 27836, "Spawned dead Sazan (27836)")
|
||
|
||
# 2. 产生鱼骨 (roll 2001~4000)
|
||
res = system.gut_live_fish(inv, 1, 3000)
|
||
_assert(res["ok"] == true, "Gut fish succeeded for bone")
|
||
_assert(res["result"] == "fish_bone", "Result is fish_bone")
|
||
_assert(res["vnum"] == 27995, "Spawned Fish Bone (27995)")
|
||
|
||
# 3. 产生珍珠蚌 (roll <= 1000)
|
||
res = system.gut_live_fish(inv, 2, 500)
|
||
_assert(res["ok"] == true, "Found clam inside fish!")
|
||
_assert(res["result"] == "clam", "Result is clam")
|
||
_assert(res["vnum"] == 27987, "Spawned Clam (27987)")
|
||
|
||
# 4. 产生蚯蚓 (roll 1001~1500)
|
||
res = system.gut_live_fish(inv, 3, 1200)
|
||
_assert(res["ok"] == true, "Found earthworm")
|
||
_assert(res["result"] == "earthworm", "Result is earthworm")
|
||
_assert(res["vnum"] == 27801, "Spawned Earthworm (27801)")
|
||
|
||
# 5. 滑入水中溜走 (roll > 1500)
|
||
res = system.gut_live_fish(inv, 4, 1800)
|
||
_assert(res["ok"] == true, "Escaped")
|
||
_assert(res["result"] == "escape", "Fish escaped back to water")
|
||
|
||
func test_clam_pearl_opening() -> void:
|
||
print("\n--- Test 2: Clam Opening & 3 Pearls (40250 1:1) ---")
|
||
var system = FishClamRoastSystem.new()
|
||
|
||
var inv: Array = [
|
||
{"vnum": 27987, "count": 1}, # 蚌 1: 碎石
|
||
{"vnum": 27987, "count": 1}, # 蚌 2: 空
|
||
{"vnum": 27987, "count": 1}, # 蚌 3: 白珍珠
|
||
{"vnum": 27987, "count": 1}, # 蚌 4: 蓝珍珠
|
||
{"vnum": 27987, "count": 1} # 蚌 5: 血红珍珠
|
||
]
|
||
|
||
# 1. 碎石 (branch roll <= 50)
|
||
var res = system.open_clam(inv, 0, 30)
|
||
_assert(res["ok"] == true, "Opened clam for stone")
|
||
_assert(res["result"] == "stone", "Result is stone")
|
||
_assert(res["vnum"] == 27990, "Vnum is 27990 (Stone Piece)")
|
||
|
||
# 2. 空蚌 (branch roll > 50, pearl roll <= 80)
|
||
res = system.open_clam(inv, 1, 70, 50)
|
||
_assert(res["ok"] == true, "Opened empty clam")
|
||
_assert(res["result"] == "empty", "Result is empty")
|
||
|
||
# 3. 白珍珠 (pearl roll 81~90)
|
||
res = system.open_clam(inv, 2, 70, 85)
|
||
_assert(res["ok"] == true, "Opened White Pearl")
|
||
_assert(res["result"] == "white_pearl", "Result is white_pearl")
|
||
_assert(res["vnum"] == 27992, "Vnum is 27992 (White Pearl)")
|
||
|
||
# 4. 蓝珍珠 (pearl roll 91~97)
|
||
res = system.open_clam(inv, 3, 70, 95)
|
||
_assert(res["ok"] == true, "Opened Blue Pearl")
|
||
_assert(res["result"] == "blue_pearl", "Result is blue_pearl")
|
||
_assert(res["vnum"] == 27993, "Vnum is 27993 (Blue Pearl)")
|
||
|
||
# 5. 血红珍珠 (pearl roll >= 98)
|
||
res = system.open_clam(inv, 4, 70, 99)
|
||
_assert(res["ok"] == true, "Opened Blood Pearl")
|
||
_assert(res["result"] == "blood_pearl", "Result is blood_pearl")
|
||
_assert(res["vnum"] == 27994, "Vnum is 27994 (Blood Pearl)")
|
||
|
||
func test_campfire_grilling() -> void:
|
||
print("\n--- Test 3: Campfire Grilling (40250 1:1) ---")
|
||
var system = FishClamRoastSystem.new()
|
||
|
||
var inv: Array = [
|
||
{"vnum": 27836, "count": 3}, # 3 条死鲤鱼
|
||
{"vnum": 27838, "count": 1} # 1 条死草鱼
|
||
]
|
||
|
||
# 未靠近营火拒绝烤制
|
||
var res = system.grill_fish(inv, 0, false)
|
||
_assert(res["ok"] == false, "Rejected grilling without campfire")
|
||
_assert(res["reason"] == "NO_CAMPFIRE", "Reason is NO_CAMPFIRE")
|
||
|
||
# 点燃营火
|
||
system.place_campfire(600.0)
|
||
_assert(system.is_near_active_campfire == true, "Campfire is active")
|
||
|
||
# 烤制 3 条死鲤鱼 (27836 -> 27866 烤鲤鱼)
|
||
res = system.grill_fish(inv, 0)
|
||
_assert(res["ok"] == true, "Grilling Sazan succeeded")
|
||
_assert(res["cooked_vnum"] == 27866, "Cooked Sazan vnum is 27866")
|
||
_assert(res["count"] == 3, "Preserved count 3")
|
||
_assert(inv[0] != null and inv[0]["vnum"] == 27866 and inv[0]["count"] == 3, "Slot holds 3 cooked fish")
|
||
|
||
func test_cooked_fish_buffs() -> void:
|
||
print("\n--- Test 4: Cooked Fish Consumption & Buffs (40250 1:1) ---")
|
||
var system = FishClamRoastSystem.new()
|
||
|
||
var inv: Array = [
|
||
{"vnum": 27866, "count": 1}, # 烤鲤鱼: 移速 +20 (600s)
|
||
{"vnum": 27868, "count": 1}, # 烤草鱼: 攻速 +20 (600s)
|
||
{"vnum": 27877, "count": 1}, # 烤泥鳅: 隐身 (300s)
|
||
{"vnum": 27874, "count": 1}, # 烤海鲈鱼: 治愈中毒
|
||
{"vnum": 27863, "count": 1} # 烤鲈鱼: 恢复 180 HP
|
||
]
|
||
|
||
# 1. 食用烤鲤鱼
|
||
var res = system.eat_cooked_fish(inv, 0)
|
||
_assert(res["ok"] == true, "Ate cooked Sazan")
|
||
_assert(res["effect"] == "move_speed", "Effect is move_speed")
|
||
_assert(res["value"] == 20, "Value is 20")
|
||
_assert(res["duration"] == 600.0, "Duration is 600s")
|
||
|
||
# 2. 食用烤草鱼
|
||
res = system.eat_cooked_fish(inv, 1)
|
||
_assert(res["ok"] == true, "Ate cooked Ot Sazani")
|
||
_assert(res["effect"] == "attack_speed", "Effect is attack_speed")
|
||
_assert(res["value"] == 20, "Value is 20")
|
||
|
||
# 3. 食用烤泥鳅
|
||
res = system.eat_cooked_fish(inv, 2)
|
||
_assert(res["ok"] == true, "Ate cooked Copra")
|
||
_assert(res["effect"] == "invisibility", "Effect is invisibility")
|
||
_assert(res["duration"] == 300.0, "Duration is 300s")
|
||
|
||
# 4. 食用解毒鱼与瞬间恢复鱼
|
||
res = system.eat_cooked_fish(inv, 3)
|
||
_assert(res["effect"] == "cure_poison", "Effect is cure_poison")
|
||
res = system.eat_cooked_fish(inv, 4)
|
||
_assert(res["effect"] == "instant_hp" and res["value"] == 180, "Instant HP restored 180")
|
||
|
||
# 检查增益状态列表
|
||
var buffs = system.get_active_buffs()
|
||
_assert(buffs.has("move_speed"), "Active buff has move_speed")
|
||
_assert(buffs.has("attack_speed"), "Active buff has attack_speed")
|
||
_assert(buffs.has("invisibility"), "Active buff has invisibility")
|
||
|
||
# 时间推移 100 秒
|
||
system.update(100.0)
|
||
_assert(buffs["invisibility"]["time_remaining"] <= 200.0, "Invisibility ticked down")
|
||
|
||
# 时间推移 250 秒 (隐身 300s 到期清除)
|
||
system.update(250.0)
|
||
_assert(not system.get_active_buffs().has("invisibility"), "Invisibility expired and removed")
|
||
_assert(system.get_active_buffs().has("move_speed"), "Move speed still active")
|
||
|
||
func test_serialization() -> void:
|
||
print("\n--- Test 5: Buff Serialization & Restoration ---")
|
||
var system = FishClamRoastSystem.new()
|
||
system.place_campfire(500.0)
|
||
var inv: Array = [{"vnum": 27866, "count": 1}]
|
||
system.eat_cooked_fish(inv, 0)
|
||
|
||
var data = system.serialize()
|
||
_assert(data.has("active_buffs") and data["active_buffs"].has("move_speed"), "Serialized move_speed buff")
|
||
|
||
var restored = FishClamRoastSystem.new()
|
||
restored.deserialize(data)
|
||
_assert(restored.is_near_active_campfire == true, "Restored active campfire")
|
||
_assert(restored.get_active_buffs().has("move_speed"), "Restored move_speed buff")
|