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

150 lines
4.3 KiB
GDScript

# polymorph_system.gd —— Metin2 40250 怪物变身球与变身术技能系统 1:1
# 对照 40250 服务端 polymorph.cpp, char_item.cpp (Item 70104, Skill 129 Polymorph)
class_name PolymorphSystem
extends RefCounted
signal polymorph_started(mob_vnum: int, duration: float)
signal polymorph_ended()
const ITEM_POLYMORPH_MARBLE := 70104
const SKILL_POLYMORPH := 129
const AFFECT_POLYMORPH := 220
# 经典变身怪物属性表 (mob_vnum -> 属性加成与基础设定)
const MOB_DATA: Dictionary = {
101: {"name": "野猪", "atk": 45, "def": 20, "speed": 120},
102: {"name": "野狼", "atk": 35, "def": 15, "speed": 135},
110: {"name": "黑熊", "atk": 60, "def": 35, "speed": 110},
114: {"name": "白虎", "atk": 85, "def": 40, "speed": 140},
501: {"name": "流浪汉", "atk": 70, "def": 30, "speed": 125}
}
var is_polymorphed: bool = false
var current_mob_vnum: int = 0
var remaining_time: float = 0.0
var original_race: int = 0 # 0: 战男, 1: 忍女, 2: 修男, 3: 萨女等
var poly_skill_level: int = 1 # 1 ~ 40 (1~19 普通, 20~29 M1~M10, 30~39 G1~G10, 40 P)
# 变身属性收益缓存
var bonus_atk: int = 0
var bonus_def: int = 0
var bonus_speed: int = 0
# 计算变身持续时间 (秒)
static func get_duration_by_skill(level: int) -> float:
if level >= 40: # P 级
return 2100.0 # 35 分钟
elif level >= 30: # G 级
return 1500.0 # 25 分钟
elif level >= 20: # M 级
return 1200.0 # 20 分钟
else:
return 600.0 + (level * 20.0) # 10~16 分钟
func can_polymorph(player_dead: bool, player_mounted: bool) -> Dictionary:
if player_dead:
return {"ok": false, "reason": "PLAYER_DEAD", "msg": "死亡状态下无法变身!"}
if player_mounted:
return {"ok": false, "reason": "PLAYER_MOUNTED", "msg": "骑马状态下无法变身,请先下马!"}
return {"ok": true}
# 使用变身球 (Item 70104) 变身
func use_polymorph_marble(item: Dictionary, player_dead: bool, player_mounted: bool, p_race: int = 0) -> Dictionary:
var check = can_polymorph(player_dead, player_mounted)
if not check["ok"]:
return check
var vnum: int = int(item.get("vnum", 0))
if vnum != ITEM_POLYMORPH_MARBLE:
return {"ok": false, "reason": "NOT_A_MARBLE", "msg": "这不是变身球!"}
var mob_vnum: int = int(item.get("mob_vnum", 0))
if mob_vnum == 0:
# 从 socket[0] 读
var sockets: Array = item.get("sockets", [])
if sockets.size() > 0:
mob_vnum = int(sockets[0])
if mob_vnum == 0 or not MOB_DATA.has(mob_vnum):
# 默认野猪 101
mob_vnum = 101
original_race = p_race
current_mob_vnum = mob_vnum
is_polymorphed = true
remaining_time = get_duration_by_skill(poly_skill_level)
# 结算属性加成 (char_item.cpp)
var mdata: Dictionary = MOB_DATA[mob_vnum]
var skill_multiplier: float = 1.0 + (float(poly_skill_level) * 0.025)
bonus_atk = int(round(float(mdata["atk"]) * skill_multiplier))
bonus_def = int(mdata["def"])
bonus_speed = int(mdata["speed"])
polymorph_started.emit(current_mob_vnum, remaining_time)
return {
"ok": true,
"mob_vnum": current_mob_vnum,
"mob_name": mdata["name"],
"duration": remaining_time,
"bonus_atk": bonus_atk,
"bonus_def": bonus_def,
"bonus_speed": bonus_speed
}
# 变身状态限制检查 1:1
func can_use_skill(skill_id: int) -> bool:
# 变身中禁止施放人物主动职业技能
if is_polymorphed:
# 变身术本身、被动或马术/公共技能除外,职业技能一律拦截
if skill_id != SKILL_POLYMORPH and skill_id < 100:
return false
return true
func can_change_weapon() -> bool:
# 变身中禁止切换武器 (equip_rules.gd:316)
return not is_polymorphed
func can_mount() -> bool:
# 变身中禁止骑马
return not is_polymorphed
# 逐帧更新倒计时
func update(dt: float) -> void:
if not is_polymorphed:
return
remaining_time -= dt
if remaining_time <= 0.0:
cancel_polymorph()
# 取消/解除变身
func cancel_polymorph() -> void:
if not is_polymorphed:
return
is_polymorphed = false
current_mob_vnum = 0
remaining_time = 0.0
bonus_atk = 0
bonus_def = 0
bonus_speed = 0
polymorph_ended.emit()
func on_player_death() -> void:
if is_polymorphed:
cancel_polymorph()
func get_state() -> Dictionary:
return {
"is_polymorphed": is_polymorphed,
"mob_vnum": current_mob_vnum,
"remaining_time": remaining_time,
"poly_skill_level": poly_skill_level,
"bonus_atk": bonus_atk,
"bonus_def": bonus_def,
"bonus_speed": bonus_speed
}