600 lines
23 KiB
C#
600 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Aetheria.State.Documents;
|
|
using GameCult.Caching;
|
|
using MessagePack;
|
|
|
|
namespace Aetheria.State;
|
|
|
|
public sealed class AetheriaRuntimeCommitApplyReport
|
|
{
|
|
public int AppliedPlayerSettings { get; set; }
|
|
public int AppliedLoadoutTemplates { get; set; }
|
|
public int AppliedRunCheckpoints { get; set; }
|
|
public string[] AppliedPaths { get; set; } = [];
|
|
}
|
|
|
|
public static class AetheriaRuntimeCommitLogApplier
|
|
{
|
|
private const string CommitSchema = "gamecult.aetheria.runtime_commit.v1";
|
|
|
|
public static async Task<AetheriaRuntimeCommitApplyReport> ApplyPendingAsync(
|
|
AetheriaStateNode node,
|
|
bool deleteApplied = true)
|
|
{
|
|
if (node == null) throw new ArgumentNullException(nameof(node));
|
|
|
|
var pendingDirectory = node.StatePath + ".pending";
|
|
var report = new AetheriaRuntimeCommitApplyReport();
|
|
var applied = new List<string>();
|
|
|
|
if (!Directory.Exists(pendingDirectory))
|
|
return report;
|
|
|
|
foreach (var path in Directory.EnumerateFiles(pendingDirectory, "*.cc").OrderBy(path => path, StringComparer.Ordinal))
|
|
{
|
|
var command = ReadCommand(path);
|
|
switch (command.Kind)
|
|
{
|
|
case "player_settings":
|
|
await node.PutPlayerSettingsAsync(command.PlayerSettings ?? throw MissingPayload(command)).ConfigureAwait(false);
|
|
report.AppliedPlayerSettings++;
|
|
break;
|
|
case "loadout_template":
|
|
var loadout = command.LoadoutTemplate ?? throw MissingPayload(command);
|
|
await node.PutLoadoutTemplateAsync(LoadoutKey(loadout.Name), loadout).ConfigureAwait(false);
|
|
report.AppliedLoadoutTemplates++;
|
|
break;
|
|
case "run_checkpoint":
|
|
var run = command.RunState ?? throw MissingPayload(command);
|
|
var zoneKeys = new List<string>();
|
|
foreach (var zone in command.ZoneSnapshots)
|
|
{
|
|
var zoneKey = ZoneKey(run.RunId, zone.ZoneIndex);
|
|
var entityKeys = new List<string>();
|
|
foreach (var entity in zone.Entities)
|
|
{
|
|
var entityKey = EntityKey(run.RunId, zone.ZoneIndex, entity.EntityIndex);
|
|
entity.Snapshot.ChildEntityKeys = entity.Snapshot.ChildEntityKeys
|
|
.Select(key => key.StartsWith("pending:entity:", StringComparison.Ordinal)
|
|
&& int.TryParse(key["pending:entity:".Length..], out var childIndex)
|
|
? EntityKey(run.RunId, zone.ZoneIndex, childIndex).ToString()
|
|
: key)
|
|
.ToArray();
|
|
await node.PutEntitySnapshotAsync(entityKey, entity.Snapshot).ConfigureAwait(false);
|
|
entityKeys.Add(entityKey.ToString());
|
|
}
|
|
|
|
zone.State.EntityKeys = entityKeys.ToArray();
|
|
await node.PutZoneStateAsync(zoneKey, zone.State).ConfigureAwait(false);
|
|
zoneKeys.Add(zoneKey.ToString());
|
|
}
|
|
|
|
if (zoneKeys.Count > 0)
|
|
run.ZoneKeys = zoneKeys.ToArray();
|
|
await node.PutRunStateAsync(RunKey(run.RunId), run).ConfigureAwait(false);
|
|
report.AppliedRunCheckpoints++;
|
|
break;
|
|
default:
|
|
throw new InvalidDataException($"Unknown Aetheria runtime commit kind '{command.Kind}' in {path}.");
|
|
}
|
|
|
|
applied.Add(path);
|
|
if (deleteApplied)
|
|
File.Delete(path);
|
|
}
|
|
|
|
await node.FlushAsync().ConfigureAwait(false);
|
|
report.AppliedPaths = applied.ToArray();
|
|
return report;
|
|
}
|
|
|
|
private static PendingCommand ReadCommand(string path)
|
|
{
|
|
var reader = new MessagePackReader(File.ReadAllBytes(path));
|
|
var fields = reader.ReadArrayHeader();
|
|
var schema = fields > 0 ? ReadString(ref reader) : "";
|
|
if (!string.Equals(schema, CommitSchema, StringComparison.Ordinal))
|
|
throw new InvalidDataException($"Unexpected Aetheria runtime commit schema '{schema}' in {path}.");
|
|
|
|
var kind = fields > 1 ? ReadString(ref reader) : "";
|
|
var commandId = fields > 2 ? ReadString(ref reader) : "";
|
|
var createdAtUtc = fields > 3 ? ReadString(ref reader) : "";
|
|
if (fields > 4)
|
|
{
|
|
var command = kind switch
|
|
{
|
|
"player_settings" => PendingCommand.ForPlayerSettings(
|
|
kind,
|
|
commandId,
|
|
createdAtUtc,
|
|
ReadPlayerSettings(ref reader, createdAtUtc),
|
|
path),
|
|
"loadout_template" => PendingCommand.ForLoadoutTemplate(
|
|
kind,
|
|
commandId,
|
|
createdAtUtc,
|
|
ReadLoadoutTemplate(ref reader, createdAtUtc),
|
|
path),
|
|
"run_checkpoint" => PendingCommand.ForRunState(
|
|
kind,
|
|
commandId,
|
|
createdAtUtc,
|
|
ReadRunState(ref reader, createdAtUtc, out var zoneSnapshots),
|
|
zoneSnapshots,
|
|
path),
|
|
_ => throw new InvalidDataException($"Unknown Aetheria runtime commit kind '{kind}' in {path}.")
|
|
};
|
|
|
|
for (var field = 5; field < fields; field++)
|
|
reader.Skip();
|
|
|
|
return command;
|
|
}
|
|
|
|
return PendingCommand.Empty(kind, commandId, createdAtUtc, path);
|
|
}
|
|
|
|
private static AetheriaPlayerSettings ReadPlayerSettings(ref MessagePackReader reader, string updatedAtUtc)
|
|
{
|
|
var fields = reader.ReadArrayHeader();
|
|
return new AetheriaPlayerSettings
|
|
{
|
|
PlayerName = ReadFieldString(ref reader, fields, 0),
|
|
TutorialPassed = ReadFieldBool(ref reader, fields, 1),
|
|
StoryFileHashes = ReadStoryFileHashes(ref reader, fields, 2),
|
|
Gameplay = new AetheriaPlayerGameplaySettings
|
|
{
|
|
TemperatureUnit = ReadFieldString(ref reader, fields, 3),
|
|
SignificantDigits = ReadFieldInt32(ref reader, fields, 4)
|
|
},
|
|
Graphics = new AetheriaPlayerGraphicsSettings
|
|
{
|
|
NebulaQuality = ReadFieldString(ref reader, fields, 5),
|
|
ShowAsteroidsInMinimap = ReadFieldBool(ref reader, fields, 6)
|
|
},
|
|
Input = new AetheriaPlayerInputSettings
|
|
{
|
|
BindingOverrides = ReadBindings(ref reader, fields, 7),
|
|
ActionBarInputs = ReadStringArray(ref reader, fields, 8)
|
|
},
|
|
LastUpdatedAtUtc = updatedAtUtc
|
|
};
|
|
}
|
|
|
|
private static AetheriaLoadoutTemplate ReadLoadoutTemplate(ref MessagePackReader reader, string updatedAtUtc)
|
|
{
|
|
var fields = reader.ReadArrayHeader();
|
|
return new AetheriaLoadoutTemplate
|
|
{
|
|
Name = ReadFieldString(ref reader, fields, 0),
|
|
OwnerPlayerKey = ReadFieldString(ref reader, fields, 1),
|
|
RootEntity = fields > 2 ? ReadEntityLoadout(ref reader) : new AetheriaEntityLoadout(),
|
|
CreatedAtUtc = updatedAtUtc,
|
|
UpdatedAtUtc = updatedAtUtc
|
|
};
|
|
}
|
|
|
|
private static AetheriaEntityLoadout ReadEntityLoadout(ref MessagePackReader reader)
|
|
{
|
|
var fields = reader.ReadArrayHeader();
|
|
return new AetheriaEntityLoadout
|
|
{
|
|
Name = ReadFieldString(ref reader, fields, 0),
|
|
Kind = ReadFieldString(ref reader, fields, 1),
|
|
FactionKey = ReferenceKey("aetheria.corporation", ReadFieldString(ref reader, fields, 2)),
|
|
Hull = fields > 3 ? ReadLoadoutItem(ref reader) : new AetheriaLoadoutItem(),
|
|
Equipment = ReadItemSlots(ref reader, fields, 4),
|
|
CargoBays = ReadItemSlots(ref reader, fields, 5),
|
|
DockingBays = ReadItemSlots(ref reader, fields, 6),
|
|
CargoContents = ReadCargoBays(ref reader, fields, 7),
|
|
DockingBayContents = ReadCargoBays(ref reader, fields, 8),
|
|
DockingBayAssignments = ReadIntArray(ref reader, fields, 9),
|
|
WeaponGroups = ReadIntArrayArray(ref reader, fields, 10),
|
|
Children = ReadChildren(ref reader, fields, 11)
|
|
};
|
|
}
|
|
|
|
private static AetheriaLoadoutItem ReadLoadoutItem(ref MessagePackReader reader)
|
|
{
|
|
var fields = reader.ReadArrayHeader();
|
|
return new AetheriaLoadoutItem
|
|
{
|
|
ItemKey = ReferenceKey("aetheria.item_definition", ReadFieldString(ref reader, fields, 0)),
|
|
Quality = ReadFieldDouble(ref reader, fields, 1),
|
|
Durability = ReadFieldDouble(ref reader, fields, 2),
|
|
Quantity = ReadFieldInt32(ref reader, fields, 3)
|
|
};
|
|
}
|
|
|
|
private static AetheriaRunState ReadRunState(
|
|
ref MessagePackReader reader,
|
|
string updatedAtUtc,
|
|
out AetheriaPendingZoneSnapshot[] zoneSnapshots)
|
|
{
|
|
var fields = reader.ReadArrayHeader();
|
|
var run = new AetheriaRunState
|
|
{
|
|
RunId = ReadFieldString(ref reader, fields, 0),
|
|
IsTutorial = ReadFieldBool(ref reader, fields, 1),
|
|
EntranceZoneIndex = ReadFieldInt32(ref reader, fields, 2),
|
|
ExitZoneIndex = ReadFieldInt32(ref reader, fields, 3),
|
|
CurrentZoneIndex = ReadFieldInt32(ref reader, fields, 4),
|
|
CurrentZoneEntityIndex = ReadFieldInt32(ref reader, fields, 5),
|
|
DiscoveredZoneIndices = ReadIntArray(ref reader, fields, 6),
|
|
UpdatedAtUtc = updatedAtUtc
|
|
};
|
|
zoneSnapshots = ReadZoneSnapshots(ref reader, fields, 7);
|
|
return run;
|
|
}
|
|
|
|
private static AetheriaPendingZoneSnapshot[] ReadZoneSnapshots(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
if (index >= fields) return [];
|
|
var count = reader.ReadArrayHeader();
|
|
var zones = new AetheriaPendingZoneSnapshot[count];
|
|
for (var item = 0; item < count; item++)
|
|
{
|
|
var itemFields = reader.ReadArrayHeader();
|
|
var zoneIndex = ReadFieldInt32(ref reader, itemFields, 0);
|
|
var state = new AetheriaZoneState
|
|
{
|
|
Name = ReadFieldString(ref reader, itemFields, 1),
|
|
Position = new AetheriaVector2
|
|
{
|
|
X = ReadFieldDouble(ref reader, itemFields, 2),
|
|
Y = ReadFieldDouble(ref reader, itemFields, 3)
|
|
},
|
|
AdjacentZoneIndices = ReadIntArray(ref reader, itemFields, 4),
|
|
FactionIndices = ReadIntArray(ref reader, itemFields, 5),
|
|
OwnerFactionIndex = ReadFieldInt32(ref reader, itemFields, 6)
|
|
};
|
|
zones[item] = new AetheriaPendingZoneSnapshot(
|
|
zoneIndex,
|
|
state,
|
|
ReadEntitySnapshots(ref reader, itemFields, 7));
|
|
}
|
|
|
|
return zones;
|
|
}
|
|
|
|
private static AetheriaPendingEntitySnapshot[] ReadEntitySnapshots(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
if (index >= fields) return [];
|
|
var count = reader.ReadArrayHeader();
|
|
var entities = new AetheriaPendingEntitySnapshot[count];
|
|
for (var item = 0; item < count; item++)
|
|
{
|
|
var itemFields = reader.ReadArrayHeader();
|
|
var entityIndex = ReadFieldInt32(ref reader, itemFields, 0);
|
|
var snapshot = new AetheriaEntitySnapshot
|
|
{
|
|
Name = ReadFieldString(ref reader, itemFields, 1),
|
|
Kind = ReadFieldString(ref reader, itemFields, 2),
|
|
Position = new AetheriaVector3
|
|
{
|
|
X = ReadFieldDouble(ref reader, itemFields, 3),
|
|
Y = ReadFieldDouble(ref reader, itemFields, 4),
|
|
Z = ReadFieldDouble(ref reader, itemFields, 5)
|
|
},
|
|
Direction = new AetheriaVector2
|
|
{
|
|
X = ReadFieldDouble(ref reader, itemFields, 6),
|
|
Y = ReadFieldDouble(ref reader, itemFields, 7)
|
|
},
|
|
FactionKey = ReferenceKey("aetheria.corporation", ReadFieldString(ref reader, itemFields, 8)),
|
|
HullItemKey = ReferenceKey("aetheria.item_definition", ReadFieldString(ref reader, itemFields, 9)),
|
|
Equipment = ReadEntityItemSlots(ref reader, itemFields, 10),
|
|
CargoBays = ReadEntityItemSlots(ref reader, itemFields, 11),
|
|
DockingBays = ReadEntityItemSlots(ref reader, itemFields, 12),
|
|
ChildEntityKeys = ReadIntArray(ref reader, itemFields, 13)
|
|
.Select(childIndex => $"pending:entity:{childIndex}")
|
|
.ToArray(),
|
|
WeaponGroups = ReadWeaponGroups(ref reader, itemFields, 14)
|
|
};
|
|
entities[item] = new AetheriaPendingEntitySnapshot(entityIndex, snapshot);
|
|
}
|
|
|
|
return entities;
|
|
}
|
|
|
|
private static AetheriaEntityItemSlot[] ReadEntityItemSlots(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
var slots = ReadItemSlots(ref reader, fields, index);
|
|
return slots
|
|
.Select(slot => new AetheriaEntityItemSlot
|
|
{
|
|
Position = slot.Position,
|
|
ItemKey = slot.Item.ItemKey
|
|
})
|
|
.ToArray();
|
|
}
|
|
|
|
private static AetheriaWeaponGroupSnapshot[] ReadWeaponGroups(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
return ReadIntArrayArray(ref reader, fields, index)
|
|
.Select(group => new AetheriaWeaponGroupSnapshot { EquipmentIndices = group })
|
|
.ToArray();
|
|
}
|
|
|
|
private static AetheriaStoryFileHash[] ReadStoryFileHashes(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
if (index >= fields) return [];
|
|
var count = reader.ReadArrayHeader();
|
|
var hashes = new AetheriaStoryFileHash[count];
|
|
for (var item = 0; item < count; item++)
|
|
{
|
|
var itemFields = reader.ReadArrayHeader();
|
|
hashes[item] = new AetheriaStoryFileHash
|
|
{
|
|
StoryPath = ReadFieldString(ref reader, itemFields, 0),
|
|
Hash = ReadFieldString(ref reader, itemFields, 1)
|
|
};
|
|
}
|
|
|
|
return hashes;
|
|
}
|
|
|
|
private static AetheriaInputBindingOverride[] ReadBindings(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
if (index >= fields) return [];
|
|
var count = reader.ReadArrayHeader();
|
|
var bindings = new AetheriaInputBindingOverride[count];
|
|
for (var item = 0; item < count; item++)
|
|
{
|
|
var itemFields = reader.ReadArrayHeader();
|
|
bindings[item] = new AetheriaInputBindingOverride
|
|
{
|
|
ActionName = ReadFieldString(ref reader, itemFields, 0),
|
|
BindingIndex = ReadFieldInt32(ref reader, itemFields, 1),
|
|
BindingPath = ReadFieldString(ref reader, itemFields, 2)
|
|
};
|
|
}
|
|
|
|
return bindings;
|
|
}
|
|
|
|
private static AetheriaLoadoutItemSlot[] ReadItemSlots(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
if (index >= fields) return [];
|
|
var count = reader.ReadArrayHeader();
|
|
var slots = new AetheriaLoadoutItemSlot[count];
|
|
for (var item = 0; item < count; item++)
|
|
{
|
|
var itemFields = reader.ReadArrayHeader();
|
|
slots[item] = new AetheriaLoadoutItemSlot
|
|
{
|
|
Position = new AetheriaGridCoord
|
|
{
|
|
X = ReadFieldInt32(ref reader, itemFields, 0),
|
|
Y = ReadFieldInt32(ref reader, itemFields, 1)
|
|
},
|
|
Item = itemFields > 2 ? ReadLoadoutItem(ref reader) : new AetheriaLoadoutItem()
|
|
};
|
|
}
|
|
|
|
return slots;
|
|
}
|
|
|
|
private static AetheriaCargoBayLoadout[] ReadCargoBays(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
if (index >= fields) return [];
|
|
var count = reader.ReadArrayHeader();
|
|
var bays = new AetheriaCargoBayLoadout[count];
|
|
for (var item = 0; item < count; item++)
|
|
{
|
|
bays[item] = new AetheriaCargoBayLoadout
|
|
{
|
|
Items = ReadItemSlots(ref reader, 1, 0)
|
|
};
|
|
}
|
|
|
|
return bays;
|
|
}
|
|
|
|
private static AetheriaEntityLoadout[] ReadChildren(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
if (index >= fields) return [];
|
|
var count = reader.ReadArrayHeader();
|
|
var children = new AetheriaEntityLoadout[count];
|
|
for (var item = 0; item < count; item++)
|
|
children[item] = ReadEntityLoadout(ref reader);
|
|
return children;
|
|
}
|
|
|
|
private static int[][] ReadIntArrayArray(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
if (index >= fields) return [];
|
|
var count = reader.ReadArrayHeader();
|
|
var arrays = new int[count][];
|
|
for (var item = 0; item < count; item++)
|
|
arrays[item] = ReadIntArray(ref reader, 1, 0);
|
|
return arrays;
|
|
}
|
|
|
|
private static string[] ReadStringArray(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
if (index >= fields) return [];
|
|
var count = reader.ReadArrayHeader();
|
|
var values = new string[count];
|
|
for (var item = 0; item < count; item++)
|
|
values[item] = ReadString(ref reader);
|
|
return values;
|
|
}
|
|
|
|
private static int[] ReadIntArray(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
if (index >= fields) return [];
|
|
var count = reader.ReadArrayHeader();
|
|
var values = new int[count];
|
|
for (var item = 0; item < count; item++)
|
|
values[item] = reader.ReadInt32();
|
|
return values;
|
|
}
|
|
|
|
private static string ReadFieldString(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
return index >= fields ? "" : ReadString(ref reader);
|
|
}
|
|
|
|
private static int ReadFieldInt32(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
return index >= fields ? 0 : reader.ReadInt32();
|
|
}
|
|
|
|
private static double ReadFieldDouble(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
return index >= fields ? 0 : reader.ReadDouble();
|
|
}
|
|
|
|
private static bool ReadFieldBool(ref MessagePackReader reader, int fields, int index)
|
|
{
|
|
return index < fields && reader.ReadBoolean();
|
|
}
|
|
|
|
private static string ReadString(ref MessagePackReader reader)
|
|
{
|
|
return reader.ReadString() ?? "";
|
|
}
|
|
|
|
private static CultRecordKey LoadoutKey(string name)
|
|
{
|
|
return new CultRecordKey($"global:aetheria.loadout_template.{StableToken(name)}.v1");
|
|
}
|
|
|
|
private static CultRecordKey RunKey(string runId)
|
|
{
|
|
return new CultRecordKey($"global:aetheria.run_state.{StableToken(runId)}.v1");
|
|
}
|
|
|
|
private static CultRecordKey ZoneKey(string runId, int zoneIndex)
|
|
{
|
|
return new CultRecordKey($"global:aetheria.run_state.{StableToken(runId)}.zone.{zoneIndex}.v1");
|
|
}
|
|
|
|
private static CultRecordKey EntityKey(string runId, int zoneIndex, int entityIndex)
|
|
{
|
|
return new CultRecordKey($"global:aetheria.run_state.{StableToken(runId)}.zone.{zoneIndex}.entity.{entityIndex}.v1");
|
|
}
|
|
|
|
private static string ReferenceKey(string documentName, string legacyId)
|
|
{
|
|
return string.IsNullOrWhiteSpace(legacyId) ? "" : $"{documentName}:legacy:{legacyId}";
|
|
}
|
|
|
|
private static string StableToken(string value)
|
|
{
|
|
var chars = (string.IsNullOrWhiteSpace(value) ? "unnamed" : value.Trim().ToLowerInvariant())
|
|
.Select(ch => char.IsLetterOrDigit(ch) ? ch : '-')
|
|
.ToArray();
|
|
var token = new string(chars).Trim('-');
|
|
while (token.Contains("--", StringComparison.Ordinal))
|
|
token = token.Replace("--", "-", StringComparison.Ordinal);
|
|
return string.IsNullOrWhiteSpace(token) ? "unnamed" : token;
|
|
}
|
|
|
|
private static InvalidDataException MissingPayload(PendingCommand command)
|
|
{
|
|
return new InvalidDataException($"Aetheria runtime commit '{command.CommandId}' has no {command.Kind} payload.");
|
|
}
|
|
|
|
private readonly struct PendingCommand
|
|
{
|
|
private PendingCommand(
|
|
string kind,
|
|
string commandId,
|
|
string createdAtUtc,
|
|
string path,
|
|
AetheriaPlayerSettings? playerSettings,
|
|
AetheriaLoadoutTemplate? loadoutTemplate,
|
|
AetheriaRunState? runState,
|
|
AetheriaPendingZoneSnapshot[] zoneSnapshots)
|
|
{
|
|
Kind = kind;
|
|
CommandId = commandId;
|
|
CreatedAtUtc = createdAtUtc;
|
|
Path = path;
|
|
PlayerSettings = playerSettings;
|
|
LoadoutTemplate = loadoutTemplate;
|
|
RunState = runState;
|
|
ZoneSnapshots = zoneSnapshots;
|
|
}
|
|
|
|
public static PendingCommand Empty(string kind, string commandId, string createdAtUtc, string path)
|
|
{
|
|
return new PendingCommand(kind, commandId, createdAtUtc, path, null, null, null, []);
|
|
}
|
|
|
|
public static PendingCommand ForPlayerSettings(
|
|
string kind,
|
|
string commandId,
|
|
string createdAtUtc,
|
|
AetheriaPlayerSettings settings,
|
|
string path)
|
|
{
|
|
return new PendingCommand(kind, commandId, createdAtUtc, path, settings, null, null, []);
|
|
}
|
|
|
|
public static PendingCommand ForLoadoutTemplate(
|
|
string kind,
|
|
string commandId,
|
|
string createdAtUtc,
|
|
AetheriaLoadoutTemplate loadout,
|
|
string path)
|
|
{
|
|
return new PendingCommand(kind, commandId, createdAtUtc, path, null, loadout, null, []);
|
|
}
|
|
|
|
public static PendingCommand ForRunState(
|
|
string kind,
|
|
string commandId,
|
|
string createdAtUtc,
|
|
AetheriaRunState run,
|
|
AetheriaPendingZoneSnapshot[] zoneSnapshots,
|
|
string path)
|
|
{
|
|
return new PendingCommand(kind, commandId, createdAtUtc, path, null, null, run, zoneSnapshots);
|
|
}
|
|
|
|
public string Kind { get; }
|
|
public string CommandId { get; }
|
|
public string CreatedAtUtc { get; }
|
|
public string Path { get; }
|
|
public AetheriaPlayerSettings? PlayerSettings { get; }
|
|
public AetheriaLoadoutTemplate? LoadoutTemplate { get; }
|
|
public AetheriaRunState? RunState { get; }
|
|
public AetheriaPendingZoneSnapshot[] ZoneSnapshots { get; }
|
|
}
|
|
|
|
private readonly struct AetheriaPendingZoneSnapshot
|
|
{
|
|
public AetheriaPendingZoneSnapshot(
|
|
int zoneIndex,
|
|
AetheriaZoneState state,
|
|
AetheriaPendingEntitySnapshot[] entities)
|
|
{
|
|
ZoneIndex = zoneIndex;
|
|
State = state;
|
|
Entities = entities;
|
|
}
|
|
|
|
public int ZoneIndex { get; }
|
|
public AetheriaZoneState State { get; }
|
|
public AetheriaPendingEntitySnapshot[] Entities { get; }
|
|
}
|
|
|
|
private readonly struct AetheriaPendingEntitySnapshot
|
|
{
|
|
public AetheriaPendingEntitySnapshot(int entityIndex, AetheriaEntitySnapshot snapshot)
|
|
{
|
|
EntityIndex = entityIndex;
|
|
Snapshot = snapshot;
|
|
}
|
|
|
|
public int EntityIndex { get; }
|
|
public AetheriaEntitySnapshot Snapshot { get; }
|
|
}
|
|
}
|