- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
186 lines
6.7 KiB
GDScript
186 lines
6.7 KiB
GDScript
# test_dragon_lair_parity.gd —— 40250 冰龙贝兰-塞陶巢穴 1:1 纯净回归测试套件
|
|
extends SceneTree
|
|
|
|
const DragonLairSystem = preload("res://dragon_lair_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_dragon_lair_parity (Direction 1: Dragon Lair Beran-Setaou 1:1) ===")
|
|
_test_entry_and_key_consumption()
|
|
_test_metin_buffs_and_debuffs()
|
|
_test_metin_hp_regen()
|
|
_test_dragon_defeat_and_drops()
|
|
_test_dungeon_timeout()
|
|
_finish()
|
|
|
|
func _test_entry_and_key_consumption() -> void:
|
|
print("\n--- Test 1: Entry Requirements & Key Consumption (30179 x3) ---")
|
|
var dls := DragonLairSystem.new()
|
|
var inv: Array = [
|
|
{"vnum": 30179, "name": "扭曲钥匙", "count": 2},
|
|
{"vnum": 30179, "name": "扭曲钥匙", "count": 1},
|
|
null
|
|
]
|
|
|
|
# Level 70 rejected
|
|
var r1 = dls.enter_lair(70, inv)
|
|
_assert(r1["ok"] == false and r1["reason"] == "LEVEL_TOO_LOW", "Level < 75 rejected")
|
|
|
|
# Level 75 but lacking keys
|
|
var empty_inv: Array = [null]
|
|
var r2 = dls.enter_lair(75, empty_inv)
|
|
_assert(r2["ok"] == false and r2["reason"] == "MISSING_KEYS", "Missing keys rejected")
|
|
|
|
# Level 75 with 3 keys across 2 stacks succeeds
|
|
var r3 = dls.enter_lair(75, inv)
|
|
_assert(r3["ok"] == true, "Entry succeeds with Lv 75 and 3 keys")
|
|
_assert(inv[0] == null and inv[1] == null, "3 keys across both slots consumed")
|
|
_assert(dls.is_active == true, "Dungeon is active")
|
|
_assert(dls.remaining_time == 3600.0, "60-minute countdown initialized")
|
|
_assert(dls.get_alive_metin_count() == 4, "4 Metin stones initialized")
|
|
_assert(dls.dragon_hp == 800000, "Dragon HP initialized to 800,000")
|
|
|
|
func _test_metin_buffs_and_debuffs() -> void:
|
|
print("\n--- Test 2: Metin Stone Buffs & Debuff Removal (8031, 8033, 8034) ---")
|
|
var dls := DragonLairSystem.new()
|
|
dls.enter_lair(80, [{"vnum": 30179, "count": 3}])
|
|
|
|
# 1. 8034 (复仇之石) ATK buff (+40%): 1800 * 1.4 = 2520
|
|
_assert(dls.get_dragon_atk() == 2520, "Dragon ATK has +40% buff (2520) while 8034 alive")
|
|
|
|
var metin_signal := {"fired": false, "vnum": 0, "name": ""}
|
|
dls.metin_destroyed.connect(func(v: int, n: String):
|
|
metin_signal["fired"] = true
|
|
metin_signal["vnum"] = v
|
|
metin_signal["name"] = n
|
|
)
|
|
|
|
var minion_signal := {"fired": false, "count": 0}
|
|
dls.minions_spawned.connect(func(cnt: int, _mob: int):
|
|
minion_signal["fired"] = true
|
|
minion_signal["count"] = cnt
|
|
)
|
|
|
|
# Destroy 8034
|
|
var des_res = dls.destroy_metin(8034)
|
|
_assert(des_res["ok"] == true, "8034 destroyed")
|
|
_assert(metin_signal["fired"] == true and metin_signal["vnum"] == 8034, "metin_destroyed signal fired")
|
|
_assert(minion_signal["fired"] == true and minion_signal["count"] == 4, "minions_spawned signal fired with 4 minions")
|
|
_assert(dls.get_dragon_atk() == 1800, "Dragon ATK dropped to base 1800 after 8034 destroyed")
|
|
|
|
# 2. 8031 (傲慢之石) Physical Resistance (-30% damage taken)
|
|
# Raw dmg: 1650. DEF: 650. Net: 1000. With 30% reduction: 700.
|
|
var phys_res = dls.apply_damage(1650, false)
|
|
_assert(phys_res["net_dmg"] == 700, "Physical damage reduced by 30% (700 net) while 8031 alive")
|
|
|
|
# Destroy 8031
|
|
dls.destroy_metin(8031)
|
|
var phys_res2 = dls.apply_damage(1650, false)
|
|
_assert(phys_res2["net_dmg"] == 1000, "Physical damage unreduced (1000 net) after 8031 destroyed")
|
|
|
|
# 3. 8033 (平静之石) Magic Resistance (-30% magic damage taken)
|
|
var mag_res = dls.apply_damage(1650, true)
|
|
_assert(mag_res["net_dmg"] == 700, "Magic damage reduced by 30% (700 net) while 8033 alive")
|
|
|
|
# Destroy 8033
|
|
dls.destroy_metin(8033)
|
|
var mag_res2 = dls.apply_damage(1650, true)
|
|
_assert(mag_res2["net_dmg"] == 1000, "Magic damage unreduced (1000 net) after 8033 destroyed")
|
|
|
|
func _test_metin_hp_regen() -> void:
|
|
print("\n--- Test 3: 8032 Metin HP Regeneration ---")
|
|
var dls := DragonLairSystem.new()
|
|
dls.enter_lair(80, [{"vnum": 30179, "count": 3}])
|
|
|
|
# Deal 50,000 net damage (damage boss from 800,000 to 750,000)
|
|
dls.destroy_metin(8031) # remove physical reduction for easy calculation
|
|
dls.apply_damage(50650, false) # 50650 - 650 DEF = 50000 net dmg
|
|
_assert(dls.dragon_hp == 750000, "Dragon HP reduced to 750,000")
|
|
|
|
# Advance 5.0 seconds -> 8032 heals 2% (16,000 HP) -> HP 766,000
|
|
dls.update(5.0)
|
|
_assert(dls.dragon_hp == 766000, "8032 regenerated 16,000 HP (+2%) at 5s tick")
|
|
|
|
# Destroy 8032 -> advance 5.0s -> no healing occurs
|
|
dls.destroy_metin(8032)
|
|
dls.update(5.0)
|
|
_assert(dls.dragon_hp == 766000, "No regen occurs after 8032 destroyed")
|
|
|
|
func _test_dragon_defeat_and_drops() -> void:
|
|
print("\n--- Test 4: Dragon Defeat & Epic Drop Generation ---")
|
|
var dls := DragonLairSystem.new()
|
|
dls.enter_lair(80, [{"vnum": 30179, "count": 3}])
|
|
|
|
var kill_signal := {"fired": false, "drops": []}
|
|
dls.dragon_defeated.connect(func(drops: Array):
|
|
kill_signal["fired"] = true
|
|
kill_signal["drops"] = drops
|
|
)
|
|
|
|
# Deal lethal damage
|
|
dls.destroy_metin(8031)
|
|
var res = dls.apply_damage(1000000, false)
|
|
_assert(res["ok"] == true and res["killed"] == true, "Beran-Setaou killed")
|
|
_assert(dls.is_dragon_alive == false, "is_dragon_alive is false")
|
|
_assert(dls.is_active == false, "Dungeon completed")
|
|
_assert(kill_signal["fired"] == true, "dragon_defeated signal fired")
|
|
|
|
var drops: Array = res["drops"]
|
|
var has_scale := false
|
|
var has_claw := false
|
|
var has_chest := false
|
|
var has_soul_stone := false
|
|
var has_gold := false
|
|
|
|
for d in drops:
|
|
if d["vnum"] == 71123: # Dragon Scale
|
|
has_scale = true
|
|
elif d["vnum"] == 71129: # Dragon Claw
|
|
has_claw = true
|
|
elif d["vnum"] == 50186: # Beran-Setaou Chest
|
|
has_chest = true
|
|
elif d["vnum"] == 50513: # Soul Stone
|
|
has_soul_stone = true
|
|
elif d["vnum"] == 1:
|
|
has_gold = true
|
|
|
|
_assert(has_scale, "Guaranteed Dragon Scale (71123) dropped")
|
|
_assert(has_claw, "Guaranteed Dragon Claw (71129) dropped")
|
|
_assert(has_chest, "Guaranteed Beran-Setaou Chest (50186) dropped")
|
|
_assert(has_soul_stone, "Guaranteed Soul Stone (50513) dropped")
|
|
_assert(has_gold, "Yang dropped")
|
|
|
|
func _test_dungeon_timeout() -> void:
|
|
print("\n--- Test 5: Dungeon Timeout Failure ---")
|
|
var dls := DragonLairSystem.new()
|
|
dls.enter_lair(80, [{"vnum": 30179, "count": 3}])
|
|
|
|
var fail_signal := {"fired": false, "reason": ""}
|
|
dls.dungeon_failed.connect(func(reason: String):
|
|
fail_signal["fired"] = true
|
|
fail_signal["reason"] = reason
|
|
)
|
|
|
|
dls.update(3601.0)
|
|
_assert(fail_signal["fired"] == true and fail_signal["reason"] == "TIME_OUT", "Timeout signal emitted")
|
|
_assert(dls.is_active == false, "Dungeon closed on timeout")
|
|
|
|
func _finish() -> void:
|
|
print("\n==========================================")
|
|
print("Dragon Lair Parity Test Results: %d Passed, %d Failed" % [_passed, _failed])
|
|
print("==========================================")
|
|
if _failed > 0:
|
|
quit(1)
|
|
else:
|
|
quit(0)
|