Files
spellforge/scripts/autoloads/wave_manager.gd
T

142 lines
5.5 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## WaveManager — 波次管理(Autoload: WaveManager
## S6 内容:Wave 1~20 完整配置(敌人原型/精英寻路/Mini Boss W8/Final Boss W20
## 设计参考:game_design.md §3.3W1-5 数值 / W6-10 机制 / W11-20 弹幕)、§4.5 Boss
extends Node
enum WaveState { IDLE = 0, BATTLE = 1, COMPLETE = 2 }
var current_wave: int = 0
var wave_state: int = WaveState.IDLE
var enemies_alive: int = 0
var _wave_start_time: float = 0.0
const ARENA_RADIUS: float = 700.0
const MAX_WAVE: int = 20
## Boss 追踪
var _boss_id: int = -1
var _boss_max_hp: float = 0.0
var _boss_wave: bool = false
## 波次配置表:纯数据驱动,唯一来源 data/waves.json(游戏设计器「波次」面板维护)
## 字段:count, hp, type, elite, gold, xp, boss(-1=无 / 4=MiniBoss / 5=Boss)
var WAVE_CONFIG: Array = []
const WAVES_JSON: String = "res://data/waves.json"
func _ready() -> void:
EventBus.subscribe(EventID.ENEMY_KILLED, _on_enemy_killed)
_load_json_waves()
## 数据驱动:从 data/waves.json 加载波次表
func _load_json_waves() -> void:
if not FileAccess.file_exists(WAVES_JSON):
push_error("WaveManager: 缺失 res://data/waves.json(无波次配置)")
return
var data = JSON.parse_string(FileAccess.get_file_as_string(WAVES_JSON))
if data is Array and not data.is_empty():
WAVE_CONFIG = data
else:
push_error("WaveManager: waves.json 格式错误(应为非空数组)")
## 开始指定波次(由 CombatManager 调用)
func start_wave(wave_num: int) -> void:
current_wave = wave_num
wave_state = WaveState.BATTLE
_wave_start_time = Time.get_ticks_msec() / 1000.0
_boss_id = -1
_boss_wave = false
var cfg: Dictionary = _get_wave_config(wave_num)
enemies_alive = int(cfg["count"])
_spawn_wave_enemies(cfg)
if int(cfg.get("boss", -1)) >= 0:
_spawn_boss(int(cfg["boss"]), wave_num)
EventBus.emit(EventID.GAME_STATE_CHANGED, {"from": "SHOP", "to": "BATTLE", "wave": wave_num})
print("[WaveManager] Wave %d 开始:%d 杂兵 + %d 精英%s" % [
wave_num, int(cfg["count"]), int(cfg.get("elite", 0)),
("Boss!)" if int(cfg.get("boss", -1)) >= 0 else "")])
func _spawn_wave_enemies(cfg: Dictionary) -> void:
# 难度乘子:怪物数量(挑战×1.2)、怪物血量(初学者×0.7)
var count: int = int(round(int(cfg["count"]) * SettingsManager.wave_count_mult()))
var hp: float = float(cfg["hp"]) * SettingsManager.enemy_hp_mult()
var etype: int = int(cfg.get("type", 0))
# enemies_alive 已按 cfg.count 初始化,按难度修正差额
enemies_alive += count - int(cfg["count"])
for _i in count:
EnemyManager.spawn_enemy(_ring_pos(), hp, etype)
# W15+ 精英:带 NavigationAgent2D 寻路(ADR-A4),更高血量
var elites: int = int(cfg.get("elite", 0))
for _e in elites:
EnemyManager.spawn_enemy(_ring_pos(), hp * 2.5, EnemyManager.Type.ELITE, true)
enemies_alive += elites # 精英计入存活
func _spawn_boss(boss_type: int, wave_num: int) -> void:
# Boss 基础血量由 SettingsManagerbalance.json 可调)提供 × 难度乘子
var hp: float = SettingsManager.get_boss_base_hp(boss_type) * SettingsManager.boss_hp_mult()
_boss_max_hp = hp
# Boss 在玩家上方稍远处入场
_boss_id = EnemyManager.spawn_enemy(Vector2(0.0, -ARENA_RADIUS * 0.8), hp, boss_type)
_boss_wave = true
enemies_alive += 1
EventBus.emit(EventID.BOSS_SPAWNED, {
"boss_id": _boss_id, "boss_type": boss_type, "wave": wave_num, "max_hp": hp})
BossManager.register_boss(_boss_id, boss_type, hp, wave_num)
print("[WaveManager] Boss 入场!type=%d hp=%.0f (wave %d)" % [boss_type, hp, wave_num])
func _ring_pos() -> Vector2:
var angle: float = randf() * TAU
var radius: float = randf_range(250.0, ARENA_RADIUS)
return Vector2(cos(angle) * radius, sin(angle) * radius)
func _on_enemy_killed(payload: Dictionary) -> void:
if wave_state != WaveState.BATTLE:
return
var killed_id: int = int(payload.get("entity_id", -1))
# Boss 波:Boss 死亡即通关本波(援军为附赠)
if _boss_wave and killed_id == _boss_id:
_complete_wave()
return
enemies_alive -= 1
if enemies_alive <= 0 and not _boss_wave:
_complete_wave()
func _complete_wave() -> void:
if wave_state != WaveState.BATTLE:
return
wave_state = WaveState.COMPLETE
_boss_wave = false
_boss_id = -1
var cfg: Dictionary = _get_wave_config(current_wave)
PlayerStats.gain_gold(int(cfg["gold"]))
PlayerStats.gain_xp(int(cfg["xp"]))
var elapsed: float = Time.get_ticks_msec() / 1000.0 - _wave_start_time
EventBus.emit(EventID.WAVE_COMPLETE, {"wave": current_wave, "elapsed_sec": elapsed})
print("[WaveManager] Wave %d 完成!奖励: %dG, %d XP" % [current_wave, int(cfg["gold"]), int(cfg["xp"])])
func _get_wave_config(wave_num: int) -> Dictionary:
var idx: int = clamp(wave_num - 1, 0, WAVE_CONFIG.size() - 1)
var base: Dictionary = WAVE_CONFIG[idx].duplicate()
# 超过 W20Endless):数值膨胀
if wave_num > MAX_WAVE:
var extra: int = wave_num - MAX_WAVE
base["count"] = int(base["count"]) + extra * 6
base["hp"] = float(base["hp"]) * pow(1.12, extra)
base["gold"] = int(base["gold"]) + extra * 8
base["xp"] = int(base["xp"]) + extra * 12
base["boss"] = -1 # Endless 不重复终 Boss
return base
func get_wave_count(wave_num: int) -> int:
return int(_get_wave_config(wave_num)["count"])
func is_boss_wave(wave_num: int) -> bool:
return int(_get_wave_config(wave_num).get("boss", -1)) >= 0
func reset() -> void:
current_wave = 0
wave_state = WaveState.IDLE
enemies_alive = 0
_boss_id = -1
_boss_wave = false