2021-03-13 03:23:58 +00:00
|
|
|
using System;
|
2026-08-10 14:57:50 +01:00
|
|
|
using System.Collections;
|
2026-06-30 19:14:49 +01:00
|
|
|
using System.Collections.Generic;
|
2026-08-10 14:57:50 +01:00
|
|
|
using System.IO;
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
using System.Linq;
|
2026-08-10 14:57:50 +01:00
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using TMPro;
|
2021-03-15 11:40:46 +00:00
|
|
|
using UniRx;
|
2021-03-13 03:23:58 +00:00
|
|
|
using UnityEngine;
|
2021-03-14 03:56:49 +00:00
|
|
|
using UnityEngine.SceneManagement;
|
2021-03-13 03:23:58 +00:00
|
|
|
using UnityEngine.UI;
|
2026-09-14 17:39:12 +02:00
|
|
|
using static CultMath.math;
|
2021-03-13 03:23:58 +00:00
|
|
|
|
|
|
|
|
public class MainMenu : MonoBehaviour
|
|
|
|
|
{
|
2022-07-18 08:44:54 +01:00
|
|
|
public VolumeCloudRenderer CloudRenderer;
|
2021-03-20 04:20:17 +00:00
|
|
|
public GameSettings Settings;
|
2021-03-15 11:40:46 +00:00
|
|
|
public ConfirmationDialog Dialog;
|
2026-08-10 14:57:50 +01:00
|
|
|
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;
|
2021-03-13 03:23:58 +00:00
|
|
|
|
|
|
|
|
void Start()
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
// 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)
|
2026-06-30 19:14:49 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_fading = false;
|
|
|
|
|
_currentMenu.panel.gameObject.SetActive(false);
|
|
|
|
|
var temp = _currentMenu;
|
|
|
|
|
_currentMenu = _nextMenu;
|
|
|
|
|
_nextMenu = temp;
|
2026-06-30 19:14:49 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
private string TitleSubtitle(string title, string subtitle) => $"{title}\n<smallcaps><size=50%>{subtitle}";
|
2026-06-30 19:14:49 +01:00
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
private void Fade(bool fromRight)
|
2026-06-30 19:14:49 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_nextMenu.panel.gameObject.SetActive(true);
|
|
|
|
|
_nextMenu.group.alpha = 0;
|
|
|
|
|
_fading = true;
|
|
|
|
|
_fadeLerp = 0;
|
|
|
|
|
_fadeFromRight = fromRight;
|
2026-06-30 19:14:49 +01:00
|
|
|
}
|
|
|
|
|
|
Ship S2 fixes: structural anchors are not mounts, mod presets refused, one bad mod never bricks the boot
- Validate: a hardpoint's anchor role is fixed by its type (thruster-emitter, radiator-mesh, else articulation); ShipModPlan's
duplicate role check is deleted.
- Loadouts.Commit refuses a preset naming a mod design (mod-hull:/mod-ship:), so the shipped catalog never references a mod.
- Compose quarantines each package: a bad package is excluded and named, a collision excludes every package involved; the
game boots on the rest (or the shipped catalog) and the main menu lists the exclusions.
- RequireDesigns refuses a save with a null item or lot instead of throwing NullReferenceException.
- Tests: per structural role, muzzle id ties, every slot/zone/lot in RequireDesigns, listing order, exclusion cases.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 08:30:52 +02:00
|
|
|
private static bool _exclusionsShown;
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
private void ShowMain()
|
2026-06-30 19:14:49 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_nextMenu.panel.Clear();
|
|
|
|
|
_nextMenu.panel.Title.text = TitleSubtitle("aetheria", "terminus");
|
A new run replaces the saved run only once it has staged; a failed start says why and leaves the saved run
Launch cleared the run store before generating, and a generation or staging failure only reached the log: the
Generating Galaxy dialog stayed up, StartGame threw, and the saved run was already gone.
RunSave.Replace runs a start with the saved run still stored: when it returns a staged run the saved run's records go,
and when it returns null or throws, only the records it wrote (the arena's orbits and bodies) go. StartGame's
new-run branch is StartScenario, chosen by PendingScenario rather than by the absence of a SavedGame; it mints into an
empty ledger, and a failed start clears CurrentGalaxy (so quitting saves nothing) and refuses on the main menu with
every failure. Launch no longer clears; a galaxy that throws is refused on the title menu. The menu offers Continue
whenever no run is live, which replaces MainMenu.InGame (cut, with its prefab value and scene override), so a
failed start can resume the saved run in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 22:03:50 +02:00
|
|
|
if (ActionGameManager.CurrentGalaxy == null)
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
{
|
A new run replaces the saved run only once it has staged; a failed start says why and leaves the saved run
Launch cleared the run store before generating, and a generation or staging failure only reached the log: the
Generating Galaxy dialog stayed up, StartGame threw, and the saved run was already gone.
RunSave.Replace runs a start with the saved run still stored: when it returns a staged run the saved run's records go,
and when it returns null or throws, only the records it wrote (the arena's orbits and bodies) go. StartGame's
new-run branch is StartScenario, chosen by PendingScenario rather than by the absence of a SavedGame; it mints into an
empty ledger, and a failed start clears CurrentGalaxy (so quitting saves nothing) and refuses on the main menu with
every failure. Launch no longer clears; a galaxy that throws is refused on the title menu. The menu offers Continue
whenever no run is live, which replaces MainMenu.InGame (cut, with its prefab value and scene override), so a
failed start can resume the saved run in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 22:03:50 +02:00
|
|
|
// No run is live: at the title, after death, or after a new run failed to start. A saved run exists only as
|
|
|
|
|
// the run store's SavedGame global.
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
var cache = ActionGameManager.CultCache;
|
Ship S2 fixes: structural anchors are not mounts, mod presets refused, one bad mod never bricks the boot
- Validate: a hardpoint's anchor role is fixed by its type (thruster-emitter, radiator-mesh, else articulation); ShipModPlan's
duplicate role check is deleted.
- Loadouts.Commit refuses a preset naming a mod design (mod-hull:/mod-ship:), so the shipped catalog never references a mod.
- Compose quarantines each package: a bad package is excluded and named, a collision excludes every package involved; the
game boots on the rest (or the shipped catalog) and the main menu lists the exclusions.
- RequireDesigns refuses a save with a null item or lot instead of throwing NullReferenceException.
- Tests: per structural role, muzzle id ties, every slot/zone/lot in RequireDesigns, listing order, exclusion cases.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 08:30:52 +02:00
|
|
|
if (ActionGameManager.ModExclusions.Length > 0 && !_exclusionsShown)
|
|
|
|
|
{
|
|
|
|
|
_exclusionsShown = true;
|
|
|
|
|
Refuse("Some mods were not loaded", string.Join("\n", ActionGameManager.ModExclusions.Select(exclusion => exclusion.ToString())));
|
|
|
|
|
}
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
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 : () =>
|
|
|
|
|
{
|
2026-09-30 06:43:52 +02:00
|
|
|
try { RunSave.RequireDesigns(cache, saved); }
|
|
|
|
|
catch (InvalidOperationException error) { Refuse("Cannot continue this run", error.Message); return; }
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
ActionGameManager.CurrentGalaxy = new Galaxy(cache, saved, Debug.Log);
|
2026-09-30 06:43:52 +02:00
|
|
|
EnterGame();
|
Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.
- RunSave (SavedGame.cs) replaces the SavedGame constructor:
- Capture builds the live run as plain documents and writes nothing. Zone
contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
zone generated earlier but not loaded this session keeps its contents
and its orbit and body records instead of being erased by the next save.
- Commit is the only writer of SavedGame and SavedZone: one Commit with
zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
stored SavedZone outside that set removed. Orbits and bodies staged by
zone generation land in the same single-file write.
- Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
Continue from it (and logs when the run store holds records but no
SavedGame), and StartGame reads it once to pick the new-run or resume
branch.
- PlayerSettings: the getter returns the player store's global and, on
first launch, commits GetDefaultPlayerSettings; it is the only creator.
SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
asset's name and throws when the catalog has none.
Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.
Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00
|
|
|
});
|
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
_nextMenu.panel.AddButton("New Game",
|
|
|
|
|
() =>
|
|
|
|
|
{
|
Every new run is a scenario: the script harness, the tutorial and main galaxies as scenarios, the New Game submenu and six test arenas
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>
2026-10-03 20:33:55 +02:00
|
|
|
ShowScenarios();
|
2026-08-10 14:57:50 +01:00
|
|
|
Fade(true);
|
|
|
|
|
});
|
|
|
|
|
_nextMenu.panel.AddButton("Settings",
|
|
|
|
|
() =>
|
|
|
|
|
{
|
|
|
|
|
ShowSettings();
|
|
|
|
|
Fade(true);
|
|
|
|
|
});
|
|
|
|
|
_nextMenu.panel.AddButton("Quit", Application.Quit);
|
2026-06-17 06:10:41 +01:00
|
|
|
}
|
|
|
|
|
|
2026-10-06 19:32:02 +02:00
|
|
|
// New Game: every way to set up a run, each with its brief. A release build lists only the modes; the other ways
|
|
|
|
|
// to set up a run and the test arenas are listed only in editor and development builds.
|
Every new run is a scenario: the script harness, the tutorial and main galaxies as scenarios, the New Game submenu and six test arenas
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>
2026-10-03 20:33:55 +02:00
|
|
|
private void ShowScenarios()
|
|
|
|
|
{
|
|
|
|
|
_nextMenu.panel.Clear();
|
|
|
|
|
_nextMenu.panel.Title.text = TitleSubtitle("new game", "scenarios");
|
|
|
|
|
AddScenarios(Scenarios.Modes);
|
|
|
|
|
if (Debug.isDebugBuild)
|
|
|
|
|
{
|
2026-10-06 19:32:02 +02:00
|
|
|
_nextMenu.panel.AddSection("development");
|
|
|
|
|
AddScenarios(Scenarios.Development);
|
Every new run is a scenario: the script harness, the tutorial and main galaxies as scenarios, the New Game submenu and six test arenas
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>
2026-10-03 20:33:55 +02:00
|
|
|
_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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
A new run replaces the saved run only once it has staged; a failed start says why and leaves the saved run
Launch cleared the run store before generating, and a generation or staging failure only reached the log: the
Generating Galaxy dialog stayed up, StartGame threw, and the saved run was already gone.
RunSave.Replace runs a start with the saved run still stored: when it returns a staged run the saved run's records go,
and when it returns null or throws, only the records it wrote (the arena's orbits and bodies) go. StartGame's
new-run branch is StartScenario, chosen by PendingScenario rather than by the absence of a SavedGame; it mints into an
empty ledger, and a failed start clears CurrentGalaxy (so quitting saves nothing) and refuses on the main menu with
every failure. Launch no longer clears; a galaxy that throws is refused on the title menu. The menu offers Continue
whenever no run is live, which replaces MainMenu.InGame (cut, with its prefab value and scene override), so a
failed start can resume the saved run in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 22:03:50 +02:00
|
|
|
// The only way into a new run: generates the scenario's galaxy off the main thread and enters the game, where
|
|
|
|
|
// StartGame has RunStart stage it and only then replaces the saved run. A galaxy that cannot generate is refused
|
|
|
|
|
// here, with the saved run untouched.
|
Every new run is a scenario: the script harness, the tutorial and main galaxies as scenarios, the New Game submenu and six test arenas
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>
2026-10-03 20:33:55 +02:00
|
|
|
private void Launch(Scenario scenario)
|
|
|
|
|
{
|
|
|
|
|
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);
|
A new run replaces the saved run only once it has staged; a failed start says why and leaves the saved run
Launch cleared the run store before generating, and a generation or staging failure only reached the log: the
Generating Galaxy dialog stayed up, StartGame threw, and the saved run was already gone.
RunSave.Replace runs a start with the saved run still stored: when it returns a staged run the saved run's records go,
and when it returns null or throws, only the records it wrote (the arena's orbits and bodies) go. StartGame's
new-run branch is StartScenario, chosen by PendingScenario rather than by the absence of a SavedGame; it mints into an
empty ledger, and a failed start clears CurrentGalaxy (so quitting saves nothing) and refuses on the main menu with
every failure. Launch no longer clears; a galaxy that throws is refused on the title menu. The menu offers Continue
whenever no run is live, which replaces MainMenu.InGame (cut, with its prefab value and scene override), so a
failed start can resume the saved run in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 22:03:50 +02:00
|
|
|
Task.Run(() => RunStart.Generate(scenario, stage)).ContinueWith(generation =>
|
Every new run is a scenario: the script harness, the tutorial and main galaxies as scenarios, the New Game submenu and six test arenas
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>
2026-10-03 20:33:55 +02:00
|
|
|
Observable.NextFrame().Subscribe(_ =>
|
|
|
|
|
{
|
A new run replaces the saved run only once it has staged; a failed start says why and leaves the saved run
Launch cleared the run store before generating, and a generation or staging failure only reached the log: the
Generating Galaxy dialog stayed up, StartGame threw, and the saved run was already gone.
RunSave.Replace runs a start with the saved run still stored: when it returns a staged run the saved run's records go,
and when it returns null or throws, only the records it wrote (the arena's orbits and bodies) go. StartGame's
new-run branch is StartScenario, chosen by PendingScenario rather than by the absence of a SavedGame; it mints into an
empty ledger, and a failed start clears CurrentGalaxy (so quitting saves nothing) and refuses on the main menu with
every failure. Launch no longer clears; a galaxy that throws is refused on the title menu. The menu offers Continue
whenever no run is live, which replaces MainMenu.InGame (cut, with its prefab value and scene override), so a
failed start can resume the saved run in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 22:03:50 +02:00
|
|
|
if (generation.IsFaulted)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogException(generation.Exception);
|
|
|
|
|
ShowMain();
|
|
|
|
|
Fade(false);
|
|
|
|
|
Refuse($"{scenario.Name} could not generate its galaxy", generation.Exception.GetBaseException().Message);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
ActionGameManager.CurrentGalaxy = generation.Result;
|
Every new run is a scenario: the script harness, the tutorial and main galaxies as scenarios, the New Game submenu and six test arenas
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>
2026-10-03 20:33:55 +02:00
|
|
|
ActionGameManager.PendingScenario = scenario;
|
|
|
|
|
EnterGame();
|
A new run replaces the saved run only once it has staged; a failed start says why and leaves the saved run
Launch cleared the run store before generating, and a generation or staging failure only reached the log: the
Generating Galaxy dialog stayed up, StartGame threw, and the saved run was already gone.
RunSave.Replace runs a start with the saved run still stored: when it returns a staged run the saved run's records go,
and when it returns null or throws, only the records it wrote (the arena's orbits and bodies) go. StartGame's
new-run branch is StartScenario, chosen by PendingScenario rather than by the absence of a SavedGame; it mints into an
empty ledger, and a failed start clears CurrentGalaxy (so quitting saves nothing) and refuses on the main menu with
every failure. Launch no longer clears; a galaxy that throws is refused on the title menu. The menu offers Continue
whenever no run is live, which replaces MainMenu.InGame (cut, with its prefab value and scene override), so a
failed start can resume the saved run in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 22:03:50 +02:00
|
|
|
}));
|
Every new run is a scenario: the script harness, the tutorial and main galaxies as scenarios, the New Game submenu and six test arenas
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>
2026-10-03 20:33:55 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-30 06:43:52 +02:00
|
|
|
// The one way into the game scene. Mod ship prototypes import asynchronously at boot, so the scene waits for them,
|
2026-10-03 15:38:35 +02:00
|
|
|
// and a preload that could not run at all stops here with its reason rather than in the first zone.
|
2026-09-30 06:43:52 +02:00
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
|
A new run replaces the saved run only once it has staged; a failed start says why and leaves the saved run
Launch cleared the run store before generating, and a generation or staging failure only reached the log: the
Generating Galaxy dialog stayed up, StartGame threw, and the saved run was already gone.
RunSave.Replace runs a start with the saved run still stored: when it returns a staged run the saved run's records go,
and when it returns null or throws, only the records it wrote (the arena's orbits and bodies) go. StartGame's
new-run branch is StartScenario, chosen by PendingScenario rather than by the absence of a SavedGame; it mints into an
empty ledger, and a failed start clears CurrentGalaxy (so quitting saves nothing) and refuses on the main menu with
every failure. Launch no longer clears; a galaxy that throws is refused on the title menu. The menu offers Continue
whenever no run is live, which replaces MainMenu.InGame (cut, with its prefab value and scene override), so a
failed start can resume the saved run in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 22:03:50 +02:00
|
|
|
public void Refuse(string title, string reason)
|
2026-09-30 06:43:52 +02:00
|
|
|
{
|
|
|
|
|
Debug.LogError($"{title}: {reason}");
|
|
|
|
|
Dialog.Clear();
|
|
|
|
|
Dialog.Title.text = title;
|
|
|
|
|
Dialog.AddProperty(() => reason);
|
|
|
|
|
Dialog.Show(onCancel: () => { }, cancelText: "OK");
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
private void ShowSettings()
|
2026-06-17 11:24:36 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_nextMenu.panel.Clear();
|
|
|
|
|
_nextMenu.panel.Title.text = "settings";
|
|
|
|
|
_nextMenu.panel.AddButton("Gameplay",
|
|
|
|
|
() =>
|
2026-06-17 12:14:16 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
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);
|
|
|
|
|
});
|
2026-06-17 11:24:36 +01:00
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
2026-06-16 21:08:20 +01:00
|
|
|
}
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
private void ShowGraphicsSettings()
|
2026-06-16 19:05:00 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_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);
|
2026-10-12 21:10:04 +02:00
|
|
|
_nextMenu.panel.AddField("Screen Shake",
|
|
|
|
|
() => ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake,
|
|
|
|
|
f => ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = f,
|
|
|
|
|
0, 1);
|
2026-08-10 14:57:50 +01:00
|
|
|
_nextMenu.panel.AddButton("Back",
|
|
|
|
|
() =>
|
|
|
|
|
{
|
2026-10-12 21:10:04 +02:00
|
|
|
ActionGameManager.SavePlayerSettings();
|
2026-08-10 14:57:50 +01:00
|
|
|
ShowSettings();
|
|
|
|
|
Fade(false);
|
|
|
|
|
});
|
2026-06-16 19:05:00 +01:00
|
|
|
}
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
private void ShowInputSettings()
|
2026-06-16 19:05:00 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_nextMenu.panel.Clear();
|
|
|
|
|
_nextMenu.panel.Title.text = TitleSubtitle("input", "settings");
|
|
|
|
|
_nextMenu.panel.AddButton("Back",
|
|
|
|
|
() =>
|
|
|
|
|
{
|
|
|
|
|
ShowSettings();
|
|
|
|
|
Fade(false);
|
|
|
|
|
});
|
2026-06-30 19:14:49 +01:00
|
|
|
}
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
private void ShowAudioSettings()
|
2026-06-30 19:14:49 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_nextMenu.panel.Clear();
|
|
|
|
|
_nextMenu.panel.Title.text = TitleSubtitle("audio", "settings");
|
|
|
|
|
_nextMenu.panel.AddButton("Back",
|
|
|
|
|
() =>
|
|
|
|
|
{
|
|
|
|
|
ShowSettings();
|
|
|
|
|
Fade(false);
|
|
|
|
|
});
|
2026-06-15 22:47:15 +01:00
|
|
|
}
|
2021-03-13 03:23:58 +00:00
|
|
|
}
|