- 桥梁与静态物体高度采样修复: - 严格对齐 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.4 KiB
GDScript
147 lines
5.4 KiB
GDScript
# wardrobe_transmog_system.gd —— Metin2 40250 装备外观幻化重塑与试衣间系统 1:1
|
||
# 对照 40250 服务端 item_transmutation.cpp, char_item.cpp
|
||
class_name WardrobeTransmogSystem
|
||
extends RefCounted
|
||
|
||
signal item_transmuted(target_slot: int, target_name: String, new_look_vnum: int, new_look_name: String)
|
||
signal transmutation_reverted(target_slot: int, item_name: String)
|
||
|
||
const VNUM_TRANSMOG_SCROLL := 72301 # 幻化秘籍
|
||
const VNUM_REVERT_SCROLL := 72302 # 幻化还原秘籍
|
||
const TRANSMOG_COST_GOLD := 10000000 # 1000 万金币
|
||
|
||
# 装备大类
|
||
const TYPE_WEAPON := 1
|
||
const TYPE_ARMOR := 2
|
||
const TYPE_COSTUME := 28
|
||
|
||
# 试衣间预览外观效果 (不消耗物品)
|
||
func preview_transmog(target_item: Dictionary, skin_item: Dictionary) -> Dictionary:
|
||
var val = _validate_compatibility(target_item, skin_item)
|
||
if not val["ok"]:
|
||
return val
|
||
|
||
return {
|
||
"ok": true,
|
||
"base_vnum": target_item.get("vnum", 0),
|
||
"base_name": target_item.get("name", "神装"),
|
||
"preview_vnum": skin_item.get("vnum", 0),
|
||
"preview_name": skin_item.get("name", "幻化外观"),
|
||
"msg": "试衣间预览生成完毕!"
|
||
}
|
||
|
||
# 执行幻化外观重塑
|
||
func apply_transmutation(scroll_slot: int, target_slot: int, skin_slot: int, inventory: Array, player_data: Dictionary) -> Dictionary:
|
||
if scroll_slot < 0 or scroll_slot >= inventory.size() or inventory[scroll_slot] == null:
|
||
return {"ok": false, "reason": "INVALID_SCROLL_SLOT"}
|
||
if target_slot < 0 or target_slot >= inventory.size() or inventory[target_slot] == null:
|
||
return {"ok": false, "reason": "INVALID_TARGET_SLOT"}
|
||
if skin_slot < 0 or skin_slot >= inventory.size() or inventory[skin_slot] == null:
|
||
return {"ok": false, "reason": "INVALID_SKIN_SLOT"}
|
||
|
||
if target_slot == skin_slot:
|
||
return {"ok": false, "reason": "SAME_SLOT", "msg": "目标装备与外观装备不能为同一格子!"}
|
||
|
||
var scroll = inventory[scroll_slot]
|
||
if int(scroll.get("vnum", 0)) != VNUM_TRANSMOG_SCROLL:
|
||
return {"ok": false, "reason": "NOT_A_TRANSMOG_SCROLL", "msg": "需要使用【幻化秘籍】(72301)!"}
|
||
|
||
var target = inventory[target_slot]
|
||
var skin = inventory[skin_slot]
|
||
|
||
var comp_check = _validate_compatibility(target, skin)
|
||
if not comp_check["ok"]:
|
||
return comp_check
|
||
|
||
var gold = int(player_data.get("gold", 0))
|
||
if gold < TRANSMOG_COST_GOLD:
|
||
return {"ok": false, "reason": "NOT_ENOUGH_GOLD", "msg": "幻化所需金币不足!需要 10,000,000 金币。"}
|
||
|
||
# 扣除金币与卷轴
|
||
player_data["gold"] = gold - TRANSMOG_COST_GOLD
|
||
var sc_cnt = int(scroll.get("count", 1))
|
||
if sc_cnt > 1:
|
||
scroll["count"] = sc_cnt - 1
|
||
else:
|
||
inventory[scroll_slot] = null
|
||
|
||
# 消耗外观材料装备
|
||
var skin_vnum = int(skin.get("vnum", 0))
|
||
var skin_name = skin.get("name", "外观")
|
||
var sk_cnt = int(skin.get("count", 1))
|
||
if sk_cnt > 1:
|
||
skin["count"] = sk_cnt - 1
|
||
else:
|
||
inventory[skin_slot] = null
|
||
|
||
# 赋予目标装备幻化外观 (所有基础属性、强化与灵石 100% 保留)
|
||
target["transmog_vnum"] = skin_vnum
|
||
target["transmog_name"] = skin_name
|
||
|
||
var tname: String = target.get("name", "装备")
|
||
item_transmuted.emit(target_slot, tname, skin_vnum, skin_name)
|
||
|
||
return {
|
||
"ok": true,
|
||
"target_slot": target_slot,
|
||
"target_name": tname,
|
||
"transmog_vnum": skin_vnum,
|
||
"transmog_name": skin_name,
|
||
"msg": "幻化成功!【%s】披上了【%s】的华丽外观!" % [tname, skin_name]
|
||
}
|
||
|
||
# 还原装备原生外观
|
||
func revert_transmutation(scroll_slot: int, target_slot: int, inventory: Array) -> Dictionary:
|
||
if scroll_slot < 0 or scroll_slot >= inventory.size() or inventory[scroll_slot] == null:
|
||
return {"ok": false, "reason": "INVALID_SCROLL_SLOT"}
|
||
if target_slot < 0 or target_slot >= inventory.size() or inventory[target_slot] == null:
|
||
return {"ok": false, "reason": "INVALID_TARGET_SLOT"}
|
||
|
||
var scroll = inventory[scroll_slot]
|
||
if int(scroll.get("vnum", 0)) != VNUM_REVERT_SCROLL:
|
||
return {"ok": false, "reason": "NOT_A_REVERT_SCROLL", "msg": "需要使用【幻化还原秘籍】(72302)!"}
|
||
|
||
var target = inventory[target_slot]
|
||
if not target.has("transmog_vnum") or int(target.get("transmog_vnum", 0)) == 0:
|
||
return {"ok": false, "reason": "NOT_TRANSMUTED", "msg": "该装备未曾幻化,无需还原!"}
|
||
|
||
# 消耗还原卷轴
|
||
var cnt = int(scroll.get("count", 1))
|
||
if cnt > 1:
|
||
scroll["count"] = cnt - 1
|
||
else:
|
||
inventory[scroll_slot] = null
|
||
|
||
target.erase("transmog_vnum")
|
||
target.erase("transmog_name")
|
||
|
||
var tname: String = target.get("name", "装备")
|
||
transmutation_reverted.emit(target_slot, tname)
|
||
|
||
return {
|
||
"ok": true,
|
||
"target_slot": target_slot,
|
||
"item_name": tname,
|
||
"msg": "幻化已抹除!【%s】恢复为原生经典外观。" % tname
|
||
}
|
||
|
||
# 校验外观类型兼容性
|
||
func _validate_compatibility(target: Dictionary, skin: Dictionary) -> Dictionary:
|
||
if target.is_empty() or skin.is_empty():
|
||
return {"ok": false, "reason": "EMPTY_DATA"}
|
||
|
||
var t_type = int(target.get("type", 1))
|
||
var s_type = int(skin.get("type", 1))
|
||
|
||
# 武器只能幻化武器或时装武器外观;防具只能幻化防具或时装衣服
|
||
if t_type == TYPE_WEAPON:
|
||
if s_type != TYPE_WEAPON and s_type != TYPE_COSTUME:
|
||
return {"ok": false, "reason": "TYPE_MISMATCH", "msg": "武器只能幻化武器或时装外观!"}
|
||
elif t_type == TYPE_ARMOR:
|
||
if s_type != TYPE_ARMOR and s_type != TYPE_COSTUME:
|
||
return {"ok": false, "reason": "TYPE_MISMATCH", "msg": "防具衣服只能幻化衣服防具外观!"}
|
||
else:
|
||
return {"ok": false, "reason": "UNSUPPORTED_TYPE", "msg": "该装备部位暂不支持幻化外观。"}
|
||
|
||
return {"ok": true}
|