saumwacht-godot/scripts/autoload/wave_manager.gd
Martin Stickelbroeck c2b447610c Baseline: Saumwacht 0.15.0 (Phase 20A) vor dem Umbau zu Champions of Luneraria
Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern
unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore
unversioniert; letztere sind unter ../backups gesichert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:00:36 +02:00

152 lines
4.5 KiB
GDScript

extends Node
## Single authoritative phase coordinator; the presentation never ticks combat.
signal updated
signal battle_started
signal wave_settled(reports: Array)
const AI_SCRIPT = preload("res://scripts/ai/simple_ai.gd")
const BUILD_FIRST := 45.0
const BUILD_DURATION := 30.0
const RESULTS_DURATION := 4.0
var battle: Node3D
var remaining: float = 0.0
var ai = AI_SCRIPT.new()
var _public_snapshots: Array = [{}, {}]
var _wave_core_damage: Array = [0, 0]
func attach_battle(controller: Node3D) -> void:
battle = controller
func start_match() -> void:
if not is_instance_valid(battle):
push_error("WaveManager needs an attached BattleController.")
return
GameState.reset_match()
EconomyManager.reset_match()
_public_snapshots = [EconomyManager.get_team(0), EconomyManager.get_team(1)]
GameState.wave = 1
_open_build()
func _open_build() -> void:
EconomyManager.open_build(GameState.wave)
GameState.set_phase(GameState.Phase.BUILD)
remaining = BUILD_FIRST if GameState.wave == 1 else BUILD_DURATION
# The rival uses the previous completed formation, never a live player queue.
ai.plan(1, GameState.wave, _public_snapshots[0], GameState.last_reports[1], GameState.ai_level)
battle.show_formation(_rosters())
updated.emit()
func _rosters() -> Array:
return [EconomyManager.get_team(0).roster, EconomyManager.get_team(1).roster]
func ready_for_battle() -> void:
if GameState.phase == GameState.Phase.RESULTS:
_next_wave()
return
if GameState.phase != GameState.Phase.BUILD:
return
_public_snapshots = [EconomyManager.get_team(0), EconomyManager.get_team(1)]
var outgoing: Array = EconomyManager.freeze(GameState.wave)
var attackers: Array = [
DataCatalog.get_attackers(GameState.wave),
DataCatalog.get_attackers(GameState.wave)
]
for sender in range(2):
for send_id in outgoing[sender]:
attackers[1 - sender].append(DataCatalog.get_send(str(send_id), GameState.wave))
_wave_core_damage = [0, 0]
battle.begin_battle(_rosters(), attackers, GameState.wave)
remaining = 90.0
GameState.set_phase(GameState.Phase.COMBAT)
battle_started.emit()
updated.emit()
func _physics_process(delta: float) -> void:
if GameState.paused:
return
# 2x repeats the same fixed step rather than doubling its integration delta.
for _substep in range(GameState.speed):
advance(delta)
func advance(delta: float) -> void:
if delta <= 0.0 or GameState.paused:
return
match GameState.phase:
GameState.Phase.BUILD:
if not GameState.timed_build:
return
remaining = maxf(0.0, remaining - delta)
if remaining <= 0.0:
ready_for_battle()
GameState.Phase.COMBAT:
var report: Dictionary = battle.step(delta)
remaining = maxf(0.0, 90.0 - float(report.elapsed))
var leak_damage: Array = report.leak_damage
if leak_damage[0] > 0 or leak_damage[1] > 0:
for team in range(2):
_wave_core_damage[team] += int(leak_damage[team])
GameState.apply_core_damage(leak_damage)
for lane: int in range(2):
if leak_damage[lane] > 0:
Soundscape.play("leak", lane)
var winner: int = GameState.decide_winner(false)
if winner != GameState.ONGOING:
_finish_match(winner)
elif report.finished:
_settle(report)
GameState.Phase.RESULTS:
if not GameState.timed_build:
return
remaining = maxf(0.0, remaining - delta)
if remaining <= 0.0:
_next_wave()
func _settle(combat_report: Dictionary) -> void:
var reports: Array = []
for team in range(2):
var report: Dictionary = EconomyManager.settle(
team, GameState.wave, int(combat_report.bounties[team]))
report["leaks"] = combat_report.leaks[team]
report["core_damage"] = _wave_core_damage[team]
report["combat_seconds"] = combat_report.elapsed
report["natural_count"] = DataCatalog.get_attackers(GameState.wave).size()
report["natural_leaks"] = combat_report.get("natural_leaks", combat_report.leaks)[team]
report["front_lifetime"] = combat_report.get("front_lifetime", [90.0, 90.0])[team]
reports.append(report)
GameState.last_reports = reports
wave_settled.emit(reports)
if GameState.wave >= DataCatalog.wave_count():
_finish_match(GameState.decide_winner(true))
else:
remaining = RESULTS_DURATION
GameState.set_phase(GameState.Phase.RESULTS)
updated.emit()
func _next_wave() -> void:
if GameState.phase != GameState.Phase.RESULTS:
return
GameState.wave += 1
_open_build()
func _finish_match(winner: int) -> void:
GameState.outcome = winner
GameState.paused = false
remaining = 0.0
GameState.set_phase(GameState.Phase.GAME_OVER)
updated.emit()
func rival_snapshot() -> Dictionary:
return _public_snapshots[1].duplicate(true)