diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index c1ee9cac4..de34c830d 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -106,7 +106,6 @@ public class ActionGameManager : MonoBehaviour } public static Galaxy CurrentGalaxy; - public static bool IsTutorial; public GameSettings Settings; //public string StarterShipTemplate = "Longinus"; @@ -250,7 +249,7 @@ public class ActionGameManager : MonoBehaviour { if (CurrentGalaxy != null) { - var (game, zones) = RunSave.Capture(CultCache, CurrentGalaxy, Zone, DockedEntity ?? CurrentEntity, IsTutorial, + var (game, zones) = RunSave.Capture(CultCache, CurrentGalaxy, Zone, DockedEntity ?? CurrentEntity, _actionBarSlots.Select(s => s.Save()).ToArray()); RunSave.Commit(CultCache, game, zones, ItemManager.Lots); } @@ -578,19 +577,7 @@ public class ActionGameManager : MonoBehaviour { var nearestFaction = CurrentGalaxy.Factions.MinBy(f => CurrentGalaxy.HomeZones[f].Distance[Zone.GalaxyZone]); - var loadoutGenerator = IsTutorial ? new LoadoutGenerator( - ref ItemManager.Random, - ItemManager, - CurrentGalaxy, - Zone.GalaxyZone, - nearestFaction, - .5f) : new LoadoutGenerator( - ref ItemManager.Random, - ItemManager, - CurrentGalaxy, - Zone.GalaxyZone, - nearestFaction, - .5f); + var loadoutGenerator = new LoadoutGenerator(ref ItemManager.Random, ItemManager, CurrentGalaxy, Zone.GalaxyZone, nearestFaction, .5f); var turret = EntitySerializer.Unpack(ItemManager, Zone, loadoutGenerator.GenerateTurretLoadout()); turret.Position.xz = _currentEntity.Position.xz + @@ -728,7 +715,7 @@ public class ActionGameManager : MonoBehaviour Settings.ZoneSettings, CurrentGalaxy, galaxyZone, - IsTutorial + CurrentGalaxy.IsPrelude ); galaxyZone.Contents = new Zone(ItemManager, Settings.PlanetSettings, galaxyZone.PackedContents, galaxyZone, CurrentGalaxy); } @@ -815,7 +802,7 @@ public class ActionGameManager : MonoBehaviour PopulateLevel(CurrentGalaxy.Entrance); var failures = new List(); var staged = RunStart.Stage(ItemManager, Zone, null, Settings.StartingHullName, - IsTutorial ? CurrentGalaxy.ResolveFaction(Settings.TutorialGenerationSettings.ProtagonistFaction) : null, failures) + CurrentGalaxy.IsPrelude ? CurrentGalaxy.ResolveFaction(Settings.TutorialGenerationSettings.ProtagonistFaction) : null, failures) ?? throw new InvalidOperationException($"The new run could not stage: {string.Join("; ", failures)}"); BindToEntity(staged.Player); } diff --git a/Assets/Scripts/ServerShared/PlayerSettings.cs b/Assets/Scripts/ServerShared/PlayerSettings.cs index d3c809fed..2566dcf59 100644 --- a/Assets/Scripts/ServerShared/PlayerSettings.cs +++ b/Assets/Scripts/ServerShared/PlayerSettings.cs @@ -10,7 +10,7 @@ using static CultMath.math; public class PlayerSettings { [Key(0)] public string Name = "Anonymous"; - [Key(2)] public bool TutorialPassed; + // Keys 1 and 2 are retired; an older player.cc that still carries them loads, and they are dropped. [Key(3)] public Dictionary HashedStoryFiles = new Dictionary(); [Key(4)] public PlayerGameplaySettings GameplaySettings = new PlayerGameplaySettings(); [Key(5)] public PlayerInputSettings InputSettings = new PlayerInputSettings(); diff --git a/Assets/Scripts/ServerShared/SavedGame.cs b/Assets/Scripts/ServerShared/SavedGame.cs index 71bd625af..a674df81d 100644 --- a/Assets/Scripts/ServerShared/SavedGame.cs +++ b/Assets/Scripts/ServerShared/SavedGame.cs @@ -55,7 +55,7 @@ public static class RunSave { // The live run as plain documents. Writes nothing. public static (SavedGame Game, SavedZone[] Zones) Capture(CultCache cache, Galaxy galaxy, Zone currentZone, - Entity currentEntity, bool isTutorial, SavedActionBarBinding[] actionBar) + Entity currentEntity, SavedActionBarBinding[] actionBar) { var factions = galaxy.HomeZones.Keys.ToArray(); var game = new SavedGame @@ -74,7 +74,7 @@ public static class RunSave CurrentZoneEntity = currentZone.Entities.IndexOf(currentEntity), Entrance = Array.IndexOf(galaxy.Zones, galaxy.Entrance), Exit = Array.IndexOf(galaxy.Zones, galaxy.Exit), - IsTutorial = isTutorial, + IsTutorial = galaxy.IsPrelude, ActionBarBindings = actionBar }; diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index 859d17c95..9fcff8f14 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -111,7 +111,6 @@ public class MainMenu : MonoBehaviour { try { RunSave.RequireDesigns(cache, saved); } catch (InvalidOperationException error) { Refuse("Cannot continue this run", error.Message); return; } - ActionGameManager.IsTutorial = saved.IsTutorial; ActionGameManager.CurrentGalaxy = new Galaxy(cache, saved, Debug.Log); EnterGame(); }); @@ -130,29 +129,6 @@ public class MainMenu : MonoBehaviour Dialog.AddProperty(() => generatorState); Dialog.Show(); - if (ActionGameManager.PlayerSettings.TutorialPassed) - { - var backgroundSettings = MessagePackSerializer.Deserialize( - MessagePackSerializer.Serialize(Settings.SectorBackgroundSettings)); - backgroundSettings.NoisePosition = Random.value * 1000; - ActionGameManager.IsTutorial = false; - Task.Run(() => - { - var sector = new Galaxy( - Settings.SectorGenerationSettings, - backgroundSettings, - Settings.NameGeneratorSettings, - ActionGameManager.CultCache, - Debug.Log, - setState); - Observable.NextFrame().Subscribe(_ => - { - ActionGameManager.CurrentGalaxy = sector; - EnterGame(); - }); - }).ContinueWith(t => Debug.LogException(t.Exception), TaskContinuationOptions.OnlyOnFaulted); - } - else { var backgroundSettings = MessagePackSerializer.Deserialize( MessagePackSerializer.Serialize(Settings.TutorialBackgroundSettings)); @@ -163,7 +139,6 @@ public class MainMenu : MonoBehaviour setState($"Finding Galaxy Position: iteration {iteration++}"); } while (backgroundSettings.CloudDensity(float2(0.5f)) < .5f); - ActionGameManager.IsTutorial = true; Task.Run(() => { var sector = new Galaxy( diff --git a/tests/Aetheria.Shared.Tests/RunSaveTests.cs b/tests/Aetheria.Shared.Tests/RunSaveTests.cs index 9384b93ed..6ce426995 100644 --- a/tests/Aetheria.Shared.Tests/RunSaveTests.cs +++ b/tests/Aetheria.Shared.Tests/RunSaveTests.cs @@ -91,7 +91,7 @@ public sealed class RunSaveTests : IDisposable var galaxy = new Galaxy(cache, cache.GetGlobal(), _ => { }); var itemManager = new ItemManager(cache, new ProvenanceLedger(), TestSettings(), _ => { }); galaxy.Zones[0].Contents = new Zone(itemManager, new PlanetSettings(), new ZonePack(), galaxy.Zones[0], galaxy); - var (game, zones) = RunSave.Capture(cache, galaxy, galaxy.Zones[0].Contents, null, false, new SavedActionBarBinding[0]); + var (game, zones) = RunSave.Capture(cache, galaxy, galaxy.Zones[0].Contents, null, new SavedActionBarBinding[0]); RunSave.Commit(cache, game, zones, RunSave.Lots(cache)); } diff --git a/tests/Aetheria.Shared.Tests/ScenarioTests.cs b/tests/Aetheria.Shared.Tests/ScenarioTests.cs new file mode 100644 index 000000000..f35cded44 --- /dev/null +++ b/tests/Aetheria.Shared.Tests/ScenarioTests.cs @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System.Buffers; +using System.Collections.Generic; +using GameCult.Caching.MessagePack; +using MessagePack; +using Xunit; + +// Scenarios S2 (docs/scenarios-cut.md, R): every new run is a scenario. The fixture is RunStartTests': the shipped +// catalog, the authored settings and a prelude galaxy at a fixed seed. +public sealed partial class RunStartTests +{ + // Whether a run is the prelude is the galaxy's fact (Galaxy.IsPrelude); a save records it with no second owner. + [Fact] + public void SaveReadsPrelude() + { + var arena = Arena(null); + var (prelude, _) = RunSave.Capture(_cache, _galaxy, arena, null, new SavedActionBarBinding[0]); + Assert.True(prelude.IsTutorial); + + var (main, _) = RunSave.Capture(_cache, MainGalaxy(), arena, null, new SavedActionBarBinding[0]); + Assert.False(main.IsTutorial); + } + + // PlayerSettings Key 2 (TutorialPassed) is retired: a player.cc written with it still loads, every other field intact. + [Fact] + public void OldPlayerSettingsLoad() + { + var options = CultDocumentMessagePackSerialization.OptionsFor(typeof(PlayerSettings).Assembly); + var buffer = new ArrayBufferWriter(); + var writer = new MessagePackWriter(buffer); + writer.WriteArrayHeader(7); + writer.Write("Pilot"); + writer.WriteNil(); // Key 1, retired before this cut + writer.Write(true); // Key 2, TutorialPassed + MessagePackSerializer.Serialize(ref writer, new Dictionary { ["story"] = "hash" }, options); + MessagePackSerializer.Serialize(ref writer, new PlayerGameplaySettings { TemperatureUnit = TemperatureUnit.Kelvin, SignificantDigits = 5 }, options); + MessagePackSerializer.Serialize(ref writer, new PlayerInputSettings { ActionBarInputs = { "/1" } }, options); + MessagePackSerializer.Serialize(ref writer, new PlayerGraphicsSettings { NebulaQuality = Quality.High, ShowAsteroidsInMinimap = true }, options); + writer.Flush(); + + var settings = MessagePackSerializer.Deserialize(buffer.WrittenMemory, options); + + Assert.Equal("Pilot", settings.Name); + Assert.Equal("hash", settings.HashedStoryFiles["story"]); + Assert.Equal((TemperatureUnit.Kelvin, 5), (settings.GameplaySettings.TemperatureUnit, settings.GameplaySettings.SignificantDigits)); + Assert.Equal("/1", Assert.Single(settings.InputSettings.ActionBarInputs)); + Assert.Equal((Quality.High, true), (settings.GraphicsSettings.NebulaQuality, settings.GraphicsSettings.ShowAsteroidsInMinimap)); + } +}