Aetheria/Assets/Scripts/Gameplay/AetheriaUnityGameplayBootShell.cs

138 lines
6.3 KiB
C#
Raw Normal View History

2026-06-25 18:09:33 +01:00
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System;
using GameCult.Aetheria.State.Verse;
using GameCult.Mesh;
2026-06-25 18:09:33 +01:00
public sealed class AetheriaUnityGameplayBootShell
{
public GameSettings Settings { get; set; }
public ZoneRenderer ZoneRenderer { get; set; }
public AetheriaUnityCockpitHudShell CockpitHudShell { get; set; }
public float TargetSpottedBlinkFrequency { get; set; }
public float TargetSpottedBlinkOffset { get; set; }
public Action<string> Log { get; set; }
public AetheriaUnityGameplayBootResult Boot()
{
var stateBoot = AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory);
Log?.Invoke($"Aetheria runtime target: {stateBoot.TargetLabel} via {stateBoot.TargetKind} ({stateBoot.TargetSource})");
Log?.Invoke($"Aetheria runtime id: {stateBoot.RuntimeId}");
Log?.Invoke($"Aetheria runtime state file: {stateBoot.StateFilePath}");
stateBoot = SyncRemoteReplicaBeforeBoot(stateBoot);
if (!stateBoot.SupportsLocalStateFileRead)
{
throw new InvalidOperationException(
$"Aetheria runtime target cannot read the daemon mirror state required for gameplay boot: {stateBoot.FailureMessage}");
}
if (!stateBoot.StateFileExists)
{
throw new InvalidOperationException(
$"Aetheria runtime state file is missing at {stateBoot.StateFilePath}; gameplay requires an authoritative daemon mirror.");
}
var runtimeState = AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId);
using var runtimeCatalogDocument = runtimeState.ReactiveCatalogSnapshot();
var runtimeCatalog = runtimeCatalogDocument.Current;
2026-06-25 18:09:33 +01:00
if (runtimeCatalog == null)
throw new InvalidOperationException("Aetheria typed runtime catalog is required before gameplay boot.");
using var sectorMapDocument = runtimeState.Reactive<AetheriaRuntimeSectorMapDocument>();
var sectorMap = sectorMapDocument.Current;
if (sectorMap == null)
throw new InvalidOperationException("Aetheria typed sector map is required before gameplay boot.");
2026-06-25 18:09:33 +01:00
Log?.Invoke($"Aetheria runtime catalog: {runtimeCatalog.Items.Count} items, {runtimeCatalog.Corporations.Count} corporations, {runtimeCatalog.NameFiles.Count} name files");
Log?.Invoke($"Aetheria observed sector-map frame: {sectorMap.FrameId}");
var backgroundSettings = sectorMap.IsTutorial
? Settings.TutorialBackgroundSettings
: Settings.SectorBackgroundSettings;
backgroundSettings.NoisePosition = sectorMap.GenerationSeed == 0 ? 1 : sectorMap.GenerationSeed;
2026-06-28 11:44:28 +01:00
var observedGalaxy = global::Galaxy.ProjectObservedSectorMap(
sectorMap,
backgroundSettings,
runtimeCatalog,
Log);
2026-06-25 18:09:33 +01:00
var itemManager = new ItemManager(
runtimeCatalog,
Settings.GameplaySettings,
message => Log?.Invoke(message));
var loadoutItemFactory = new AetheriaUnityLoadoutItemFactory(itemManager, runtimeCatalog);
ZoneRenderer.SetDroppedPickupItemFactory(loadoutItemFactory.CreateLoadoutItem);
2026-06-25 18:09:33 +01:00
ZoneRenderer.BodySettingsCollections = Settings.BodySettingsCollections;
ZoneRenderer.RenderSettings = AetheriaUnityRenderSettingsBridge.Build(
Settings,
TargetSpottedBlinkFrequency,
TargetSpottedBlinkOffset);
CockpitHudShell.SetRenderSettings(ZoneRenderer.RenderSettings);
if (!AetheriaUnityRuntimeClientProvider.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap)
ZoneRenderer.ShowAsteroidUI = false;
return new AetheriaUnityGameplayBootResult(
runtimeCatalog,
observedGalaxy,
2026-06-25 18:09:33 +01:00
itemManager,
loadoutItemFactory);
2026-06-25 18:09:33 +01:00
}
private AetheriaRuntimeStateBootReport SyncRemoteReplicaBeforeBoot(AetheriaRuntimeStateBootReport stateBoot)
{
if (!ShouldSyncRemoteReplicaBeforeBoot(stateBoot))
return stateBoot;
Log?.Invoke($"Aetheria remote Verse replica missing; syncing {stateBoot.TargetLabel} from {stateBoot.CultMeshAddress}");
try
{
var target = AetheriaState.At(AetheriaUnityRuntimePaths.GameDataDirectory)
.ClientTarget
.SyncReplica();
AetheriaUnityRuntimePaths.ClearRuntimeStateFilePathCache();
var refreshed = AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory);
Log?.Invoke($"Aetheria remote Verse replica synced: {refreshed.StateFilePath}");
if (!string.IsNullOrWhiteSpace(target.LastReplicaSyncAtUtc))
Log?.Invoke($"Aetheria remote Verse replica sync time: {target.LastReplicaSyncAtUtc}");
return refreshed;
}
catch (Exception ex)
{
AetheriaUnityRuntimePaths.ClearRuntimeStateFilePathCache();
throw new InvalidOperationException(
$"Aetheria remote Verse replica sync failed before gameplay boot: {ex.Message}",
ex);
}
}
private static bool ShouldSyncRemoteReplicaBeforeBoot(AetheriaRuntimeStateBootReport stateBoot)
{
return string.Equals(stateBoot.TargetSource, "client-target", StringComparison.Ordinal)
&& string.Equals(stateBoot.TargetKind, AetheriaRuntimeClientTargetKinds.CultMeshVerse, StringComparison.Ordinal)
&& (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists);
}
}
public readonly struct AetheriaUnityGameplayBootResult
{
public AetheriaUnityGameplayBootResult(
AetheriaRuntimeCatalogSnapshot runtimeCatalog,
Galaxy observedGalaxy,
2026-06-25 18:09:33 +01:00
ItemManager itemManager,
AetheriaUnityLoadoutItemFactory loadoutItemFactory)
2026-06-25 18:09:33 +01:00
{
RuntimeCatalog = runtimeCatalog;
ObservedGalaxy = observedGalaxy;
2026-06-25 18:09:33 +01:00
ItemManager = itemManager;
LoadoutItemFactory = loadoutItemFactory;
2026-06-25 18:09:33 +01:00
}
public AetheriaRuntimeCatalogSnapshot RuntimeCatalog { get; }
public Galaxy ObservedGalaxy { get; }
2026-06-25 18:09:33 +01:00
public ItemManager ItemManager { get; }
public AetheriaUnityLoadoutItemFactory LoadoutItemFactory { get; }
2026-06-25 18:09:33 +01:00
}