Files
zfxaction26_1/godot/scripts/debug/commands/DebugCommandService.cs
2026-04-21 21:16:30 +02:00

283 lines
11 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using SideScrollerGame.Content;
namespace SideScrollerGame.Debug.Commands;
public sealed class DebugCommandService
{
public DebugCommandService(ContentRegistry registry, int seed)
{
m_Registry = registry;
State = new DebugRuntimeState(FindDefaultDifficultyId(registry), seed);
}
public DebugCommandResult Execute(DebugCommandId commandId, string? argument = null)
{
DebugCommandResult result = commandId switch
{
DebugCommandId.ToggleOverlay => ToggleOverlay(),
DebugCommandId.Pause => SetPaused(commandId, true),
DebugCommandId.Resume => SetPaused(commandId, false),
DebugCommandId.TogglePause => SetPaused(commandId, !State.IsPaused),
DebugCommandId.FrameStep => FrameStep(),
DebugCommandId.SetTimeScale => SetTimeScale(argument),
DebugCommandId.ReloadScene => ReloadScene(),
DebugCommandId.RestartMission => RestartMission(),
DebugCommandId.SetDifficulty => SetDifficulty(argument),
DebugCommandId.SetSeed => SetSeed(argument),
DebugCommandId.SpawnActor => SpawnActor(argument),
DebugCommandId.JumpToMarker => JumpToMarker(argument),
DebugCommandId.ToggleCollisionShapes => ToggleFlag(commandId, nameof(State.ShowCollisionShapes)),
DebugCommandId.ToggleGameplayBounds => ToggleFlag(commandId, nameof(State.ShowGameplayBounds)),
DebugCommandId.ToggleInvulnerability => ToggleFlag(commandId, nameof(State.Invulnerable)),
DebugCommandId.ToggleInfiniteSpecialAmmo => ToggleFlag(commandId, nameof(State.InfiniteSpecialAmmo)),
DebugCommandId.ToggleNoEnemyFire => ToggleFlag(commandId, nameof(State.NoEnemyFire)),
_ => DebugCommandResult.Failure(commandId, $"Unsupported debug command '{commandId}'.", argument)
};
CommandExecuted?.Invoke(result);
return result;
}
public void RegisterSpawnHandler(Func<string, DebugCommandResult> handler)
{
m_SpawnHandler = handler;
}
public void RegisterTimelineJumpHandler(Func<string, DebugCommandResult> handler)
{
m_TimelineJumpHandler = handler;
}
public void RegisterReloadHandler(Func<DebugCommandResult> handler)
{
m_ReloadHandler = handler;
}
public void RegisterRestartHandler(Func<DebugCommandResult> handler)
{
m_RestartHandler = handler;
}
public DebugRuntimeState State { get; }
public event Action<DebugRuntimeState>? StateChanged;
public event Action<DebugCommandResult>? CommandExecuted;
private DebugCommandResult ToggleOverlay()
{
State.OverlayVisible = !State.OverlayVisible;
NotifyStateChanged();
return DebugCommandResult.Success(DebugCommandId.ToggleOverlay, $"Overlay visible: {State.OverlayVisible}");
}
private DebugCommandResult SetPaused(DebugCommandId commandId, bool isPaused)
{
State.IsPaused = isPaused;
NotifyStateChanged();
return DebugCommandResult.Success(commandId, State.IsPaused ? "Paused" : "Resumed");
}
private DebugCommandResult FrameStep()
{
State.IsPaused = true;
State.FrameStepRequestCount++;
NotifyStateChanged();
return DebugCommandResult.Success(DebugCommandId.FrameStep, "Frame step requested");
}
private DebugCommandResult SetTimeScale(string? argument)
{
if (!double.TryParse(argument, NumberStyles.Float, CultureInfo.InvariantCulture, out double timeScale) || !s_SupportedTimeScales.Contains(timeScale))
{
return DebugCommandResult.Failure(DebugCommandId.SetTimeScale, $"Unsupported time scale '{argument}'.", argument);
}
State.TimeScale = timeScale;
NotifyStateChanged();
return DebugCommandResult.Success(DebugCommandId.SetTimeScale, $"Time scale set to {timeScale.ToString(CultureInfo.InvariantCulture)}", argument);
}
private DebugCommandResult ReloadScene()
{
State.ReloadSceneRequestCount++;
DebugCommandResult result = m_ReloadHandler?.Invoke() ?? DebugCommandResult.Success(DebugCommandId.ReloadScene, "Reload scene requested");
NotifyStateChanged();
return result.Succeeded ? DebugCommandResult.Success(DebugCommandId.ReloadScene, result.Message) : result;
}
private DebugCommandResult RestartMission()
{
State.RestartMissionRequestCount++;
State.CurrentMarkerId = string.Empty;
State.LastSpawnedActorId = string.Empty;
State.SpawnedActorCount = 0;
DebugCommandResult result = m_RestartHandler?.Invoke() ?? DebugCommandResult.Success(DebugCommandId.RestartMission, "Restart mission requested");
NotifyStateChanged();
return result.Succeeded ? DebugCommandResult.Success(DebugCommandId.RestartMission, result.Message) : result;
}
private DebugCommandResult SetDifficulty(string? argument)
{
string difficultyId = RequireArgument(argument);
if (difficultyId.Length == 0)
{
return DebugCommandResult.Failure(DebugCommandId.SetDifficulty, "Missing difficulty id.", argument);
}
if (!m_Registry.Difficulties.ContainsKey(difficultyId))
{
return DebugCommandResult.Failure(DebugCommandId.SetDifficulty, $"Unknown difficulty '{difficultyId}'.", difficultyId);
}
State.ActiveDifficultyId = difficultyId;
NotifyStateChanged();
return DebugCommandResult.Success(DebugCommandId.SetDifficulty, $"Difficulty set to {difficultyId}", difficultyId);
}
private DebugCommandResult SetSeed(string? argument)
{
if (!int.TryParse(argument, NumberStyles.Integer, CultureInfo.InvariantCulture, out int seed))
{
return DebugCommandResult.Failure(DebugCommandId.SetSeed, $"Invalid seed '{argument}'.", argument);
}
State.Seed = seed;
NotifyStateChanged();
return DebugCommandResult.Success(DebugCommandId.SetSeed, $"Seed set to {seed}", argument);
}
private DebugCommandResult SpawnActor(string? argument)
{
string actorId = RequireArgument(argument);
if (actorId.Length == 0)
{
return DebugCommandResult.Failure(DebugCommandId.SpawnActor, "Missing actor id.", argument);
}
if (!m_Registry.EnemyTypes.ContainsKey(actorId))
{
return DebugCommandResult.Failure(DebugCommandId.SpawnActor, $"Unknown actor '{actorId}'.", actorId);
}
DebugCommandResult result = m_SpawnHandler?.Invoke(actorId) ?? DebugCommandResult.Success(DebugCommandId.SpawnActor, $"Spawn actor requested: {actorId}", actorId);
if (!result.Succeeded)
{
return result;
}
State.LastSpawnedActorId = actorId;
State.SpawnedActorCount++;
NotifyStateChanged();
return DebugCommandResult.Success(DebugCommandId.SpawnActor, result.Message, actorId);
}
private DebugCommandResult JumpToMarker(string? argument)
{
string markerId = RequireArgument(argument);
if (markerId.Length == 0)
{
return DebugCommandResult.Failure(DebugCommandId.JumpToMarker, "Missing marker id.", argument);
}
if (!HasMissionMarker(markerId))
{
return DebugCommandResult.Failure(DebugCommandId.JumpToMarker, $"Unknown marker '{markerId}'.", markerId);
}
DebugCommandResult result = m_TimelineJumpHandler?.Invoke(markerId) ?? DebugCommandResult.Success(DebugCommandId.JumpToMarker, $"Jump marker requested: {markerId}", markerId);
if (!result.Succeeded)
{
return result;
}
State.CurrentMarkerId = markerId;
NotifyStateChanged();
return DebugCommandResult.Success(DebugCommandId.JumpToMarker, result.Message, markerId);
}
private DebugCommandResult ToggleFlag(DebugCommandId commandId, string propertyName)
{
string message = propertyName switch
{
nameof(State.ShowCollisionShapes) => ToggleShowCollisionShapes(),
nameof(State.ShowGameplayBounds) => ToggleShowGameplayBounds(),
nameof(State.Invulnerable) => ToggleInvulnerable(),
nameof(State.InfiniteSpecialAmmo) => ToggleInfiniteSpecialAmmo(),
nameof(State.NoEnemyFire) => ToggleNoEnemyFire(),
_ => $"Unsupported flag '{propertyName}'."
};
NotifyStateChanged();
return DebugCommandResult.Success(commandId, message);
}
private string ToggleShowCollisionShapes()
{
State.ShowCollisionShapes = !State.ShowCollisionShapes;
return $"Collision shapes: {State.ShowCollisionShapes}";
}
private string ToggleShowGameplayBounds()
{
State.ShowGameplayBounds = !State.ShowGameplayBounds;
return $"Gameplay bounds: {State.ShowGameplayBounds}";
}
private string ToggleInvulnerable()
{
State.Invulnerable = !State.Invulnerable;
return $"Invulnerable: {State.Invulnerable}";
}
private string ToggleInfiniteSpecialAmmo()
{
State.InfiniteSpecialAmmo = !State.InfiniteSpecialAmmo;
return $"Infinite special ammo: {State.InfiniteSpecialAmmo}";
}
private string ToggleNoEnemyFire()
{
State.NoEnemyFire = !State.NoEnemyFire;
return $"No enemy fire: {State.NoEnemyFire}";
}
private bool HasMissionMarker(string markerId)
{
return m_Registry.MissionDefinitions.Any(mission => mission.TimelineMarkers.Contains(markerId));
}
private void NotifyStateChanged()
{
StateChanged?.Invoke(State);
}
private static string RequireArgument(string? argument)
{
return string.IsNullOrWhiteSpace(argument) ? string.Empty : argument.Trim();
}
private static string FindDefaultDifficultyId(ContentRegistry registry)
{
if (registry.TryGetMission("mission.test", out var mission) && mission is not null && registry.Difficulties.ContainsKey(mission.DefaultDifficultyId))
{
return mission.DefaultDifficultyId;
}
return registry.DifficultyDefinitions.FirstOrDefault()?.Id ?? string.Empty;
}
private static readonly HashSet<double> s_SupportedTimeScales = [0.25, 0.5, 1.0, 2.0, 4.0];
private readonly ContentRegistry m_Registry;
private Func<string, DebugCommandResult>? m_SpawnHandler;
private Func<string, DebugCommandResult>? m_TimelineJumpHandler;
private Func<DebugCommandResult>? m_ReloadHandler;
private Func<DebugCommandResult>? m_RestartHandler;
}