A Scenario is an abstract C# class compiled with the game: Generate picks the galaxy through GalaxyStage (Main or Prelude, at the scenario's seed, the background noise drawn from the same seed), Stage places the run's first entities through ScenarioStage (Fit, Bare, Generated, Preset, Player, Place, Cargo), which collects named failures and admits nothing. RunStart generates the galaxy, the arena under Ambient and admits everything through Zone.Admit, all or nothing. The aetheria.scenario record, ScenarioShip, ScenarioEntity, RunStart.Check and the null-scenario branch are cut. New Game opens a submenu: Tutorial Galaxy and Main Galaxy, then (editor and development builds) Djinni Shakedown, Starved Reactor, Arcs, Duel, Launcher Angles and Long Haul, each with its brief. MainMenu.Launch is the one way into a new run; ActionGameManager.PendingScenario carries the scenario to StartGame once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
326 lines
11 KiB
C#
326 lines
11 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using TMPro;
|
|
using UniRx;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.UI;
|
|
using static CultMath.math;
|
|
|
|
public class MainMenu : MonoBehaviour
|
|
{
|
|
public VolumeCloudRenderer CloudRenderer;
|
|
public GameSettings Settings;
|
|
public ConfirmationDialog Dialog;
|
|
public bool InGame;
|
|
public Prototype PanelPrototype;
|
|
public float FadeTime = .5f;
|
|
public float FadeDistance = 512;
|
|
public float FadeAlphaExponent = 2;
|
|
public float FadePositionExponent = 2;
|
|
|
|
private (PropertiesPanel panel, CanvasGroup group) _currentMenu, _nextMenu;
|
|
private bool _fadeFromRight;
|
|
private float _fadeLerp;
|
|
private bool _fading;
|
|
private Vector3 _panelPosition;
|
|
//private Task<DatabaseCache> _databaseLoad;
|
|
|
|
void Start()
|
|
{
|
|
// Start loading the database in the background 'cause it takes a few seconds
|
|
//_databaseLoad = Task.Run(() => ActionGameManager.Database);
|
|
|
|
_panelPosition = PanelPrototype.transform.position;
|
|
|
|
var panel1 = PanelPrototype.Instantiate<PropertiesPanel>();
|
|
_currentMenu = (panel1, panel1.GetComponent<CanvasGroup>());
|
|
|
|
var panel2 = PanelPrototype.Instantiate<PropertiesPanel>();
|
|
_nextMenu = (panel2, panel2.GetComponent<CanvasGroup>());
|
|
|
|
_currentMenu.panel.gameObject.SetActive(false);
|
|
//_saveDirectory = ActionGameManager.GameDataDirectory.CreateSubdirectory("Saves");
|
|
|
|
ShowMain();
|
|
Fade(true);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (_fading)
|
|
{
|
|
_fadeLerp += Time.deltaTime / FadeTime;
|
|
|
|
_currentMenu.panel.transform.position =
|
|
_panelPosition + (_fadeFromRight ? Vector3.left : Vector3.right) * (FadeDistance * pow(_fadeLerp, FadePositionExponent));
|
|
_nextMenu.panel.transform.position =
|
|
_panelPosition + (_fadeFromRight ? Vector3.right : Vector3.left) * (FadeDistance * pow(1-_fadeLerp, FadePositionExponent));
|
|
_currentMenu.group.alpha = pow(1 - _fadeLerp, FadeAlphaExponent);
|
|
_nextMenu.group.alpha = pow(_fadeLerp, FadeAlphaExponent);
|
|
|
|
if (_fadeLerp > 1)
|
|
{
|
|
_fading = false;
|
|
_currentMenu.panel.gameObject.SetActive(false);
|
|
var temp = _currentMenu;
|
|
_currentMenu = _nextMenu;
|
|
_nextMenu = temp;
|
|
}
|
|
}
|
|
}
|
|
|
|
private string TitleSubtitle(string title, string subtitle) => $"{title}\n<smallcaps><size=50%>{subtitle}";
|
|
|
|
private void Fade(bool fromRight)
|
|
{
|
|
_nextMenu.panel.gameObject.SetActive(true);
|
|
_nextMenu.group.alpha = 0;
|
|
_fading = true;
|
|
_fadeLerp = 0;
|
|
_fadeFromRight = fromRight;
|
|
}
|
|
|
|
private static bool _exclusionsShown;
|
|
|
|
private void ShowMain()
|
|
{
|
|
_nextMenu.panel.Clear();
|
|
_nextMenu.panel.Title.text = TitleSubtitle("aetheria", "terminus");
|
|
if (!InGame)
|
|
{
|
|
// A run exists only as the run store's SavedGame global.
|
|
var cache = ActionGameManager.CultCache;
|
|
if (ActionGameManager.ModExclusions.Length > 0 && !_exclusionsShown)
|
|
{
|
|
_exclusionsShown = true;
|
|
Refuse("Some mods were not loaded", string.Join("\n", ActionGameManager.ModExclusions.Select(exclusion => exclusion.ToString())));
|
|
}
|
|
var saved = cache.GetGlobal<SavedGame>();
|
|
if (saved == null && cache.AllStoredDocuments.Any(stored => RunSave.IsRunRecord(stored.Descriptor.DocumentType)))
|
|
Debug.Log("run store has no SavedGame; Continue disabled");
|
|
_nextMenu.panel.AddButton("Continue", saved == null ? (Action) null : () =>
|
|
{
|
|
try { RunSave.RequireDesigns(cache, saved); }
|
|
catch (InvalidOperationException error) { Refuse("Cannot continue this run", error.Message); return; }
|
|
ActionGameManager.CurrentGalaxy = new Galaxy(cache, saved, Debug.Log);
|
|
EnterGame();
|
|
});
|
|
}
|
|
_nextMenu.panel.AddButton("New Game",
|
|
() =>
|
|
{
|
|
ShowScenarios();
|
|
Fade(true);
|
|
});
|
|
_nextMenu.panel.AddButton("Settings",
|
|
() =>
|
|
{
|
|
ShowSettings();
|
|
Fade(true);
|
|
});
|
|
_nextMenu.panel.AddButton("Quit", Application.Quit);
|
|
}
|
|
|
|
// New Game: every way to set up a run, each with its brief. The test arenas are listed only in editor and
|
|
// development builds.
|
|
private void ShowScenarios()
|
|
{
|
|
_nextMenu.panel.Clear();
|
|
_nextMenu.panel.Title.text = TitleSubtitle("new game", "scenarios");
|
|
AddScenarios(Scenarios.Modes);
|
|
if (Debug.isDebugBuild)
|
|
{
|
|
_nextMenu.panel.AddSection("test arenas");
|
|
AddScenarios(Scenarios.Tests);
|
|
}
|
|
_nextMenu.panel.AddButton("Back",
|
|
() =>
|
|
{
|
|
ShowMain();
|
|
Fade(false);
|
|
});
|
|
}
|
|
|
|
private void AddScenarios(IEnumerable<Scenario> scenarios)
|
|
{
|
|
foreach (var scenario in scenarios)
|
|
{
|
|
_nextMenu.panel.AddButton(scenario.Name, () => Launch(scenario));
|
|
_nextMenu.panel.AddProperty(() => scenario.Brief);
|
|
}
|
|
}
|
|
|
|
// The only way into a new run: clears the saved run, generates the scenario's galaxy off the main thread, and
|
|
// enters the game, where StartGame has RunStart stage it.
|
|
private void Launch(Scenario scenario)
|
|
{
|
|
RunSave.Clear(ActionGameManager.CultCache);
|
|
var generatorState = "Loading Database Contents";
|
|
|
|
Fade(true);
|
|
_nextMenu.panel.gameObject.SetActive(false);
|
|
Dialog.Clear();
|
|
Dialog.Title.text = "Generating Galaxy";
|
|
Dialog.AddProperty(() => generatorState);
|
|
Dialog.Show();
|
|
|
|
var stage = new GalaxyStage(
|
|
Settings.SectorGenerationSettings,
|
|
Settings.SectorBackgroundSettings,
|
|
Settings.TutorialGenerationSettings,
|
|
Settings.TutorialBackgroundSettings,
|
|
Settings.NameGeneratorSettings,
|
|
ActionGameManager.CultCache,
|
|
ActionGameManager.PlayerSettings,
|
|
ActionGameManager.GameDataDirectory.CreateSubdirectory("Narrative"),
|
|
Debug.Log,
|
|
state => generatorState = state);
|
|
Task.Run(() =>
|
|
{
|
|
var galaxy = RunStart.Generate(scenario, stage);
|
|
Observable.NextFrame().Subscribe(_ =>
|
|
{
|
|
ActionGameManager.CurrentGalaxy = galaxy;
|
|
ActionGameManager.PendingScenario = scenario;
|
|
EnterGame();
|
|
});
|
|
}).ContinueWith(t => Debug.LogException(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
|
|
}
|
|
|
|
// The one way into the game scene. Mod ship prototypes import asynchronously at boot, so the scene waits for them,
|
|
// and a preload that could not run at all stops here with its reason rather than in the first zone.
|
|
private void EnterGame() => StartCoroutine(EnterGameWhenModShipsAreReady());
|
|
|
|
private IEnumerator EnterGameWhenModShipsAreReady()
|
|
{
|
|
var loading = ShipModShips.Loading;
|
|
while (!loading.IsCompleted) yield return null;
|
|
if (loading.IsFaulted)
|
|
{
|
|
Refuse("Mod ships failed to load", loading.Exception.GetBaseException().Message);
|
|
yield break;
|
|
}
|
|
SceneManager.LoadScene("ARPG");
|
|
}
|
|
|
|
private void Refuse(string title, string reason)
|
|
{
|
|
Debug.LogError($"{title}: {reason}");
|
|
Dialog.Clear();
|
|
Dialog.Title.text = title;
|
|
Dialog.AddProperty(() => reason);
|
|
Dialog.Show(onCancel: () => { }, cancelText: "OK");
|
|
}
|
|
|
|
private void ShowSettings()
|
|
{
|
|
_nextMenu.panel.Clear();
|
|
_nextMenu.panel.Title.text = "settings";
|
|
_nextMenu.panel.AddButton("Gameplay",
|
|
() =>
|
|
{
|
|
ShowGameplaySettings();
|
|
Fade(true);
|
|
});
|
|
_nextMenu.panel.AddButton("Graphics",
|
|
() =>
|
|
{
|
|
ShowGraphicsSettings();
|
|
Fade(true);
|
|
});
|
|
_nextMenu.panel.AddButton("Input",
|
|
() =>
|
|
{
|
|
ShowInputSettings();
|
|
Fade(true);
|
|
});
|
|
_nextMenu.panel.AddButton("Audio",
|
|
() =>
|
|
{
|
|
ShowAudioSettings();
|
|
Fade(true);
|
|
});
|
|
_nextMenu.panel.AddButton("Back",
|
|
() =>
|
|
{
|
|
ShowMain();
|
|
Fade(false);
|
|
});
|
|
}
|
|
|
|
private void ShowGameplaySettings()
|
|
{
|
|
_nextMenu.panel.Clear();
|
|
_nextMenu.panel.Title.text = TitleSubtitle("gameplay", "settings");
|
|
_nextMenu.panel.AddField("Name",
|
|
() => ActionGameManager.PlayerSettings.Name,
|
|
s => ActionGameManager.PlayerSettings.Name = s);
|
|
_nextMenu.panel.AddField("Temperature Unit",
|
|
() => (int) ActionGameManager.PlayerSettings.GameplaySettings.TemperatureUnit,
|
|
i => ActionGameManager.PlayerSettings.GameplaySettings.TemperatureUnit = (TemperatureUnit) i,
|
|
Enum.GetNames(typeof(TemperatureUnit)));
|
|
_nextMenu.panel.AddField("Significant Digits",
|
|
() => ActionGameManager.PlayerSettings.GameplaySettings.SignificantDigits,
|
|
i => ActionGameManager.PlayerSettings.GameplaySettings.SignificantDigits = i);
|
|
_nextMenu.panel.AddButton("Back",
|
|
() =>
|
|
{
|
|
ActionGameManager.SavePlayerSettings();
|
|
ShowSettings();
|
|
Fade(false);
|
|
});
|
|
}
|
|
|
|
private void ShowGraphicsSettings()
|
|
{
|
|
_nextMenu.panel.Clear();
|
|
_nextMenu.panel.Title.text = TitleSubtitle("graphics", "settings");
|
|
_nextMenu.panel.AddField("Nebula Quality",
|
|
() => (int)ActionGameManager.PlayerSettings.GraphicsSettings.NebulaQuality,
|
|
i =>
|
|
{
|
|
ActionGameManager.PlayerSettings.GraphicsSettings.NebulaQuality = (Quality)i;
|
|
CloudRenderer.quality = ActionGameManager.PlayerSettings.GraphicsSettings.NebulaQuality;
|
|
},
|
|
Enum.GetNames(typeof(Quality)));
|
|
_nextMenu.panel.AddField("Show Asteroids in Minimap",
|
|
() => ActionGameManager.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap,
|
|
b => ActionGameManager.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap = b);
|
|
_nextMenu.panel.AddButton("Back",
|
|
() =>
|
|
{
|
|
ShowSettings();
|
|
Fade(false);
|
|
});
|
|
}
|
|
|
|
private void ShowInputSettings()
|
|
{
|
|
_nextMenu.panel.Clear();
|
|
_nextMenu.panel.Title.text = TitleSubtitle("input", "settings");
|
|
_nextMenu.panel.AddButton("Back",
|
|
() =>
|
|
{
|
|
ShowSettings();
|
|
Fade(false);
|
|
});
|
|
}
|
|
|
|
private void ShowAudioSettings()
|
|
{
|
|
_nextMenu.panel.Clear();
|
|
_nextMenu.panel.Title.text = TitleSubtitle("audio", "settings");
|
|
_nextMenu.panel.AddButton("Back",
|
|
() =>
|
|
{
|
|
ShowSettings();
|
|
Fade(false);
|
|
});
|
|
}
|
|
}
|