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

127 lines
4.2 KiB
GDScript

# costume_system.gd —— Metin2 40250 独立时装发型与武器衣柜系统 1:1
# 对照 40250 服务端 costume.cpp, char_item.cpp, uiinventory.py, item_data.h
class_name CostumeSystem
extends RefCounted
signal costume_equipped(slot_type: int, item: Dictionary)
signal costume_unequipped(slot_type: int)
signal costume_expired(slot_type: int, item_name: String)
enum CostumeSlot {
COSTUME_BODY = 0, # 时装衣服
COSTUME_HAIR = 1, # 时装发型
COSTUME_WEAPON = 2 # 时装武器外观
}
const DEFAULT_COSTUME_DURATION := 604800.0 # 默认 7 天 (7 * 86400)
var equipped_costumes: Dictionary = {
CostumeSlot.COSTUME_BODY: null,
CostumeSlot.COSTUME_HAIR: null,
CostumeSlot.COSTUME_WEAPON: null
}
# 穿戴时装
func equip_costume(slot_type: int, costume_item: Dictionary) -> Dictionary:
if not equipped_costumes.has(slot_type):
return {"ok": false, "reason": "INVALID_SLOT", "msg": "无效的时装栏位!"}
if costume_item.is_empty():
return {"ok": false, "reason": "EMPTY_ITEM", "msg": "时装物品数据为空!"}
# 校验时装部位匹配
var c_type: String = costume_item.get("costume_type", "")
match slot_type:
CostumeSlot.COSTUME_BODY:
if c_type != "body":
return {"ok": false, "reason": "SLOT_TYPE_MISMATCH", "msg": "此物品不是时装衣服!"}
CostumeSlot.COSTUME_HAIR:
if c_type != "hair":
return {"ok": false, "reason": "SLOT_TYPE_MISMATCH", "msg": "此物品不是时装发型!"}
CostumeSlot.COSTUME_WEAPON:
if c_type != "weapon":
return {"ok": false, "reason": "SLOT_TYPE_MISMATCH", "msg": "此物品不是时装武器外观!"}
var old_item = equipped_costumes[slot_type]
var item_to_equip = costume_item.duplicate(true)
if not item_to_equip.has("remaining_time"):
item_to_equip["remaining_time"] = DEFAULT_COSTUME_DURATION
equipped_costumes[slot_type] = item_to_equip
costume_equipped.emit(slot_type, item_to_equip)
return {
"ok": true,
"slot_type": slot_type,
"item": item_to_equip,
"old_item": old_item,
"msg": "时装【%s】已成功穿戴!" % item_to_equip.get("name", "时装")
}
# 卸下时装
func unequip_costume(slot_type: int) -> Dictionary:
if not equipped_costumes.has(slot_type):
return {"ok": false, "reason": "INVALID_SLOT", "msg": "无效的时装栏位!"}
var item = equipped_costumes[slot_type]
if item == null:
return {"ok": false, "reason": "SLOT_EMPTY", "msg": "该时装栏位未穿戴时装!"}
equipped_costumes[slot_type] = null
costume_unequipped.emit(slot_type)
return {
"ok": true,
"slot_type": slot_type,
"item": item,
"msg": "已卸下时装【%s】。" % item.get("name", "时装")
}
# 汇总所有已穿戴时装的战力属性
func get_costume_bonuses() -> Dictionary:
var total_bonuses: Dictionary = {}
for slot in equipped_costumes.keys():
var it = equipped_costumes[slot]
if it == null:
continue
var bonuses: Dictionary = it.get("bonuses", {})
for b_key in bonuses.keys():
var val: int = int(bonuses[b_key])
total_bonuses[b_key] = total_bonuses.get(b_key, 0) + val
return total_bonuses
# 获取 3D 外观覆盖模型 ID
func get_appearance_overrides() -> Dictionary:
var overrides: Dictionary = {
"body_model": 0,
"hair_model": 0,
"weapon_model": 0
}
if equipped_costumes[CostumeSlot.COSTUME_BODY] != null:
overrides["body_model"] = int(equipped_costumes[CostumeSlot.COSTUME_BODY].get("model_id", 0))
if equipped_costumes[CostumeSlot.COSTUME_HAIR] != null:
overrides["hair_model"] = int(equipped_costumes[CostumeSlot.COSTUME_HAIR].get("model_id", 0))
if equipped_costumes[CostumeSlot.COSTUME_WEAPON] != null:
overrides["weapon_model"] = int(equipped_costumes[CostumeSlot.COSTUME_WEAPON].get("model_id", 0))
return overrides
# 逐帧时效衰减更新
func update(dt: float) -> Array:
var expired_list: Array = []
for slot in equipped_costumes.keys():
var it = equipped_costumes[slot]
if it == null:
continue
it["remaining_time"] = maxf(0.0, float(it.get("remaining_time", 0.0)) - dt)
if it["remaining_time"] <= 0.0:
var iname: String = it.get("name", "时装")
expired_list.append({"slot": slot, "item": it})
equipped_costumes[slot] = null
costume_expired.emit(slot, iname)
costume_unequipped.emit(slot)
return expired_list