using System; using System.Linq; using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Verse; using GameCult.Eve.Surface; using TMPro; using UniRx; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityEngine.UIElements; public class MainMenu : MonoBehaviour { public VolumeCloudRenderer CloudRenderer; public GameSettings Settings; public ConfirmationDialog Dialog; public bool InGame; private UIDocument _menuSurfaceDocument; private readonly AetheriaEveUnitySurfaceChrome _menuSurfaceChrome = new AetheriaEveUnitySurfaceChrome { RootAlignItems = Align.Center, RootJustifyContent = Justify.Center, RootPaddingLeft = 24f, RootPaddingRight = 24f, RootPaddingTop = 24f, RootPaddingBottom = 24f, RootBackgroundColor = new Color(0f, 0f, 0f, 0.6f), Width = 560f, MinWidth = 0f, MaxWidth = 560f, PaddingLeft = 20f, PaddingRight = 20f, PaddingTop = 20f, PaddingBottom = 20f, BorderRadius = 0f, BorderWidth = 0f, BackgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.96f) }; void Start() { ShowMain(); } private void ShowMain() { var stateBoot = CurrentStateBoot(); var frame = LatestDaemonFrame(stateBoot); var verseHost = LatestVerseHostSettings(stateBoot); RenderMenuSurface( AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot( ProjectMainMenuSurfaceState( stateBoot, frame, verseHost, CanOpenRuntimeInputScreen(), InGame)), HandleMainSurfaceCommand); } private void HandleMainSurfaceCommand(EveSurfaceCommandRequest request) { if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)) { Debug.LogWarning("Unknown main menu command."); return; } switch (command.Kind) { case AetheriaRuntimeMainMenuCommandKind.ContinueRun: if (LatestDaemonFrame(CurrentStateBoot()) == null) { Debug.LogWarning("Main-menu Continue requested without an authoritative daemon frame."); ShowMain(); return; } ContinueGame(); return; case AetheriaRuntimeMainMenuCommandKind.NewGame: StartNewGame(); return; case AetheriaRuntimeMainMenuCommandKind.ShowSettings: ShowSettings(); return; case AetheriaRuntimeMainMenuCommandKind.Quit: Application.Quit(); return; default: Debug.LogWarning($"Unhandled main menu command kind: {command.Kind}"); return; } } private void StartNewGame() { if (!TryStartDaemonObservedGame("Starting Daemon Run")) { ShowMain(); } } private bool TryStartDaemonObservedGame(string title) { var stateBoot = CurrentStateBoot(); if (!AetheriaRuntimeStateReader.TryReadDaemonFrame(stateBoot.StateFilePath, out var frame) || frame == null || !frame.IsAuthoritative || frame.Run == null || frame.Run.Zones == null || frame.Run.Zones.Count == 0) { Debug.LogWarning($"Cannot start Aetheria observer scene without an authoritative daemon frame at {AetheriaRuntimeDaemonFrameStore.GetFramePath(stateBoot.StateFilePath)}."); return false; } var generatorState = "Loading runtime catalog"; Action setState = s => generatorState = s; HideMenuSurface(); Dialog.Clear(); Dialog.Title.text = title; Dialog.AddProperty(() => generatorState); Dialog.Show(); setState($"Observing daemon frame {frame.FrameId}"); ActionGameManager.IsTutorial = frame.Run.IsTutorial; var backgroundSettings = frame.Run.IsTutorial ? Settings.TutorialBackgroundSettings : Settings.SectorBackgroundSettings; backgroundSettings.NoisePosition = frame.Run.GenerationSeed == 0 ? 1 : frame.Run.GenerationSeed; ActionGameManager.ObservedGalaxy = Galaxy.ProjectObservedDaemonRun( frame.Run, backgroundSettings, ActionGameManager.RuntimeCatalog, Debug.Log); SceneManager.LoadScene("ARPG"); return true; } private static AetheriaRuntimeStateBootReport CurrentStateBoot() { return AetheriaRuntimeStateBoot.Inspect(ActionGameManager.GameDataDirectory); } private static AetheriaRuntimeDaemonFrameDocument LatestDaemonFrame(AetheriaRuntimeStateBootReport stateBoot) { if (!AetheriaRuntimeStateReader.TryReadDaemonFrame(stateBoot.StateFilePath, out var frame) || frame == null || !frame.IsAuthoritative || frame.Run == null || frame.Run.Zones == null || frame.Run.Zones.Count == 0) { return null; } return frame; } private static AetheriaRuntimeVerseHostSettingsSnapshot LatestVerseHostSettings(AetheriaRuntimeStateBootReport stateBoot) { if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists) return null; try { return AetheriaRuntimeStateReader.ReadVerseHostSettings(stateBoot.StateFilePath); } catch (Exception ex) { Debug.LogError($"Failed to read typed Aetheria Verse host settings for the main menu: {ex}"); return null; } } private void ContinueGame() { if (!TryStartDaemonObservedGame("Continuing Daemon Run")) { ShowMain(); } } private void ShowSettings() { RenderMenuSurface( AetheriaRuntimeMainMenuSurfaceBuilder.BuildSettings(DateTime.UtcNow.ToString("O")), HandleSettingsSurfaceCommand); } private void ShowInputSettings() { RenderMenuSurface( AetheriaRuntimeMainMenuSurfaceBuilder.BuildInputSettings( ProjectMainMenuSurfaceState( CurrentStateBoot(), null, null, CanOpenRuntimeInputScreen(), InGame)), HandleInputSettingsSurfaceCommand); } private void ShowPlayerSettingsSurface() { RenderMenuSurface( AetheriaRuntimeMainMenuSurfaceBuilder.BuildPlayerSettingsShell(ProjectPlayerSettingsSurfaceState()), HandlePlayerSettingsSurfaceCommand); } private void ShowVerseSettingsSurface() { RenderMenuSurface( AetheriaRuntimeMainMenuSurfaceBuilder.BuildVerseSettingsShell(ProjectVerseSettingsSurfaceState()), HandleVerseSettingsSurfaceCommand); } private void RenderMenuSurface( AetheriaRuntimeSurfaceDocument document, Action commandHandler) { _menuSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( transform, _menuSurfaceDocument, "Aetheria Menu Surface", document, commandHandler, _menuSurfaceChrome); } private void HandleSettingsSurfaceCommand(EveSurfaceCommandRequest request) { if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)) { Debug.LogWarning("Unknown settings menu command."); return; } switch (command.Kind) { case AetheriaRuntimeMainMenuCommandKind.ShowPlayerSettings: ShowPlayerSettingsSurface(); return; case AetheriaRuntimeMainMenuCommandKind.ShowVerseSettings: ShowVerseSettingsSurface(); return; case AetheriaRuntimeMainMenuCommandKind.ShowInputSettings: ShowInputSettings(); return; case AetheriaRuntimeMainMenuCommandKind.BackToMain: ShowMain(); return; default: Debug.LogWarning($"Unhandled settings menu command kind: {command.Kind}"); return; } } private void HandleInputSettingsSurfaceCommand(EveSurfaceCommandRequest request) { if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)) { Debug.LogWarning("Unknown input settings command."); return; } switch (command.Kind) { case AetheriaRuntimeMainMenuCommandKind.BackToSettings: ShowSettings(); return; case AetheriaRuntimeMainMenuCommandKind.OpenRuntimeInputScreen: if (!TryOpenRuntimeInputScreen()) { ShowInputSettings(); } return; default: Debug.LogWarning($"Unhandled input settings command kind: {command.Kind}"); return; } } private void HandlePlayerSettingsSurfaceCommand(EveSurfaceCommandRequest request) { if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)) { Debug.LogWarning("Unknown player-settings command."); return; } switch (command.Kind) { case AetheriaRuntimeMainMenuCommandKind.BackToSettings: ShowSettings(); return; case AetheriaRuntimeMainMenuCommandKind.PlayerSettingsCommand: SendKnownAetheriaEveCommand(request, "player-settings"); ShowPlayerSettingsSurface(); return; default: Debug.LogWarning($"Unhandled player-settings command kind: {command.Kind}"); return; } } private void HandleVerseSettingsSurfaceCommand(EveSurfaceCommandRequest request) { if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)) { Debug.LogWarning("Unknown verse-settings command."); return; } switch (command.Kind) { case AetheriaRuntimeMainMenuCommandKind.BackToSettings: ShowSettings(); return; case AetheriaRuntimeMainMenuCommandKind.ClientTargetCommand: RequestClientTargetCommand(request); ShowVerseSettingsSurface(); return; case AetheriaRuntimeMainMenuCommandKind.VerseHostCommand: SendKnownAetheriaEveCommand(request, "Verse-host"); ShowVerseSettingsSurface(); return; default: Debug.LogWarning($"Unhandled verse-settings command kind: {command.Kind}"); return; } } private void HideMenuSurface() { if (_menuSurfaceDocument == null) return; AetheriaEveUnitySurfaceHost.Hide(_menuSurfaceDocument); } private static AetheriaRuntimePlayerSettingsSurfaceState ProjectPlayerSettingsSurfaceState() { return new AetheriaRuntimePlayerSettingsSurfaceState( ActionGameManager.RuntimePlayerSettings.Name, ActionGameManager.RuntimePlayerSettings.TutorialPassed, "", ActionGameManager.RuntimePlayerSettings.GameplaySettings.TemperatureUnit.ToString(), Math.Max(0, ActionGameManager.RuntimePlayerSettings.GameplaySettings.SignificantDigits), ActionGameManager.RuntimePlayerSettings.GraphicsSettings.NebulaQuality.ToString(), ActionGameManager.RuntimePlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap, DateTime.UtcNow.ToString("O")); } private static AetheriaRuntimeClientTargetSurfaceState ProjectVerseSettingsSurfaceState() { var stateBoot = CurrentStateBoot(); var verseHost = LatestVerseHostSettings(stateBoot); return new AetheriaRuntimeClientTargetSurfaceState( stateBoot.TargetKind, stateBoot.Title, stateBoot.VerseId, stateBoot.CultMeshAddress, stateBoot.StateFilePath, stateBoot.ReplicaStateFilePath, string.Join(", ", stateBoot.DiscoveryEndpoints ?? Array.Empty()), stateBoot.DiscoveredVerses ?? Array.Empty(), stateBoot.LastDiscoveryAtUtc, stateBoot.LastDiscoveryError, stateBoot.LastReplicaSyncAtUtc, stateBoot.LastReplicaSyncError, 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")); } private static AetheriaRuntimeMainMenuSurfaceState ProjectMainMenuSurfaceState( AetheriaRuntimeStateBootReport stateBoot, AetheriaRuntimeDaemonFrameDocument daemonFrame, AetheriaRuntimeVerseHostSettingsSnapshot verseHost, bool canOpenRuntimeInputScreen, bool inGame) { return new AetheriaRuntimeMainMenuSurfaceState( stateBoot.TargetLabel, stateBoot.TargetKind, stateBoot.TargetSource, verseHost?.Title ?? stateBoot.Title, verseHost?.VerseId ?? stateBoot.VerseId, verseHost?.Visibility ?? "unknown", verseHost?.CultMeshAddress ?? stateBoot.CultMeshAddress, inGame, daemonFrame != null, daemonFrame?.Run?.RunId ?? "", daemonFrame?.FrameId ?? -1, ActionGameManager.RuntimePlayerSettings.InputSettings.InputActionMap.Count, ActionGameManager.RuntimePlayerSettings.InputSettings.ActionBarInputs.Count, canOpenRuntimeInputScreen, DateTime.UtcNow.ToString("O")); } 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; } private static void RequestClientTargetCommand(EveSurfaceCommandRequest request) { try { if (!AetheriaRuntimeClientTargetSurfaceCommands.TryRequest( AetheriaState.At(ActionGameManager.GameDataDirectory).ClientTarget, request, out _)) { return; } } catch (Exception ex) { Debug.LogError($"Failed to update typed Aetheria client target: {ex}"); } } private static void SendKnownAetheriaEveCommand( EveSurfaceCommandRequest request, string label) { if (request == null) return; var stateBoot = CurrentStateBoot(); if (!CanSendLocalEveCommand(stateBoot, label)) return; try { if (!AetheriaRuntimeEveCommands.TrySendKnownSurfaceCommand( stateBoot.StateFilePath, request, "unity-main-menu", out var submitted, out var error)) { Debug.LogError($"Failed to submit Aetheria {label} Eve command: {error}"); return; } Debug.Log($"Submitted Aetheria {label} Eve command: {submitted!.CommandId}"); } catch (Exception ex) { Debug.LogError($"Failed to send Aetheria {label} Eve command: {ex}"); } } private static bool CanSendLocalEveCommand( AetheriaRuntimeStateBootReport stateBoot, string label) { if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists) { Debug.LogWarning( $"Cannot send {label} command because the active target is not a readable local Verse state file."); return false; } return true; } private void OnDestroy() { if (_menuSurfaceDocument != null) { Destroy(_menuSurfaceDocument.gameObject); _menuSurfaceDocument = null; } } }