Publish daemon menu Eve surfaces

This commit is contained in:
Meta Vens 2026-07-02 21:00:36 +01:00
commit 59e783f6a0
5 changed files with 261 additions and 6 deletions

View file

@ -795,6 +795,189 @@ static async Task PublishDaemonApiDocumentsAsync(
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface)
.ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(result.EditorTuiSurface))
.ConfigureAwait(false);
await PublishDaemonMenuSurfacesAsync(node, result.Frame).ConfigureAwait(false);
}
static async Task PublishDaemonMenuSurfacesAsync(
AetheriaStateNode node,
AetheriaRuntimeDaemonFrameDocument frame)
{
if (frame == null)
return;
var updatedAtUtc = string.IsNullOrWhiteSpace(frame.PublishedAtUtc)
? DateTimeOffset.UtcNow.ToString("O")
: frame.PublishedAtUtc;
var catalog = AetheriaRuntimeCatalogStore.OpenReadOnly(node.StatePath);
var loadoutTemplates = AetheriaRuntimeCatalogStore.ReadLoadoutTemplates(node.StatePath);
var playerSettings = await ReadRuntimePlayerSettingsAsync(node).ConfigureAwait(false);
var currentEntity = AetheriaRuntimeRtsDocuments.CurrentEntity(frame);
var stationRefit = AetheriaRuntimeRtsDocuments.StationRefit(frame, loadoutTemplates, catalog);
var dropdownRequest = new AetheriaRuntimeInventoryDropdownSurfaceRequest
{
CurrentView = "Current Entity",
DisplayedEntityKey = currentEntity.EntityKey,
DisplayedCargoEntityKey = currentEntity.EntityKey,
DisplayedCargoIndex = 0,
CanSaveLoadout = !string.IsNullOrWhiteSpace(currentEntity.EntityKey)
};
var inventoryRequest = new AetheriaRuntimeInventoryPanelSurfaceRequest
{
ViewTitle = string.IsNullOrWhiteSpace(currentEntity.Entity?.DisplayName)
? "Current Entity Inventory"
: currentEntity.Entity.DisplayName,
DisplayedEntityKey = currentEntity.EntityKey,
DisplayedEntityIndex = currentEntity.EntityIndex,
DisplayedCargoEntityKey = currentEntity.EntityKey,
DisplayedCargoEntityIndex = currentEntity.EntityIndex,
DisplayedCargoIndex = 0
};
var inventory = currentEntity.EntityIndex < 0
? new AetheriaRuntimeInventoryDocument()
: AetheriaRuntimeRtsDocuments.Inventory(frame, currentEntity.EntityIndex);
var mainMenu = AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot(
AetheriaRuntimeStateBoot.Inspect(
new DirectoryInfo(Path.GetDirectoryName(node.StatePath) ?? "."),
node.StatePath),
playerSettings,
canOpenRuntimeInputScreen: true,
inGame: true,
updatedAtUtc);
var inventoryPanel = AetheriaRuntimeInventoryPanelSurfaceBuilder.BuildFromDocuments(
currentEntity,
stationRefit,
inventory,
catalog,
playerSettings,
inventoryRequest,
AetheriaRuntimeVerseRecordKeys.InventoryDropdownSurface.ToString(),
updatedAtUtc);
var inventoryDropdown = AetheriaRuntimeInventoryDropdownSurfaceBuilder.BuildFromDocuments(
stationRefit,
dropdownRequest,
updatedAtUtc);
var mapMenu = AetheriaRuntimeZoneDetailsSurfaceBuilder.BuildFromDocuments(
AetheriaRuntimeRtsDocuments.ZoneDetails(frame, currentEntity.ZoneIndex),
AetheriaRuntimeRtsDocuments.SectorMap(frame),
catalog,
playerSettings,
updatedAtUtc);
var tradeMenu = BuildTradeMenuSurface(stationRefit, catalog, updatedAtUtc, frame.FrameId);
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MainMenuSurface)
.ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenu))
.ConfigureAwait(false);
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.InventoryPanelSurface)
.ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(inventoryPanel))
.ConfigureAwait(false);
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.InventoryDropdownSurface)
.ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(inventoryDropdown))
.ConfigureAwait(false);
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MapMenuSurface)
.ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mapMenu))
.ConfigureAwait(false);
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.TradeMenuSurface)
.ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(tradeMenu))
.ConfigureAwait(false);
}
static async Task<AetheriaRuntimePlayerSettingsDocument> ReadRuntimePlayerSettingsAsync(AetheriaStateNode node)
{
var settings = await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey)
.ReadAsync()
.ConfigureAwait(false);
settings ??= new AetheriaPlayerSettings();
return new AetheriaRuntimePlayerSettingsDocument
{
PlayerName = settings.PlayerName ?? "",
TutorialPassed = settings.TutorialPassed,
TemperatureUnit = settings.Gameplay?.TemperatureUnit ?? "",
SignificantDigits = settings.Gameplay?.SignificantDigits ?? 3,
DefaultShutdownPerformance = settings.Gameplay?.DefaultShutdownPerformance ?? 0,
NebulaQuality = settings.Graphics?.NebulaQuality ?? "",
ShowAsteroidsInMinimap = settings.Graphics?.ShowAsteroidsInMinimap ?? false,
BindingOverrides = Array.Empty<AetheriaRuntimeInputBindingOverrideDocument>(),
ActionBarInputs = settings.Input?.ActionBarInputs ?? Array.Empty<string>()
};
}
static AetheriaRuntimeSurfaceDocument BuildTradeMenuSurface(
AetheriaRuntimeStationRefitDocument stationRefit,
AetheriaRuntimeCatalogSnapshot catalog,
string updatedAtUtc,
long version)
{
stationRefit ??= new AetheriaRuntimeStationRefitDocument();
var rows = (stationRefit.StationStock ?? Array.Empty<AetheriaRuntimeStationStockItem>())
.Take(12)
.Select((item, index) =>
{
var typedItem = catalog?.FindItem(item.ItemKey);
return SurfaceLeaf(
$"aetheria.trade.menu.stock.{index}",
"row",
("item", typedItem?.Name ?? item.ItemKey),
("key", item.ItemKey ?? ""),
("qty", item.Quantity.ToString(CultureInfo.InvariantCulture)),
("price", item.Price.ToString("N0", CultureInfo.InvariantCulture)),
("owned", item.OwnedQuantity.ToString(CultureInfo.InvariantCulture)),
("afford", item.CanAfford ? "yes" : "no"));
})
.DefaultIfEmpty(SurfaceLeaf(
"aetheria.trade.menu.stock.empty",
"text",
("value", "No station stock is available in the current daemon frame.")))
.ToArray();
return new AetheriaRuntimeSurfaceDocument(
providerId: "aetheria.daemon",
providerKind: "trade.menu",
title: "Trade Menu",
version: version,
updatedAtUtc: updatedAtUtc ?? "",
surface: new AetheriaRuntimeSurfaceTree(
"aetheria.trade.menu",
SurfaceNode(
"aetheria.trade.menu.root",
"surface",
Array.Empty<(string Key, string Value)>(),
SurfaceNode(
"aetheria.trade.menu.summary",
"card",
new[] { ("title", "Station Trade") },
SurfaceLeaf("aetheria.trade.menu.summary.docked", "metric", ("label", "Docked"), ("value", stationRefit.IsDocked ? "yes" : "no")),
SurfaceLeaf("aetheria.trade.menu.summary.station", "metric", ("label", "Station"), ("value", stationRefit.DockParent?.DisplayName ?? stationRefit.DockParentEntityKey ?? "")),
SurfaceLeaf("aetheria.trade.menu.summary.credits", "metric", ("label", "Credits"), ("value", stationRefit.Credits.ToString("N0", CultureInfo.InvariantCulture)))),
SurfaceNode(
"aetheria.trade.menu.stock",
"card",
new[] { ("title", "Station Stock") },
rows)),
Array.Empty<AetheriaRuntimeSurfaceStyleToken>()),
commands: Array.Empty<AetheriaRuntimeSurfaceCommandTemplate>());
}
static AetheriaRuntimeSurfaceComponent SurfaceLeaf(
string id,
string kind,
params (string Key, string Value)[] props)
{
return SurfaceNode(id, kind, props, Array.Empty<AetheriaRuntimeSurfaceComponent>());
}
static AetheriaRuntimeSurfaceComponent SurfaceNode(
string id,
string kind,
(string Key, string Value)[] props,
params AetheriaRuntimeSurfaceComponent[] children)
{
return new AetheriaRuntimeSurfaceComponent(
id,
kind,
props.ToDictionary(prop => prop.Key, prop => prop.Value),
children);
}
static async Task AcceptEveCommandsAsync(AetheriaStateNode node, AetheriaDaemonHostOptions options)

