Files
mtgodot-poc/project/test_costume_system_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

147 lines
5.8 KiB
GDScript

# test_costume_system_parity.gd —— 40250 独立时装衣柜系统 1:1 纯净回归测试套件
extends SceneTree
const CostumeSystem = preload("res://costume_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_costume_system_parity (Direction 1: Costume System 1:1) ===")
_test_slot_type_validation()
_test_costume_equip_and_unequip()
_test_stat_stacking_and_appearance()
_test_costume_expiration()
_finish()
func _test_slot_type_validation() -> void:
print("\n--- Test 1: Slot Type & Item Category Validation ---")
var cs := CostumeSystem.new()
var body_costume := {"vnum": 41001, "name": "沙漠战袍", "costume_type": "body", "model_id": 101}
var hair_costume := {"vnum": 45001, "name": "头巾发型", "costume_type": "hair", "model_id": 201}
# Invalid slot
var r_bad = cs.equip_costume(99, body_costume)
_assert(r_bad["ok"] == false and r_bad["reason"] == "INVALID_SLOT", "Invalid slot rejected")
# Put body costume into hair slot
var r_mismatch1 = cs.equip_costume(CostumeSystem.CostumeSlot.COSTUME_HAIR, body_costume)
_assert(r_mismatch1["ok"] == false and r_mismatch1["reason"] == "SLOT_TYPE_MISMATCH", "Body costume rejected in Hair slot")
# Put hair costume into weapon slot
var r_mismatch2 = cs.equip_costume(CostumeSystem.CostumeSlot.COSTUME_WEAPON, hair_costume)
_assert(r_mismatch2["ok"] == false and r_mismatch2["reason"] == "SLOT_TYPE_MISMATCH", "Hair costume rejected in Weapon slot")
func _test_costume_equip_and_unequip() -> void:
print("\n--- Test 2: Equip & Unequip Operations ---")
var cs := CostumeSystem.new()
var body_item := {
"vnum": 41001,
"name": "黑骑士战袍",
"costume_type": "body",
"model_id": 105,
"bonuses": {"max_hp": 1000}
}
var equip_signal := {"fired": false, "slot": -1}
cs.costume_equipped.connect(func(s: int, _it: Dictionary):
equip_signal["fired"] = true
equip_signal["slot"] = s
)
var unequip_signal := {"fired": false, "slot": -1}
cs.costume_unequipped.connect(func(s: int):
unequip_signal["fired"] = true
unequip_signal["slot"] = s
)
# Equip
var r_eq = cs.equip_costume(CostumeSystem.CostumeSlot.COSTUME_BODY, body_item)
_assert(r_eq["ok"] == true, "Body costume equipped successfully")
_assert(cs.equipped_costumes[CostumeSystem.CostumeSlot.COSTUME_BODY] != null, "Body slot occupied")
_assert(equip_signal["fired"] == true and equip_signal["slot"] == CostumeSystem.CostumeSlot.COSTUME_BODY, "costume_equipped signal fired")
# Unequip
var r_uneq = cs.unequip_costume(CostumeSystem.CostumeSlot.COSTUME_BODY)
_assert(r_uneq["ok"] == true, "Body costume unequipped")
_assert(cs.equipped_costumes[CostumeSystem.CostumeSlot.COSTUME_BODY] == null, "Body slot now null")
_assert(unequip_signal["fired"] == true and unequip_signal["slot"] == CostumeSystem.CostumeSlot.COSTUME_BODY, "costume_unequipped signal fired")
# Unequip empty slot
var r_empty = cs.unequip_costume(CostumeSystem.CostumeSlot.COSTUME_BODY)
_assert(r_empty["ok"] == false and r_empty["reason"] == "SLOT_EMPTY", "Unequipping empty slot rejected")
func _test_stat_stacking_and_appearance() -> void:
print("\n--- Test 3: Stat Stacking & 3D Appearance Overrides ---")
var cs := CostumeSystem.new()
var body := {"vnum": 41001, "costume_type": "body", "model_id": 101, "bonuses": {"max_hp": 1000, "att_pct": 5}}
var hair := {"vnum": 45001, "costume_type": "hair", "model_id": 202, "bonuses": {"max_hp": 500, "cast_speed": 10}}
var weapon := {"vnum": 49001, "costume_type": "weapon", "model_id": 303, "bonuses": {"monster_dmg_pct": 10}}
cs.equip_costume(CostumeSystem.CostumeSlot.COSTUME_BODY, body)
cs.equip_costume(CostumeSystem.CostumeSlot.COSTUME_HAIR, hair)
cs.equip_costume(CostumeSystem.CostumeSlot.COSTUME_WEAPON, weapon)
# Verify Stat Stacking
var bonuses = cs.get_costume_bonuses()
_assert(bonuses["max_hp"] == 1500, "Stacked Max HP is 1500 (1000 + 500)")
_assert(bonuses["att_pct"] == 5, "ATK is +5%")
_assert(bonuses["cast_speed"] == 10, "Casting Speed is +10")
_assert(bonuses["monster_dmg_pct"] == 10, "Monster Dmg is +10%")
# Verify 3D Model Overrides
var models = cs.get_appearance_overrides()
_assert(models["body_model"] == 101, "Body model is 101")
_assert(models["hair_model"] == 202, "Hair model is 202")
_assert(models["weapon_model"] == 303, "Weapon model is 303")
func _test_costume_expiration() -> void:
print("\n--- Test 4: Duration Decay & Expiration Auto-Unequip ---")
var cs := CostumeSystem.new()
var temp_body := {
"vnum": 41002,
"name": "限时战袍",
"costume_type": "body",
"remaining_time": 10.0,
"bonuses": {"max_hp": 800}
}
cs.equip_costume(CostumeSystem.CostumeSlot.COSTUME_BODY, temp_body)
var expire_signal := {"fired": false, "name": ""}
cs.costume_expired.connect(func(_s: int, iname: String):
expire_signal["fired"] = true
expire_signal["name"] = iname
)
# Update 5.0 seconds -> still active
cs.update(5.0)
_assert(cs.equipped_costumes[CostumeSystem.CostumeSlot.COSTUME_BODY] != null, "Still active after 5s")
_assert(cs.get_costume_bonuses()["max_hp"] == 800, "Bonus HP still active")
# Update 6.0 seconds -> expired!
var expired = cs.update(6.0)
_assert(expired.size() == 1, "1 item expired")
_assert(cs.equipped_costumes[CostumeSystem.CostumeSlot.COSTUME_BODY] == null, "Auto-unequipped on expiration")
_assert(expire_signal["fired"] == true and expire_signal["name"] == "限时战袍", "costume_expired signal fired")
_assert(cs.get_costume_bonuses().is_empty(), "Bonuses cleared after expiration")
func _finish() -> void:
print("\n==========================================")
print("Costume System Parity Test Results: %d Passed, %d Failed" % [_passed, _failed])
print("==========================================")
if _failed > 0:
quit(1)
else:
quit(0)