Add debug foundation
This commit is contained in:
283
godot/scripts/debug/commands/DebugCommandService.cs
Normal file
283
godot/scripts/debug/commands/DebugCommandService.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user