View file

@ -64,7 +64,7 @@ namespace GameCult.Aetheria.State.Verse
payload);
}
private static byte[] WriteRuntimeSurfaceDocument(AetheriaRuntimeSurfaceDocument document)
internal static byte[] WriteRuntimeSurfaceDocument(AetheriaRuntimeSurfaceDocument document)
{
var buffer = new ArrayBufferWriter<byte>();
var writer = new MessagePackWriter(buffer);
@ -173,7 +173,7 @@ namespace GameCult.Aetheria.State.Verse
}
}
private static AetheriaRuntimeSurfaceDocument ReadRuntimeSurfaceDocument(byte[] payload)
internal static AetheriaRuntimeSurfaceDocument ReadRuntimeSurfaceDocument(byte[] payload)
{
var reader = new MessagePackReader(payload);
var fields = reader.ReadArrayHeader();

View file

@ -63,7 +63,7 @@ namespace GameCult.Aetheria.State.Verse
Surface(AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId, "Aetheria Daemon TUI", AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface.ToString(), "agent", "Compact daemon game surface for dense inspection.", providerId: "aetheria.daemon", providerKind: "game.daemon", transport: "cultmesh-record"),
Surface(AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId, "Aetheria Daemon Editor", AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface.ToString(), "operator", "Daemon editor and surface inventory.", providerId: "aetheria.daemon", providerKind: "editor.daemon", transport: "cultmesh-record"),
Surface(AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId, "Aetheria Daemon Editor TUI", AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface.ToString(), "agent", "Compact daemon editor and surface inventory.", providerId: "aetheria.daemon", providerKind: "editor.daemon", transport: "cultmesh-record"),
Surface(AetheriaRuntimeMainMenuCommands.RootSurfaceId, "Main Menu", "", "player", "Runtime main menu root.", status: "client-contextual"),
Surface(AetheriaRuntimeMainMenuCommands.RootSurfaceId, "Main Menu", AetheriaRuntimeVerseRecordKeys.MainMenuSurface.ToString(), "player", "Runtime main menu root.", transport: "cultmesh-record"),
Surface(AetheriaRuntimeMainMenuCommands.SettingsSurfaceId, "Main Menu Settings", "", "player", "Runtime main menu settings shell.", status: "client-contextual"),
Surface(AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId, "Main Menu Input Settings", "", "player", "Main menu input settings handoff shell.", status: "client-contextual"),
Surface(AetheriaRuntimeMainMenuCommands.PlayerSettingsShellSurfaceId, "Main Menu Player Settings", "", "player", "Main menu player settings shell.", status: "client-contextual"),
@ -73,12 +73,13 @@ namespace GameCult.Aetheria.State.Verse
Surface(AetheriaRuntimeLoadoutTemplateCommands.SurfaceId, "Loadout Templates", "", "player", "Loadout template save surface.", status: "client-contextual"),
Surface(AetheriaRuntimeClientTargetCommands.SurfaceId, "Client Target", "", "operator", "Local client target and Verse discovery surface.", status: "client-contextual"),
Surface(AetheriaRuntimeLocalStorySurfaceBuilder.SurfaceId, "Local Story", "", "player", "Local runtime story/menu surface.", status: "client-contextual"),
Surface(AetheriaRuntimeZoneDetailsSurfaceBuilder.SurfaceId, "Zone Details", "", "player", "Selected zone details surface.", status: "contextual"),
Surface(AetheriaRuntimeInventoryPanelSurfaceBuilder.SurfaceId, "Inventory Panel", "", "player", "Current inventory panel surface.", status: "contextual"),
Surface(AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId, "Inventory Dropdown", "", "player", "Inventory navigation dropdown surface.", status: "contextual"),
Surface(AetheriaRuntimeZoneDetailsSurfaceBuilder.SurfaceId, "Map Menu", AetheriaRuntimeVerseRecordKeys.MapMenuSurface.ToString(), "player", "Current-zone map details surface.", transport: "cultmesh-record"),
Surface(AetheriaRuntimeInventoryPanelSurfaceBuilder.SurfaceId, "Inventory Panel", AetheriaRuntimeVerseRecordKeys.InventoryPanelSurface.ToString(), "player", "Current inventory panel surface.", transport: "cultmesh-record"),
Surface(AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId, "Inventory Dropdown", AetheriaRuntimeVerseRecordKeys.InventoryDropdownSurface.ToString(), "player", "Inventory navigation dropdown surface.", transport: "cultmesh-record"),
Surface(AetheriaRuntimeShipSettingsSurfaceBuilder.SurfaceId, "Current Ship Settings", "", "player", "Current ship settings surface.", status: "contextual"),
Surface(AetheriaRuntimeCargoItemDetailsSurfaceBuilder.SurfaceId, "Cargo Item Details", "", "player", "Selected cargo item details surface.", status: "contextual"),
Surface(AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SurfaceId, "Equipped Item Details", "", "player", "Selected equipped item details surface.", status: "contextual"),
Surface("aetheria.trade.menu", "Trade Menu", AetheriaRuntimeVerseRecordKeys.TradeMenuSurface.ToString(), "player", "Daemon-derived station stock trade menu.", transport: "cultmesh-record"),
Surface(AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.SurfaceId, "Trade Cargo Selector", "", "player", "Trade cargo target selector surface.", status: "contextual"),
Surface(AetheriaRuntimeTradeInteractionSurfaceBuilder.FilterSurfaceId, "Trade Filter Selector", "", "player", "Trade filter selector surface.", status: "contextual"),
Surface(AetheriaRuntimeTradeInteractionSurfaceBuilder.RowActionSurfaceId, "Trade Row Actions", "", "player", "Trade row action selector surface.", status: "contextual"),

View file

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using GameCult.Caching;
using GameCult.Eve.Surface;
[assembly: CultGeneratedDocumentMetadataProvider(typeof(GameCult.Aetheria.State.Verse.AetheriaRuntimeEveSurfaceDocumentMetadataProvider))]
namespace GameCult.Aetheria.State.Verse
{
public sealed class AetheriaRuntimeEveSurfaceDocumentMetadataProvider : ICultGeneratedDocumentMetadataProvider
{
public IEnumerable<CultGeneratedDocumentDefinition> GetDocumentDefinitions()
{
yield return new CultGeneratedDocumentDefinition(
typeof(EveSurfaceDocument),
"gamecult.eve.surface",
"gamecult.eve.surface.v1",
isGlobal: false,
nameMember: null,
nameAccessor: null,
serializePayload: value => AetheriaRuntimeCultCacheDocumentStore.WriteRuntimeSurfaceDocument(
AetheriaRuntimeSurfaceDocuments.FromPortableSurface((EveSurfaceDocument)value)),
deserializePayload: payload => AetheriaRuntimeSurfaceDocuments.ToPortableSurface(
AetheriaRuntimeCultCacheDocumentStore.ReadRuntimeSurfaceDocument(payload)),
indexAccessors: Array.Empty<CultGeneratedDocumentIndexAccessor>(),
members: new[]
{
Member("Type", 0, "string"),
Member("Schema", 1, "string"),
Member("ProviderId", 2, "string"),
Member("ProviderKind", 3, "string"),
Member("Title", 4, "string"),
Member("Version", 5, "int64"),
Member("UpdatedAtUtc", 6, "string"),
Member("Surface", 7, "object"),
Member("Commands", 8, "array")
});
}
private static CultGeneratedDocumentMemberDefinition Member(
string name,
int slot,
string typeName)
{
return new CultGeneratedDocumentMemberDefinition(
name,
slot,
typeName,
isReference: false,
isMany: false,
targetSchemaName: null,
isName: false,
indexAlias: null);
}
}
}

View file

@ -65,6 +65,21 @@ namespace GameCult.Aetheria.State.Verse
public static CultRecordKey DaemonEditorTuiSurface { get; } =
new CultRecordKey("eve:surface:aetheria.daemon.editor.tui");
public static CultRecordKey MainMenuSurface { get; } =
new CultRecordKey("eve:surface:aetheria.main_menu.root");
public static CultRecordKey InventoryPanelSurface { get; } =
new CultRecordKey("eve:surface:aetheria.inventory.panel");
public static CultRecordKey InventoryDropdownSurface { get; } =
new CultRecordKey("eve:surface:aetheria.inventory.panel.dropdown");
public static CultRecordKey MapMenuSurface { get; } =
new CultRecordKey("eve:surface:aetheria.map.zone_details");
public static CultRecordKey TradeMenuSurface { get; } =
new CultRecordKey("eve:surface:aetheria.trade.menu");
public static CultRecordKey DaemonCommand(string commandId)
{
return new CultRecordKey($"daemon:commands:{StableToken(commandId)}:gamecult.aetheria.daemon_command.v1");