326 lines
12 KiB
C#
326 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using GameCult.Eve.Surface;
|
|
|
|
namespace GameCult.Aetheria.State.Verse
|
|
{
|
|
public sealed class AetheriaRuntimeTradeCargoTargetOption
|
|
{
|
|
public AetheriaRuntimeTradeCargoTargetOption(string id, string label, string command)
|
|
{
|
|
Id = id ?? "";
|
|
Label = label ?? "";
|
|
Command = command ?? "";
|
|
}
|
|
|
|
public string Id { get; }
|
|
public string Label { get; }
|
|
public string Command { get; }
|
|
}
|
|
|
|
public enum AetheriaRuntimeTradeCargoTargetKind
|
|
{
|
|
Unknown = 0,
|
|
DockingBay = 1,
|
|
ShipBay = 2
|
|
}
|
|
|
|
public sealed class AetheriaRuntimeTradeCargoModelOption
|
|
{
|
|
public AetheriaRuntimeTradeCargoModelOption(
|
|
AetheriaRuntimeTradeCargoTargetKind kind,
|
|
string label,
|
|
string entityKey = "",
|
|
int shipIndex = -1,
|
|
int bayIndex = -1,
|
|
bool isCurrent = false)
|
|
{
|
|
Kind = kind;
|
|
Label = label ?? "";
|
|
EntityKey = entityKey ?? "";
|
|
ShipIndex = shipIndex;
|
|
BayIndex = bayIndex;
|
|
IsCurrent = isCurrent;
|
|
}
|
|
|
|
public AetheriaRuntimeTradeCargoTargetKind Kind { get; }
|
|
public string Label { get; }
|
|
public string EntityKey { get; }
|
|
public int ShipIndex { get; }
|
|
public int BayIndex { get; }
|
|
public bool IsCurrent { get; }
|
|
}
|
|
|
|
public readonly struct AetheriaRuntimeTradeCargoSelection
|
|
{
|
|
public AetheriaRuntimeTradeCargoSelection(
|
|
AetheriaRuntimeTradeCargoTargetKind kind,
|
|
string command,
|
|
string label,
|
|
string entityKey = "",
|
|
int shipIndex = -1,
|
|
int bayIndex = -1)
|
|
{
|
|
Kind = kind;
|
|
Command = command ?? "";
|
|
Label = label ?? "";
|
|
EntityKey = entityKey ?? "";
|
|
ShipIndex = shipIndex;
|
|
BayIndex = bayIndex;
|
|
}
|
|
|
|
public AetheriaRuntimeTradeCargoTargetKind Kind { get; }
|
|
public string Command { get; }
|
|
public string Label { get; }
|
|
public string EntityKey { get; }
|
|
public int ShipIndex { get; }
|
|
public int BayIndex { get; }
|
|
}
|
|
|
|
public sealed class AetheriaRuntimeTradeCargoSelectorSurfaceModel
|
|
{
|
|
public AetheriaRuntimeTradeCargoSelectorSurfaceModel(
|
|
AetheriaRuntimeSurfaceDocument document,
|
|
IReadOnlyDictionary<string, AetheriaRuntimeTradeCargoSelection> selections)
|
|
{
|
|
Document = document ?? throw new ArgumentNullException(nameof(document));
|
|
Selections = selections ?? new Dictionary<string, AetheriaRuntimeTradeCargoSelection>(StringComparer.Ordinal);
|
|
}
|
|
|
|
public AetheriaRuntimeSurfaceDocument Document { get; }
|
|
public IReadOnlyDictionary<string, AetheriaRuntimeTradeCargoSelection> Selections { get; }
|
|
|
|
public bool TryResolve(string command, out AetheriaRuntimeTradeCargoSelection selection)
|
|
{
|
|
return Selections.TryGetValue(command ?? "", out selection);
|
|
}
|
|
}
|
|
|
|
public static class AetheriaRuntimeTradeCargoSelectorSurfaceBuilder
|
|
{
|
|
public const string SurfaceId = "aetheria.trade.target_cargo_selector";
|
|
public const string Close = "aetheria.trade.target_cargo_selector.close";
|
|
public const string DockingBay = "aetheria.trade.target_cargo_selector.docking_bay";
|
|
|
|
public static string ShipBayCommand(int shipIndex, int bayIndex)
|
|
{
|
|
return $"{SurfaceId}.ship_{shipIndex}_bay_{bayIndex}";
|
|
}
|
|
|
|
public static AetheriaRuntimeTradeCargoSelectorSurfaceModel Build(
|
|
string currentTarget,
|
|
IEnumerable<AetheriaRuntimeTradeCargoModelOption> targets,
|
|
string updatedAtUtc)
|
|
{
|
|
var options = new List<AetheriaRuntimeTradeCargoTargetOption>();
|
|
var selections = new Dictionary<string, AetheriaRuntimeTradeCargoSelection>(StringComparer.Ordinal);
|
|
|
|
foreach (var target in targets ?? Array.Empty<AetheriaRuntimeTradeCargoModelOption>())
|
|
{
|
|
if (target == null ||
|
|
target.IsCurrent ||
|
|
target.Kind == AetheriaRuntimeTradeCargoTargetKind.Unknown)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var command = CommandFor(target);
|
|
if (string.IsNullOrWhiteSpace(command))
|
|
continue;
|
|
|
|
var label = string.IsNullOrWhiteSpace(target.Label)
|
|
? command
|
|
: target.Label;
|
|
options.Add(new AetheriaRuntimeTradeCargoTargetOption(
|
|
$"{SurfaceId}.{StableToken(command)}",
|
|
label,
|
|
command));
|
|
selections[command] = new AetheriaRuntimeTradeCargoSelection(
|
|
target.Kind,
|
|
command,
|
|
label,
|
|
target.EntityKey,
|
|
target.ShipIndex,
|
|
target.BayIndex);
|
|
}
|
|
|
|
return new AetheriaRuntimeTradeCargoSelectorSurfaceModel(
|
|
Build(currentTarget, options, updatedAtUtc),
|
|
selections);
|
|
}
|
|
|
|
private static AetheriaRuntimeSurfaceDocument Build(
|
|
string currentTarget,
|
|
IReadOnlyList<AetheriaRuntimeTradeCargoTargetOption> options,
|
|
string updatedAtUtc,
|
|
long version = 1)
|
|
{
|
|
options ??= Array.Empty<AetheriaRuntimeTradeCargoTargetOption>();
|
|
|
|
var targets = options
|
|
.OrderBy(target => target.Label, StringComparer.Ordinal)
|
|
.ToArray();
|
|
|
|
return new AetheriaRuntimeSurfaceDocument(
|
|
providerId: "aetheria",
|
|
providerKind: "trade.menu",
|
|
title: "Trade Target Cargo Selector",
|
|
version: version,
|
|
updatedAtUtc: updatedAtUtc ?? "",
|
|
surface: new AetheriaRuntimeSurfaceTree(
|
|
SurfaceId,
|
|
Node(
|
|
$"{SurfaceId}.root",
|
|
"surface",
|
|
Array.Empty<(string Key, string Value)>(),
|
|
Card(
|
|
$"{SurfaceId}.card",
|
|
"Target Cargo",
|
|
Metric($"{SurfaceId}.current", "Current", currentTarget),
|
|
Text(
|
|
$"{SurfaceId}.note",
|
|
"The observing client lists available cargo targets; the shared runtime surface owns the cargo selector contract."),
|
|
ButtonColumn(
|
|
$"{SurfaceId}.options",
|
|
targets
|
|
.Select(target => Button(target.Id, target.Label, target.Command))
|
|
.ToArray()),
|
|
ButtonRow(
|
|
$"{SurfaceId}.actions",
|
|
Button($"{SurfaceId}.close", "Close", Close)))),
|
|
Array.Empty<AetheriaRuntimeSurfaceStyleToken>()),
|
|
commands: targets
|
|
.Select(target => new AetheriaRuntimeSurfaceCommandTemplate(target.Command, target.Label, AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport))
|
|
.Append(new AetheriaRuntimeSurfaceCommandTemplate(Close, "Close", AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport))
|
|
.ToArray());
|
|
}
|
|
|
|
private static AetheriaRuntimeSurfaceComponent Card(
|
|
string id,
|
|
string title,
|
|
params AetheriaRuntimeSurfaceComponent[] children)
|
|
{
|
|
return Node(id, "card", new[] { ("title", title ?? "") }, children);
|
|
}
|
|
|
|
private static AetheriaRuntimeSurfaceComponent Metric(string id, string label, string value)
|
|
{
|
|
return Node(id, "metric", new[] { ("label", label ?? ""), ("value", value ?? "") });
|
|
}
|
|
|
|
private static AetheriaRuntimeSurfaceComponent Text(string id, string value)
|
|
{
|
|
return Node(id, "text", new[] { ("value", value ?? "") });
|
|
}
|
|
|
|
private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command)
|
|
{
|
|
return Node(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") });
|
|
}
|
|
|
|
private static AetheriaRuntimeSurfaceComponent ButtonRow(
|
|
string id,
|
|
params AetheriaRuntimeSurfaceComponent[] children)
|
|
{
|
|
return Node(id, "row", Array.Empty<(string Key, string Value)>(), children);
|
|
}
|
|
|
|
private static AetheriaRuntimeSurfaceComponent ButtonColumn(
|
|
string id,
|
|
params AetheriaRuntimeSurfaceComponent[] children)
|
|
{
|
|
return Node(id, "column", Array.Empty<(string Key, string Value)>(), children);
|
|
}
|
|
|
|
private static AetheriaRuntimeSurfaceComponent Node(
|
|
string id,
|
|
string kind,
|
|
IEnumerable<(string Key, string Value)> props,
|
|
params AetheriaRuntimeSurfaceComponent[] children)
|
|
{
|
|
return new AetheriaRuntimeSurfaceComponent(
|
|
id ?? "",
|
|
kind ?? "",
|
|
(props ?? Array.Empty<(string Key, string Value)>())
|
|
.ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal),
|
|
children ?? Array.Empty<AetheriaRuntimeSurfaceComponent>());
|
|
}
|
|
|
|
private static string CommandFor(AetheriaRuntimeTradeCargoModelOption target)
|
|
{
|
|
switch (target.Kind)
|
|
{
|
|
case AetheriaRuntimeTradeCargoTargetKind.DockingBay:
|
|
return DockingBay;
|
|
case AetheriaRuntimeTradeCargoTargetKind.ShipBay:
|
|
return target.ShipIndex >= 0 && target.BayIndex >= 0
|
|
? ShipBayCommand(target.ShipIndex, target.BayIndex)
|
|
: "";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
private static string StableToken(string value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
return "empty";
|
|
|
|
return new string(value
|
|
.Select(character => char.IsLetterOrDigit(character) ? char.ToLowerInvariant(character) : '-')
|
|
.ToArray()).Trim('-');
|
|
}
|
|
}
|
|
|
|
public enum AetheriaRuntimeTradeCargoSelectorCommandKind
|
|
{
|
|
Unknown = 0,
|
|
Close = 1,
|
|
Select = 2
|
|
}
|
|
|
|
public readonly struct AetheriaRuntimeTradeCargoSelectorCommand
|
|
{
|
|
public AetheriaRuntimeTradeCargoSelectorCommand(
|
|
AetheriaRuntimeTradeCargoSelectorCommandKind kind,
|
|
string command)
|
|
{
|
|
Kind = kind;
|
|
Command = command ?? "";
|
|
}
|
|
|
|
public AetheriaRuntimeTradeCargoSelectorCommandKind Kind { get; }
|
|
public string Command { get; }
|
|
}
|
|
|
|
public static class AetheriaRuntimeTradeCargoSelectorSurfaceCommands
|
|
{
|
|
public static bool TryRead(
|
|
EveSurfaceCommandRequest request,
|
|
out AetheriaRuntimeTradeCargoSelectorCommand command)
|
|
{
|
|
command = default;
|
|
if (request == null ||
|
|
!string.Equals(request.SurfaceId, AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.SurfaceId, StringComparison.Ordinal))
|
|
return false;
|
|
|
|
var commandText = request.Operation?.OperationId ?? "";
|
|
if (string.Equals(commandText, AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.Close, StringComparison.Ordinal))
|
|
{
|
|
command = new AetheriaRuntimeTradeCargoSelectorCommand(
|
|
AetheriaRuntimeTradeCargoSelectorCommandKind.Close,
|
|
commandText);
|
|
return true;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(commandText))
|
|
return false;
|
|
|
|
command = new AetheriaRuntimeTradeCargoSelectorCommand(
|
|
AetheriaRuntimeTradeCargoSelectorCommandKind.Select,
|
|
commandText);
|
|
return true;
|
|
}
|
|
}
|
|
}
|