524 lines
22 KiB
C#
524 lines
22 KiB
C#
using Aetheria.State;
|
|
using Aetheria.State.Documents;
|
|
using GameCult.Aetheria.State.Verse;
|
|
using GameCult.Caching;
|
|
|
|
internal static class AetheriaDaemonTerminusScenarios
|
|
{
|
|
public const string Standard = "standard";
|
|
public const string ReleasedClientProof = "released-client-proof";
|
|
public const string CargoCapacityRejectionProof = "cargo-capacity-rejection-proof";
|
|
|
|
public static string Parse(string? value)
|
|
{
|
|
var scenario = string.IsNullOrWhiteSpace(value) ? Standard : value.Trim().ToLowerInvariant();
|
|
return scenario switch
|
|
{
|
|
Standard => Standard,
|
|
ReleasedClientProof => ReleasedClientProof,
|
|
CargoCapacityRejectionProof => CargoCapacityRejectionProof,
|
|
_ => throw new ArgumentException(
|
|
$"Unknown Terminus scenario '{value}'. Expected {Standard}, {ReleasedClientProof}, or {CargoCapacityRejectionProof}.")
|
|
};
|
|
}
|
|
}
|
|
|
|
internal static class AetheriaDaemonZoneGenerator
|
|
{
|
|
public const string RunId = "local-terminus";
|
|
public const uint GenerationSeed = 0xA37E_2026u;
|
|
|
|
public static async Task WritePlayableRunAsync(
|
|
AetheriaStateNode node,
|
|
AetheriaRuntimeCatalogSnapshot catalog,
|
|
string now,
|
|
string scenario)
|
|
{
|
|
scenario = AetheriaDaemonTerminusScenarios.Parse(scenario);
|
|
var runId = RunIdFor(scenario);
|
|
var runKey = new CultRecordKey($"global:aetheria.run_state.{runId}.v1");
|
|
var zoneKey = new CultRecordKey($"global:aetheria.zone_state.{runId}.0.v1");
|
|
var corporationKeys = (catalog.Corporations ?? Array.Empty<AetheriaRuntimeCorporation>())
|
|
.Select(value => value.CorporationKey)
|
|
.Where(value => !string.IsNullOrWhiteSpace(value))
|
|
.ToArray();
|
|
if (corporationKeys.Length == 0)
|
|
{
|
|
throw new InvalidDataException(
|
|
"Terminus generation requires at least one typed corporation with a stable corporation key.");
|
|
}
|
|
var homeZones = corporationKeys.ToDictionary(value => value, _ => 0, StringComparer.Ordinal);
|
|
var availabilityFactions = new Dictionary<string, string>(StringComparer.Ordinal)
|
|
{
|
|
["player"] = corporationKeys.ElementAtOrDefault(0) ?? "",
|
|
["raider"] = corporationKeys.ElementAtOrDefault(1) ?? corporationKeys.ElementAtOrDefault(0) ?? "",
|
|
["neutral"] = corporationKeys.ElementAtOrDefault(2) ?? corporationKeys.ElementAtOrDefault(0) ?? ""
|
|
};
|
|
var rootRandom = new CultMath.Random(GenerationSeed);
|
|
var adjacency = new Dictionary<int, IReadOnlyList<int>> { [0] = Array.Empty<int>() };
|
|
var loadouts = availabilityFactions.Values
|
|
.Where(value => !string.IsNullOrWhiteSpace(value))
|
|
.Distinct(StringComparer.Ordinal)
|
|
.OrderBy(value => value, StringComparer.Ordinal)
|
|
.ToDictionary(
|
|
value => value,
|
|
_ => new AetheriaDaemonLoadoutGenerator(
|
|
catalog,
|
|
(uint)rootRandom.NextInt(1, int.MaxValue),
|
|
0,
|
|
homeZones,
|
|
adjacency),
|
|
StringComparer.Ordinal);
|
|
var entities = GenerateEntities(loadouts, availabilityFactions, catalog, scenario);
|
|
var entityKeys = Enumerable.Range(0, entities.Length)
|
|
.Select(index => EntityKey(scenario, 0, index))
|
|
.ToArray();
|
|
|
|
var settings = await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey)
|
|
.ReadAsync()
|
|
.ConfigureAwait(false) ?? new AetheriaPlayerSettings();
|
|
settings.ActiveRunKey = runKey.ToString();
|
|
settings.PlayerName = string.IsNullOrWhiteSpace(settings.PlayerName) ? "Terminus Pilot" : settings.PlayerName;
|
|
settings.LastUpdatedAtUtc = now;
|
|
await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey)
|
|
.ReplaceAsync(settings)
|
|
.ConfigureAwait(false);
|
|
|
|
await node.MutableDocument<AetheriaRunState>(runKey).ReplaceAsync(new AetheriaRunState
|
|
{
|
|
RunId = runId,
|
|
EntranceZoneIndex = 0,
|
|
ExitZoneIndex = 0,
|
|
CurrentZoneIndex = 0,
|
|
DiscoveredZoneIndices = [0],
|
|
ZoneKeys = [zoneKey.ToString()],
|
|
GenerationSeed = GenerationSeed,
|
|
CurrentEntityKey = entityKeys[1],
|
|
LifecyclePhase = AetheriaRuntimeRunLifecycle.Active,
|
|
TerminalFrameId = -1,
|
|
GameMode = AetheriaGameSessionState.TerminusMode,
|
|
UpdatedAtUtc = now
|
|
}).ConfigureAwait(false);
|
|
|
|
await node.MutableDocument<AetheriaZoneState>(zoneKey).ReplaceAsync(new AetheriaZoneState
|
|
{
|
|
Name = "Daemon Generated Terminus",
|
|
Position = Vec2(0, 0),
|
|
EntityKeys = entityKeys,
|
|
FactionIndices = [0, 1, 2],
|
|
OwnerFactionIndex = 0,
|
|
Orbits = GenerateOrbits(),
|
|
Bodies = GenerateBodies(),
|
|
GravityTerrainRadius = 1900,
|
|
GravityTerrainDepth = 7,
|
|
GravityTerrainDepthExponent = 1.18,
|
|
GravityTerrainBoundaryFog = 0.25,
|
|
GravityTerrainWaveFrequency = 0.55,
|
|
NextPickupIndex = 0,
|
|
DroppedPickups = Array.Empty<AetheriaDroppedPickupSnapshot>()
|
|
}).ConfigureAwait(false);
|
|
|
|
for (var i = 0; i < entities.Length; i++)
|
|
{
|
|
await node.MutableDocument<AetheriaEntitySnapshot>(new CultRecordKey(entityKeys[i]))
|
|
.ReplaceAsync(entities[i])
|
|
.ConfigureAwait(false);
|
|
}
|
|
|
|
await node.FlushAsync().ConfigureAwait(false);
|
|
}
|
|
|
|
public static string RunIdFor(string scenario)
|
|
{
|
|
scenario = AetheriaDaemonTerminusScenarios.Parse(scenario);
|
|
return string.Equals(scenario, AetheriaDaemonTerminusScenarios.Standard, StringComparison.Ordinal)
|
|
? RunId
|
|
: $"{RunId}-{scenario}";
|
|
}
|
|
|
|
public static string EntityKey(string scenario, int zoneIndex, int entityIndex)
|
|
{
|
|
return $"global:aetheria.run_state.{RunIdFor(scenario)}.zone.{zoneIndex}.entity.{entityIndex}.v1";
|
|
}
|
|
|
|
private static AetheriaOrbitSnapshot[] GenerateOrbits()
|
|
{
|
|
var bodies = BodyPlans();
|
|
return bodies.Select(body => new AetheriaOrbitSnapshot
|
|
{
|
|
OrbitKey = $"{body.Key}.orbit",
|
|
ParentOrbitKey = body.ParentKey.Length == 0 ? "" : $"{body.ParentKey}.orbit",
|
|
Distance = body.Distance,
|
|
Phase = body.Phase,
|
|
FixedPosition = Vec2(body.X, body.Z)
|
|
}).ToArray();
|
|
}
|
|
|
|
private static AetheriaBodySnapshot[] GenerateBodies()
|
|
{
|
|
return BodyPlans().Select(body => new AetheriaBodySnapshot
|
|
{
|
|
BodyKey = body.Key,
|
|
Kind = body.Kind,
|
|
Name = body.Name,
|
|
OrbitKey = $"{body.Key}.orbit",
|
|
Mass = body.Mass,
|
|
BodyRadiusMultiplier = body.BodyRadius,
|
|
GravityRadiusMultiplier = 1,
|
|
GravityDepthMultiplier = 1,
|
|
GravityDepthExponent = body.DepthExponent,
|
|
GravityInfluenceCenterX = body.X,
|
|
GravityInfluenceCenterZ = body.Z,
|
|
GravityInfluenceRadius = body.GravityRadius,
|
|
GravityWellDepth = body.GravityDepth,
|
|
GravityWaveRadius = body.WaveRadius,
|
|
GravityWaveDepth = body.WaveDepth,
|
|
GravityWaveSpeed = body.WaveSpeed,
|
|
Asteroids = body.Kind == "asteroid_belt" ? Asteroids(body.Distance) : [],
|
|
GasGiantVisual = GasGiantVisual(body),
|
|
SunVisual = SunVisual(body)
|
|
}).ToArray();
|
|
}
|
|
|
|
private static BodyPlan[] BodyPlans()
|
|
{
|
|
return
|
|
[
|
|
Body("local.sun", "", "sun", "Terminus", 0, 0, 1600, 2.8, 980, 92, 3.0, 480, 11, 1.8),
|
|
Body("local.inner", "local.sun", "planet", "Cairn", 250, 0.45, 110, 0.78, 230, 20, 2.2, 92, 3, 1.0),
|
|
Body("local.blue", "local.sun", "planet", "Blueglass", 440, 2.45, 260, 1.18, 360, 36, 2.55, 150, 5, 1.1),
|
|
Body("local.blue.moon", "local.blue", "moon", "Latch", 520, 2.78, 70, 0.58, 170, 15, 2.8, 70, 2.2, 1.7),
|
|
Body("local.ember", "local.sun", "planet", "Emberhook", 640, 3.78, 220, 1.05, 330, 31, 2.25, 128, 4.2, 1.3),
|
|
Body("local.ember.moon", "local.ember", "moon", "Ashwake", 735, 3.45, 86, 0.62, 185, 16, 2.7, 72, 2.4, 1.45),
|
|
Body("local.belt", "local.sun", "asteroid_belt", "Cinder Belt", 760, -0.28, 140, 1.0, 310, 24, 2.0, 122, 4, 0.8),
|
|
Body("local.green", "local.sun", "planet", "Marrow", 930, 1.28, 180, 0.96, 300, 26, 2.35, 112, 3.6, 1.0),
|
|
Body("local.outer", "local.sun", "gas_giant", "Vesper", 1120, 0.58, 420, 1.55, 500, 48, 2.4, 230, 7, 0.72),
|
|
Body("local.outer.moon.a", "local.outer", "moon", "Iris", 1240, 0.88, 92, 0.66, 195, 17, 2.75, 78, 2.5, 1.2),
|
|
Body("local.outer.moon.b", "local.outer", "moon", "Hush", 1025, 0.28, 76, 0.55, 180, 15, 2.9, 70, 2.1, 1.5),
|
|
Body("local.deep", "local.sun", "gas_giant", "Nacre", 1420, 5.72, 340, 1.36, 440, 41, 2.55, 200, 5.8, 0.95)
|
|
];
|
|
}
|
|
|
|
private static BodyPlan Body(
|
|
string key,
|
|
string parentKey,
|
|
string kind,
|
|
string name,
|
|
double distance,
|
|
double phase,
|
|
double mass,
|
|
double bodyRadius,
|
|
double gravityRadius,
|
|
double gravityDepth,
|
|
double depthExponent,
|
|
double waveRadius,
|
|
double waveDepth,
|
|
double waveSpeed)
|
|
{
|
|
if (gravityDepth <= 0)
|
|
throw new ArgumentOutOfRangeException(nameof(gravityDepth), "Gravity depth is an unsigned well magnitude; Ymir positive radial strength attracts.");
|
|
|
|
return new BodyPlan(
|
|
key,
|
|
parentKey,
|
|
kind,
|
|
name,
|
|
distance,
|
|
phase,
|
|
Math.Cos(phase) * distance,
|
|
Math.Sin(phase) * distance,
|
|
mass,
|
|
bodyRadius,
|
|
gravityRadius,
|
|
gravityDepth,
|
|
depthExponent,
|
|
waveRadius,
|
|
waveDepth,
|
|
waveSpeed);
|
|
}
|
|
|
|
private static AetheriaEntitySnapshot[] GenerateEntities(
|
|
IReadOnlyDictionary<string, AetheriaDaemonLoadoutGenerator> loadouts,
|
|
IReadOnlyDictionary<string, string> availabilityFactions,
|
|
AetheriaRuntimeCatalogSnapshot catalog,
|
|
string scenario)
|
|
{
|
|
var keys = Enumerable.Range(0, 12)
|
|
.Select(index => EntityKey(scenario, 0, index))
|
|
.ToArray();
|
|
|
|
var entities = new[]
|
|
{
|
|
Entity(loadouts, availabilityFactions, "Anchor Station", "station", -50, -30, 0, 0, "player", 760, keys[6], [keys[1], keys[2], keys[3], keys[4], keys[6], keys[7], keys[11]], 2, 900, AetheriaDaemonNativeCatalog.DockyardHullItemKey),
|
|
Entity(loadouts, availabilityFactions, "Vanguard One", "ship", -40, -30, 0, 0, "player", 540, keys[6], [keys[0], keys[2], keys[4], keys[6], keys[7]]),
|
|
Entity(loadouts, availabilityFactions, "Wing Two", "ship", 145, 125, -5, 7, "player", 450, keys[6], [keys[0], keys[1], keys[6], keys[8]]),
|
|
Entity(loadouts, availabilityFactions, "Torch Three", "ship", -235, 210, 8, -4, "player", 500, keys[7], [keys[0], keys[1], keys[7]]),
|
|
Entity(loadouts, availabilityFactions, "Foundry Tug", "ship", 330, -155, -2, 3, "player", 390, keys[6], [keys[0], keys[1], keys[6], keys[10]]),
|
|
Entity(loadouts, availabilityFactions, "Derelict Relay", "station", -390, 270, 0, 0, "neutral", 160, "", [keys[0], keys[3]]),
|
|
Entity(loadouts, availabilityFactions, "Ash Raider", "ship", 20, -30, -5, -2, "raider", 320, "", [keys[0], keys[1], keys[2], keys[4]]),
|
|
Entity(loadouts, availabilityFactions, "Cinder Knife", "ship", 585, -115, -7, 1, "raider", 280, "", [keys[0], keys[3]]),
|
|
Entity(loadouts, availabilityFactions, "Blackwake", "ship", 690, 300, -4, -3, "raider", 230, "", [keys[2]]),
|
|
Entity(loadouts, availabilityFactions, "Vesper Sloop", "ship", 960, 620, -3, -5, "raider", 250, "", [keys[4]]),
|
|
Entity(loadouts, availabilityFactions, "Survey Skiff", "ship", -610, -365, 3, 5, "neutral", 210, "", [keys[4], keys[5]]),
|
|
Entity(loadouts, availabilityFactions, "Lagrange Beacon", "station", 105, 510, 0, 0, "neutral", 190, "", [keys[0], keys[1], keys[6]])
|
|
};
|
|
foreach (var index in new[] { 1, 2, 3, 4 })
|
|
entities[index].HomeEntityKey = keys[0];
|
|
if (!string.Equals(scenario, AetheriaDaemonTerminusScenarios.Standard, StringComparison.Ordinal))
|
|
ApplyReleasedClientProofScenario(entities, catalog, scenario);
|
|
entities[2].AgentTaskCapabilities = ["attack", "defend", "explore"];
|
|
entities[3].AgentTaskCapabilities = ["attack", "defend"];
|
|
entities[4].AgentTaskCapabilities = ["mine", "haul", "tow", "explore"];
|
|
return entities;
|
|
}
|
|
|
|
private static void ApplyReleasedClientProofScenario(
|
|
AetheriaEntitySnapshot[] entities,
|
|
AetheriaRuntimeCatalogSnapshot catalog,
|
|
string scenario)
|
|
{
|
|
// Start the proof pilot at rest on the station's authored center. The proof session owns
|
|
// its paused initial clock, so client connection latency cannot alter this starting state.
|
|
entities[1].Position.X = entities[0].Position.X;
|
|
entities[1].Position.Z = entities[0].Position.Z;
|
|
entities[1].Velocity = Vec2(0, 0);
|
|
var playerFactionKey = entities[1].FactionKey;
|
|
for (var index = 0; index < entities.Length; index++)
|
|
if (index != 6)
|
|
entities[index].FactionKey = playerFactionKey;
|
|
entities[1].CargoContents = [new AetheriaCargoBayLoadout()];
|
|
entities[6].StatGrids =
|
|
[
|
|
StatGrid("hull", 1),
|
|
StatGrid("shield", 0),
|
|
StatGrid("heat", 0)
|
|
];
|
|
var salvage = entities[6].Equipment.FirstOrDefault()
|
|
?? throw new InvalidOperationException("The starter raider loadout produced no canonical salvage item.");
|
|
var generation = entities[6].LoadoutGeneration
|
|
?? throw new InvalidOperationException("The starter raider loadout produced no generation receipt.");
|
|
var selections = generation.Selections ?? Array.Empty<AetheriaLoadoutGenerationSelection>();
|
|
var salvageSelection = selections.First(selection =>
|
|
string.Equals(selection.Role, "equipment", StringComparison.Ordinal) &&
|
|
string.Equals(selection.ItemKey, salvage.ItemKey, StringComparison.Ordinal));
|
|
entities[6].CargoContents =
|
|
[
|
|
new AetheriaCargoBayLoadout
|
|
{
|
|
Items =
|
|
[
|
|
new AetheriaLoadoutItemSlot
|
|
{
|
|
Item = new AetheriaLoadoutItem
|
|
{
|
|
ItemKey = salvage.ItemKey,
|
|
Quality = salvage.Quality,
|
|
Durability = salvage.Durability,
|
|
Quantity = 1,
|
|
Enabled = true
|
|
}
|
|
}
|
|
]
|
|
}
|
|
];
|
|
entities[6].Equipment = Array.Empty<AetheriaEntityItemSlot>();
|
|
entities[6].WeaponGroups = Array.Empty<AetheriaWeaponGroupSnapshot>();
|
|
generation.Selections = selections
|
|
.Where(selection => string.Equals(selection.Role, "hull", StringComparison.Ordinal))
|
|
.Append(new AetheriaLoadoutGenerationSelection
|
|
{
|
|
Role = "cargo",
|
|
ItemKey = salvageSelection.ItemKey,
|
|
ManufacturerKey = salvageSelection.ManufacturerKey,
|
|
Price = salvageSelection.Price,
|
|
ManufacturerDistance = salvageSelection.ManufacturerDistance,
|
|
Allegiance = salvageSelection.Allegiance
|
|
})
|
|
.ToArray();
|
|
|
|
if (string.Equals(scenario, AetheriaDaemonTerminusScenarios.CargoCapacityRejectionProof, StringComparison.Ordinal))
|
|
FillCargoBays(entities[1], salvage.ItemKey, catalog);
|
|
}
|
|
|
|
private static void FillCargoBays(
|
|
AetheriaEntitySnapshot entity,
|
|
string itemKey,
|
|
AetheriaRuntimeCatalogSnapshot catalog)
|
|
{
|
|
var item = catalog.FindItem(itemKey)
|
|
?? throw new InvalidOperationException($"The cargo-capacity proof item '{itemKey}' is absent from the typed catalog.");
|
|
if (item.Volume <= 0)
|
|
throw new InvalidOperationException($"The cargo-capacity proof item '{itemKey}' must have positive volume.");
|
|
|
|
var cargoBays = (entity.CargoBays ?? Array.Empty<AetheriaEntityItemSlot>())
|
|
.Select(slot => catalog.FindItem(slot.ItemKey))
|
|
.Where(candidate => candidate != null && candidate.InteriorOccupiedCells > 0)
|
|
.ToArray();
|
|
if (cargoBays.Length == 0)
|
|
throw new InvalidOperationException("The cargo-capacity proof ship has no generated cargo bay.");
|
|
|
|
entity.CargoContents = cargoBays.Select((bay, index) =>
|
|
{
|
|
var quantity = (int)Math.Floor(bay!.InteriorOccupiedCells / item.Volume);
|
|
if (quantity <= 0)
|
|
throw new InvalidOperationException($"Cargo bay {index} cannot contain one '{itemKey}' proof item.");
|
|
return new AetheriaCargoBayLoadout
|
|
{
|
|
Items =
|
|
[
|
|
new AetheriaLoadoutItemSlot
|
|
{
|
|
Item = new AetheriaLoadoutItem
|
|
{
|
|
ItemKey = itemKey,
|
|
Quantity = quantity,
|
|
Quality = 1,
|
|
Durability = 1,
|
|
Enabled = true
|
|
}
|
|
}
|
|
]
|
|
};
|
|
}).ToArray();
|
|
}
|
|
|
|
private static AetheriaEntitySnapshot Entity(
|
|
IReadOnlyDictionary<string, AetheriaDaemonLoadoutGenerator> loadouts,
|
|
IReadOnlyDictionary<string, string> availabilityFactions,
|
|
string name,
|
|
string kind,
|
|
double x,
|
|
double z,
|
|
double vx,
|
|
double vy,
|
|
string faction,
|
|
double visibility,
|
|
string target,
|
|
string[] contactKeys,
|
|
int securityLevel = 0,
|
|
double securityRadius = 0,
|
|
string hullItemKey = "")
|
|
{
|
|
var availabilityFaction = availabilityFactions[faction];
|
|
var loadout = loadouts[availabilityFaction].Build(kind, availabilityFaction, hullItemKey);
|
|
var entity = AetheriaDaemonGeneratedEntityFactory.Create(
|
|
name,
|
|
kind,
|
|
availabilityFaction,
|
|
loadout,
|
|
x,
|
|
z,
|
|
securityLevel: securityLevel,
|
|
securityRadius: securityRadius);
|
|
entity.Direction = Vec2(vx == 0 && vy == 0 ? 0 : vx, vx == 0 && vy == 0 ? 1 : vy);
|
|
entity.LookDirection = Vec2(vx == 0 && vy == 0 ? 0 : vx, vx == 0 && vy == 0 ? 1 : vy);
|
|
entity.Velocity = Vec2(vx, vy);
|
|
entity.Visibility = visibility;
|
|
entity.TargetEntityKey = target;
|
|
entity.Contacts = contactKeys.Select(key => new AetheriaEntityContactSnapshot
|
|
{
|
|
TargetEntityKey = key,
|
|
InfoGathered = 1
|
|
}).ToArray();
|
|
if (string.Equals(faction, "raider", StringComparison.OrdinalIgnoreCase))
|
|
entity.StatGrids[0].Values[0] = 85;
|
|
return entity;
|
|
}
|
|
|
|
private static AetheriaGasGiantVisualState GasGiantVisual(BodyPlan body)
|
|
{
|
|
if (body.Kind != "gas_giant" && body.Kind != "sun")
|
|
return new AetheriaGasGiantVisualState();
|
|
|
|
return new AetheriaGasGiantVisualState
|
|
{
|
|
FirstOffsetDomainRotationSpeed = body.Kind == "sun" ? 5 : 0,
|
|
FirstOffsetRotationSpeed = 5,
|
|
SecondOffsetRotationSpeed = 10,
|
|
SecondOffsetDomainRotationSpeed = -25,
|
|
AlbedoRotationSpeed = -3,
|
|
WaveRadiusMultiplier = 1,
|
|
WaveDepthMultiplier = 1,
|
|
Colors =
|
|
[
|
|
Color(0.85, 0.32, 0.18, 0),
|
|
Color(0.18, 0.54, 0.92, 0.5),
|
|
Color(0.92, 0.78, 0.38, 1)
|
|
]
|
|
};
|
|
}
|
|
|
|
private static AetheriaSunVisualState SunVisual(BodyPlan body)
|
|
{
|
|
return body.Kind == "sun"
|
|
? new AetheriaSunVisualState
|
|
{
|
|
LightColor = Vec3(1, 0.86, 0.58),
|
|
FogTintColor = Vec3(0.32, 0.52, 1),
|
|
LightRadiusMultiplier = 1.8
|
|
}
|
|
: new AetheriaSunVisualState();
|
|
}
|
|
|
|
private static AetheriaAsteroidSnapshot[] Asteroids(double orbitDistance)
|
|
{
|
|
return Enumerable.Range(0, 18)
|
|
.Select(index =>
|
|
{
|
|
var phase = index * Math.Tau / 18.0;
|
|
return new AetheriaAsteroidSnapshot
|
|
{
|
|
Distance = orbitDistance + Math.Sin(phase * 3.0) * 42.0,
|
|
Phase = phase,
|
|
Size = 0.65 + (index % 5) * 0.24,
|
|
RotationSpeed = (index % 2 == 0 ? 1 : -1) * (0.04 + index * 0.006)
|
|
};
|
|
})
|
|
.ToArray();
|
|
}
|
|
|
|
private static AetheriaEntityStatGrid StatGrid(string name, double value)
|
|
{
|
|
return new AetheriaEntityStatGrid
|
|
{
|
|
Name = name,
|
|
Width = 1,
|
|
Height = 1,
|
|
Values = [value]
|
|
};
|
|
}
|
|
|
|
private static AetheriaColor Color(double x, double y, double z, double w)
|
|
{
|
|
return new AetheriaColor { X = x, Y = y, Z = z, W = w };
|
|
}
|
|
|
|
private static AetheriaVector2 Vec2(double x, double y)
|
|
{
|
|
return new AetheriaVector2 { X = x, Y = y };
|
|
}
|
|
|
|
private static AetheriaVector3 Vec3(double x, double y, double z)
|
|
{
|
|
return new AetheriaVector3 { X = x, Y = y, Z = z };
|
|
}
|
|
|
|
private sealed record BodyPlan(
|
|
string Key,
|
|
string ParentKey,
|
|
string Kind,
|
|
string Name,
|
|
double Distance,
|
|
double Phase,
|
|
double X,
|
|
double Z,
|
|
double Mass,
|
|
double BodyRadius,
|
|
double GravityRadius,
|
|
double GravityDepth,
|
|
double DepthExponent,
|
|
double WaveRadius,
|
|
double WaveDepth,
|
|
double WaveSpeed);
|
|
|
|
}
|