diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 8c2b38345..74f495afb 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -854,21 +854,12 @@ public class ActionGameManager : MonoBehaviour var scenario = PendingScenario; PendingScenario = null; var failures = new List(); - RunStart.Staged staged = null; - try + var staged = RunStart.Launch(() => RunSave.Replace(CultCache, () => { - staged = RunSave.Replace(CultCache, () => - { - RunStart.GenerateArena(ItemManager, Settings.ZoneSettings, Settings.PlanetSettings, CurrentGalaxy, scenario); - PopulateLevel(CurrentGalaxy.Entrance); - return RunStart.Stage(ItemManager, Zone, scenario, Settings.StartingHullName, Settings.TutorialGenerationSettings, failures); - }); - } - catch (Exception exception) - { - Debug.LogException(exception); - failures.Add(exception.Message); - } + RunStart.GenerateArena(ItemManager, Settings.ZoneSettings, Settings.PlanetSettings, CurrentGalaxy, scenario); + PopulateLevel(CurrentGalaxy.Entrance); + return RunStart.Stage(ItemManager, Zone, scenario, Settings.StartingHullName, Settings.TutorialGenerationSettings, failures); + }), failures, Debug.LogException); if (staged == null) { @@ -1348,6 +1339,8 @@ public class ActionGameManager : MonoBehaviour void Update() { + // No live run (a start that failed, a run that ended): the main menu is up, and nothing steps a zone or reads a player. + if (CurrentGalaxy == null) return; if(!_paused) { _time += Time.deltaTime; diff --git a/Assets/Scripts/ServerShared/RunStart.cs b/Assets/Scripts/ServerShared/RunStart.cs index cadd66bcc..4e6808859 100644 --- a/Assets/Scripts/ServerShared/RunStart.cs +++ b/Assets/Scripts/ServerShared/RunStart.cs @@ -30,6 +30,21 @@ public static class RunStart itemManager, zoneSettings, planetSettings, galaxy, galaxy.Entrance, galaxy.IsPrelude, scenario.Ambient); } + // Runs a start and answers whether it produced a run: null, with the reason in `failures`, when `start` throws or + // returns nothing. The caller shows the failure and clears its run; nothing past a null may run the game. + public static Staged Launch(Func start, List failures, Action log = null) + { + Staged staged = null; + try { staged = start(); } + catch (Exception exception) + { + log?.Invoke(exception); + failures.Add(exception.Message); + } + if (staged == null && failures.Count == 0) failures.Add("the start produced no run"); + return staged; + } + // Runs the scenario's Stage, then admits its player and everything it placed: each entity's stance set both ways // with the player, a pilot for each piloted one. Returns null, with every failure listed and the arena unchanged, // unless the whole scenario staged. diff --git a/tests/Aetheria.Shared.Tests/RunLaunchTests.cs b/tests/Aetheria.Shared.Tests/RunLaunchTests.cs new file mode 100644 index 000000000..1c8b0e380 --- /dev/null +++ b/tests/Aetheria.Shared.Tests/RunLaunchTests.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Xunit; + +// RunStart.Launch decides whether a start produced a run; the game scene halts on its answer (a failed start clears the +// galaxy, and a scene with no galaxy steps nothing). Kills: a start that throws or returns nothing reading as a run, a +// failure with no reason to show on the menu, a thrown exception that is neither logged nor listed, and a start that +// worked being reported failed. +public sealed class RunLaunchTests +{ + [Fact] + public void AStartThatThrowsIsNoRunAndSaysWhy() + { + var failures = new List(); + var logged = new List(); + var staged = RunStart.Launch(() => throw new InvalidOperationException("the arena would not generate"), failures, logged.Add); + Assert.Null(staged); + Assert.Equal(new[] { "the arena would not generate" }, failures); + Assert.Single(logged); + } + + [Fact] + public void AStartThatReturnsNothingIsNoRunAndKeepsItsOwnReasons() + { + var failures = new List(); + Assert.Null(RunStart.Launch(() => { failures.Add("player: the scenario places no player"); return null; }, failures)); + Assert.Equal(new[] { "player: the scenario places no player" }, failures); + } + + [Fact] + public void AStartThatReturnsNothingAndSaysNothingStillGivesAReason() + { + var failures = new List(); + Assert.Null(RunStart.Launch(() => null, failures)); + Assert.Single(failures); + } + + [Fact] + public void AStartThatStagedIsARunWithNoFailures() + { + var failures = new List(); + var made = new RunStart.Staged(); + Assert.Same(made, RunStart.Launch(() => made, failures)); + Assert.Empty(failures); + } +}