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

201 lines
6.3 KiB
GDScript

# polymorph_marble_system.gd —— Metin2 40250 官方变身宝珠与怪物精魂附体系统 1:1
# 对照 40250 服务端 polymorph.cpp, polymorph.h, char.cpp, affect.h
class_name PolymorphMarbleSystem
extends RefCounted
signal polymorph_started(mob_vnum: int, mob_name: String, duration: float, bonus_type: int, bonus_val: int)
signal polymorph_ended(mob_vnum: int, mob_name: String)
const VNUM_POLYMORPH_MARBLE := 70104 # 变身宝珠 (Polymorph Marble)
# 40250 变身法术技能境界 (Skill Master Type)
const SKILL_NORMAL := 0 # 普通熟练度 (1~19 级): 10 分钟 (600s)
const SKILL_MASTER := 1 # Master (M1~M10): 15 分钟 (900s)
const SKILL_GRAND_MASTER := 2 # Grand Master (G1~G10): 20 分钟 (1200s)
const SKILL_PERFECT_MASTER := 3 # Perfect Master (P): 25 分钟 (1500s)
# 40250 polymorph.h 加成属性类型枚举
const POLYMORPH_NO_BONUS := 0
const POLYMORPH_ATK_BONUS := 1 # 攻击力增益 (POINT_ATT_BONUS)
const POLYMORPH_DEF_BONUS := 2 # 防御力增益 (POINT_DEF_BONUS)
const POLYMORPH_SPD_BONUS := 3 # 移动速度增益 (POINT_MOV_SPEED)
# 40250 经典怪物精魂图鉴与加成配置表
const MONSTER_POLY_TABLE: Dictionary = {
501: {
"mob_vnum": 501,
"name": "野蛮步兵",
"bonus_type": POLYMORPH_ATK_BONUS,
"bonus_val": 40,
"stat_key": "attack_power"
},
502: {
"mob_vnum": 502,
"name": "野蛮弓手",
"bonus_type": POLYMORPH_ATK_BONUS,
"bonus_val": 35,
"stat_key": "attack_power"
},
492: {
"mob_vnum": 492,
"name": "白发蜘蛛",
"bonus_type": POLYMORPH_DEF_BONUS,
"bonus_val": 50,
"stat_key": "defense"
},
101: {
"mob_vnum": 101,
"name": "野狗",
"bonus_type": POLYMORPH_SPD_BONUS,
"bonus_val": 25,
"stat_key": "move_speed"
},
1901: {
"mob_vnum": 1901,
"name": "九尾妖狐",
"bonus_type": POLYMORPH_SPD_BONUS,
"bonus_val": 30,
"stat_key": "move_speed"
}
}
var is_polymorphed: bool = false
var active_mob_vnum: int = 0
var active_mob_name: String = ""
var time_remaining: float = 0.0
var total_duration: float = 0.0
var active_bonus_type: int = POLYMORPH_NO_BONUS
var active_bonus_val: int = 0
var active_stat_key: String = ""
# 计算变身持续时长 (40250 polymorph.cpp:44-59)
static func get_duration_by_mastery(master_type: int) -> float:
match master_type:
SKILL_NORMAL:
return 600.0 # 10 分钟
SKILL_MASTER:
return 900.0 # 15 分钟
SKILL_GRAND_MASTER:
return 1200.0 # 20 分钟
SKILL_PERFECT_MASTER:
return 1500.0 # 25 分钟
_:
return 600.0
# 使用变身宝珠进行怪物附体变身
func polymorph_character(marble_item: Dictionary, skill_mastery: int, player_stats: Dictionary) -> Dictionary:
var vnum := int(marble_item.get("vnum", 0))
if vnum != VNUM_POLYMORPH_MARBLE:
return {"ok": false, "reason": "NOT_A_POLYMORPH_MARBLE", "msg": "该物品不是变身宝珠!"}
var mob_vnum := int(marble_item.get("mob_vnum", 0))
if mob_vnum == 0:
var sockets: Array = marble_item.get("sockets", [])
if not sockets.is_empty():
mob_vnum = int(sockets[0])
if not MONSTER_POLY_TABLE.has(mob_vnum):
return {"ok": false, "reason": "INVALID_MONSTER_VNUM", "msg": "未知的变身怪物精魂!"}
# 若当前已处于变身状态,先恢复原形
if is_polymorphed:
revert_polymorph(player_stats)
var mob_info: Dictionary = MONSTER_POLY_TABLE[mob_vnum]
var duration: float = get_duration_by_mastery(skill_mastery)
is_polymorphed = true
active_mob_vnum = mob_vnum
active_mob_name = mob_info["name"]
time_remaining = duration
total_duration = duration
active_bonus_type = int(mob_info["bonus_type"])
active_bonus_val = int(mob_info["bonus_val"])
active_stat_key = str(mob_info["stat_key"])
# 注入变身属性加成
if not active_stat_key.is_empty() and active_bonus_val > 0:
player_stats[active_stat_key] = int(player_stats.get(active_stat_key, 0)) + active_bonus_val
polymorph_started.emit(active_mob_vnum, active_mob_name, duration, active_bonus_type, active_bonus_val)
return {
"ok": true,
"mob_vnum": active_mob_vnum,
"mob_name": active_mob_name,
"duration": duration,
"bonus_type": active_bonus_type,
"bonus_val": active_bonus_val,
"msg": "怪物精魂附体!成功变身为【%s】,持续 %.0f 秒。" % [active_mob_name, duration]
}
# 解除变身恢复真身原型
func revert_polymorph(player_stats: Dictionary) -> Dictionary:
if not is_polymorphed:
return {"ok": false, "reason": "NOT_POLYMORPHED", "msg": "当前未处于变身状态!"}
# 清除施加的加成属性
if not active_stat_key.is_empty() and active_bonus_val > 0:
player_stats[active_stat_key] = int(player_stats.get(active_stat_key, 0)) - active_bonus_val
var old_mob := active_mob_vnum
var old_name := active_mob_name
is_polymorphed = false
active_mob_vnum = 0
active_mob_name = ""
time_remaining = 0.0
total_duration = 0.0
active_bonus_type = POLYMORPH_NO_BONUS
active_bonus_val = 0
active_stat_key = ""
polymorph_ended.emit(old_mob, old_name)
return {
"ok": true,
"mob_vnum": old_mob,
"mob_name": old_name,
"msg": "变身时效已过,恢复人物原形。"
}
# 逻辑时钟心跳
func update(delta: float, player_stats: Dictionary) -> void:
if not is_polymorphed:
return
time_remaining -= delta
if time_remaining <= 0.0:
revert_polymorph(player_stats)
# 40250 行为限制检查:变身期间禁止乘骑坐骑
func can_mount_horse() -> bool:
return not is_polymorphed
# 40250 行为限制检查:变身期间禁止释放原生职业技能
func can_cast_player_skills() -> bool:
return not is_polymorphed
# 序列化
func serialize() -> Dictionary:
return {
"is_polymorphed": is_polymorphed,
"active_mob_vnum": active_mob_vnum,
"active_mob_name": active_mob_name,
"time_remaining": time_remaining,
"total_duration": total_duration,
"active_bonus_type": active_bonus_type,
"active_bonus_val": active_bonus_val,
"active_stat_key": active_stat_key
}
# 反序列化
func deserialize(data: Dictionary) -> void:
is_polymorphed = bool(data.get("is_polymorphed", false))
active_mob_vnum = int(data.get("active_mob_vnum", 0))
active_mob_name = str(data.get("active_mob_name", ""))
time_remaining = float(data.get("time_remaining", 0.0))
total_duration = float(data.get("total_duration", 0.0))
active_bonus_type = int(data.get("active_bonus_type", POLYMORPH_NO_BONUS))
active_bonus_val = int(data.get("active_bonus_val", 0))
active_stat_key = str(data.get("active_stat_key", ""))