- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
147 lines
5.8 KiB
GDScript
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)
|