- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
122 lines
5.2 KiB
GDScript
122 lines
5.2 KiB
GDScript
# test_title_achievement_parity.gd —— 40250 头衔成就系统 1:1 纯净回归测试套件
|
|
extends SceneTree
|
|
|
|
const TitleAchievementSystem = preload("res://title_achievement_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_title_achievement_parity (Direction 3: Title & Achievements 1:1) ===")
|
|
_test_achievement_definitions()
|
|
_test_achievement_unlock()
|
|
_test_title_equip_and_bonuses()
|
|
_test_multiple_achievements_points_accumulation()
|
|
_finish()
|
|
|
|
func _test_achievement_definitions() -> void:
|
|
print("\n--- Test 1: Achievement Milestone Configurations ---")
|
|
var tas := TitleAchievementSystem.new()
|
|
_assert(TitleAchievementSystem.ACHIEVEMENTS.has("ACH_DEMON_TOWER"), "Demon Tower achievement defined")
|
|
_assert(TitleAchievementSystem.ACHIEVEMENTS.has("ACH_SPIDER_BARONESS"), "Spider Baroness achievement defined")
|
|
_assert(TitleAchievementSystem.ACHIEVEMENTS.has("ACH_BERAN_SETAOU"), "Beran-Setaou achievement defined")
|
|
_assert(TitleAchievementSystem.ACHIEVEMENTS.has("ACH_AZRAEL"), "Azrael achievement defined")
|
|
_assert(TitleAchievementSystem.ACHIEVEMENTS.has("ACH_FOUR_WORLD_BOSSES"), "Four World Bosses achievement defined")
|
|
_assert(TitleAchievementSystem.ACHIEVEMENTS.has("ACH_SKILL_GRANDMASTER"), "Skill Grandmaster achievement defined")
|
|
_assert(TitleAchievementSystem.ACHIEVEMENTS.has("ACH_EPIC_BLACKSMITH"), "Epic Blacksmith achievement defined")
|
|
|
|
func _test_achievement_unlock() -> void:
|
|
print("\n--- Test 2: Milestone Unlock & Signal Emission ---")
|
|
var tas := TitleAchievementSystem.new()
|
|
|
|
# Invalid ach_id
|
|
var r_bad = tas.complete_achievement("INVALID_ACH")
|
|
_assert(r_bad["ok"] == false and r_bad["reason"] == "INVALID_ACH_ID", "Invalid achievement rejected")
|
|
|
|
var unlock_signal := {"fired": false, "id": "", "title": ""}
|
|
tas.achievement_unlocked.connect(func(aid: String, _aname: String, tname: String):
|
|
unlock_signal["fired"] = true
|
|
unlock_signal["id"] = aid
|
|
unlock_signal["title"] = tname
|
|
)
|
|
|
|
# Complete ACH_BERAN_SETAOU
|
|
var r = tas.complete_achievement("ACH_BERAN_SETAOU")
|
|
_assert(r["ok"] == true, "Beran-Setaou achievement completed")
|
|
_assert(tas.is_achievement_unlocked("ACH_BERAN_SETAOU") == true, "Achievement marked unlocked")
|
|
_assert(tas.total_achievement_points == 100, "100 achievement points awarded")
|
|
_assert(tas.unlocked_titles.has("屠龙勇士"), "Title '屠龙勇士' added to unlocked titles")
|
|
_assert(unlock_signal["fired"] == true and unlock_signal["title"] == "屠龙勇士", "achievement_unlocked signal fired")
|
|
|
|
# Duplicate complete rejected
|
|
var r_dup = tas.complete_achievement("ACH_BERAN_SETAOU")
|
|
_assert(r_dup["ok"] == false and r_dup["reason"] == "ALREADY_COMPLETED", "Duplicate completion rejected")
|
|
|
|
func _test_title_equip_and_bonuses() -> void:
|
|
print("\n--- Test 3: Title Equip, Unequip, and Stat Buffs ---")
|
|
var tas := TitleAchievementSystem.new()
|
|
|
|
# Try to equip locked title
|
|
var r_lock = tas.equip_title("深渊征服者")
|
|
_assert(r_lock["ok"] == false and r_lock["reason"] == "TITLE_NOT_UNLOCKED", "Locked title cannot be equipped")
|
|
|
|
# Unlock and equip "屠龙勇士"
|
|
tas.complete_achievement("ACH_BERAN_SETAOU")
|
|
|
|
var equip_signal := {"fired": false, "title": ""}
|
|
tas.title_equipped.connect(func(t: String, _b: Dictionary):
|
|
equip_signal["fired"] = true
|
|
equip_signal["title"] = t
|
|
)
|
|
|
|
var r_eq = tas.equip_title("屠龙勇士")
|
|
_assert(r_eq["ok"] == true, "Equipped '屠龙勇士' successfully")
|
|
_assert(tas.active_title == "屠龙勇士", "active_title is '屠龙勇士'")
|
|
_assert(equip_signal["fired"] == true and equip_signal["title"] == "屠龙勇士", "title_equipped signal fired")
|
|
|
|
var bonuses = tas.get_active_bonuses()
|
|
_assert(bonuses.has("boss_dmg_pct") and bonuses["boss_dmg_pct"] == 10, "Title grants +10% Boss damage")
|
|
|
|
# Unequip title
|
|
var unequip_signal := {"fired": false}
|
|
tas.title_unequipped.connect(func(): unequip_signal["fired"] = true)
|
|
|
|
tas.unequip_title()
|
|
_assert(tas.active_title == "", "active_title cleared")
|
|
_assert(unequip_signal["fired"] == true, "title_unequipped signal fired")
|
|
_assert(tas.get_active_bonuses().is_empty(), "No bonuses active after unequip")
|
|
|
|
func _test_multiple_achievements_points_accumulation() -> void:
|
|
print("\n--- Test 4: Points Accumulation & Title Switching ---")
|
|
var tas := TitleAchievementSystem.new()
|
|
tas.complete_achievement("ACH_DEMON_TOWER") # 50 pts, "恶魔征服者", +500 HP
|
|
tas.complete_achievement("ACH_AZRAEL") # 100 pts, "深渊征服者", +50 DEF
|
|
|
|
_assert(tas.total_achievement_points == 150, "Total points accumulated: 150 (50 + 100)")
|
|
_assert(tas.unlocked_titles.size() == 2, "2 titles unlocked")
|
|
|
|
# Equip Demon Tower title
|
|
tas.equip_title("恶魔征服者")
|
|
_assert(tas.get_active_bonuses()["max_hp"] == 500, "Grants +500 Max HP")
|
|
|
|
# Switch to Azrael title
|
|
tas.equip_title("深渊征服者")
|
|
_assert(tas.get_active_bonuses()["def"] == 50, "Switched to +50 DEF")
|
|
|
|
func _finish() -> void:
|
|
print("\n==========================================")
|
|
print("Title Achievement Parity Test Results: %d Passed, %d Failed" % [_passed, _failed])
|
|
print("==========================================")
|
|
if _failed > 0:
|
|
quit(1)
|
|
else:
|
|
quit(0)
|