Aetheria/Aetheria.State.Daemon/AetheriaDaemonRegularRunWriter.cs
2026-07-17 11:09:24 +01:00

134 lines
6.9 KiB
C#

using Aetheria.State;
using Aetheria.State.Documents;
using GameCult.Aetheria.State.Verse;
using GameCult.Caching;
public static class AetheriaDaemonRegularRunWriter
{
public static async Task<AetheriaDaemonWrittenRun> WriteAsync(
AetheriaStateNode node,
AetheriaRuntimeCatalogSnapshot catalog,
string now,
uint generationSeed,
AetheriaDaemonRegularTopologySettings? topologySettings = null)
{
if (node == null) throw new ArgumentNullException(nameof(node));
if (catalog == null) throw new ArgumentNullException(nameof(catalog));
if (generationSeed == 0) throw new ArgumentOutOfRangeException(nameof(generationSeed));
var factions = AetheriaDaemonRegularTopologyGenerator.ResolveFossilFactions(catalog);
var world = AetheriaDaemonTutorialWorldGenerator.GenerateRegular(
factions, generationSeed, topologySettings);
var materialized = AetheriaDaemonTutorialWorldMaterializer.Materialize(
world, factions, catalog, isPrelude: false, identityPrefix: "regular");
var runId = $"sector-{generationSeed:x8}";
var runKey = RunKey(runId);
var corporationIndices = (catalog.Corporations ?? Array.Empty<AetheriaRuntimeCorporation>())
.Select((corporation, index) => (corporation.CorporationKey, index))
.Where(value => !string.IsNullOrWhiteSpace(value.CorporationKey))
.ToDictionary(value => value.CorporationKey, value => value.index, StringComparer.Ordinal);
var zoneKeys = materialized.Topology.Zones
.OrderBy(zone => zone.ZoneIndex)
.Select(zone => ZoneKey(runId, zone.ZoneIndex))
.ToArray();
var playerEntityKey = EntityKey(runId, materialized.PlayerZoneIndex, materialized.PlayerEntityIndex);
foreach (var zone in materialized.Zones.Values.OrderBy(zone => zone.Topology.ZoneIndex))
{
var entityKeys = Enumerable.Range(0, zone.Entities.Length)
.Select(index => EntityKey(runId, zone.Topology.ZoneIndex, index))
.ToArray();
await node.MutableDocument<AetheriaZoneState>(new CultRecordKey(ZoneKey(runId, zone.Topology.ZoneIndex)))
.ReplaceAsync(new AetheriaZoneState
{
Name = zone.Topology.Name,
Position = new AetheriaVector2 { X = zone.Topology.X, Y = zone.Topology.Y },
AdjacentZoneIndices = zone.Topology.AdjacentZoneIndices.ToArray(),
FactionIndices = zone.Topology.FactionKeys.Select(key => corporationIndices[key]).ToArray(),
OwnerFactionIndex = string.IsNullOrWhiteSpace(zone.Topology.OwnerFactionKey)
? -1
: corporationIndices[zone.Topology.OwnerFactionKey],
EntityKeys = entityKeys,
Orbits = zone.Orbits,
Bodies = zone.CelestialPlan.Bodies,
GravityTerrainRadius = zone.CelestialPlan.Radius,
GravityTerrainDepth = zone.CelestialPlan.GravityTerrainDepth,
GravityTerrainDepthExponent = zone.CelestialPlan.GravityTerrainDepthExponent,
GravityTerrainBoundaryFog = zone.CelestialPlan.GravityTerrainBoundaryFog,
GravityTerrainWaveFrequency = 1,
DroppedPickups = [],
NextPickupIndex = 0
}).ConfigureAwait(false);
for (var entityIndex = 0; entityIndex < zone.Entities.Length; entityIndex++)
{
await node.MutableDocument<AetheriaEntitySnapshot>(new CultRecordKey(entityKeys[entityIndex]))
.ReplaceAsync(zone.Entities[entityIndex])
.ConfigureAwait(false);
}
}
await node.MutableDocument<AetheriaRunState>(new CultRecordKey(runKey)).ReplaceAsync(new AetheriaRunState
{
RunId = runId,
IsTutorial = false,
GameMode = AetheriaGameSessionState.AetheriaMode,
EntranceZoneIndex = materialized.Topology.EntranceZoneIndex,
ExitZoneIndex = materialized.Topology.ExitZoneIndex,
CurrentZoneIndex = materialized.PlayerZoneIndex,
DiscoveredZoneIndices = materialized.Topology.DiscoveredZoneIndices.ToArray(),
ZoneKeys = zoneKeys,
FactionRelationships = materialized.Topology.HomeZoneByFactionKey.Keys
.Select(key => new AetheriaFactionRelationshipState
{
FactionKey = key,
Relationship = "Neutral",
Standing = 0
})
.ToArray(),
HomeZones = materialized.Topology.HomeZoneByFactionKey
.OrderBy(pair => pair.Key, StringComparer.Ordinal)
.Select(pair => new AetheriaFactionZoneState
{
FactionIndex = corporationIndices[pair.Key],
ZoneIndex = pair.Value
})
.ToArray(),
BossZones = materialized.Topology.BossZoneByFactionKey
.OrderBy(pair => pair.Key, StringComparer.Ordinal)
.Select(pair => new AetheriaFactionZoneState
{
FactionIndex = corporationIndices[pair.Key],
ZoneIndex = pair.Value
})
.ToArray(),
GenerationSeed = generationSeed,
CurrentEntityKey = playerEntityKey,
LifecyclePhase = AetheriaRuntimeRunLifecycle.Active,
TerminalFrameId = -1,
AgentTasks = materialized.Zones.Values
.OrderBy(zone => zone.Topology.ZoneIndex)
.SelectMany(zone => zone.AgentTasks)
.ToArray(),
UpdatedAtUtc = now
}).ConfigureAwait(false);
var settings = await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey)
.ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings();
settings.ActiveRunKey = runKey;
settings.PlayerName = string.IsNullOrWhiteSpace(settings.PlayerName) ? "Pilot" : settings.PlayerName;
settings.LastUpdatedAtUtc = now;
await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey)
.ReplaceAsync(settings).ConfigureAwait(false);
await node.FlushAsync().ConfigureAwait(false);
return new AetheriaDaemonWrittenRun(runId, runKey, playerEntityKey, false)
{
SessionMode = AetheriaGameSessionState.AetheriaMode
};
}
public static string RunKey(string runId) => $"global:aetheria.run_state.{runId}.v1";
public static string ZoneKey(string runId, int zoneIndex) =>
$"global:aetheria.zone_state.{runId}.{zoneIndex}.v1";
public static string EntityKey(string runId, int zoneIndex, int entityIndex) =>
$"global:aetheria.run_state.{runId}.zone.{zoneIndex}.entity.{entityIndex}.v1";
}