# classic_pet_system.gd —— Metin2 40250 经典封印宠物跟随与守护系统 1:1 # 对照 40250 服务端 PetSystem.cpp, PetSystem.h, questlua_pet.cpp class_name ClassicPetSystem extends RefCounted signal pet_summoned(mob_vnum: int, pet_name: String, buffs: Dictionary) signal pet_unsummoned(mob_vnum: int, pet_name: String) signal pet_moved(current_pos: Vector2, is_teleport: bool) # 40250 官方经典宠物召唤 Mob Vnum const MOB_BABY_BOAR := 34001 # 幼野猪 const MOB_BABY_WOLF := 34002 # 幼狼 const MOB_BABY_TIGER := 34003 # 幼虎 const MOB_BABY_LION := 34004 # 幼狮 const MOB_BABY_FIREDRAKE := 34005 # 幼火龙 const MOB_ICE_PHOENIX := 34006 # 冰凤凰 const MOB_BABY_AZRAEL := 34009 # 幼小阿兹瑞尔 # 40250 官方经典封印物品 Vnum const SEAL_BABY_BOAR := 53001 # 幼野猪封印 const SEAL_BABY_FIREDRAKE := 53002 # 幼火龙封印 const SEAL_ICE_PHOENIX := 53003 # 冰凤凰封印 const SEAL_BABY_LION := 53004 # 幼狮封印 const SEAL_BABY_AZRAEL := 53005 # 幼小阿兹瑞尔封印 # 40250 AI 跟随与瞬移距离参数 const MIN_FOLLOW_DIST := 2.0 # 最小跟随停步距离 (米) const MAX_TELEPORT_DIST := 15.0 # 最大跟随超距,超过直接瞬移到身边 (米) const FOLLOW_SPEED := 6.0 # 跟随移动速度 # 40250 宠物封印配置表 const PET_SEAL_CONFIGS: Dictionary = { SEAL_BABY_BOAR: { "mob_vnum": MOB_BABY_BOAR, "name": "幼野猪", "buffs": {"monster_damage_pct": 15}, "dungeon_only": false }, SEAL_BABY_FIREDRAKE: { "mob_vnum": MOB_BABY_FIREDRAKE, "name": "幼火龙", "buffs": {"max_hp": 1500, "attack_pct": 15}, "dungeon_only": false }, SEAL_ICE_PHOENIX: { "mob_vnum": MOB_ICE_PHOENIX, "name": "冰凤凰", "buffs": {"max_hp": 1500, "defense_pct": 15}, "dungeon_only": false }, SEAL_BABY_LION: { "mob_vnum": MOB_BABY_LION, "name": "幼狮", "buffs": {"crit_pct": 10}, "dungeon_only": false }, SEAL_BABY_AZRAEL: { "mob_vnum": MOB_BABY_AZRAEL, "name": "小阿兹瑞尔", "buffs": {"dungeon_attack_pct": 20, "max_hp": 1000}, "dungeon_only": true # 40250 源码判定:若不在地牢中,专属地牢增益不生效 } } # 当前宠物出战状态 var is_summoned: bool = false var active_mob_vnum: int = 0 var active_seal_vnum: int = 0 var active_pet_name: String = "" var active_buffs: Dictionary = {} var pet_pos: Vector2 = Vector2.ZERO # 检查物品是否为 40250 宠物封印 static func is_pet_seal(vnum: int) -> bool: return PET_SEAL_CONFIGS.has(vnum) # 获取宠物封印配置 static func get_pet_config_by_seal(seal_vnum: int) -> Dictionary: return PET_SEAL_CONFIGS.get(seal_vnum, {}).duplicate(true) # 召唤经典封印宠物 (40250 CPetSystem::Summon) func summon_pet(seal_item: Dictionary, player_stats: Dictionary, is_in_dungeon: bool = false, owner_pos: Vector2 = Vector2.ZERO) -> Dictionary: var seal_vnum := int(seal_item.get("vnum", 0)) if not PET_SEAL_CONFIGS.has(seal_vnum): return {"ok": false, "reason": "NOT_A_PET_SEAL", "msg": "该物品不是有效的宠物封印!"} # 若已有宠物出战,先解散旧宠物 (40250 规则:同时只出战一只) if is_summoned: unsummon_pet(player_stats) var conf: Dictionary = PET_SEAL_CONFIGS[seal_vnum] active_mob_vnum = conf["mob_vnum"] active_seal_vnum = seal_vnum active_pet_name = conf["name"] pet_pos = owner_pos + Vector2(1.5, 0.0) # 出生在身侧 # 计算注入的 Buff (40250 GiveBuff 规则) var buffs_to_apply: Dictionary = {} var base_buffs: Dictionary = conf.get("buffs", {}) var is_dungeon_only: bool = bool(conf.get("dungeon_only", false)) for stat_key in base_buffs.keys(): var val = base_buffs[stat_key] # 40250 特殊判定:小阿兹瑞尔等特定宠物仅在地牢中激活特定地牢 Buff if stat_key == "dungeon_attack_pct": if is_in_dungeon: buffs_to_apply[stat_key] = val else: buffs_to_apply[stat_key] = val # 应用属性加成至玩家 _apply_buffs_to_player(buffs_to_apply, player_stats, 1) active_buffs = buffs_to_apply is_summoned = true pet_summoned.emit(active_mob_vnum, active_pet_name, active_buffs) return { "ok": true, "mob_vnum": active_mob_vnum, "pet_name": active_pet_name, "buffs": active_buffs, "pos": pet_pos, "msg": "成功召唤宠物【%s】!守护属性已激活。" % active_pet_name } # 召回/解散宠物 (40250 CPetSystem::Unsummon / ClearBuff) func unsummon_pet(player_stats: Dictionary) -> Dictionary: if not is_summoned: return {"ok": false, "reason": "NO_PET_SUMMONED", "msg": "当前没有出战的宠物!"} # 清除施加在玩家身上的属性 _apply_buffs_to_player(active_buffs, player_stats, -1) var old_mob := active_mob_vnum var old_name := active_pet_name is_summoned = false active_mob_vnum = 0 active_seal_vnum = 0 active_pet_name = "" active_buffs.clear() pet_pos = Vector2.ZERO pet_unsummoned.emit(old_mob, old_name) return { "ok": true, "mob_vnum": old_mob, "pet_name": old_name, "msg": "宠物【%s】已召回,守护属性已解除。" % old_name } # 跟随 AI 逻辑步进 (40250 CPetActor::_UpdateFollowAI) func update_follow_ai(owner_pos: Vector2, delta: float) -> Dictionary: if not is_summoned: return {"action": "none"} var dist := pet_pos.distance_to(owner_pos) # 超过最大瞬移距离,直接瞬移回主人身边 if dist > MAX_TELEPORT_DIST: pet_pos = owner_pos + Vector2(1.5, 0.0) pet_moved.emit(pet_pos, true) return {"action": "teleport", "pos": pet_pos, "dist": dist} # 超过最小跟随距离,平滑朝主人位移 if dist > MIN_FOLLOW_DIST: var move_dist: float = minf(FOLLOW_SPEED * delta, dist - MIN_FOLLOW_DIST) pet_pos = pet_pos.move_toward(owner_pos, move_dist) pet_moved.emit(pet_pos, false) return {"action": "follow", "pos": pet_pos, "dist": pet_pos.distance_to(owner_pos)} # 处于舒适距离,保持待机 return {"action": "idle", "pos": pet_pos, "dist": dist} # 属性加成注入或反向扣减 func _apply_buffs_to_player(buffs: Dictionary, player_stats: Dictionary, factor: int) -> void: for stat_key in buffs.keys(): var val = int(buffs[stat_key]) * factor var cur = int(player_stats.get(stat_key, 0)) player_stats[stat_key] = cur + val # 序列化 func serialize() -> Dictionary: return { "is_summoned": is_summoned, "active_mob_vnum": active_mob_vnum, "active_seal_vnum": active_seal_vnum, "active_pet_name": active_pet_name, "active_buffs": active_buffs.duplicate(true), "pos_x": pet_pos.x, "pos_y": pet_pos.y } # 反序列化 func deserialize(data: Dictionary) -> void: is_summoned = bool(data.get("is_summoned", false)) active_mob_vnum = int(data.get("active_mob_vnum", 0)) active_seal_vnum = int(data.get("active_seal_vnum", 0)) active_pet_name = str(data.get("active_pet_name", "")) active_buffs = data.get("active_buffs", {}).duplicate(true) pet_pos = Vector2(float(data.get("pos_x", 0.0)), float(data.get("pos_y", 0.0)))