283 lines
11 KiB
C#
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;
|
|
} |