2021-03-13 03:23:58 +00:00
using System ;
using System.Collections ;
using System.Collections.Generic ;
2021-03-14 03:56:49 +00:00
using System.IO ;
2026-06-16 14:22:52 +01:00
using System.Linq ;
2021-03-15 11:40:46 +00:00
using System.Threading ;
using System.Threading.Tasks ;
2026-06-16 06:09:46 +01:00
using GameCult.Aetheria.State.Unity ;
2026-06-16 19:05:00 +01:00
using GameCult.Eve.Surface ;
using GameCult.Eve.UnityUIToolkit ;
2021-03-13 03:23:58 +00:00
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-06-16 19:05:00 +01:00
using UnityEngine.UIElements ;
2021-03-14 03:56:49 +00:00
using static Unity . Mathematics . math ;
2021-04-22 02:53:06 +01:00
using float2 = Unity . Mathematics . float2 ;
2021-03-15 11:40:46 +00:00
using Random = UnityEngine . Random ;
2021-03-13 03:23:58 +00:00
public class MainMenu : MonoBehaviour
{
2026-06-16 21:08:20 +01:00
private const string MenuSurfaceType = "surface-state" ;
private const string MenuSurfaceSchema = "gamecult.eve.surface.v1" ;
private const string MenuSurfaceProviderId = "aetheria" ;
private const string MenuSurfaceProviderKind = "game.menu" ;
2026-06-17 04:57:56 +01:00
private const string MainSurfaceId = "aetheria.main_menu.root" ;
2026-06-16 21:08:20 +01:00
private const string SettingsSurfaceId = "aetheria.main_menu.settings" ;
private const string InputSettingsSurfaceId = "aetheria.main_menu.input_settings" ;
private const string PlayerSettingsShellSurfaceId = "aetheria.main_menu.player_settings" ;
2026-06-17 11:24:36 +01:00
private const string VerseSettingsShellSurfaceId = "aetheria.main_menu.verse_settings" ;
2026-06-17 04:57:56 +01:00
private const string ContinueRunCommand = "aetheria.main_menu.root.continue" ;
private const string NewGameCommand = "aetheria.main_menu.root.new_game" ;
private const string ShowSettingsCommand = "aetheria.main_menu.root.show_settings" ;
private const string QuitCommand = "aetheria.main_menu.root.quit" ;
2026-06-17 06:10:41 +01:00
private const string OpenRuntimeInputScreenCommand = "aetheria.main_menu.input_settings.open_runtime_screen" ;
2026-06-16 21:08:20 +01:00
private const string ShowPlayerSettingsCommand = "aetheria.main_menu.settings.show_player_settings" ;
2026-06-17 11:24:36 +01:00
private const string ShowVerseSettingsCommand = "aetheria.main_menu.settings.show_verse_settings" ;
2026-06-16 21:08:20 +01:00
private const string ShowInputSettingsCommand = "aetheria.main_menu.settings.show_input_settings" ;
private const string BackToMainCommand = "aetheria.main_menu.settings.back_to_main" ;
private const string BackToSettingsCommand = "aetheria.main_menu.settings.back_to_settings" ;
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 ;
2021-03-14 03:56:49 +00:00
public bool InGame ;
2026-06-17 04:57:56 +01:00
2026-06-16 21:08:20 +01:00
private UIDocument _menuSurfaceDocument ;
2021-03-13 03:23:58 +00:00
void Start ( )
{
ShowMain ( ) ;
2021-03-14 03:56:49 +00:00
}
2026-06-17 04:57:56 +01:00
private void ShowMain ( )
2021-03-14 03:56:49 +00:00
{
2026-06-17 10:39:28 +01:00
var stateBoot = CurrentStateBoot ( ) ;
RenderMenuSurface (
BuildMainSurfaceDefinition (
stateBoot ,
LatestContinueRun ( stateBoot ) ,
LatestVerseHostSettings ( stateBoot ) ,
InGame ) ,
HandleMainSurfaceCommand ) ;
2021-03-14 03:56:49 +00:00
}
2026-06-17 04:57:56 +01:00
private void HandleMainSurfaceCommand ( EveSurfaceCommandRequest request )
2021-03-14 03:56:49 +00:00
{
2026-06-17 04:57:56 +01:00
switch ( request . Command )
{
case ContinueRunCommand :
2026-06-17 10:39:28 +01:00
var continueRun = LatestContinueRun ( CurrentStateBoot ( ) ) ;
2026-06-17 04:57:56 +01:00
if ( continueRun = = null )
{
Debug . LogWarning ( "Main-menu Continue requested without a typed run state." ) ;
ShowMain ( ) ;
return ;
}
2021-03-13 03:23:58 +00:00
2026-06-17 04:57:56 +01:00
ContinueGame ( continueRun ) ;
return ;
case NewGameCommand :
StartNewGame ( ) ;
return ;
case ShowSettingsCommand :
ShowSettings ( ) ;
return ;
case QuitCommand :
Application . Quit ( ) ;
return ;
default :
Debug . LogWarning ( $"Unknown main menu command: {request.Command}" ) ;
return ;
}
2026-06-16 21:08:20 +01:00
}
2026-06-17 04:57:56 +01:00
private void StartNewGame ( )
2021-03-13 03:23:58 +00:00
{
2026-06-17 04:57:56 +01:00
var generatorState = "Loading typed catalog" ;
Action < string > setState = s = > generatorState = s ;
2026-06-16 21:08:20 +01:00
HideMenuSurface ( ) ;
2026-06-17 04:57:56 +01:00
Dialog . Clear ( ) ;
Dialog . Title . text = "Generating Galaxy" ;
Dialog . AddProperty ( ( ) = > generatorState ) ;
Dialog . Show ( ) ;
if ( ActionGameManager . RuntimePlayerSettings . TutorialPassed )
2021-03-14 03:56:49 +00:00
{
2026-06-17 04:57:56 +01:00
Settings . SectorBackgroundSettings . NoisePosition = Random . value * 1000 ;
ActionGameManager . IsTutorial = false ;
var generationSeed = NextGenerationSeed ( ) ;
Task . Run ( ( ) = >
{
var sector = new Galaxy (
Settings . SectorGenerationSettings ,
Settings . SectorBackgroundSettings ,
Settings . NameGeneratorSettings ,
ActionGameManager . RuntimeCatalog ,
Debug . Log ,
setState ,
generationSeed ) ;
Observable . NextFrame ( ) . Subscribe ( _ = >
{
ActionGameManager . CurrentGalaxy = sector ;
SceneManager . LoadScene ( "ARPG" ) ;
} ) ;
} ) . WrapErrors ( ) ;
2021-03-14 03:56:49 +00:00
}
2026-06-17 04:57:56 +01:00
else
{
int iteration = 1 ;
do
2021-03-14 03:56:49 +00:00
{
2026-06-17 04:57:56 +01:00
Settings . TutorialBackgroundSettings . NoisePosition = Random . value * 1000 ;
setState ( $"Finding Galaxy Position: iteration {iteration++}" ) ;
} while ( Settings . TutorialBackgroundSettings . CloudDensity ( float2 ( 0.5f ) ) < . 5f ) ;
2021-03-15 11:40:46 +00:00
2026-06-17 04:57:56 +01:00
ActionGameManager . IsTutorial = true ;
var generationSeed = NextGenerationSeed ( ) ;
Task . Run ( ( ) = >
2021-03-14 03:56:49 +00:00
{
2026-06-17 04:57:56 +01:00
var sector = new Galaxy (
Settings . TutorialGenerationSettings ,
Settings . TutorialBackgroundSettings ,
Settings . NameGeneratorSettings ,
ActionGameManager . RuntimeCatalog ,
ActionGameManager . RuntimePlayerSettings ,
ActionGameManager . GameDataDirectory . CreateSubdirectory ( "Narrative" ) ,
Debug . Log ,
setState ,
generationSeed ) ;
Observable . NextFrame ( ) . Subscribe ( _ = >
{
ActionGameManager . CurrentGalaxy = sector ;
SceneManager . LoadScene ( "ARPG" ) ;
} ) ;
} ) . WrapErrors ( ) ;
}
2021-03-14 03:56:49 +00:00
}
2026-06-14 15:12:38 +01:00
private static uint NextGenerationSeed ( )
{
var seed = ( uint ) Random . Range ( 1 , int . MaxValue ) ;
return seed = = 0 ? 1 u : seed ;
}
2026-06-17 10:39:28 +01:00
private static AetheriaRuntimeStateBootReport CurrentStateBoot ( )
{
return AetheriaRuntimeStateBoot . Inspect ( ActionGameManager . GameDataDirectory ) ;
}
private static AetheriaRuntimeRunStateSnapshot LatestContinueRun ( AetheriaRuntimeStateBootReport stateBoot )
2026-06-16 06:09:46 +01:00
{
2026-06-17 10:39:28 +01:00
if ( ! stateBoot . SupportsLocalStateFileRead | | ! stateBoot . StateFileExists )
return null ;
2026-06-16 06:09:46 +01:00
try
{
2026-06-17 09:19:11 +01:00
return AetheriaRuntimeStateReader
2026-06-17 10:39:28 +01:00
. ReadRunStates ( stateBoot . StateFilePath )
2026-06-16 06:09:46 +01:00
. Where ( run = > ! string . IsNullOrWhiteSpace ( run . RunId ) )
. OrderByDescending ( run = > run . UpdatedAtUtc , StringComparer . Ordinal )
. ThenByDescending ( run = > run . RunId , StringComparer . Ordinal )
. FirstOrDefault ( ) ;
}
catch ( Exception ex )
{
Debug . LogError ( $"Failed to read typed Aetheria run state for Continue: {ex}" ) ;
return null ;
}
}
2026-06-17 10:39:28 +01:00
private static AetheriaRuntimeVerseHostSettingsSnapshot LatestVerseHostSettings ( AetheriaRuntimeStateBootReport stateBoot )
2026-06-17 09:51:49 +01:00
{
2026-06-17 10:39:28 +01:00
if ( ! stateBoot . SupportsLocalStateFileRead | | ! stateBoot . StateFileExists )
return null ;
2026-06-17 09:51:49 +01:00
try
{
2026-06-17 10:39:28 +01:00
return AetheriaRuntimeStateReader . ReadVerseHostSettings ( stateBoot . StateFilePath ) ;
2026-06-17 09:51:49 +01:00
}
catch ( Exception ex )
{
Debug . LogError ( $"Failed to read typed Aetheria Verse host settings for the main menu: {ex}" ) ;
return null ;
}
}
2026-06-16 06:09:46 +01:00
private void ContinueGame ( AetheriaRuntimeRunStateSnapshot run )
{
if ( run = = null )
return ;
var generatorState = "Loading typed run" ;
Action < string > setState = s = > generatorState = s ;
2026-06-17 04:57:56 +01:00
HideMenuSurface ( ) ;
2026-06-16 06:09:46 +01:00
Dialog . Clear ( ) ;
Dialog . Title . text = "Continuing Run" ;
Dialog . AddProperty ( ( ) = > generatorState ) ;
Dialog . Show ( ) ;
ActionGameManager . IsTutorial = run . IsTutorial ;
ActionGameManager . ContinueRunState = run ;
var generationSeed = run . GenerationSeed = = 0 ? NextGenerationSeed ( ) : run . GenerationSeed ;
var backgroundSettings = run . IsTutorial ? Settings . TutorialBackgroundSettings : Settings . SectorBackgroundSettings ;
backgroundSettings . NoisePosition = generationSeed ;
Task . Run ( ( ) = >
{
var sector = run . IsTutorial
? new Galaxy (
Settings . TutorialGenerationSettings ,
Settings . TutorialBackgroundSettings ,
Settings . NameGeneratorSettings ,
ActionGameManager . RuntimeCatalog ,
ActionGameManager . RuntimePlayerSettings ,
ActionGameManager . GameDataDirectory . CreateSubdirectory ( "Narrative" ) ,
Debug . Log ,
setState ,
generationSeed )
: new Galaxy (
2026-06-16 14:22:52 +01:00
Settings . SectorGenerationSettings ,
2026-06-16 06:09:46 +01:00
backgroundSettings ,
Settings . NameGeneratorSettings ,
ActionGameManager . RuntimeCatalog ,
Debug . Log ,
setState ,
generationSeed ) ;
Observable . NextFrame ( ) . Subscribe ( _ = >
{
ActionGameManager . CurrentGalaxy = sector ;
SceneManager . LoadScene ( "ARPG" ) ;
} ) ;
} ) . WrapErrors ( ) ;
}
2021-03-14 03:56:49 +00:00
private void ShowSettings ( )
{
2026-06-16 21:08:20 +01:00
RenderMenuSurface ( BuildSettingsSurfaceDefinition ( ) , HandleSettingsSurfaceCommand ) ;
2021-03-14 03:56:49 +00:00
}
2021-03-13 03:23:58 +00:00
2021-03-14 03:56:49 +00:00
private void ShowInputSettings ( )
2021-03-13 03:23:58 +00:00
{
2026-06-17 06:10:41 +01:00
RenderMenuSurface ( BuildInputSettingsSurfaceDefinition ( CanOpenRuntimeInputScreen ( ) , InGame ) , HandleInputSettingsSurfaceCommand ) ;
2021-03-13 03:23:58 +00:00
}
2026-06-16 19:05:00 +01:00
private void ShowPlayerSettingsSurface ( )
{
2026-06-16 21:08:20 +01:00
RenderMenuSurface (
WithBackAction (
ToEveSurfaceDocument ( BuildPlayerSettingsSurfaceDefinition ( ) ) ,
PlayerSettingsShellSurfaceId ,
BackToSettingsCommand ,
"Back" ) ,
HandlePlayerSettingsSurfaceCommand ) ;
2026-06-16 19:05:00 +01:00
}
2026-06-17 11:24:36 +01:00
private void ShowVerseSettingsSurface ( )
{
RenderMenuSurface (
WithBackAction (
ToEveSurfaceDocument ( BuildVerseSettingsSurfaceDefinition ( ) ) ,
VerseSettingsShellSurfaceId ,
BackToSettingsCommand ,
"Back" ) ,
HandleVerseSettingsSurfaceCommand ) ;
}
2026-06-16 21:08:20 +01:00
private void RenderMenuSurface (
EveSurfaceDocument document ,
Action < EveSurfaceCommandRequest > commandHandler )
2026-06-16 18:32:19 +01:00
{
2026-06-16 21:08:20 +01:00
var surfaceDocument = ResolveMenuSurfaceDocument ( ) ;
surfaceDocument . gameObject . SetActive ( true ) ;
2026-06-16 19:05:00 +01:00
2026-06-16 21:08:20 +01:00
var root = surfaceDocument . rootVisualElement ;
2026-06-16 19:05:00 +01:00
root . Clear ( ) ;
root . style . flexGrow = 1 ;
root . style . justifyContent = Justify . Center ;
root . style . alignItems = Align . Center ;
root . style . paddingLeft = 24 ;
root . style . paddingRight = 24 ;
root . style . paddingTop = 24 ;
root . style . paddingBottom = 24 ;
root . style . backgroundColor = new Color ( 0f , 0f , 0f , 0.6f ) ;
var shell = new VisualElement ( ) ;
shell . style . flexDirection = FlexDirection . Column ;
shell . style . width = 560 ;
shell . style . maxWidth = 560 ;
shell . style . paddingLeft = 20 ;
shell . style . paddingRight = 20 ;
shell . style . paddingTop = 20 ;
shell . style . paddingBottom = 20 ;
shell . style . backgroundColor = new Color ( 0.08f , 0.1f , 0.14f , 0.96f ) ;
root . Add ( shell ) ;
var lowerer = new EveUiToolkitSurfaceLowerer ( ) ;
2026-06-16 21:08:20 +01:00
shell . Add ( lowerer . Lower ( document , commandHandler ) ) ;
}
2026-06-16 19:05:00 +01:00
2026-06-16 21:08:20 +01:00
private void HandleSettingsSurfaceCommand ( EveSurfaceCommandRequest request )
{
switch ( request . Command )
{
case ShowPlayerSettingsCommand :
ShowPlayerSettingsSurface ( ) ;
return ;
2026-06-17 11:24:36 +01:00
case ShowVerseSettingsCommand :
ShowVerseSettingsSurface ( ) ;
return ;
2026-06-16 21:08:20 +01:00
case ShowInputSettingsCommand :
ShowInputSettings ( ) ;
return ;
case BackToMainCommand :
ShowMain ( ) ;
return ;
default :
Debug . LogWarning ( $"Unknown settings menu command: {request.Command}" ) ;
return ;
}
}
private void HandleInputSettingsSurfaceCommand ( EveSurfaceCommandRequest request )
{
if ( string . Equals ( request . Command , BackToSettingsCommand , StringComparison . Ordinal ) )
{
ShowSettings ( ) ;
return ;
}
2026-06-17 06:10:41 +01:00
if ( string . Equals ( request . Command , OpenRuntimeInputScreenCommand , StringComparison . Ordinal ) )
{
if ( ! TryOpenRuntimeInputScreen ( ) )
{
ShowInputSettings ( ) ;
}
return ;
}
2026-06-16 21:08:20 +01:00
Debug . LogWarning ( $"Unknown input settings command: {request.Command}" ) ;
}
private void HandlePlayerSettingsSurfaceCommand ( EveSurfaceCommandRequest request )
{
if ( string . Equals ( request . Command , BackToSettingsCommand , StringComparison . Ordinal ) )
2026-06-16 19:05:00 +01:00
{
ShowSettings ( ) ;
2026-06-16 21:08:20 +01:00
return ;
}
if ( ! ActionGameManager . CommitRuntimePlayerSettingsCommand ( request . Command , request . Payload ) )
2026-06-16 19:05:00 +01:00
{
2026-06-16 21:08:20 +01:00
Debug . LogWarning ( $"Unknown player-settings command: {request.Command}" ) ;
return ;
}
CloudRenderer . quality = ActionGameManager . RuntimePlayerSettings . GraphicsSettings . NebulaQuality ;
ShowPlayerSettingsSurface ( ) ;
2026-06-16 18:32:19 +01:00
}
2026-06-17 11:24:36 +01:00
private void HandleVerseSettingsSurfaceCommand ( EveSurfaceCommandRequest request )
{
if ( string . Equals ( request . Command , BackToSettingsCommand , StringComparison . Ordinal ) )
{
ShowSettings ( ) ;
return ;
}
if ( TryCommitClientTargetCommand ( request . Command , request . Payload ) )
{
ShowVerseSettingsSurface ( ) ;
return ;
}
if ( TryQueueVerseHostCommand ( request . Command , request . Payload ) )
{
ShowVerseSettingsSurface ( ) ;
return ;
}
Debug . LogWarning ( $"Unknown verse-settings command: {request.Command}" ) ;
}
2026-06-16 21:08:20 +01:00
private void HideMenuSurface ( )
2026-06-15 22:47:15 +01:00
{
2026-06-16 21:08:20 +01:00
if ( _menuSurfaceDocument = = null )
2026-06-16 19:05:00 +01:00
return ;
2026-06-16 21:08:20 +01:00
_menuSurfaceDocument . rootVisualElement . Clear ( ) ;
_menuSurfaceDocument . gameObject . SetActive ( false ) ;
2026-06-16 19:05:00 +01:00
}
2026-06-16 21:08:20 +01:00
private UIDocument ResolveMenuSurfaceDocument ( )
2026-06-16 19:05:00 +01:00
{
2026-06-16 21:08:20 +01:00
if ( _menuSurfaceDocument ! = null )
return _menuSurfaceDocument ;
2026-06-16 19:05:00 +01:00
2026-06-16 21:08:20 +01:00
var host = new GameObject ( "Aetheria Menu Surface" ) ;
2026-06-16 19:05:00 +01:00
host . transform . SetParent ( transform , false ) ;
var document = host . AddComponent < UIDocument > ( ) ;
document . sortingOrder = 1000 ;
host . SetActive ( false ) ;
2026-06-16 21:08:20 +01:00
_menuSurfaceDocument = document ;
2026-06-16 19:05:00 +01:00
return document ;
}
private static AetheriaRuntimeSurfaceDocument BuildPlayerSettingsSurfaceDefinition ( )
{
return AetheriaRuntimePlayerSettingsSurfaceBuilder . Build (
new AetheriaRuntimePlayerSettingsSurfaceState (
ActionGameManager . RuntimePlayerSettings . Name ,
ActionGameManager . RuntimePlayerSettings . TutorialPassed ,
ActionGameManager . ContinueRunState ? . RunId ? ? "" ,
ActionGameManager . RuntimePlayerSettings . GameplaySettings . TemperatureUnit . ToString ( ) ,
max ( 0 , ActionGameManager . RuntimePlayerSettings . GameplaySettings . SignificantDigits ) ,
ActionGameManager . RuntimePlayerSettings . GraphicsSettings . NebulaQuality . ToString ( ) ,
ActionGameManager . RuntimePlayerSettings . GraphicsSettings . ShowAsteroidsInMinimap ,
DateTime . UtcNow . ToString ( "O" ) ) ) ;
}
2026-06-17 11:24:36 +01:00
private static AetheriaRuntimeSurfaceDocument BuildVerseSettingsSurfaceDefinition ( )
{
var stateBoot = CurrentStateBoot ( ) ;
var verseHost = LatestVerseHostSettings ( stateBoot ) ;
return AetheriaRuntimeClientTargetSurfaceBuilder . Build (
new AetheriaRuntimeClientTargetSurfaceState (
stateBoot . TargetKind ,
stateBoot . Title ,
stateBoot . VerseId ,
stateBoot . CultMeshAddress ,
stateBoot . StateFilePath ,
stateBoot . TargetSource ,
stateBoot . SupportsLocalStateFileRead ,
stateBoot . FailureMessage ,
verseHost ? . Title ? ? stateBoot . Title ,
verseHost ? . VerseId ? ? stateBoot . VerseId ,
verseHost ? . Visibility ? ? "unknown" ,
verseHost ? . CultMeshAddress ? ? stateBoot . CultMeshAddress ,
DateTime . UtcNow . ToString ( "O" ) ) ) ;
}
2026-06-17 04:57:56 +01:00
private static EveSurfaceDocument BuildMainSurfaceDefinition (
2026-06-17 10:39:28 +01:00
AetheriaRuntimeStateBootReport stateBoot ,
2026-06-17 04:57:56 +01:00
AetheriaRuntimeRunStateSnapshot continueRun ,
2026-06-17 09:51:49 +01:00
AetheriaRuntimeVerseHostSettingsSnapshot verseHost ,
2026-06-17 04:57:56 +01:00
bool inGame )
{
var commands = new List < EveCommandTemplate > ( ) ;
var actionButtons = new List < EveSurfaceComponent > ( ) ;
var cardChildren = new List < EveSurfaceComponent > ( ) ;
2026-06-17 10:39:28 +01:00
var targetLabel = stateBoot . TargetLabel ;
var targetKind = string . IsNullOrWhiteSpace ( stateBoot . TargetKind ) ? "unknown" : stateBoot . TargetKind ;
var targetSource = string . IsNullOrWhiteSpace ( stateBoot . TargetSource ) ? "unknown" : stateBoot . TargetSource ;
2026-06-17 09:51:49 +01:00
var verseTitle = string . IsNullOrWhiteSpace ( verseHost ? . Title ) ? "Unknown Verse" : verseHost . Title ;
var verseId = string . IsNullOrWhiteSpace ( verseHost ? . VerseId ) ? "unknown" : verseHost . VerseId ;
var verseVisibility = string . IsNullOrWhiteSpace ( verseHost ? . Visibility ) ? "unknown" : verseHost . Visibility ;
var verseMeshAddress = string . IsNullOrWhiteSpace ( verseHost ? . CultMeshAddress ) ? "unpublished" : verseHost . CultMeshAddress ;
var verseLabel = string . Equals ( verseTitle , verseId , StringComparison . Ordinal )
? verseTitle
: $"{verseTitle} ({verseId})" ;
2026-06-17 04:57:56 +01:00
if ( ! inGame )
{
2026-06-17 10:39:28 +01:00
if ( ! stateBoot . SupportsLocalStateFileRead )
{
cardChildren . Add ( Text (
$"{MainSurfaceId}.continue.note" ,
stateBoot . FailureMessage ) ) ;
}
else if ( continueRun = = null )
2026-06-17 04:57:56 +01:00
{
cardChildren . Add ( Text (
$"{MainSurfaceId}.continue.note" ,
2026-06-17 09:51:49 +01:00
$"No typed run state is available in {verseLabel} yet. Start a new galaxy or connect to a Verse that already has one." ) ) ;
2026-06-17 04:57:56 +01:00
}
else
{
commands . Add ( new EveCommandTemplate ( ContinueRunCommand , "Continue" , "unity-uitoolkit" ) ) ;
actionButtons . Add ( Button ( $"{MainSurfaceId}.continue" , "Continue" , ContinueRunCommand ) ) ;
cardChildren . Add ( Metric (
$"{MainSurfaceId}.continue.run" ,
"Latest Run" ,
continueRun . RunId ) ) ;
}
}
2026-06-17 10:39:28 +01:00
cardChildren . Add ( Metric (
$"{MainSurfaceId}.target.title" ,
"Client Target" ,
targetLabel ) ) ;
cardChildren . Add ( Metric (
$"{MainSurfaceId}.target.transport" ,
"Transport" ,
targetKind ) ) ;
cardChildren . Add ( Metric (
$"{MainSurfaceId}.target.source" ,
"Target Source" ,
targetSource ) ) ;
2026-06-17 09:51:49 +01:00
cardChildren . Add ( Metric (
$"{MainSurfaceId}.verse.title" ,
"Verse" ,
verseLabel ) ) ;
cardChildren . Add ( Metric (
$"{MainSurfaceId}.verse.visibility" ,
"Visibility" ,
verseVisibility ) ) ;
cardChildren . Add ( Metric (
$"{MainSurfaceId}.verse.mesh" ,
"CultMesh" ,
verseMeshAddress ) ) ;
2026-06-17 04:57:56 +01:00
commands . Add ( new EveCommandTemplate ( NewGameCommand , "New Game" , "unity-uitoolkit" ) ) ;
commands . Add ( new EveCommandTemplate ( ShowSettingsCommand , "Settings" , "unity-uitoolkit" ) ) ;
commands . Add ( new EveCommandTemplate ( QuitCommand , "Quit" , "unity-uitoolkit" ) ) ;
actionButtons . Add ( Button ( $"{MainSurfaceId}.newGame" , "New Game" , NewGameCommand ) ) ;
actionButtons . Add ( Button ( $"{MainSurfaceId}.settings" , "Settings" , ShowSettingsCommand ) ) ;
actionButtons . Add ( Button ( $"{MainSurfaceId}.quit" , "Quit" , QuitCommand ) ) ;
cardChildren . Add ( Text (
$"{MainSurfaceId}.note" ,
2026-06-17 10:39:28 +01:00
$"The client lowers this shell through Eve. The client target chooses which Verse it follows; game truth belongs to the daemon serving {verseLabel}." ) ) ;
2026-06-17 04:57:56 +01:00
cardChildren . Add ( ButtonColumn ( $"{MainSurfaceId}.actions" , actionButtons . ToArray ( ) ) ) ;
return BuildMenuSurfaceDocument (
MainSurfaceId ,
"Aetheria Terminus" ,
commands ,
Card (
$"{MainSurfaceId}.card" ,
"Aetheria Terminus" ,
cardChildren . ToArray ( ) ) ) ;
}
2026-06-16 21:08:20 +01:00
private static EveSurfaceDocument BuildSettingsSurfaceDefinition ( )
{
return BuildMenuSurfaceDocument (
SettingsSurfaceId ,
"Aetheria Settings" ,
new [ ]
{
new EveCommandTemplate ( ShowPlayerSettingsCommand , "Player Settings" , "unity-uitoolkit" ) ,
2026-06-17 11:24:36 +01:00
new EveCommandTemplate ( ShowVerseSettingsCommand , "Verse" , "unity-uitoolkit" ) ,
2026-06-16 21:08:20 +01:00
new EveCommandTemplate ( ShowInputSettingsCommand , "Input" , "unity-uitoolkit" ) ,
new EveCommandTemplate ( BackToMainCommand , "Back" , "unity-uitoolkit" )
} ,
Card (
"aetheria.mainMenu.settings.card" ,
"Settings" ,
Text (
"aetheria.mainMenu.settings.note" ,
2026-06-17 11:24:36 +01:00
"Player settings, client target selection, and input rebinding all lower through typed Eve surfaces. Audio still has no typed owner." ) ,
2026-06-16 21:08:20 +01:00
ButtonRow (
"aetheria.mainMenu.settings.actions" ,
Button ( "aetheria.mainMenu.settings.playerSettings" , "Player Settings" , ShowPlayerSettingsCommand ) ,
2026-06-17 11:24:36 +01:00
Button ( "aetheria.mainMenu.settings.verse" , "Verse" , ShowVerseSettingsCommand ) ,
2026-06-16 21:08:20 +01:00
Button ( "aetheria.mainMenu.settings.input" , "Input" , ShowInputSettingsCommand ) ,
Button ( "aetheria.mainMenu.settings.back" , "Back" , BackToMainCommand ) ) ) ) ;
}
2026-06-17 06:10:41 +01:00
private bool CanOpenRuntimeInputScreen ( )
{
return InGame & &
ActionGameManager . Instance ! = null & &
ActionGameManager . Instance . CanShowInputScreenFromMenu ( ) & &
ActionGameManager . Instance . InputDisplayLayout ! = null ;
}
private bool TryOpenRuntimeInputScreen ( )
{
if ( ! CanOpenRuntimeInputScreen ( ) )
return false ;
HideMenuSurface ( ) ;
gameObject . SetActive ( false ) ;
ActionGameManager . Instance . ShowInputScreenFromMenu ( ) ;
return true ;
}
2026-06-17 11:24:36 +01:00
private static bool TryCommitClientTargetCommand ( string command , IReadOnlyDictionary < string , string > payload )
{
if ( ! AetheriaRuntimeClientTargetCommands . IsKnown ( command ) )
return false ;
if ( string . Equals ( command , AetheriaRuntimeClientTargetCommands . Refresh , StringComparison . Ordinal ) )
return true ;
try
{
var gameDataDirectory = ActionGameManager . GameDataDirectory ;
var clientTargetPath = AetheriaRuntimeStateBoundary . GetClientTargetPath ( gameDataDirectory ) ;
var defaultStateFilePath = AetheriaRuntimeStateBoundary . GetStateFilePath ( gameDataDirectory ) ;
AetheriaRuntimeClientTargetStore . Update (
clientTargetPath ,
defaultStateFilePath ,
document = >
{
switch ( command )
{
case var _ when string . Equals ( command , AetheriaRuntimeClientTargetCommands . CycleTargetKind , StringComparison . Ordinal ) :
document . TargetKind = string . Equals ( document . TargetKind , AetheriaRuntimeClientTargetKinds . CultMeshVerse , StringComparison . Ordinal )
? AetheriaRuntimeClientTargetKinds . StateFile
: AetheriaRuntimeClientTargetKinds . CultMeshVerse ;
break ;
case var _ when string . Equals ( command , AetheriaRuntimeClientTargetCommands . SetTitle , StringComparison . Ordinal ) :
document . Title = ReadPayloadValue ( payload , "value" ) ;
break ;
case var _ when string . Equals ( command , AetheriaRuntimeClientTargetCommands . SetVerseId , StringComparison . Ordinal ) :
document . VerseId = ReadPayloadValue ( payload , "value" ) ;
break ;
case var _ when string . Equals ( command , AetheriaRuntimeClientTargetCommands . SetCultMeshAddress , StringComparison . Ordinal ) :
document . CultMeshAddress = ReadPayloadValue ( payload , "value" ) ;
break ;
case var _ when string . Equals ( command , AetheriaRuntimeClientTargetCommands . SetStateFilePath , StringComparison . Ordinal ) :
document . StateFilePath = ReadPayloadValue ( payload , "value" ) ;
break ;
}
if ( string . IsNullOrWhiteSpace ( document . TargetKind ) )
document . TargetKind = AetheriaRuntimeClientTargetKinds . StateFile ;
if ( string . IsNullOrWhiteSpace ( document . StateFilePath ) )
document . StateFilePath = defaultStateFilePath ;
document . UpdatedAtUtc = DateTime . UtcNow . ToString ( "O" ) ;
} ) ;
return true ;
}
catch ( Exception ex )
{
Debug . LogError ( $"Failed to update typed Aetheria client target: {ex}" ) ;
return true ;
}
}
private static bool TryQueueVerseHostCommand ( string command , IReadOnlyDictionary < string , string > payload )
{
if ( ! AetheriaRuntimeVerseHostCommands . IsKnown ( command ) )
return false ;
var stateBoot = CurrentStateBoot ( ) ;
if ( ! stateBoot . SupportsLocalStateFileRead | | ! stateBoot . StateFileExists )
{
Debug . LogWarning (
$"Cannot queue Verse-host command '{command}' because the active target is not a readable local Verse state file." ) ;
return true ;
}
try
{
var request = new EveSurfaceCommandRequest (
"aetheria" ,
AetheriaRuntimeVerseHostCommands . SurfaceId ,
command ,
payload ? . ToDictionary ( entry = > entry . Key , entry = > entry . Value ? ? "" , StringComparer . Ordinal )
? ? new Dictionary < string , string > ( StringComparer . Ordinal ) ,
DateTimeOffset . UtcNow ,
"unity-main-menu" ) ;
var envelope = AetheriaRuntimeEveCommandLog . QueueCommand ( stateBoot . StateFilePath , request ) ;
Debug . Log ( $"Queued Aetheria Verse-host Eve command: {envelope.Path}" ) ;
return true ;
}
catch ( Exception ex )
{
Debug . LogError ( $"Failed to queue typed Aetheria Verse-host Eve command: {ex}" ) ;
return true ;
}
}
private static string ReadPayloadValue ( IReadOnlyDictionary < string , string > payload , string key )
{
return payload ! = null & & payload . TryGetValue ( key , out var value )
? value ? ? ""
: "" ;
}
2026-06-17 06:10:41 +01:00
private static EveSurfaceDocument BuildInputSettingsSurfaceDefinition ( bool canOpenRuntimeInputScreen , bool inGame )
2026-06-16 21:08:20 +01:00
{
2026-06-17 06:10:41 +01:00
var commands = new List < EveCommandTemplate >
{
new EveCommandTemplate ( BackToSettingsCommand , "Back" , "unity-uitoolkit" )
} ;
var cardChildren = new List < EveSurfaceComponent >
{
Metric (
"aetheria.mainMenu.input.bindingOverrides" ,
"Binding Overrides" ,
ActionGameManager . RuntimePlayerSettings . InputSettings . InputActionMap . Count . ToString ( ) ) ,
Metric (
"aetheria.mainMenu.input.actionBarInputs" ,
"Action-Bar Inputs" ,
ActionGameManager . RuntimePlayerSettings . InputSettings . ActionBarInputs . Count . ToString ( ) )
} ;
if ( canOpenRuntimeInputScreen )
{
commands . Insert ( 0 , new EveCommandTemplate ( OpenRuntimeInputScreenCommand , "Open Remap Screen" , "unity-uitoolkit" ) ) ;
cardChildren . Add ( Text (
"aetheria.mainMenu.input.note" ,
2026-06-17 08:41:06 +01:00
"The runtime Eve input screen owns low-level InputSystem rebinding and action-bar input edits. This title shell reports typed player-settings state and hands off to that owner." ) ) ;
2026-06-17 06:10:41 +01:00
}
else if ( inGame )
{
cardChildren . Add ( Text (
"aetheria.mainMenu.input.note" ,
2026-06-17 08:41:06 +01:00
"The runtime Eve input screen should own rebinding here, but this scene has no active input surface to hand off to." ) ) ;
2026-06-17 06:10:41 +01:00
}
else
{
cardChildren . Add ( Text (
"aetheria.mainMenu.input.note" ,
2026-06-17 08:41:06 +01:00
"This title shell reports the typed player-settings state. Launch a run to open the runtime Eve input screen that owns low-level InputSystem rebinding." ) ) ;
2026-06-17 06:10:41 +01:00
}
var buttons = new List < EveSurfaceComponent > ( ) ;
if ( canOpenRuntimeInputScreen )
{
buttons . Add ( Button ( "aetheria.mainMenu.input.openRuntimeScreen" , "Open Remap Screen" , OpenRuntimeInputScreenCommand ) ) ;
}
buttons . Add ( Button ( "aetheria.mainMenu.input.back" , "Back" , BackToSettingsCommand ) ) ;
cardChildren . Add ( ButtonRow ( "aetheria.mainMenu.input.actions" , buttons . ToArray ( ) ) ) ;
2026-06-16 21:08:20 +01:00
return BuildMenuSurfaceDocument (
InputSettingsSurfaceId ,
"Aetheria Input Settings" ,
2026-06-17 06:10:41 +01:00
commands ,
2026-06-16 21:08:20 +01:00
Card (
"aetheria.mainMenu.input.card" ,
"Input Settings" ,
2026-06-17 06:10:41 +01:00
cardChildren . ToArray ( ) ) ) ;
2026-06-16 21:08:20 +01:00
}
private static EveSurfaceDocument WithBackAction (
EveSurfaceDocument document ,
string surfaceId ,
string backCommand ,
string backLabel )
{
return new EveSurfaceDocument (
document . Type ,
document . Schema ,
document . ProviderId ,
document . ProviderKind ,
document . Title ,
document . Version ,
document . UpdatedAtUtc ,
new EveSurfaceTree (
surfaceId ,
Node (
$"{surfaceId}.root" ,
"surface" ,
Array . Empty < ( string Key , string Value ) > ( ) ,
document . Surface . Root ,
ButtonRow (
$"{surfaceId}.actions" ,
Button ( $"{surfaceId}.back" , backLabel , backCommand ) ) ) ,
document . Surface . Styles ) ,
document . Commands
. Concat ( new [ ]
{
new EveCommandTemplate ( backCommand , backLabel , "unity-uitoolkit" )
} )
. ToArray ( ) ) ;
}
private static EveSurfaceDocument BuildMenuSurfaceDocument (
string surfaceId ,
string title ,
IReadOnlyList < EveCommandTemplate > commands ,
params EveSurfaceComponent [ ] children )
{
return new EveSurfaceDocument (
MenuSurfaceType ,
MenuSurfaceSchema ,
MenuSurfaceProviderId ,
MenuSurfaceProviderKind ,
title ,
version : 1 ,
DateTime . UtcNow . ToString ( "O" ) ,
new EveSurfaceTree (
surfaceId ,
Node ( $"{surfaceId}.root" , "surface" , Array . Empty < ( string Key , string Value ) > ( ) , children ) ,
Array . Empty < EveStyleToken > ( ) ) ,
commands ) ;
}
private static EveSurfaceComponent Card (
string id ,
string title ,
params EveSurfaceComponent [ ] children )
{
return Node ( id , "card" , new [ ] { ( "title" , title ) } , children ) ;
}
private static EveSurfaceComponent Metric ( string id , string label , string value )
{
return Node ( id , "metric" , new [ ] { ( "label" , label ) , ( "value" , value ) } ) ;
}
private static EveSurfaceComponent Text ( string id , string value )
{
return Node ( id , "text" , new [ ] { ( "value" , value ) } ) ;
}
private static EveSurfaceComponent Button ( string id , string label , string command )
{
return Node ( id , "control.button" , new [ ] { ( "label" , label ) , ( "command" , command ) } ) ;
}
private static EveSurfaceComponent ButtonRow (
string id ,
params EveSurfaceComponent [ ] children )
{
return Node ( id , "row" , Array . Empty < ( string Key , string Value ) > ( ) , children ) ;
}
2026-06-17 04:57:56 +01:00
private static EveSurfaceComponent ButtonColumn (
string id ,
params EveSurfaceComponent [ ] children )
{
return Node ( id , "column" , Array . Empty < ( string Key , string Value ) > ( ) , children ) ;
}
2026-06-16 21:08:20 +01:00
private static EveSurfaceComponent Node (
string id ,
string kind ,
IEnumerable < ( string Key , string Value ) > props ,
params EveSurfaceComponent [ ] children )
{
return new EveSurfaceComponent (
id ,
kind ,
props . ToDictionary ( prop = > prop . Key , prop = > prop . Value , StringComparer . Ordinal ) ,
children ? ? Array . Empty < EveSurfaceComponent > ( ) ) ;
}
2026-06-16 19:05:00 +01:00
private static EveSurfaceDocument ToEveSurfaceDocument ( AetheriaRuntimeSurfaceDocument document )
{
return new EveSurfaceDocument (
"surface-state" ,
"gamecult.eve.surface.v1" ,
document . ProviderId ,
document . ProviderKind ,
document . Title ,
document . Version ,
document . UpdatedAtUtc ,
new EveSurfaceTree (
document . Surface . Id ,
ToEveSurfaceComponent ( document . Surface . Root ) ,
document . Surface . Styles
. Select ( style = > new EveStyleToken ( style . Name , style . Value ) )
. ToArray ( ) ) ,
document . Commands
. Select ( command = > new EveCommandTemplate ( command . Command , command . Label , command . Transport ) )
. ToArray ( ) ) ;
}
private static EveSurfaceComponent ToEveSurfaceComponent ( AetheriaRuntimeSurfaceComponent component )
{
return new EveSurfaceComponent (
component . Id ,
component . Kind ,
new Dictionary < string , string > ( component . Props , StringComparer . Ordinal ) ,
component . Children . Select ( ToEveSurfaceComponent ) . ToArray ( ) ) ;
}
private void OnDestroy ( )
{
2026-06-16 21:08:20 +01:00
if ( _menuSurfaceDocument ! = null )
2026-06-16 19:05:00 +01:00
{
2026-06-16 21:08:20 +01:00
Destroy ( _menuSurfaceDocument . gameObject ) ;
_menuSurfaceDocument = null ;
2026-06-16 19:05:00 +01:00
}
2026-06-15 22:47:15 +01:00
}
2021-03-13 03:23:58 +00:00
}