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

154 lines
4.0 KiB
GDScript

# pet_companion_system.gd —— Metin2 40250 伴随宠物封印召唤与战力注入系统 1:1
# 对照 40250 服务端 PetSystem.cpp, char_pet.cpp
class_name PetCompanionSystem
extends RefCounted
signal pet_summoned(pet_name: String, vnum: int)
signal pet_dismissed(pet_name: String)
signal pet_state_changed(state: String)
const PET_TABLE: Dictionary = {
53001: {
"name": "火凤凰",
"mob_vnum": 34001,
"bonuses": {"max_hp": 1500, "monster_dmg_pct": 15},
"desc": "最大生命 +1500,对怪物伤害提升 15%"
},
53002: {
"name": "幼鹿",
"mob_vnum": 34002,
"bonuses": {"max_hp": 1000, "exp_pct": 15},
"desc": "最大生命 +1000,获得经验值增加 15%"
},
53003: {
"name": "冰凤凰",
"mob_vnum": 34003,
"bonuses": {"max_hp": 1500, "def_pct": 15},
"desc": "最大生命 +1500,物理防御提升 15%"
},
53005: {
"name": "小胖熊猫",
"mob_vnum": 34005,
"bonuses": {"max_hp": 1000, "critical_pct": 15},
"desc": "最大生命 +1000,致命一击几率提升 15%"
},
53006: {
"name": "幼狮",
"mob_vnum": 34006,
"bonuses": {"max_hp": 1500, "att_pct": 10},
"desc": "最大生命 +1500,物理攻击力提升 10%"
}
}
const FOLLOW_STOP_DIST := 2.0 # 停止距离
const FOLLOW_RUN_DIST := 3.5 # 开始跟随奔跑距离
const TELEPORT_DIST := 25.0 # 瞬间拉回距离
const PET_MOVE_SPEED := 4.5 # 移动速度 (m/s)
var is_summoned: bool = false
var current_seal_vnum: int = 0
var active_pet: Dictionary = {}
var pet_position: Vector3 = Vector3.ZERO
var pet_state: String = "wait" # "wait" 或 "run"
var remaining_time: float = 0.0
# 召唤 / 收回宠物 (右键点击宠物封印石)
func toggle_pet_seal(seal_item: Dictionary, player_pos: Vector3) -> Dictionary:
var vnum: int = int(seal_item.get("vnum", 0))
if not PET_TABLE.has(vnum):
return {"ok": false, "reason": "NOT_A_PET_SEAL", "msg": "这不是有效的宠物封印!"}
# 若当前已召唤同一只宠物,则收回
if is_summoned and current_seal_vnum == vnum:
var pet_name = active_pet.get("name", "宠物")
dismiss_pet()
return {
"ok": true,
"action": "dismiss",
"pet_name": pet_name,
"msg": "已将【%s】收回封印石。" % pet_name
}
# 若当前召唤了不同宠物,先收回
if is_summoned:
dismiss_pet()
# 召唤新宠物
var cfg: Dictionary = PET_TABLE[vnum]
current_seal_vnum = vnum
is_summoned = true
remaining_time = float(seal_item.get("duration", 86400.0)) # 默认 24 小时
pet_position = player_pos + Vector3(-1.5, 0.0, -1.5)
pet_state = "wait"
active_pet = {
"vnum": vnum,
"name": cfg["name"],
"mob_vnum": cfg["mob_vnum"],
"bonuses": cfg["bonuses"].duplicate()
}
pet_summoned.emit(cfg["name"], vnum)
return {
"ok": true,
"action": "summon",
"pet_name": cfg["name"],
"bonuses": cfg["bonuses"],
"msg": "伴随宠物【%s】已召唤出战!" % cfg["name"]
}
# 收回当前宠物
func dismiss_pet() -> void:
if not is_summoned:
return
var pname = active_pet.get("name", "")
is_summoned = false
current_seal_vnum = 0
active_pet.clear()
pet_state = "wait"
pet_dismissed.emit(pname)
# 获取当前宠物注入的战力属性
func get_active_bonuses() -> Dictionary:
if not is_summoned:
return {}
return active_pet.get("bonuses", {}).duplicate()
# 3D 伴随移动逻辑
func update_movement(player_pos: Vector3, dt: float) -> void:
if not is_summoned:
return
var dist: float = pet_position.distance_to(player_pos)
# 超远距离瞬间传回身边
if dist >= TELEPORT_DIST:
pet_position = player_pos + Vector3(-1.5, 0.0, -1.5)
pet_state = "wait"
pet_state_changed.emit("wait")
return
# 跟随靠近
if dist > FOLLOW_STOP_DIST:
var dir := (player_pos - pet_position).normalized()
pet_position += dir * PET_MOVE_SPEED * dt
if pet_state != "run":
pet_state = "run"
pet_state_changed.emit("run")
else:
if pet_state != "wait":
pet_state = "wait"
pet_state_changed.emit("wait")
# 逐帧倒计时衰减
func update(dt: float) -> void:
if not is_summoned:
return
remaining_time -= dt
if remaining_time <= 0.0:
dismiss_pet()