Establish shared Hangar deployment spine
This commit is contained in:
parent
54895e28fe
commit
e9d1e04897
11 changed files with 674 additions and 7 deletions
|
|
@ -0,0 +1,114 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using GameCult.Caching;
|
||||
using MessagePack;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace GameCult.Aetheria.State.Verse
|
||||
{
|
||||
public static class AetheriaGameModes
|
||||
{
|
||||
public const string Terminus = "terminus";
|
||||
public const string Starbridge = "starbridge";
|
||||
public const string Arena = "arena";
|
||||
|
||||
public static bool IsKnown(string? mode) =>
|
||||
string.Equals(mode, Terminus, StringComparison.Ordinal) ||
|
||||
string.Equals(mode, Starbridge, StringComparison.Ordinal) ||
|
||||
string.Equals(mode, Arena, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public static class AetheriaModePolicies
|
||||
{
|
||||
public const string TerminusLocal = "aetheria.mode.terminus.local.v1";
|
||||
public const string StarbridgeMixed = "aetheria.mode.starbridge.mixed.v1";
|
||||
public const string ArenaServer = "aetheria.mode.arena.server.v1";
|
||||
|
||||
public static string ForMode(string? mode)
|
||||
{
|
||||
if (string.Equals(mode, AetheriaGameModes.Terminus, StringComparison.Ordinal)) return TerminusLocal;
|
||||
if (string.Equals(mode, AetheriaGameModes.Starbridge, StringComparison.Ordinal)) return StarbridgeMixed;
|
||||
if (string.Equals(mode, AetheriaGameModes.Arena, StringComparison.Ordinal)) return ArenaServer;
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static class AetheriaHangarShipStatuses
|
||||
{
|
||||
public const string Available = "available";
|
||||
public const string Deployed = "deployed";
|
||||
public const string Lost = "lost";
|
||||
}
|
||||
|
||||
[CultDocument("gamecult.aetheria.hangar", "gamecult.aetheria.hangar.v1")]
|
||||
[CultGlobal]
|
||||
[MessagePackObject]
|
||||
public sealed class AetheriaHangarState
|
||||
{
|
||||
[Key(0), CultName] public string Name { get; set; } = "Aetheria Hangar";
|
||||
[Key(1)] public string HangarId { get; set; } = "local";
|
||||
[Key(2)] public string PlayerKey { get; set; } = "";
|
||||
[Key(3)] public long Revision { get; set; }
|
||||
[Key(4)] public AetheriaHangarShip[] Ships { get; set; } = Array.Empty<AetheriaHangarShip>();
|
||||
[Key(5)] public AetheriaHangarItemStack[] Inventory { get; set; } = Array.Empty<AetheriaHangarItemStack>();
|
||||
[Key(6)] public AetheriaHangarCurrency[] Currencies { get; set; } = Array.Empty<AetheriaHangarCurrency>();
|
||||
[Key(7)] public string[] UnlockKeys { get; set; } = Array.Empty<string>();
|
||||
[Key(8)] public string[] LoadoutTemplateKeys { get; set; } = Array.Empty<string>();
|
||||
[Key(9)] public AetheriaDeploymentReceipt[] Deployments { get; set; } = Array.Empty<AetheriaDeploymentReceipt>();
|
||||
[Key(10)] public string UpdatedAtUtc { get; set; } = "";
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
public sealed class AetheriaHangarShip
|
||||
{
|
||||
[Key(0)] public string ShipId { get; set; } = "";
|
||||
[Key(1)] public string HullItemKey { get; set; } = "";
|
||||
[Key(2)] public string LoadoutTemplateKey { get; set; } = "";
|
||||
[Key(3)] public string Status { get; set; } = AetheriaHangarShipStatuses.Available;
|
||||
[Key(4)] public string ActiveDeploymentId { get; set; } = "";
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
public sealed class AetheriaHangarItemStack
|
||||
{
|
||||
[Key(0)] public string ItemKey { get; set; } = "";
|
||||
[Key(1)] public long Quantity { get; set; }
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
public sealed class AetheriaHangarCurrency
|
||||
{
|
||||
[Key(0)] public string CurrencyKey { get; set; } = "";
|
||||
[Key(1)] public long Quantity { get; set; }
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
public sealed class AetheriaDeploymentRequest
|
||||
{
|
||||
[Key(0)] public string RequestId { get; set; } = "";
|
||||
[Key(1)] public string PlayerKey { get; set; } = "";
|
||||
[Key(2)] public string Mode { get; set; } = AetheriaGameModes.Terminus;
|
||||
[Key(3)] public string ShipId { get; set; } = "";
|
||||
[Key(4)] public string LoadoutTemplateKey { get; set; } = "";
|
||||
[Key(5)] public long ExpectedHangarRevision { get; set; }
|
||||
[Key(6)] public string ModePolicyId { get; set; } = "";
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
public sealed class AetheriaDeploymentReceipt
|
||||
{
|
||||
[Key(0)] public string DeploymentId { get; set; } = "";
|
||||
[Key(1)] public string RequestId { get; set; } = "";
|
||||
[Key(2)] public bool Accepted { get; set; }
|
||||
[Key(3)] public string Diagnostic { get; set; } = "";
|
||||
[Key(4)] public string PlayerKey { get; set; } = "";
|
||||
[Key(5)] public string Mode { get; set; } = "";
|
||||
[Key(6)] public string ShipId { get; set; } = "";
|
||||
[Key(7)] public string LoadoutTemplateKey { get; set; } = "";
|
||||
[Key(8)] public long HangarRevision { get; set; }
|
||||
[Key(9)] public string ModePolicyId { get; set; } = "";
|
||||
[Key(10)] public AetheriaRuntimeEntityLoadoutCommit Loadout { get; set; } = new AetheriaRuntimeEntityLoadoutCommit();
|
||||
[Key(11)] public string CommittedAtUtc { get; set; } = "";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d95d5e88e2194e88a2af84af6c5cd893
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace GameCult.Aetheria.State.Verse
|
||||
{
|
||||
public static class AetheriaRuntimeHangarCommands
|
||||
{
|
||||
public const string SurfaceId = "aetheria.hangar";
|
||||
public const string SelectShip = "aetheria.hangar.select_ship";
|
||||
public const string SelectTerminus = "aetheria.hangar.select_mode.terminus";
|
||||
public const string SelectStarbridge = "aetheria.hangar.select_mode.starbridge";
|
||||
public const string SelectArena = "aetheria.hangar.select_mode.arena";
|
||||
public const string EditLoadout = "aetheria.hangar.edit_loadout";
|
||||
public const string Launch = "aetheria.hangar.launch";
|
||||
}
|
||||
|
||||
public static class AetheriaRuntimeHangarSurfaceBuilder
|
||||
{
|
||||
public static AetheriaRuntimeSurfaceDocument Build(
|
||||
AetheriaHangarState hangar,
|
||||
string selectedShipId,
|
||||
string selectedMode,
|
||||
string updatedAtUtc,
|
||||
long version = 1)
|
||||
{
|
||||
if (hangar == null) throw new ArgumentNullException(nameof(hangar));
|
||||
var ships = hangar.Ships ?? Array.Empty<AetheriaHangarShip>();
|
||||
var selected = ships.FirstOrDefault(ship => string.Equals(ship.ShipId, selectedShipId, StringComparison.Ordinal))
|
||||
?? ships.FirstOrDefault();
|
||||
var mode = AetheriaGameModes.IsKnown(selectedMode) ? selectedMode : AetheriaGameModes.Terminus;
|
||||
var canLaunch = selected != null && string.Equals(selected.Status, AetheriaHangarShipStatuses.Available, StringComparison.Ordinal);
|
||||
|
||||
var root = Component(
|
||||
"aetheria.hangar.root",
|
||||
"surface",
|
||||
Props(("title", "HANGAR"), ("selectedMode", mode), ("hangarRevision", hangar.Revision.ToString(CultureInfo.InvariantCulture))),
|
||||
new[]
|
||||
{
|
||||
Panel("aetheria.hangar.ship_summary", "SHIP", selected == null
|
||||
? new[] { Text("aetheria.hangar.ship.none", "No ship selected") }
|
||||
: new[]
|
||||
{
|
||||
Metric("aetheria.hangar.ship.id", "Ship", selected.ShipId),
|
||||
Metric("aetheria.hangar.ship.hull", "Hull", selected.HullItemKey),
|
||||
Metric("aetheria.hangar.ship.status", "Status", selected.Status),
|
||||
Metric("aetheria.hangar.ship.loadout", "Loadout", selected.LoadoutTemplateKey)
|
||||
}, "summary"),
|
||||
Panel("aetheria.hangar.preview", "SHIP PREVIEW", new[]
|
||||
{
|
||||
Component("aetheria.hangar.preview.slot", "asset.preview", Props(
|
||||
("assetRole", "ship.preview"),
|
||||
("subjectKey", selected?.ShipId ?? "")), Array.Empty<AetheriaRuntimeSurfaceComponent>())
|
||||
}, "preview"),
|
||||
Panel("aetheria.hangar.fit", "FIT SUMMARY", new[]
|
||||
{
|
||||
Metric("aetheria.hangar.fit.hull", "Hull", selected?.HullItemKey ?? "-"),
|
||||
Metric("aetheria.hangar.fit.template", "Template", selected?.LoadoutTemplateKey ?? "-"),
|
||||
Metric("aetheria.hangar.fit.policy", "Authority", AetheriaModePolicies.ForMode(mode)),
|
||||
Button("aetheria.hangar.fit.edit", "EDIT LOADOUT", AetheriaRuntimeHangarCommands.EditLoadout,
|
||||
("targetSurfaceId", AetheriaRuntimeInventoryPanelSurfaceBuilder.SurfaceId))
|
||||
}, "fit"),
|
||||
Component("aetheria.hangar.bays", "row", Props(("label", "OWNED SHIPS")),
|
||||
ships.Select(ship => Button(
|
||||
"aetheria.hangar.bay." + StableToken(ship.ShipId),
|
||||
ship.ShipId,
|
||||
AetheriaRuntimeHangarCommands.SelectShip,
|
||||
("shipId", ship.ShipId),
|
||||
("selected", string.Equals(ship.ShipId, selected?.ShipId, StringComparison.Ordinal) ? "true" : "false"),
|
||||
("status", ship.Status))).ToArray(),
|
||||
Layout(("gridArea", "bays"), ("display", "flex"), ("overflowX", "auto"), ("gap", "8"))),
|
||||
Component("aetheria.hangar.launcher", "row", Props(), new[]
|
||||
{
|
||||
Button("aetheria.hangar.mode.terminus", "TERMINUS", AetheriaRuntimeHangarCommands.SelectTerminus,
|
||||
("selected", (mode == AetheriaGameModes.Terminus).ToString().ToLowerInvariant())),
|
||||
Button("aetheria.hangar.mode.starbridge", "STARBRIDGE", AetheriaRuntimeHangarCommands.SelectStarbridge,
|
||||
("selected", (mode == AetheriaGameModes.Starbridge).ToString().ToLowerInvariant())),
|
||||
Button("aetheria.hangar.mode.arena", "ARENA", AetheriaRuntimeHangarCommands.SelectArena,
|
||||
("selected", (mode == AetheriaGameModes.Arena).ToString().ToLowerInvariant())),
|
||||
Button("aetheria.hangar.launch", "LAUNCH", AetheriaRuntimeHangarCommands.Launch,
|
||||
("enabled", canLaunch.ToString().ToLowerInvariant()),
|
||||
("shipId", selected?.ShipId ?? ""),
|
||||
("mode", mode),
|
||||
("expectedHangarRevision", hangar.Revision.ToString(CultureInfo.InvariantCulture)))
|
||||
}, Layout(("gridArea", "launch"), ("display", "flex"), ("justifyContent", "flex-end"), ("gap", "8")))
|
||||
},
|
||||
Layout(
|
||||
("display", "grid"),
|
||||
("gridTemplateColumns", "minmax(260px, 0.75fr) minmax(480px, 1.6fr) minmax(280px, 0.8fr)"),
|
||||
("gridTemplateRows", "auto minmax(360px, 1fr) auto"),
|
||||
("gridTemplateAreas", "\"launch launch launch\" \"summary preview fit\" \"bays bays bays\""),
|
||||
("gap", "10"),
|
||||
("height", "100%")),
|
||||
Style(("background", "#070b0d"), ("color", "#d8eef2")));
|
||||
|
||||
return new AetheriaRuntimeSurfaceDocument(
|
||||
"aetheria",
|
||||
"game.hangar",
|
||||
"Aetheria Hangar",
|
||||
version,
|
||||
updatedAtUtc ?? "",
|
||||
new AetheriaRuntimeSurfaceTree(AetheriaRuntimeHangarCommands.SurfaceId, root, Array.Empty<AetheriaRuntimeSurfaceStyleToken>()),
|
||||
new[]
|
||||
{
|
||||
Command(AetheriaRuntimeHangarCommands.SelectShip, "Select Ship"),
|
||||
Command(AetheriaRuntimeHangarCommands.SelectTerminus, "Select Terminus"),
|
||||
Command(AetheriaRuntimeHangarCommands.SelectStarbridge, "Select Starbridge"),
|
||||
Command(AetheriaRuntimeHangarCommands.SelectArena, "Select Arena"),
|
||||
Command(AetheriaRuntimeHangarCommands.EditLoadout, "Edit Loadout"),
|
||||
Command(AetheriaRuntimeHangarCommands.Launch, "Launch")
|
||||
});
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeSurfaceComponent Panel(string id, string title, IReadOnlyList<AetheriaRuntimeSurfaceComponent> children, string area) =>
|
||||
Component(id, "panel", Props(("title", title)), children, Layout(("gridArea", area)), Style(("background", "#11181c"), ("border", "1px solid #60737a"), ("padding", "12")));
|
||||
|
||||
private static AetheriaRuntimeSurfaceComponent Metric(string id, string label, string value) =>
|
||||
Component(id, "metric", Props(("label", label), ("value", value)), Array.Empty<AetheriaRuntimeSurfaceComponent>());
|
||||
|
||||
private static AetheriaRuntimeSurfaceComponent Text(string id, string value) =>
|
||||
Component(id, "text", Props(("value", value)), Array.Empty<AetheriaRuntimeSurfaceComponent>());
|
||||
|
||||
private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command, params (string Key, string Value)[] extra) =>
|
||||
Component(id, "button", Props(new[] { ("label", label), ("command", command) }.Concat(extra).ToArray()), Array.Empty<AetheriaRuntimeSurfaceComponent>());
|
||||
|
||||
private static AetheriaRuntimeSurfaceCommandTemplate Command(string command, string label) =>
|
||||
new AetheriaRuntimeSurfaceCommandTemplate(command, label, AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport);
|
||||
|
||||
private static AetheriaRuntimeSurfaceComponent Component(
|
||||
string id,
|
||||
string kind,
|
||||
IReadOnlyDictionary<string, string> props,
|
||||
IReadOnlyList<AetheriaRuntimeSurfaceComponent> children,
|
||||
IReadOnlyDictionary<string, string>? layout = null,
|
||||
IReadOnlyDictionary<string, string>? style = null) =>
|
||||
new AetheriaRuntimeSurfaceComponent(id, kind, props, children, Array.Empty<GameCult.Mesh.CultMeshStateBindingDescriptor>(), Array.Empty<AetheriaRuntimeEmbeddedDocumentSlot>(), layout, style);
|
||||
|
||||
private static IReadOnlyDictionary<string, string> Props(params (string Key, string Value)[] values) =>
|
||||
values.ToDictionary(value => value.Key, value => value.Value ?? "", StringComparer.Ordinal);
|
||||
|
||||
private static IReadOnlyDictionary<string, string> Layout(params (string Key, string Value)[] values) => Props(values);
|
||||
private static IReadOnlyDictionary<string, string> Style(params (string Key, string Value)[] values) => Props(values);
|
||||
|
||||
private static string StableToken(string value) =>
|
||||
new string((value ?? "").Select(character => char.IsLetterOrDigit(character) ? char.ToLowerInvariant(character) : '.').ToArray()).Trim('.');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4c79a2f47dbf437c91d6cb4d4310f9c4
|
||||
Loading…
Reference in a new issue