# world_boss_ai_system.gd —— Metin2 40250 野外世界首领动态巡逻与仇恨反击 AI 1:1 # 对照 40250 服务端 char_battle.cpp, battle.cpp, mob_manager.cpp, char_state.cpp class_name WorldBossAISystem extends RefCounted signal state_changed(old_state: String, new_state: String) signal target_acquired(target_id: String, distance: float) signal skill_cast(skill_name: String, damage: int, aoe_radius: float) signal boss_enraged(atk_multiplier: float) signal leash_reset_started() signal leash_reset_completed() const STATE_IDLE := "idle" const STATE_PATROL := "patrol" const STATE_CHASE := "chase" const STATE_ATTACK := "attack" const STATE_SKILL := "skill" const STATE_ENRAGED := "enraged" const STATE_LEASH_RETURN := "leash_return" const STATE_DEAD := "dead" const AGGRO_RADIUS: float = 18.0 # 索敌警戒范围 (米) const ATTACK_RANGE: float = 3.5 # 普通近战攻击距离 (米) const SKILL_RANGE: float = 9.0 # 首领大招攻击距离 (米) const LEASH_DISTANCE: float = 35.0 # 脱战最远距离 (米) const PATROL_RADIUS: float = 12.0 # 巡逻游荡半径 (米) const SKILL_COOLDOWN: float = 8.0 # 技能冷却时间 (秒) const ENRAGE_HP_RATIO: float = 0.30 # 30% 残血狂暴触发线 const BOSS_CONFIG: Dictionary = { 1901: { "name": "九尾妖狐", "level": 72, "max_hp": 280000, "atk": 850, "def": 450, "skill_name": "暴风雪呼啸", "skill_mult": 1.8, "skill_aoe": 8.0 }, 1304: { "name": "黄虎领主", "level": 75, "max_hp": 320000, "atk": 920, "def": 480, "skill_name": "神虎震地啸", "skill_mult": 2.0, "skill_aoe": 7.5 }, 2191: { "name": "巨龟仙人", "level": 67, "max_hp": 350000, "atk": 780, "def": 600, "skill_name": "地刺岩崩", "skill_mult": 1.6, "skill_aoe": 9.0 }, 2206: { "name": "烈焰之王", "level": 73, "max_hp": 300000, "atk": 980, "def": 420, "skill_name": "地狱火海", "skill_mult": 2.2, "skill_aoe": 8.5 } } var vnum: int = 0 var boss_name: String = "" var level: int = 1 var max_hp: int = 100000 var current_hp: int = 100000 var base_atk: int = 500 var base_def: int = 300 var skill_name: String = "" var skill_mult: float = 1.8 var skill_aoe: float = 8.0 var spawn_position: Vector3 = Vector3.ZERO var current_position: Vector3 = Vector3.ZERO var patrol_destination: Vector3 = Vector3.ZERO var current_state: String = STATE_IDLE var is_enraged: bool = false var skill_timer: float = 0.0 var patrol_wait_timer: float = 0.0 var current_target_id: String = "" var aggro_table: Dictionary = {} # target_id -> threat_points # 初始化世界首领 func init_boss(boss_vnum: int, pos: Vector3 = Vector3.ZERO) -> Dictionary: if not BOSS_CONFIG.has(boss_vnum): return {"ok": false, "reason": "INVALID_BOSS_VNUM", "msg": "未知的世界 Boss 代码!"} var cfg = BOSS_CONFIG[boss_vnum] vnum = boss_vnum boss_name = cfg["name"] level = cfg["level"] max_hp = cfg["max_hp"] current_hp = cfg["max_hp"] base_atk = cfg["atk"] base_def = cfg["def"] skill_name = cfg["skill_name"] skill_mult = cfg["skill_mult"] skill_aoe = cfg["skill_aoe"] spawn_position = pos current_position = pos patrol_destination = pos current_state = STATE_IDLE is_enraged = false skill_timer = 0.0 patrol_wait_timer = 2.0 current_target_id = "" aggro_table.clear() return { "ok": true, "vnum": vnum, "name": boss_name, "level": level, "hp": current_hp, "pos": current_position } # 改变当前状态 func _change_state(new_state: String) -> void: if current_state == new_state: return var old = current_state current_state = new_state state_changed.emit(old, new_state) # 核心 AI 帧心跳更新驱动 (对照 40250 char_state.cpp) func update_ai(delta: float, target_pos: Vector3, target_id: String = "player", is_target_alive: bool = true) -> Dictionary: if current_state == STATE_DEAD: return {"state": STATE_DEAD, "action": "none"} # 技能冷却计时 if skill_timer > 0.0: skill_timer = max(0.0, skill_timer - delta) # 1. 脱战判定:如果距离出生点超出 LEASH_DISTANCE,强制触发回拉 var dist_from_spawn = current_position.distance_to(spawn_position) if dist_from_spawn > LEASH_DISTANCE and current_state != STATE_LEASH_RETURN: _start_leash_return() return {"state": STATE_LEASH_RETURN, "action": "leashing", "msg": "超出脱战范围,首领无敌回满血并返回出生点!"} # 2. 回拉返回出生点过程 if current_state == STATE_LEASH_RETURN: var step = 12.0 * delta # 高速回跑 current_position = current_position.move_toward(spawn_position, step) if current_position.distance_to(spawn_position) < 0.5: current_position = spawn_position current_hp = max_hp aggro_table.clear() current_target_id = "" is_enraged = false _change_state(STATE_IDLE) leash_reset_completed.emit() return {"state": STATE_IDLE, "action": "reset_done"} return {"state": STATE_LEASH_RETURN, "action": "moving_home"} # 3. 仇恨目标存活与索敌检测 var dist_to_target = current_position.distance_to(target_pos) var has_aggro = is_target_alive and (dist_to_target <= AGGRO_RADIUS or aggro_table.has(target_id)) if not has_aggro or not is_target_alive: # 失去仇恨或目标死亡 -> 回归巡逻/待机 current_target_id = "" _handle_patrol_idle(delta) return {"state": current_state, "action": "wandering"} # 确认锁定仇恨目标 if current_target_id != target_id: current_target_id = target_id target_acquired.emit(target_id, dist_to_target) # 4. 处于战斗追击 / 攻击决策 if dist_to_target > SKILL_RANGE: # 距离过远,快速追赶 _change_state(STATE_CHASE) var move_speed = 6.0 if not is_enraged else 7.8 current_position = current_position.move_toward(target_pos, move_speed * delta) return {"state": STATE_CHASE, "action": "chasing", "dist": dist_to_target} # 5. 技能大招释放判定 if skill_timer <= 0.0 and dist_to_target <= SKILL_RANGE: skill_timer = SKILL_COOLDOWN _change_state(STATE_SKILL) var dmg_mult = skill_mult * (1.5 if is_enraged else 1.0) var skill_dmg = int(float(base_atk) * dmg_mult) skill_cast.emit(skill_name, skill_dmg, skill_aoe) return { "state": STATE_SKILL, "action": "skill", "skill_name": skill_name, "damage": skill_dmg, "aoe": skill_aoe } # 6. 普通攻击判定 if dist_to_target <= ATTACK_RANGE: _change_state(STATE_ATTACK) var atk_dmg = int(float(base_atk) * (1.5 if is_enraged else 1.0)) return { "state": STATE_ATTACK, "action": "basic_attack", "damage": atk_dmg } else: # 在技能范围内但未到普攻范围 -> 继续靠近 _change_state(STATE_CHASE) var move_speed = 5.0 if not is_enraged else 6.5 current_position = current_position.move_toward(target_pos, move_speed * delta) return {"state": STATE_CHASE, "action": "closing_in", "dist": dist_to_target} # 待机与路点漫步巡逻处理 func _handle_patrol_idle(delta: float) -> void: if current_state != STATE_PATROL: _change_state(STATE_PATROL) patrol_wait_timer -= delta if patrol_wait_timer <= 0.0: # 选取出生点周围随机路点 var angle = randf_range(0, TAU) var r = randf_range(2.0, PATROL_RADIUS) patrol_destination = spawn_position + Vector3(cos(angle) * r, 0.0, sin(angle) * r) patrol_wait_timer = randf_range(4.0, 7.0) if current_position.distance_to(patrol_destination) > 0.5: current_position = current_position.move_toward(patrol_destination, 2.5 * delta) # 开始拉回重置 func _start_leash_return() -> void: _change_state(STATE_LEASH_RETURN) leash_reset_started.emit() # 受到攻击与伤害结算 func receive_damage(raw_dmg: int, attacker_id: String = "player") -> Dictionary: if current_state == STATE_DEAD: return {"ok": false, "reason": "ALREADY_DEAD"} if current_state == STATE_LEASH_RETURN: # 脱战回跑期间无敌 return {"ok": true, "immune": true, "net_dmg": 0, "msg": "首领脱战回归中,处于神圣免疫状态!"} var net_dmg = max(1, raw_dmg - base_def) current_hp = max(0, current_hp - net_dmg) # 累计仇恨度 aggro_table[attacker_id] = aggro_table.get(attacker_id, 0) + net_dmg # 狂暴判定:HP <= 30% var hp_ratio = float(current_hp) / float(max_hp) if hp_ratio <= ENRAGE_HP_RATIO and not is_enraged: is_enraged = true boss_enraged.emit(1.5) # 死亡判定 if current_hp <= 0: _change_state(STATE_DEAD) return { "ok": true, "dead": true, "net_dmg": net_dmg, "remaining_hp": 0, "msg": "【%s】发出震天哀鸣,轰然倒地!" % boss_name } return { "ok": true, "dead": false, "net_dmg": net_dmg, "remaining_hp": current_hp, "hp_ratio": hp_ratio, "is_enraged": is_enraged } # 获取当前首领状态摘要 func get_boss_status() -> Dictionary: return { "vnum": vnum, "name": boss_name, "level": level, "state": current_state, "current_hp": current_hp, "max_hp": max_hp, "is_enraged": is_enraged, "pos": current_position, "spawn_pos": spawn_position }