Restore the stable legacy Unity project

This commit is contained in:
Meta Vens 2026-08-10 14:57:50 +01:00
commit 7006a6b0dd
2070 changed files with 180490 additions and 169303 deletions

View file

@ -2,31 +2,22 @@
"dependencies": {
"com.unity.2d.sprite": "1.0.0",
"com.unity.2d.tilemap": "1.0.0",
"com.unity.ai.navigation": "2.0.12",
"com.unity.burst": "1.8.29",
"com.unity.cinemachine": "2.10.7",
"com.unity.entities.graphics": "1.4.20",
"com.unity.ide.rider": "3.0.39",
"com.unity.ide.visualstudio": "2.0.27",
"com.unity.inputsystem": "1.19.0",
"com.unity.mathematics": "1.3.3",
"com.unity.multiplayer.center": "1.0.1",
"com.unity.recorder": "5.1.5",
"com.unity.render-pipelines.universal": "17.4.0",
"com.unity.test-framework": "1.6.0",
"com.unity.timeline": "1.8.12",
"com.unity.ai.navigation": "2.0.8",
"com.unity.burst": "1.8.23",
"com.unity.cinemachine": "2.10.4",
"com.unity.ide.rider": "3.0.36",
"com.unity.ide.visualstudio": "2.0.23",
"com.unity.inputsystem": "1.14.0",
"com.unity.localization": "1.5.4",
"com.unity.mathematics": "1.3.2",
"com.unity.multiplayer.center": "1.0.0",
"com.unity.postprocessing": "3.5.0",
"com.unity.recorder": "5.1.2",
"com.unity.test-framework": "1.5.1",
"com.unity.timeline": "1.8.7",
"com.unity.ugui": "2.0.0",
"com.unity.xr.legacyinputhelpers": "3.0.1",
"org.gamecult.aetheria.eve-runtime": "file:org.gamecult.aetheria.eve-runtime",
"org.gamecult.aetheria.state": "file:org.gamecult.aetheria.state",
"org.gamecult.cultmath": "https://github.com/GameCult/CultMath.git?path=/unity/org.gamecult.cultmath#cultmath-unity-v0.1.2",
"org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.43",
"org.gamecult.eve.surface": "file:../../EveUnity/packages/org.gamecult.eve.surface",
"org.gamecult.eve.plugin-fields": "https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v0.2.3",
"org.gamecult.eve.unity-scene": "file:../../EveUnity/packages/org.gamecult.eve.unity-scene",
"org.gamecult.eve.unity-uitoolkit": "file:../../EveUnity/packages/org.gamecult.eve.unity-uitoolkit",
"com.unity.xr.legacyinputhelpers": "2.1.12",
"com.unity.modules.accessibility": "1.0.0",
"com.unity.modules.adaptiveperformance": "1.0.0",
"com.unity.modules.ai": "1.0.0",
"com.unity.modules.androidjni": "1.0.0",
"com.unity.modules.animation": "1.0.0",
@ -53,7 +44,6 @@
"com.unity.modules.unitywebrequestaudio": "1.0.0",
"com.unity.modules.unitywebrequesttexture": "1.0.0",
"com.unity.modules.unitywebrequestwww": "1.0.0",
"com.unity.modules.vectorgraphics": "1.0.0",
"com.unity.modules.vehicles": "1.0.0",
"com.unity.modules.video": "1.0.0",
"com.unity.modules.vr": "1.0.0",

View file

@ -1,57 +0,0 @@
# Aetheria Eve Runtime
This package mounts provider-owned Eve surfaces from Aetheria's typed
CultCache state into Unity UI Toolkit.
`AetheriaEveUnitySceneProviderBridge` is the local-replica adapter. It reads a
provider-owned `.cc` replica in the Unity process; it is not evidence of a
network connection to the daemon. Remote clients open
`AetheriaRuntimeVerseClient.OpenRemoteAsync`, synchronize published documents
through CultMesh, and forward writes to the daemon's primary shard. The generic
EveUnity PlayMode client must compose that remote path before this package can
claim live daemon playability.
Remote command completion is derived only from provider-owned
`gamecult.aetheria.committed_command_fact.v1` documents. The adapter correlates
`CommandId` and waits until the synchronized game surface version reaches the
fact's `SourceFrameId` before emitting a terminal EveUnity receipt. Submission
success is never treated as command acceptance.
`AetheriaEveSurfacePresenter` is a typed surface document lowerer:
- it resolves the active client target from `GameData/aetheria-client.cc`;
- it reads `gamecult.eve.surface.v1` from the selected typed state source, which
is currently still a local `.cc` file transport;
- it lowers the retained tree through `org.gamecult.eve.unity-uitoolkit`;
- it submits renderer-emitted commands as typed `gamecult.eve.command.v1` state
records for Aetheria's provider-owned command bridge.
`AetheriaEveRuntimeBootstrap` mounts the first runtime surface after scene load.
By default it creates a `UIDocument` host for `aetheria.game`, the daemon-owned
game surface, so Unity starts as a lowering surface for the daemon Verse member
without a hand-wired scene object. Set `AETHERIA_EVE_SURFACE_ID` to mount a
different daemon or provider surface such as `aetheria.game.tui`,
`aetheria.daemon.editor`, or `aetheria.daemon.editor.tui`; set
`AETHERIA_STATE_PATH` to override the selected local `.cc` state file, or disable the automatic mount with
`AETHERIA_DISABLE_EVE_RUNTIME_BOOTSTRAP=true` or
`--aetheria-disable-eve-runtime-bootstrap`. Batchmode disables the bootstrap so
compile and smoke gates do not accidentally create renderer state.
Nested CultUI regions are expressed as `embeddedDocuments` / `surface.slot`
entries on the retained surface component. Aetheria mirrors Eve's
`EveEmbeddedDocumentSlot` as `AetheriaRuntimeEmbeddedDocumentSlot`, and the
Unity host resolves those child documents through the same managed CultMesh
state handles as the parent surface. Use this for daemon-owned composite UI
such as inventory panels with dropdown child surfaces; do not rebuild the child
state as a Unity-only model, projector, or drag/drop workaround.
Discovery starts from Eve's `web/fixtures/cultui-embedded-surface.json` fixture
and `docs/parity-testing-harness.md` runtime matrix. Keep Unity covered by the
Aetheria state verifier and `verify-stage7d-unity-parity.ps1`; keep the shared
CultUI contract covered by Eve's browser DSL test, parity harness, and Flutter
parity smoke.
Commands are surfaced as `gamecult.eve.command.v1` requests. The presenter does
not accept or apply them locally; provider acceptance still belongs to the
CultMesh command bridge. Renderer-local command effects would be the wrong
organ wearing a clean shirt.

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: e1c93ed85db7ef24abd1a24ca16abe4e
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 2860def02295c5045af9ba820ea8e696
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,348 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using GameCult.Eve.Surface;
using GameCult.Mesh;
using UnityEngine;
using UnityEngine.UIElements;
#nullable enable
namespace GameCult.Aetheria.EveRuntime
{
[DisallowMultipleComponent]
[RequireComponent(typeof(UIDocument))]
public sealed class AetheriaEveFileSurfacePresenter : MonoBehaviour
{
public const string DebugSurfacePathEnvironmentVariable = "AETHERIA_CULTUI_DEBUG_SURFACE_PATH";
public const string DefaultDebugSurfaceRelativePath = "GameData/cultui-debug-surface.cultui";
[SerializeField]
private string surfaceFilePath = "";
[SerializeField]
private bool mountOnEnable = true;
[SerializeField]
private bool refreshInUpdate = true;
[SerializeField]
private float refreshIntervalSeconds = 0.1f;
private UIDocument? _document;
private string _mountedPath = "";
private DateTime _mountedWriteTimeUtc = DateTime.MinValue;
private long _mountedLength = -1;
private float _nextRefreshTime;
public string SurfaceFilePath
{
get => surfaceFilePath;
set => surfaceFilePath = value ?? "";
}
private void OnEnable()
{
if (mountOnEnable)
Mount();
}
private void Update()
{
if (!refreshInUpdate || Time.unscaledTime < _nextRefreshTime)
return;
_nextRefreshTime = Time.unscaledTime + Mathf.Max(0.01f, refreshIntervalSeconds);
RefreshIfChanged();
}
public void Mount()
{
var path = ResolveSurfacePath(surfaceFilePath);
var document = ResolveDocument();
if (!File.Exists(path))
{
MountError(document, $"CultUI debug surface file not found: {path}");
_mountedPath = "";
return;
}
try
{
var surface = AetheriaEveFileSurfaceDocuments.Read(path);
AetheriaEveUnitySurfaceHost.Render(
transform,
document,
"Aetheria CultUI File Surface",
surface,
request => Debug.Log(
$"CultUI debug file surface command preview: {request.ProviderId}/{request.SurfaceId}/{request.Operation.OperationId}"),
CreateChrome());
CaptureMountedState(path);
}
catch (Exception ex)
{
MountError(document, $"Failed to read CultUI debug surface file: {ex.Message}");
_mountedPath = "";
}
}
private void RefreshIfChanged()
{
var path = ResolveSurfacePath(surfaceFilePath);
if (!File.Exists(path))
return;
var info = new FileInfo(path);
if (string.Equals(_mountedPath, path, StringComparison.Ordinal) &&
_mountedWriteTimeUtc == info.LastWriteTimeUtc &&
_mountedLength == info.Length)
{
return;
}
Mount();
}
private UIDocument ResolveDocument()
{
if (_document == null)
_document = GetComponent<UIDocument>();
return _document;
}
private void CaptureMountedState(string path)
{
var info = new FileInfo(path);
_mountedPath = path;
_mountedWriteTimeUtc = info.LastWriteTimeUtc;
_mountedLength = info.Length;
}
private static void MountError(UIDocument document, string message)
{
var root = document.rootVisualElement;
root.Clear();
var label = new Label(message);
label.AddToClassList("aetheria-eve-runtime-error-label");
root.Add(label);
Debug.LogWarning(message);
}
public static string ResolveSurfacePath(string configuredPath)
{
var environmentPath = Environment.GetEnvironmentVariable(DebugSurfacePathEnvironmentVariable);
var path = string.IsNullOrWhiteSpace(configuredPath) ? environmentPath : configuredPath;
if (string.IsNullOrWhiteSpace(path))
path = Path.Combine(ProjectRoot(), DefaultDebugSurfaceRelativePath);
if (Path.IsPathRooted(path))
return Path.GetFullPath(path);
return Path.GetFullPath(Path.Combine(ProjectRoot(), path));
}
private static string ProjectRoot()
{
return Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
}
private static AetheriaEveUnitySurfaceChrome CreateChrome()
{
return new AetheriaEveUnitySurfaceChrome
{
UseShell = true,
RootAlignItems = Align.FlexEnd,
RootJustifyContent = Justify.FlexStart,
RootPaddingTop = 72f,
RootPaddingRight = 24f,
MaxWidth = 420f,
MinWidth = 360f,
BackgroundColor = new Color(0.05f, 0.07f, 0.09f, 0.94f),
BorderColor = new Color(0.35f, 0.64f, 0.74f, 0.82f)
};
}
}
public static class AetheriaEveFileSurfaceDocuments
{
public static EveSurfaceDocument Read(string path)
{
if (string.IsNullOrWhiteSpace(path))
throw new ArgumentException("CultUI surface path is required.", nameof(path));
using var stream = File.OpenRead(path);
var serializer = new DataContractJsonSerializer(
typeof(FileSurfaceDocument),
new DataContractJsonSerializerSettings
{
UseSimpleDictionaryFormat = true
});
var document = (FileSurfaceDocument?)serializer.ReadObject(stream)
?? throw new InvalidDataException("CultUI surface file is empty.");
return ToEveSurfaceDocument(document, path);
}
private static EveSurfaceDocument ToEveSurfaceDocument(FileSurfaceDocument document, string path)
{
var surface = document.Surface;
var surfaceId = string.IsNullOrWhiteSpace(surface?.Id)
? "aetheria.debug.file_surface"
: surface!.Id!;
var root = document.Surface?.Root == null
? new EveSurfaceComponent(
surfaceId + ".root",
"surface",
new Dictionary<string, string>(StringComparer.Ordinal),
Array.Empty<EveSurfaceComponent>())
: ToComponent(document.Surface.Root);
var writeTime = File.GetLastWriteTimeUtc(path);
var providerId = string.IsNullOrWhiteSpace(document.ProviderId) ? "aetheria.debug" : document.ProviderId!;
var providerKind = string.IsNullOrWhiteSpace(document.ProviderKind) ? "debug.file_surface" : document.ProviderKind!;
var updatedAtUtc = string.IsNullOrWhiteSpace(document.UpdatedAtUtc) ? writeTime.ToString("O") : document.UpdatedAtUtc!;
return new EveSurfaceDocument(
providerId,
providerKind,
document.Title ?? "",
document.Version <= 0 ? writeTime.Ticks : document.Version,
updatedAtUtc,
new EveSurfaceTree(
surfaceId,
root,
(document.Surface?.Styles ?? Array.Empty<FileStyleToken>())
.Select(style => new EveStyleToken(style.Name ?? "", style.Value ?? ""))
.ToArray()),
(document.Commands ?? Array.Empty<FileCommand>())
.Where(command => !string.IsNullOrWhiteSpace(command.Command))
.Select(command => new EveCommandTemplate(CultMesh.OperationBindingRecord(
command.Command ?? "",
command.Label ?? command.Command ?? "",
"",
nameof(CultMeshLocalityKind.Automatic),
command.Transport ?? "debug-log").ToBinding()))
.ToArray());
}
private static EveSurfaceComponent ToComponent(FileSurfaceComponent component)
{
return new EveSurfaceComponent(
component.Id ?? "",
component.Kind ?? "",
ToProps(component.Props),
(component.Children ?? Array.Empty<FileSurfaceComponent>())
.Select(ToComponent)
.ToArray());
}
private static Dictionary<string, string> ToProps(IReadOnlyList<FileProp>? props)
{
var result = new Dictionary<string, string>(StringComparer.Ordinal);
if (props == null)
return result;
foreach (var prop in props)
{
var key = prop.Key;
if (!string.IsNullOrWhiteSpace(key))
result[key!] = prop.Value ?? "";
}
return result;
}
#pragma warning disable 0649
[DataContract]
private sealed class FileSurfaceDocument
{
[DataMember(Name = "schema")]
public string? Schema;
[DataMember(Name = "providerId")]
public string? ProviderId;
[DataMember(Name = "providerKind")]
public string? ProviderKind;
[DataMember(Name = "title")]
public string? Title;
[DataMember(Name = "version")]
public long Version;
[DataMember(Name = "updatedAtUtc")]
public string? UpdatedAtUtc;
[DataMember(Name = "surface")]
public FileSurfaceTree? Surface;
[DataMember(Name = "commands")]
public FileCommand[]? Commands;
}
[DataContract]
private sealed class FileSurfaceTree
{
[DataMember(Name = "id")]
public string? Id;
[DataMember(Name = "root")]
public FileSurfaceComponent? Root;
[DataMember(Name = "styles")]
public FileStyleToken[]? Styles;
}
[DataContract]
private sealed class FileSurfaceComponent
{
[DataMember(Name = "id")]
public string? Id;
[DataMember(Name = "kind")]
public string? Kind;
[DataMember(Name = "props")]
public FileProp[]? Props;
[DataMember(Name = "children")]
public FileSurfaceComponent[]? Children;
}
[DataContract]
private sealed class FileProp
{
[DataMember(Name = "key")]
public string? Key;
[DataMember(Name = "value")]
public string? Value;
}
[DataContract]
private sealed class FileStyleToken
{
[DataMember(Name = "name")]
public string? Name;
[DataMember(Name = "value")]
public string? Value;
}
[DataContract]
private sealed class FileCommand
{
[DataMember(Name = "command")]
public string? Command;
[DataMember(Name = "label")]
public string? Label;
[DataMember(Name = "transport")]
public string? Transport;
}
#pragma warning restore 0649
}
}

View file

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 55fd8c7b47c94f2c9407eb5d59d8e17f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,73 +0,0 @@
using System;
using System.Linq;
using GameCult.Aetheria.State.Verse;
using UnityEngine;
using UnityEngine.UIElements;
#nullable enable
namespace GameCult.Aetheria.EveRuntime
{
public static class AetheriaEveRuntimeBootstrap
{
public const string DefaultSurfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId;
public const string DisableEnvironmentVariable = "AETHERIA_DISABLE_EVE_RUNTIME_BOOTSTRAP";
public const string SurfaceEnvironmentVariable = "AETHERIA_EVE_SURFACE_ID";
public const string FileSurfaceEnvironmentVariable = "AETHERIA_EVE_FILE_SURFACE_PATH";
public const string DisableCommandLineSwitch = "--aetheria-disable-eve-runtime-bootstrap";
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
private static void MountDefaultSurface()
{
if (IsDisabled() ||
UnityEngine.Object.FindAnyObjectByType<AetheriaEveSurfacePresenter>() != null ||
UnityEngine.Object.FindAnyObjectByType<AetheriaEveFileSurfacePresenter>() != null)
{
return;
}
var host = new GameObject("Aetheria Eve Runtime Surface");
UnityEngine.Object.DontDestroyOnLoad(host);
host.AddComponent<UIDocument>();
var fileSurfacePath = FileSurfacePath();
if (!string.IsNullOrWhiteSpace(fileSurfacePath))
{
var presenter = host.AddComponent<AetheriaEveFileSurfacePresenter>();
presenter.SurfaceFilePath = fileSurfacePath;
return;
}
var statePresenter = host.AddComponent<AetheriaEveSurfacePresenter>();
statePresenter.SurfaceId = SurfaceId();
}
private static bool IsDisabled()
{
if (Application.isBatchMode)
return true;
var environment = Environment.GetEnvironmentVariable(DisableEnvironmentVariable);
if (string.Equals(environment, "1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(environment, "true", StringComparison.OrdinalIgnoreCase))
{
return true;
}
return Environment.GetCommandLineArgs()
.Any(argument => string.Equals(argument, DisableCommandLineSwitch, StringComparison.OrdinalIgnoreCase));
}
private static string SurfaceId()
{
var configured = Environment.GetEnvironmentVariable(SurfaceEnvironmentVariable);
return string.IsNullOrWhiteSpace(configured) ? DefaultSurfaceId : configured;
}
private static string FileSurfacePath()
{
var configured = Environment.GetEnvironmentVariable(FileSurfaceEnvironmentVariable);
return string.IsNullOrWhiteSpace(configured) ? "" : configured;
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: dfb126623705ee04c9747f63b945c5f1

View file

@ -1,99 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using GameCult.Aetheria.State.Verse;
using GameCult.Mesh;
using UnityEngine;
#nullable enable
namespace GameCult.Aetheria.EveRuntime
{
public static class AetheriaEveRuntimeUnityClientCache
{
private static readonly Dictionary<string, AetheriaClient> RuntimeClients =
new Dictionary<string, AetheriaClient>(StringComparer.Ordinal);
private static DirectoryInfo? s_gameDataDirectory;
private static string? s_runtimeStateFilePath;
public static DirectoryInfo GameDataDirectory =>
s_gameDataDirectory ??= new DirectoryInfo(Application.dataPath).Parent.CreateSubdirectory("GameData");
public static string RuntimeStateFilePath =>
s_runtimeStateFilePath ??= AetheriaRuntimeStateBoot.Inspect(GameDataDirectory).StateFilePath;
public static AetheriaClientState RuntimeState(
AetheriaRuntimeStateBootReport stateBoot,
string runtimeId = "")
{
return RuntimeClient(stateBoot, runtimeId).State;
}
public static AetheriaControl Control(
AetheriaRuntimeStateBootReport stateBoot,
string runtimeId = "")
{
return RuntimeClient(stateBoot, runtimeId).Control;
}
public static AetheriaUi Ui(
AetheriaRuntimeStateBootReport stateBoot,
string runtimeId = "")
{
return RuntimeClient(stateBoot, runtimeId).Ui;
}
public static CultMeshStateRefResolver EveSurfaceCultMeshStateRefResolver(
AetheriaRuntimeStateBootReport stateBoot,
string runtimeId = "")
{
return RuntimeState(stateBoot, runtimeId).CreateEveSurfaceCultMeshStateRefResolver();
}
public static void ClearRuntimeStateFilePathCache()
{
s_runtimeStateFilePath = null;
}
public static void Dispose()
{
foreach (var client in RuntimeClients.Values)
client.Dispose();
RuntimeClients.Clear();
s_runtimeStateFilePath = null;
}
private static AetheriaClient RuntimeClient(
AetheriaRuntimeStateBootReport stateBoot,
string runtimeId = "")
{
return ResolveClient(
stateBoot.StateFilePath,
string.IsNullOrWhiteSpace(runtimeId) ? stateBoot.RuntimeId : runtimeId);
}
private static AetheriaClient ResolveClient(string stateFilePath, string runtimeId)
{
var effectiveStateFilePath = stateFilePath ?? "";
var effectiveRuntimeId = string.IsNullOrWhiteSpace(runtimeId)
? AetheriaRuntimeStateBoundary.DefaultClientRuntimeId
: runtimeId;
var cacheKey = effectiveStateFilePath + "\n" + effectiveRuntimeId;
if (RuntimeClients.TryGetValue(cacheKey, out var runtimeClient))
return runtimeClient;
runtimeClient = AetheriaClient
.OpenAsync(
effectiveStateFilePath,
effectiveRuntimeId,
"local",
startServer: false,
pullOnOpen: true)
.GetAwaiter()
.GetResult();
RuntimeClients[cacheKey] = runtimeClient;
return runtimeClient;
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 2a2c3bf5e2954d1f8aeab60ee92f7f0a

View file

@ -1,28 +0,0 @@
using GameCult.Aetheria.State.Verse;
using UnityEngine;
#nullable enable
namespace GameCult.Aetheria.EveRuntime
{
public static class AetheriaEveRuntimeUnityHookInstaller
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void Install()
{
AetheriaEveRuntimeUnityHooks.ResolveStateBoot =
overridePath => AetheriaRuntimeStateBoot.Inspect(
AetheriaEveRuntimeUnityClientCache.GameDataDirectory,
overridePath ?? "");
AetheriaEveRuntimeUnityHooks.RuntimeState =
(stateBoot, runtimeId) => AetheriaEveRuntimeUnityClientCache.RuntimeState(stateBoot, runtimeId);
AetheriaEveRuntimeUnityHooks.Control =
(stateBoot, runtimeId) => AetheriaEveRuntimeUnityClientCache.Control(stateBoot, runtimeId);
AetheriaEveRuntimeUnityHooks.Ui =
(stateBoot, runtimeId) => AetheriaEveRuntimeUnityClientCache.Ui(stateBoot, runtimeId);
AetheriaEveRuntimeUnityHooks.StateRefResolver =
(stateBoot, runtimeId) =>
AetheriaEveRuntimeUnityClientCache.EveSurfaceCultMeshStateRefResolver(stateBoot, runtimeId);
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: d60d5ddc6b3240438128f85f90464b42

View file

@ -1,290 +0,0 @@
using System;
using System.Collections.Generic;
using GameCult.Aetheria.State.Verse;
using GameCult.Eve.Surface;
using GameCult.Mesh;
using UnityEngine;
using UnityEngine.UIElements;
using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument;
#nullable enable
namespace GameCult.Aetheria.EveRuntime
{
[DisallowMultipleComponent]
[RequireComponent(typeof(UIDocument))]
public sealed class AetheriaEveSurfacePresenter : MonoBehaviour
{
[SerializeField]
private string surfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId;
[SerializeField]
private string stateFilePathOverride = "";
[SerializeField]
private bool mountOnEnable = true;
[SerializeField]
private bool refreshInUpdate = true;
[SerializeField]
private float refreshIntervalSeconds = 0.1f;
private UIDocument? _document;
private float _nextRefreshTime;
private string _mountedStatePath = "";
private string _mountedSurfaceId = "";
private long _mountedSurfaceVersion = -1;
private string _mountedSurfaceUpdatedAtUtc = "";
private CultMeshReactiveDocument<EveSurfaceDocument>? _reactiveSurfaceState;
private string _reactiveSurfaceStatePath = "";
private string _reactiveSurfaceId = "";
private static readonly AetheriaEveUnitySurfaceChrome RootOnlyChrome = new AetheriaEveUnitySurfaceChrome
{
UseShell = false,
RootPaddingTop = 0f,
RootAlignItems = Align.Stretch,
RootJustifyContent = Justify.FlexStart,
RootPickingMode = PickingMode.Position
};
public string SurfaceId
{
get => surfaceId;
set => surfaceId = value ?? "";
}
public string StateFilePathOverride
{
get => stateFilePathOverride;
set => stateFilePathOverride = value ?? "";
}
public bool RefreshInUpdate
{
get => refreshInUpdate;
set => refreshInUpdate = value;
}
public void Mount()
{
var document = ResolveDocument();
var root = document.rootVisualElement;
root.Clear();
var stateBoot = ResolveStateBoot();
if (!stateBoot.SupportsLocalStateFileRead)
{
root.Add(BuildError(stateBoot.FailureMessage));
return;
}
var statePath = stateBoot.StateFilePath;
if (!stateBoot.StateFileExists)
{
root.Add(BuildError($"Aetheria state file not found: {statePath}"));
return;
}
var surface = ReadDaemonSurface(stateBoot);
if (surface == null)
{
ClearMountedSurface();
return;
}
MountSurface(stateBoot, surface);
}
private void OnEnable()
{
if (mountOnEnable)
Mount();
}
private void OnDisable()
{
DisposeReactiveSurfaceState();
}
private void Update()
{
if (!refreshInUpdate || Time.unscaledTime < _nextRefreshTime)
return;
_nextRefreshTime = Time.unscaledTime + Mathf.Max(0.01f, refreshIntervalSeconds);
RefreshIfChanged();
}
private void RefreshIfChanged()
{
var stateBoot = ResolveStateBoot();
if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists)
return;
var surface = ReadDaemonSurface(stateBoot);
if (surface == null || !ShouldMountSurface(stateBoot.StateFilePath, surface))
return;
MountSurface(stateBoot, surface);
}
private EveSurfaceDocument? ReadDaemonSurface(AetheriaRuntimeStateBootReport stateBoot)
{
var surface = ReadDaemonSurfaceState(stateBoot);
return surface == null
? null
: AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument(
surface,
AetheriaEveRuntimeUnityHooks.TryCreateStateRefResolver(
stateBoot,
"unity-eve-surface-presenter"));
}
private EveSurfaceDocument? ReadDaemonSurfaceState(
AetheriaRuntimeStateBootReport stateBoot)
{
var reactive = ResolveReactiveDaemonSurfaceState(stateBoot);
return reactive?.Current;
}
private CultMeshReactiveDocument<EveSurfaceDocument>? ResolveReactiveDaemonSurfaceState(
AetheriaRuntimeStateBootReport stateBoot)
{
if (_reactiveSurfaceState != null &&
string.Equals(_reactiveSurfaceStatePath, stateBoot.StateFilePath, StringComparison.Ordinal) &&
string.Equals(_reactiveSurfaceId, surfaceId, StringComparison.Ordinal))
{
return _reactiveSurfaceState;
}
DisposeReactiveSurfaceState();
_reactiveSurfaceState = CreateReactiveDaemonSurfaceState(stateBoot);
if (_reactiveSurfaceState != null)
{
_reactiveSurfaceStatePath = stateBoot.StateFilePath;
_reactiveSurfaceId = surfaceId ?? "";
}
return _reactiveSurfaceState;
}
private CultMeshReactiveDocument<EveSurfaceDocument>? CreateReactiveDaemonSurfaceState(
AetheriaRuntimeStateBootReport stateBoot)
{
try
{
return AetheriaEveRuntimeUnityHooks
.RequireRuntimeState(stateBoot, "unity-eve-surface-presenter")
.EveSurfaceDocument(surfaceId)
?.Reactive();
}
catch (KeyNotFoundException)
{
return null;
}
}
private UIDocument ResolveDocument()
{
if (_document == null)
_document = GetComponent<UIDocument>();
return _document;
}
private AetheriaRuntimeStateBootReport ResolveStateBoot()
{
return AetheriaEveRuntimeUnityHooks.RequireStateBoot(stateFilePathOverride);
}
private static VisualElement BuildError(string message)
{
var container = new VisualElement();
container.AddToClassList("aetheria-eve-runtime-error");
var label = new Label(message);
label.AddToClassList("aetheria-eve-runtime-error-label");
container.Add(label);
return container;
}
private bool ShouldMountSurface(string statePath, EveSurfaceDocument surface)
{
return !string.Equals(_mountedStatePath, statePath, StringComparison.Ordinal) ||
!string.Equals(_mountedSurfaceId, surface.Surface.Id, StringComparison.Ordinal) ||
_mountedSurfaceVersion != surface.Version ||
!string.Equals(_mountedSurfaceUpdatedAtUtc, surface.UpdatedAtUtc, StringComparison.Ordinal);
}
private void ClearMountedSurface()
{
var document = ResolveDocument();
document.rootVisualElement.Clear();
_mountedStatePath = "";
_mountedSurfaceId = "";
_mountedSurfaceVersion = -1;
_mountedSurfaceUpdatedAtUtc = "";
}
private void MountSurface(AetheriaRuntimeStateBootReport stateBoot, EveSurfaceDocument surface)
{
var statePath = stateBoot.StateFilePath;
AetheriaEveUnitySurfaceHost.Render(
transform,
ResolveDocument(),
"Aetheria Eve Surface",
surface,
request => EmitCommand(stateBoot, request),
RootOnlyChrome,
AetheriaEveRuntimeUnityHooks.TryCreateStateRefResolver(
stateBoot,
"unity-eve-surface-presenter"));
_mountedStatePath = statePath;
_mountedSurfaceId = surface.Surface.Id;
_mountedSurfaceVersion = surface.Version;
_mountedSurfaceUpdatedAtUtc = surface.UpdatedAtUtc ?? "";
}
private void EmitCommand(AetheriaRuntimeStateBootReport stateBoot, EveSurfaceCommandRequest request)
{
var clientId = string.IsNullOrWhiteSpace(request.ClientId)
? "unity-eve-surface-presenter"
: request.ClientId;
var control = AetheriaEveRuntimeUnityHooks.RequireControl(
stateBoot,
clientId);
if (control.TrySubmitSurfaceCommand(request, out var daemonEnvelope))
{
Debug.Log(
$"Submitted Aetheria daemon operation from Eve surface: {daemonEnvelope!.Kind} {daemonEnvelope.CommandId}");
return;
}
try
{
var envelope = AetheriaEveRuntimeUnityHooks
.RequireUi(stateBoot, "unity-eve-surface-presenter")
.SurfaceCommandAsync(
request,
string.IsNullOrWhiteSpace(request.ClientId) ? "unity-uitoolkit" : request.ClientId)
.GetAwaiter()
.GetResult();
Debug.Log(
$"Submitted Eve operation for CultMesh bridge: {envelope.OperationId}");
}
catch (Exception ex)
{
Debug.LogWarning(
$"Ignored or failed Aetheria Eve command: {request.ProviderId}/{request.SurfaceId}/{request.Operation?.OperationId}: {ex}");
}
}
private void DisposeReactiveSurfaceState()
{
_reactiveSurfaceState?.Dispose();
_reactiveSurfaceState = null;
_reactiveSurfaceStatePath = "";
_reactiveSurfaceId = "";
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: f538568d3b6341c43adeb7e023573756

View file

@ -1,464 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GameCult.Aetheria.State.Verse;
using GameCult.Eve.Surface;
using GameCult.Eve.UnityScene;
using GameCult.Mesh;
using UnityEngine;
using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument;
#nullable enable
namespace GameCult.Aetheria.EveRuntime
{
public sealed class AetheriaEveUnitySceneProviderBridge :
IEveUnitySceneLiveProviderTransport,
IEveUnitySceneProviderSurfaceDocumentSource,
IEveUnityPlayableWorldAssetManifestDocumentSource,
IEveUnitySceneCommandSink,
IEveUnitySceneCommandReceiptSource,
IEveUnityProviderRefreshSource,
IDisposable
{
private readonly string _stateFilePathOverride;
private readonly string _surfaceId;
private readonly string _runtimeId;
private readonly string _cultMeshEndpoint;
private AetheriaRuntimeStateBootReport? _stateBoot;
private AetheriaClientState? _runtimeState;
private AetheriaClient? _remoteClient;
private readonly AetheriaEveUnityRemoteReceiptTracker _remoteReceipts =
new AetheriaEveUnityRemoteReceiptTracker();
private event Action<EveUnityPlayableWorldAssetManifestDocument>? AssetManifestDocumentAvailable;
public AetheriaEveUnitySceneProviderBridge(
string stateFilePathOverride = "",
string surfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId,
string runtimeId = "unity-scene",
string cultMeshEndpoint = "")
{
_stateFilePathOverride = stateFilePathOverride ?? "";
_surfaceId = string.IsNullOrWhiteSpace(surfaceId)
? AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId
: surfaceId;
_runtimeId = string.IsNullOrWhiteSpace(runtimeId) ? "unity-scene" : runtimeId;
_cultMeshEndpoint = cultMeshEndpoint?.Trim() ?? "";
CurrentDocument = CreateSurfaceDocument(AetheriaRuntimeSurfaceDocuments.EmptySurface(_surfaceId), 0);
CurrentDocumentManifest = new EveUnityPlayableWorldAssetManifestDocument(
AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest.ToString(),
Array.Empty<EveUnityPlayableWorldAssetManifestDocumentEntry>(),
"aetheria.daemon");
}
public string TransportKind => IsRemote
? "aetheria-remote-cultmesh-replica"
: "aetheria-local-cultmesh-replica";
public string SurfacePointer => CurrentDocument.SourcePointer;
public string AssetManifestPointer => CurrentDocumentManifest.ManifestRef;
public string SinkKind => "aetheria-daemon-command-boundary";
public string ManifestRef => CurrentDocumentManifest.ManifestRef;
public EveUnitySceneProviderSurfaceDocument CurrentDocument { get; private set; }
EveUnitySceneProviderSurfaceDocument IEveUnitySceneLiveProviderTransport.CurrentSurfaceDocument =>
CurrentDocument;
EveUnityPlayableWorldAssetManifestDocument IEveUnityPlayableWorldAssetManifestDocumentSource.CurrentDocument =>
CurrentDocumentManifest;
EveUnityPlayableWorldAssetManifestDocument IEveUnitySceneLiveProviderTransport.CurrentAssetManifestDocument =>
CurrentDocumentManifest;
public EveUnityPlayableWorldAssetManifestDocument CurrentDocumentManifest { get; private set; }
public event Action<EveUnitySceneProviderSurfaceDocument>? DocumentAvailable;
public event Action<EveUnitySceneCommandReceipt>? ReceiptAvailable;
event Action<EveUnitySceneProviderSurfaceDocument> IEveUnitySceneLiveProviderTransport.SurfaceDocumentAvailable
{
add => DocumentAvailable += value;
remove => DocumentAvailable -= value;
}
event Action<EveUnityPlayableWorldAssetManifestDocument> IEveUnityPlayableWorldAssetManifestDocumentSource.DocumentAvailable
{
add => AssetManifestDocumentAvailable += value;
remove => AssetManifestDocumentAvailable -= value;
}
event Action<EveUnityPlayableWorldAssetManifestDocument> IEveUnitySceneLiveProviderTransport.AssetManifestDocumentAvailable
{
add => AssetManifestDocumentAvailable += value;
remove => AssetManifestDocumentAvailable -= value;
}
event Action<EveUnitySceneCommandReceipt> IEveUnitySceneLiveProviderTransport.CommandReceiptAvailable
{
add => ReceiptAvailable += value;
remove => ReceiptAvailable -= value;
}
public void Connect()
{
Refresh();
}
public void Disconnect()
{
}
public void Refresh()
{
if (IsRemote)
{
var remote = ResolveRemoteClient();
remote.RefreshRemoteAsync().GetAwaiter().GetResult();
RefreshFromRuntimeState(remote.State, null);
return;
}
var stateBoot = ResolveStateBoot();
if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists)
return;
RefreshFromRuntimeState(ResolveRuntimeState(stateBoot), stateBoot);
}
private void RefreshFromRuntimeState(
AetheriaClientState runtimeState,
AetheriaRuntimeStateBootReport? stateBoot)
{
var surface = ReadSurface(runtimeState, stateBoot);
if (surface != null)
{
CurrentDocument = CreateSurfaceDocument(
surface,
surface.Version,
ReadAdvertisedSurface(runtimeState));
DocumentAvailable?.Invoke(CurrentDocument);
}
CurrentDocumentManifest = ReadAssetManifest(runtimeState);
AssetManifestDocumentAvailable?.Invoke(CurrentDocumentManifest);
ReconcileRemoteReceipts();
}
public void Submit(EveSurfaceCommandRequest request)
{
if (request == null) throw new ArgumentNullException(nameof(request));
var clientId = string.IsNullOrWhiteSpace(request.ClientId) ? _runtimeId : request.ClientId;
if (IsRemote)
{
var remote = ResolveRemoteClient();
if (!remote.Control.TrySubmitSurfaceCommand(request, out var submitted))
throw new InvalidOperationException($"Aetheria daemon did not advertise command {request.Operation?.OperationId ?? "unknown"}.");
Debug.Log(
$"Forwarded Aetheria operation to remote CultMesh authority: {submitted!.Kind} {submitted.CommandId}");
_remoteReceipts.Track(submitted.CommandId, request);
return;
}
var stateBoot = ResolveStateBoot();
var control = AetheriaEveRuntimeUnityHooks.RequireControl(stateBoot, clientId);
if (control.TrySubmitSurfaceCommand(request, out var daemonEnvelope))
{
Debug.Log(
$"Submitted Aetheria daemon operation from Eve Unity scene: {daemonEnvelope!.Kind} {daemonEnvelope.CommandId}");
ReceiptAvailable?.Invoke(ToReceipt(request, daemonEnvelope));
return;
}
var envelope = AetheriaEveRuntimeUnityHooks
.RequireUi(stateBoot, _runtimeId)
.SurfaceCommandAsync(request, clientId)
.GetAwaiter()
.GetResult();
Debug.Log($"Submitted Eve Unity scene operation for CultMesh bridge: {envelope.OperationId}");
ReceiptAvailable?.Invoke(ToReceipt(request, envelope));
}
public void SubmitCommand(EveSurfaceCommandRequest request)
{
Submit(request);
}
public void Dispose()
{
_remoteClient?.Dispose();
_remoteClient = null;
_remoteReceipts.Clear();
}
private bool IsRemote => !string.IsNullOrWhiteSpace(_cultMeshEndpoint);
private AetheriaClient ResolveRemoteClient()
{
if (_remoteClient != null)
return _remoteClient;
var replicaPath = string.IsNullOrWhiteSpace(_stateFilePathOverride)
? System.IO.Path.Combine(Application.persistentDataPath, "aetheria-remote-replica.cc")
: _stateFilePathOverride;
_remoteClient = AetheriaClient
.OpenRemoteAsync(
replicaPath,
_cultMeshEndpoint,
_runtimeId,
sessionId: "eve-unity",
synchronizeOnOpen: false)
.GetAwaiter()
.GetResult();
return _remoteClient;
}
private void ReconcileRemoteReceipts()
{
if (!IsRemote || !_remoteReceipts.HasPending || _remoteClient == null)
return;
foreach (var receipt in _remoteReceipts.Reconcile(
_remoteClient.CommittedCommandFacts(),
CurrentDocument.Version))
ReceiptAvailable?.Invoke(receipt);
}
private AetheriaRuntimeStateBootReport ResolveStateBoot()
{
var stateBoot = AetheriaEveRuntimeUnityHooks.RequireStateBoot(_stateFilePathOverride);
_stateBoot = stateBoot;
return stateBoot;
}
private AetheriaClientState ResolveRuntimeState(AetheriaRuntimeStateBootReport stateBoot)
{
var runtimeState = AetheriaEveRuntimeUnityHooks.RequireRuntimeState(stateBoot, _runtimeId);
_runtimeState = runtimeState;
return runtimeState;
}
private EveSurfaceDocument? ReadSurface(
AetheriaClientState runtimeState,
AetheriaRuntimeStateBootReport? stateBoot)
{
try
{
var handle = runtimeState.EveSurfaceDocument(_surfaceId);
if (handle == null)
return null;
var resolver = stateBoot == null
? null
: AetheriaEveRuntimeUnityHooks.TryCreateStateRefResolver(stateBoot.Value, _runtimeId);
return AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument(handle.Latest(), resolver);
}
catch (KeyNotFoundException)
{
return null;
}
}
private EveUnityPlayableWorldAssetManifestDocument ReadAssetManifest(AetheriaClientState runtimeState)
{
var manifest = runtimeState.AssetManifest.Latest();
return ToUnityPlayableWorldAssetManifest(manifest);
}
private AetheriaRuntimeEveSurfaceAdvertisement? ReadAdvertisedSurface(AetheriaClientState runtimeState)
{
var provider = runtimeState.ProviderAdvertisement.Latest();
return (provider.EveSurfaces ?? Array.Empty<AetheriaRuntimeEveSurfaceAdvertisement>())
.FirstOrDefault(surface => surface != null && string.Equals(surface.SurfaceId, _surfaceId, StringComparison.Ordinal))
?? AetheriaRuntimeEveSurfaceCatalog.Find(_surfaceId);
}
private EveUnitySceneProviderSurfaceDocument CreateSurfaceDocument(
EveSurfaceDocument surface,
long version,
AetheriaRuntimeEveSurfaceAdvertisement? advertisedSurface = null)
{
advertisedSurface ??= AetheriaRuntimeEveSurfaceCatalog.Find(_surfaceId);
var worldInteraction = advertisedSurface?.WorldInteraction;
return new EveUnitySceneProviderSurfaceDocument(
surface,
new EveUnitySceneProviderSurfaceAdvertisement(
_surfaceId,
advertisedSurface?.SurfaceKind ?? "",
new EveUnitySceneWorldInteraction(
worldInteraction?.ProjectionKind ?? "",
worldInteraction?.CommandBoundary ?? "",
worldInteraction?.ReceiptSchema ?? "",
worldInteraction?.Ownership ?? "")),
SourcePointer(advertisedSurface),
version);
}
private string SourcePointer(AetheriaRuntimeEveSurfaceAdvertisement? advertisedSurface)
{
if (!string.IsNullOrWhiteSpace(advertisedSurface?.RecordRef))
return advertisedSurface.RecordRef;
var path = _stateBoot?.StateFilePath ?? _stateFilePathOverride;
return string.IsNullOrWhiteSpace(path)
? AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString()
: $"{path}#{_surfaceId}";
}
private static EveUnitySceneCommandReceipt ToReceipt(
EveSurfaceCommandRequest request,
AetheriaRuntimeDaemonCommandEnvelope envelope)
{
if (request == null) throw new ArgumentNullException(nameof(request));
if (envelope == null) throw new ArgumentNullException(nameof(envelope));
return new EveUnitySceneCommandReceipt(
string.IsNullOrWhiteSpace(envelope.CommandId) ? envelope.OperationId : envelope.CommandId,
envelope.OperationId,
envelope.CommandId,
envelope.Accepted ? "accepted" : "denied",
"Aetheria",
"aetheria-daemon-command-boundary",
request.ReceiptSchema,
request.ProviderId,
request.SurfaceId,
envelope.Diagnostic ?? "",
ParseIssuedAt(envelope.IssuedAtUtc, request.IssuedAt));
}
private static EveUnitySceneCommandReceipt ToReceipt(
EveSurfaceCommandRequest request,
CultMeshOperationReceipt receipt)
{
if (request == null) throw new ArgumentNullException(nameof(request));
if (receipt == null) throw new ArgumentNullException(nameof(receipt));
var commandId = request.Operation?.IdempotencyKey ?? "";
return new EveUnitySceneCommandReceipt(
string.IsNullOrWhiteSpace(commandId) ? receipt.OperationId : commandId,
receipt.OperationId,
commandId,
receipt.Accepted ? "accepted" : "denied",
"Aetheria",
"aetheria-eve-command-boundary",
request.ReceiptSchema,
request.ProviderId,
request.SurfaceId,
receipt.Diagnostic ?? "",
request.IssuedAt);
}
private static DateTimeOffset ParseIssuedAt(string value, DateTimeOffset fallback)
{
return DateTimeOffset.TryParse(value, out var parsed) ? parsed : fallback;
}
private static EveUnityPlayableWorldAssetManifestDocument ToUnityPlayableWorldAssetManifest(
AetheriaRuntimeAssetManifestDocument manifest)
{
var entries = (manifest.Assets ?? Array.Empty<AetheriaRuntimeAssetManifestEntry>())
.Where(entry => entry?.Ref != null && !string.IsNullOrWhiteSpace(entry.Ref.AssetKey))
.Select(entry => new EveUnityPlayableWorldAssetManifestDocumentEntry(
entry.Ref.AssetKey,
EntityKind(entry),
ResourcePath(entry.Ref),
entry.Ref.AssetKey,
"provider-asset-ref"))
.ToArray();
return new EveUnityPlayableWorldAssetManifestDocument(
AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest.ToString(),
entries,
"aetheria.daemon");
}
private static string EntityKind(AetheriaRuntimeAssetManifestEntry entry)
{
if (entry.Ref.Metadata != null &&
entry.Ref.Metadata.TryGetValue("presentationRole", out var role) &&
!string.IsNullOrWhiteSpace(role))
return role;
return (entry.Tags ?? Array.Empty<string>())
.FirstOrDefault(tag => !string.IsNullOrWhiteSpace(tag) && !string.Equals(tag, "map", StringComparison.OrdinalIgnoreCase) && !string.Equals(tag, "icon", StringComparison.OrdinalIgnoreCase))
?? "";
}
private static string ResourcePath(AetheriaRuntimeAssetRef asset)
{
if (asset.Metadata != null && asset.Metadata.TryGetValue("resourcesPath", out var resourcesPath))
return resourcesPath ?? "";
return string.IsNullOrWhiteSpace(asset.AssetKey)
? ""
: asset.AssetKey.Replace('.', '/');
}
}
public sealed class AetheriaEveUnityRemoteReceiptTracker
{
private readonly Dictionary<string, EveSurfaceCommandRequest> _pending =
new Dictionary<string, EveSurfaceCommandRequest>(StringComparer.Ordinal);
public bool HasPending => _pending.Count > 0;
public void Track(string commandId, EveSurfaceCommandRequest request)
{
if (string.IsNullOrWhiteSpace(commandId))
throw new ArgumentException("Remote command id must be non-empty.", nameof(commandId));
_pending[commandId] = request ?? throw new ArgumentNullException(nameof(request));
}
public IReadOnlyList<EveUnitySceneCommandReceipt> Reconcile(
IReadOnlyList<AetheriaRuntimeCommittedCommandFactDocument> facts,
long observedSurfaceVersion)
{
if (facts == null) throw new ArgumentNullException(nameof(facts));
var receipts = new List<EveUnitySceneCommandReceipt>();
var byCommandId = facts
.Where(fact => fact != null && !string.IsNullOrWhiteSpace(fact.CommandId))
.GroupBy(fact => fact.CommandId, StringComparer.Ordinal)
.ToDictionary(
group => group.Key,
group => group.OrderByDescending(fact => fact.SourceFrameId).First(),
StringComparer.Ordinal);
foreach (var commandId in _pending.Keys.ToArray())
{
if (!byCommandId.TryGetValue(commandId, out var fact) ||
observedSurfaceVersion < fact.SourceFrameId)
continue;
var request = _pending[commandId];
_pending.Remove(commandId);
var accepted = string.Equals(
fact.Outcome,
AetheriaRuntimeCommandFactOutcomes.Applied,
StringComparison.Ordinal);
receipts.Add(new EveUnitySceneCommandReceipt(
fact.FactId,
request.Operation?.OperationId ?? "",
commandId,
accepted ? "accepted" : "denied",
"Aetheria",
fact.SourceDaemonId,
AetheriaRuntimeDaemonSchemas.CommittedCommandFact,
request.ProviderId,
request.SurfaceId,
accepted ? "Command applied by authoritative daemon." : "Command rejected by authoritative daemon.",
DateTimeOffset.TryParse(fact.CommittedAtUtc, out var committedAt) ? committedAt : request.IssuedAt,
fact.SourceFrameId));
}
return receipts;
}
public void Clear()
{
_pending.Clear();
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 3e2e1c6c7bb24b44b89529166d1676a2

View file

@ -1,161 +0,0 @@
using System;
using GameCult.Aetheria.State.Verse;
using GameCult.Eve.Surface;
using GameCult.Eve.UnityScene;
using UnityEngine;
#nullable enable
namespace GameCult.Aetheria.EveRuntime
{
public sealed class AetheriaEveUnitySceneProviderComponent :
EveUnitySceneLiveProviderTransportBehaviour,
IEveUnitySceneProviderSurfaceDocumentSource,
IEveUnityPlayableWorldAssetManifestDocumentSource,
IEveUnitySceneCommandSink,
IEveUnitySceneCommandReceiptSource,
IEveUnityProviderRefreshSource,
IDisposable
{
[SerializeField] private string stateFilePathOverride = "";
[SerializeField] private string cultMeshEndpoint = "";
[SerializeField] private string surfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId;
[SerializeField] private string runtimeId = "unity-scene";
[SerializeField] private bool refreshOnEnable = true;
private AetheriaEveUnitySceneProviderBridge? _bridge;
public override string TransportKind => Bridge.TransportKind;
public override string SurfacePointer => Bridge.SurfacePointer;
public override string AssetManifestPointer => Bridge.AssetManifestPointer;
public override EveUnitySceneProviderSurfaceDocument CurrentSurfaceDocument =>
((IEveUnitySceneLiveProviderTransport)Bridge).CurrentSurfaceDocument;
public override EveUnityPlayableWorldAssetManifestDocument CurrentAssetManifestDocument =>
((IEveUnitySceneLiveProviderTransport)Bridge).CurrentAssetManifestDocument;
public string SinkKind => Bridge.SinkKind;
public string ManifestRef => ((IEveUnityPlayableWorldAssetManifestDocumentSource)Bridge).ManifestRef;
public EveUnitySceneProviderSurfaceDocument CurrentDocument => Bridge.CurrentDocument;
EveUnityPlayableWorldAssetManifestDocument IEveUnityPlayableWorldAssetManifestDocumentSource.CurrentDocument =>
((IEveUnityPlayableWorldAssetManifestDocumentSource)Bridge).CurrentDocument;
public event Action<EveUnitySceneProviderSurfaceDocument>? DocumentAvailable
{
add => Bridge.DocumentAvailable += value;
remove => Bridge.DocumentAvailable -= value;
}
public override event Action<EveUnitySceneProviderSurfaceDocument> SurfaceDocumentAvailable
{
add => ((IEveUnitySceneLiveProviderTransport)Bridge).SurfaceDocumentAvailable += value;
remove => ((IEveUnitySceneLiveProviderTransport)Bridge).SurfaceDocumentAvailable -= value;
}
public event Action<EveUnitySceneCommandReceipt>? ReceiptAvailable
{
add => Bridge.ReceiptAvailable += value;
remove => Bridge.ReceiptAvailable -= value;
}
public override event Action<EveUnitySceneCommandReceipt> CommandReceiptAvailable
{
add => ((IEveUnitySceneLiveProviderTransport)Bridge).CommandReceiptAvailable += value;
remove => ((IEveUnitySceneLiveProviderTransport)Bridge).CommandReceiptAvailable -= value;
}
event Action<EveUnityPlayableWorldAssetManifestDocument> IEveUnityPlayableWorldAssetManifestDocumentSource.DocumentAvailable
{
add => ((IEveUnityPlayableWorldAssetManifestDocumentSource)Bridge).DocumentAvailable += value;
remove => ((IEveUnityPlayableWorldAssetManifestDocumentSource)Bridge).DocumentAvailable -= value;
}
public override event Action<EveUnityPlayableWorldAssetManifestDocument> AssetManifestDocumentAvailable
{
add => ((IEveUnitySceneLiveProviderTransport)Bridge).AssetManifestDocumentAvailable += value;
remove => ((IEveUnitySceneLiveProviderTransport)Bridge).AssetManifestDocumentAvailable -= value;
}
public void Configure(
string stateFilePath,
string surfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId,
string runtimeId = "unity-scene",
string endpoint = "")
{
Dispose();
stateFilePathOverride = stateFilePath ?? "";
this.surfaceId = string.IsNullOrWhiteSpace(surfaceId)
? AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId
: surfaceId;
this.runtimeId = string.IsNullOrWhiteSpace(runtimeId)
? "unity-scene"
: runtimeId;
cultMeshEndpoint = endpoint ?? "";
}
public override void Connect()
{
Bridge.Connect();
}
public override void Disconnect()
{
Bridge.Disconnect();
}
public override void Refresh()
{
Bridge.Refresh();
}
public void Submit(EveSurfaceCommandRequest request)
{
Bridge.Submit(request);
}
public override void SubmitCommand(EveSurfaceCommandRequest request)
{
Bridge.SubmitCommand(request);
}
public void Dispose()
{
_bridge?.Dispose();
_bridge = null;
}
private AetheriaEveUnitySceneProviderBridge Bridge
{
get
{
if (_bridge == null)
{
_bridge = new AetheriaEveUnitySceneProviderBridge(
stateFilePathOverride,
surfaceId,
runtimeId,
cultMeshEndpoint);
}
return _bridge;
}
}
private void OnEnable()
{
if (refreshOnEnable)
Refresh();
}
private void OnDestroy()
{
Dispose();
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 456b6126c7330234799fc1609e117b9a

View file

@ -1,411 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GameCult.Aetheria.State.Verse;
using GameCult.Eve.Surface;
using GameCult.Eve.UnityUIToolkit;
using GameCult.Mesh;
using UnityEngine;
using UnityEngine.TextCore.LowLevel;
using UnityEngine.UIElements;
using TextCoreFontAsset = UnityEngine.TextCore.Text.FontAsset;
namespace GameCult.Aetheria.EveRuntime
{
public sealed class AetheriaEveUnitySurfaceChrome
{
public float RootPaddingTop = 16f;
public Align RootAlignItems = Align.Center;
public Justify RootJustifyContent = Justify.FlexStart;
public PickingMode RootPickingMode = PickingMode.Ignore;
public float RootPaddingLeft = 0f;
public float RootPaddingRight = 0f;
public float RootPaddingBottom = 0f;
public Color RootBackgroundColor = new Color(0f, 0f, 0f, 0f);
public float Width = 0f;
public float MinWidth = 420f;
public float MaxWidth = 760f;
public float MaxHeight = 0f;
public float FlexGrow = 0f;
public FlexDirection FlexDirection = FlexDirection.Column;
public float PaddingLeft = 16f;
public float PaddingRight = 16f;
public float PaddingTop = 12f;
public float PaddingBottom = 12f;
public float BorderRadius = 8f;
public float BorderWidth = 1f;
public Color BackgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.94f);
public Color BorderColor = new Color(0.3f, 0.47f, 0.71f, 0.8f);
public PickingMode ShellPickingMode = PickingMode.Position;
public bool UseShell = true;
}
public static class AetheriaEveUnitySurfaceHost
{
private static PanelSettings _runtimePanelSettings;
private static readonly Dictionary<string, TextCoreFontAsset> FontCache = new Dictionary<string, TextCoreFontAsset>(StringComparer.Ordinal);
public static UIDocument Render(
Transform owner,
UIDocument document,
string hostName,
EveSurfaceDocument surface,
Action<EveSurfaceCommandRequest> commandHandler,
AetheriaEveUnitySurfaceChrome chrome,
CultMeshStateRefResolver stateRefResolver = null,
Func<EveEmbeddedDocumentSlot, EveSurfaceDocument> embeddedDocumentResolver = null,
int sortingOrder = 1000)
{
if (owner == null)
throw new ArgumentNullException(nameof(owner));
if (surface == null)
throw new ArgumentNullException(nameof(surface));
if (commandHandler == null)
throw new ArgumentNullException(nameof(commandHandler));
document = ResolveDocument(owner, document, hostName, sortingOrder);
document.gameObject.SetActive(true);
var root = document.rootVisualElement;
ConfigureRoot(root, chrome);
var effectiveStateRefResolver = stateRefResolver ?? (
ContainsStateRefs(surface)
? CreateDefaultStateRefResolver()
: null);
surface = AetheriaRuntimeSurfaceDocuments.ResolveStateRefs(surface, effectiveStateRefResolver);
var lowerer = new EveUiToolkitSurfaceLowerer(new EveUiToolkitSurfaceOptions(embeddedDocumentResolver));
VisualElement lowered;
if (chrome.UseShell)
{
var shell = CreateShell(chrome);
root.Add(shell);
lowered = lowerer.Lower(surface, commandHandler);
ApplyTheme(lowered, surface);
shell.Add(lowered);
}
else
{
lowered = lowerer.Lower(surface, commandHandler);
ApplyTheme(lowered, surface);
root.Add(lowered);
}
Debug.Log(
$"Rendered Eve surface '{surface.Surface.Id}' into UIDocument '{document.name}' with panel '{document.panelSettings?.name ?? "none"}'.");
return document;
}
public static UIDocument RenderRuntime(
Transform owner,
UIDocument document,
string hostName,
AetheriaRuntimeSurfaceDocument surface,
Action<EveSurfaceCommandRequest> commandHandler,
AetheriaEveUnitySurfaceChrome chrome,
CultMeshStateRefResolver stateRefResolver = null,
Func<EveEmbeddedDocumentSlot, AetheriaRuntimeSurfaceDocument> embeddedDocumentResolver = null,
int sortingOrder = 1000)
{
if (surface == null)
throw new ArgumentNullException(nameof(surface));
return Render(
owner,
document,
hostName,
AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument(surface),
commandHandler,
chrome,
stateRefResolver,
embeddedDocumentResolver == null
? null
: slot =>
{
var nested = embeddedDocumentResolver(slot);
return nested == null ? null : AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument(nested);
},
sortingOrder);
}
public static void Hide(UIDocument document)
{
if (document == null)
return;
document.rootVisualElement.Clear();
document.gameObject.SetActive(false);
}
public static void DestroyDocument(UIDocument document)
{
if (document == null)
return;
UnityEngine.Object.Destroy(document.gameObject);
}
private static UIDocument ResolveDocument(
Transform owner,
UIDocument document,
string hostName,
int sortingOrder)
{
if (document != null)
{
if (document.panelSettings == null)
document.panelSettings = ResolveRuntimePanelSettings();
return document;
}
var host = new GameObject(string.IsNullOrWhiteSpace(hostName) ? "Aetheria Eve Surface" : hostName);
host.SetActive(false);
host.transform.SetParent(owner, false);
host.layer = owner.gameObject.layer;
document = host.AddComponent<UIDocument>();
document.sortingOrder = sortingOrder;
document.panelSettings = ResolveRuntimePanelSettings();
return document;
}
private static PanelSettings ResolveRuntimePanelSettings()
{
if (_runtimePanelSettings != null)
return _runtimePanelSettings;
_runtimePanelSettings = ScriptableObject.CreateInstance<PanelSettings>();
_runtimePanelSettings.name = "Aetheria Runtime Eve Panel Settings";
_runtimePanelSettings.scaleMode = PanelScaleMode.ScaleWithScreenSize;
_runtimePanelSettings.referenceResolution = new Vector2Int(1920, 1080);
_runtimePanelSettings.screenMatchMode = PanelScreenMatchMode.MatchWidthOrHeight;
_runtimePanelSettings.match = 0.5f;
_runtimePanelSettings.themeStyleSheet = Resources.Load<ThemeStyleSheet>("UnityDefaultRuntimeTheme");
if (_runtimePanelSettings.themeStyleSheet == null)
Debug.LogWarning("Aetheria runtime Eve panel could not load UnityDefaultRuntimeTheme.tss.");
return _runtimePanelSettings;
}
private static void ConfigureRoot(VisualElement root, AetheriaEveUnitySurfaceChrome chrome)
{
root.Clear();
root.style.flexGrow = 1;
root.style.position = Position.Absolute;
root.style.left = 0;
root.style.top = 0;
root.style.right = 0;
root.style.bottom = 0;
root.style.alignItems = chrome.RootAlignItems;
root.style.justifyContent = chrome.RootJustifyContent;
root.style.paddingLeft = chrome.RootPaddingLeft;
root.style.paddingTop = chrome.RootPaddingTop;
root.style.paddingRight = chrome.RootPaddingRight;
root.style.paddingBottom = chrome.RootPaddingBottom;
if (chrome.RootBackgroundColor.a > 0f)
root.style.backgroundColor = chrome.RootBackgroundColor;
root.pickingMode = chrome.RootPickingMode;
}
private static VisualElement CreateShell(AetheriaEveUnitySurfaceChrome chrome)
{
var shell = new VisualElement();
shell.style.flexDirection = chrome.FlexDirection;
shell.style.flexGrow = chrome.FlexGrow;
if (chrome.Width > 0f)
shell.style.width = chrome.Width;
shell.style.minWidth = chrome.MinWidth;
shell.style.maxWidth = chrome.MaxWidth;
if (chrome.MaxHeight > 0f)
shell.style.maxHeight = chrome.MaxHeight;
shell.style.backgroundColor = chrome.BackgroundColor;
shell.style.borderTopLeftRadius = chrome.BorderRadius;
shell.style.borderTopRightRadius = chrome.BorderRadius;
shell.style.borderBottomLeftRadius = chrome.BorderRadius;
shell.style.borderBottomRightRadius = chrome.BorderRadius;
shell.style.paddingLeft = chrome.PaddingLeft;
shell.style.paddingRight = chrome.PaddingRight;
shell.style.paddingTop = chrome.PaddingTop;
shell.style.paddingBottom = chrome.PaddingBottom;
shell.style.borderLeftWidth = chrome.BorderWidth;
shell.style.borderRightWidth = chrome.BorderWidth;
shell.style.borderTopWidth = chrome.BorderWidth;
shell.style.borderBottomWidth = chrome.BorderWidth;
shell.style.borderLeftColor = chrome.BorderColor;
shell.style.borderRightColor = chrome.BorderColor;
shell.style.borderTopColor = chrome.BorderColor;
shell.style.borderBottomColor = chrome.BorderColor;
shell.pickingMode = chrome.ShellPickingMode;
return shell;
}
private static void ApplyTheme(VisualElement root, EveSurfaceDocument surface)
{
if (root == null || surface?.Surface?.Styles == null)
return;
var tokens = surface.Surface.Styles
.Where(token => !string.IsNullOrWhiteSpace(token?.Name))
.GroupBy(token => token.Name, StringComparer.Ordinal)
.ToDictionary(group => group.Key, group => group.Last().Value ?? "", StringComparer.Ordinal);
var bodyFont = ResolveFont(tokens, "body");
var titleFont = ResolveFont(tokens, "title") ?? bodyFont;
if (bodyFont != null)
{
root.Query<TextElement>().ForEach(element =>
element.style.unityFontDefinition = new StyleFontDefinition(bodyFont));
}
if (titleFont != null)
{
root.Query<TextElement>(className: "eve-kind-text-title").ForEach(element =>
element.style.unityFontDefinition = new StyleFontDefinition(titleFont));
}
}
private static TextCoreFontAsset ResolveFont(IReadOnlyDictionary<string, string> tokens, string role)
{
if (tokens == null || string.IsNullOrWhiteSpace(role))
return null;
if (!tokens.TryGetValue($"font.{role}.family", out var family) || string.IsNullOrWhiteSpace(family))
return null;
tokens.TryGetValue($"font.{role}.style", out var style);
style = string.IsNullOrWhiteSpace(style) ? "Regular" : style;
var key = $"{family}\n{style}";
if (FontCache.TryGetValue(key, out var cached))
return cached;
var font = TryCreateFontAsset(family, style)
?? TryCreateFontAsset(family, "Regular")
?? TryCreateFontAsset($"{family} {style}", "Regular");
if (font == null)
{
Debug.LogWarning($"Aetheria Eve UI could not resolve runtime font '{family}' style '{style}'.");
return null;
}
FontCache[key] = font;
return font;
}
private static TextCoreFontAsset TryCreateFontAsset(string family, string style)
{
try
{
return TextCoreFontAsset.CreateFontAsset(
family,
string.IsNullOrWhiteSpace(style) ? "Regular" : style,
90,
9,
GlyphRenderMode.SDFAA);
}
catch
{
return null;
}
}
private static bool ContainsStateRefs(EveSurfaceDocument surface)
{
return surface?.Surface?.Root != null && ContainsStateRefs(surface.Surface.Root);
}
private static bool ContainsStateRefs(EveSurfaceComponent component)
{
if (component.Props != null &&
component.Props.Any(prop =>
!string.IsNullOrWhiteSpace(prop.Value) &&
(string.Equals(prop.Key, AetheriaRuntimeSurfaceStateRefs.Source, StringComparison.Ordinal) ||
prop.Key.EndsWith("Ref", StringComparison.Ordinal))))
{
return true;
}
foreach (var child in component.Children)
{
if (ContainsStateRefs(child))
return true;
}
return false;
}
private static CultMeshStateRefResolver CreateDefaultStateRefResolver()
{
return AetheriaEveRuntimeUnityHooks.TryCreateDefaultStateRefResolver();
}
}
public static class AetheriaEveRuntimeUnityHooks
{
public static Func<string, AetheriaRuntimeStateBootReport> ResolveStateBoot { get; set; }
public static Func<AetheriaRuntimeStateBootReport, string, AetheriaClientState> RuntimeState { get; set; }
public static Func<AetheriaRuntimeStateBootReport, string, AetheriaControl> Control { get; set; }
public static Func<AetheriaRuntimeStateBootReport, string, AetheriaUi> Ui { get; set; }
public static Func<AetheriaRuntimeStateBootReport, string, CultMeshStateRefResolver> StateRefResolver { get; set; }
public static AetheriaRuntimeStateBootReport RequireStateBoot(string stateFilePathOverride)
{
if (ResolveStateBoot == null)
throw new InvalidOperationException("Aetheria Eve runtime hooks are not installed.");
return ResolveStateBoot(stateFilePathOverride ?? "");
}
public static AetheriaClientState RequireRuntimeState(
AetheriaRuntimeStateBootReport stateBoot,
string runtimeId)
{
if (RuntimeState == null)
throw new InvalidOperationException("Aetheria Eve runtime state hook is not installed.");
return RuntimeState(stateBoot, runtimeId ?? "");
}
public static AetheriaControl RequireControl(
AetheriaRuntimeStateBootReport stateBoot,
string runtimeId)
{
if (Control == null)
throw new InvalidOperationException("Aetheria Eve runtime control hook is not installed.");
return Control(stateBoot, runtimeId ?? "");
}
public static AetheriaUi RequireUi(
AetheriaRuntimeStateBootReport stateBoot,
string runtimeId)
{
if (Ui == null)
throw new InvalidOperationException("Aetheria Eve runtime UI hook is not installed.");
return Ui(stateBoot, runtimeId ?? "");
}
public static CultMeshStateRefResolver TryCreateStateRefResolver(
AetheriaRuntimeStateBootReport stateBoot,
string runtimeId)
{
return StateRefResolver?.Invoke(stateBoot, runtimeId ?? "");
}
public static CultMeshStateRefResolver TryCreateDefaultStateRefResolver()
{
if (ResolveStateBoot == null || StateRefResolver == null)
return null;
var stateBoot = ResolveStateBoot("");
if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists)
return null;
return StateRefResolver(stateBoot, "unity-eve-surface-host");
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: eb0db70846a048e7b15232a70d9f5f8e

View file

@ -1,19 +0,0 @@
{
"name": "GameCult.Aetheria.EveRuntime",
"rootNamespace": "GameCult.Aetheria.EveRuntime",
"references": [
"GameCult.Aetheria.State.Unity",
"GameCult.Eve.Surface",
"GameCult.Eve.UnityScene",
"GameCult.Eve.UnityUIToolkit"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 71b47dbc13067094fb870e349a6cbd14
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,20 +0,0 @@
{
"name": "org.gamecult.aetheria.eve-runtime",
"displayName": "Aetheria Eve Runtime",
"version": "0.1.0",
"unity": "2021.3",
"description": "Aetheria provider bridge from typed CultCache Eve surfaces to generic Eve Unity lowerers.",
"dependencies": {
"org.gamecult.aetheria.state": "0.1.0",
"org.gamecult.eve.unity-scene": "0.1.0",
"org.gamecult.eve.unity-uitoolkit": "0.1.0"
},
"keywords": [
"Aetheria",
"Eve",
"CultCache",
"UI Toolkit"
],
"license": "MPL-2.0",
"category": "Scripting"
}

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: bb8d7a317e284914da0dfaccc300c17c
PackageManifestImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: bd2e1c0bcf53796468d8246c0a4bdf72
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,229 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using GameCult.Eve.Surface;
using GameCult.Mesh;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public sealed class AetheriaClient : IDisposable
{
private readonly AetheriaRuntimeVerseClient _verse;
private readonly string _clientId;
private readonly string _sessionId;
private readonly AetheriaRuntimeDaemonOperationsClient _operations;
private readonly AetheriaControl _control;
private readonly AetheriaUi _ui;
private readonly AetheriaClientState _state;
private CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument>? _daemonFrame;
private bool _disposed;
private AetheriaClient(AetheriaRuntimeVerseClient verse, string clientId, string sessionId)
{
_verse = verse ?? throw new ArgumentNullException(nameof(verse));
_clientId = string.IsNullOrWhiteSpace(clientId) ? AetheriaRuntimeVerseClient.DefaultRuntimeId : clientId;
_sessionId = string.IsNullOrWhiteSpace(sessionId) ? "local" : sessionId;
_operations = new AetheriaRuntimeDaemonOperationsClient(SendOperation);
_control = new AetheriaControl(_operations);
_ui = new AetheriaUi(this);
_state = _verse.Aetheria();
}
public string StatePath => _verse.StatePath;
public string RuntimeId => _verse.RuntimeId;
public AetheriaControl Control => _control;
public AetheriaUi Ui => _ui;
public AetheriaClientState State => _state;
internal AetheriaRuntimeDaemonOperationsClient Operations => _operations;
public static async Task<AetheriaClient> OpenAsync(
string statePath,
string runtimeId = AetheriaRuntimeVerseClient.DefaultRuntimeId,
string sessionId = "local",
bool startServer = false,
bool pullOnOpen = true)
{
var verse = await AetheriaRuntimeVerseClient
.OpenAsync(statePath, runtimeId, startServer, pullOnOpen)
.ConfigureAwait(false);
return new AetheriaClient(verse, runtimeId, sessionId);
}
public static async Task<AetheriaClient> OpenRemoteAsync(
string replicaStatePath,
string endpoint,
string runtimeId = AetheriaRuntimeVerseClient.DefaultRuntimeId,
string sessionId = "remote",
bool synchronizeOnOpen = true)
{
var verse = await AetheriaRuntimeVerseClient
.OpenRemoteAsync(
replicaStatePath,
endpoint,
runtimeId,
pullOnOpen: false,
synchronizeOnOpen: synchronizeOnOpen)
.ConfigureAwait(false);
return new AetheriaClient(verse, runtimeId, sessionId);
}
public Task<int> RefreshRemoteAsync()
{
ThrowIfDisposed();
return _verse.RefreshRemoteAsync();
}
public IReadOnlyList<AetheriaRuntimeCommittedCommandFactDocument> CommittedCommandFacts()
{
ThrowIfDisposed();
return _verse.CommittedCommandFacts();
}
public static Task<AetheriaClient> OpenLocalAsync(
DirectoryInfo gameDataDirectory,
string runtimeId,
string sessionId = "local",
bool pullOnOpen = true)
{
if (gameDataDirectory == null) throw new ArgumentNullException(nameof(gameDataDirectory));
var stateBoot = AetheriaRuntimeStateBoot.Inspect(gameDataDirectory);
if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists)
{
throw new InvalidOperationException(
$"Aetheria client requires a readable managed Verse target: {stateBoot.FailureMessage}");
}
return OpenAsync(
stateBoot.StateFilePath,
runtimeId,
sessionId,
startServer: false,
pullOnOpen: pullOnOpen);
}
internal Task<AetheriaRuntimeEveCommandEnvelope> SubmitInputSettingsCommandAsync(
AetheriaRuntimeEveCommandKind command,
AetheriaRuntimeInputSettingsCommandBody body,
string? clientId = null,
bool flush = true)
{
ThrowIfDisposed();
return _verse.SubmitInputSettingsCommandAsync(
command,
body,
string.IsNullOrWhiteSpace(clientId) ? _clientId : clientId!,
flush);
}
internal Task<AetheriaRuntimeEveCommandEnvelope> SubmitLoadoutTemplateCommandAsync(
AetheriaRuntimeLoadoutTemplateCommit loadoutTemplate,
string? clientId = null,
bool flush = true)
{
ThrowIfDisposed();
return _verse.SubmitLoadoutTemplateCommandAsync(
loadoutTemplate,
string.IsNullOrWhiteSpace(clientId) ? _clientId : clientId!,
flush);
}
internal Task<AetheriaRuntimeEveCommandEnvelope> SubmitKnownSurfaceCommandAsync(
EveSurfaceCommandRequest request,
string? clientId = null,
bool flush = true)
{
ThrowIfDisposed();
return _verse.SubmitKnownSurfaceCommandAsync(
request,
string.IsNullOrWhiteSpace(clientId) ? _clientId : clientId!,
flush);
}
public AetheriaRuntimeDaemonCommandEnvelope SetMoveVector(
double directionX,
double directionY,
double scalarValue = 1.0)
{
return Control.SetMoveVector(directionX, directionY, scalarValue);
}
public AetheriaRuntimeDaemonCommandEnvelope SetTarget(string targetEntityKey)
{
return Control.SetTarget(targetEntityKey);
}
internal AetheriaRuntimeDaemonCommandEnvelope SubmitDaemonCommandDocument(
AetheriaRuntimeDaemonCommandDocument command)
{
ThrowIfDisposed();
return _verse
.SubmitDaemonCommandAsync(command)
.GetAwaiter()
.GetResult();
}
internal AetheriaRuntimeEveCommandEnvelope SubmitEveCommandDocument(
AetheriaRuntimeEveCommandDocument command)
{
ThrowIfDisposed();
return _verse
.SubmitEveCommandAsync(command)
.GetAwaiter()
.GetResult();
}
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
_daemonFrame?.Dispose();
_verse.Dispose();
}
private AetheriaRuntimeDaemonCommandEnvelope SendOperation(
Func<AetheriaRuntimeDaemonOperationClient, AetheriaRuntimeDaemonFrameDocument?, AetheriaRuntimeDaemonCommandEnvelope> submit)
{
ThrowIfDisposed();
if (submit == null) throw new ArgumentNullException(nameof(submit));
var frame = CurrentDaemonFrame();
var operationClient = new AetheriaRuntimeDaemonOperationClient(
StatePath,
_clientId,
frame?.SessionId ?? _sessionId,
command => _verse
.SubmitDaemonCommandAsync(command)
.GetAwaiter()
.GetResult());
return submit(operationClient, frame);
}
internal AetheriaRuntimeDaemonFrameDocument? CurrentDaemonFrame()
{
ThrowIfDisposed();
try
{
_daemonFrame ??= State.DaemonFrame.Reactive();
return _daemonFrame.Current;
}
catch
{
return null;
}
}
private void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(nameof(AetheriaClient));
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 3e8912ea164c37d4d95fccb217f44667

View file

@ -1,344 +0,0 @@
using System;
using System.Collections.Generic;
using GameCult.Mesh;
using GameCult.Eve.PluginFields;
using MessagePack;
using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public enum AetheriaClientEveSurface
{
Game,
GameTui,
Editor,
EditorTui
}
public sealed class AetheriaClientState : IDisposable
{
private readonly Func<AetheriaRuntimeViewportBounds, CultMeshDocumentHandle<AetheriaRuntimeGameViewportDocument>> _mapViewport;
private readonly Func<AetheriaRuntimeViewportBounds, CultMeshDocumentHandle<AetheriaRuntimeObjectsViewportDocument>> _objectsViewport;
private readonly Func<AetheriaRuntimeViewportBounds, CultMeshDocumentHandle<AetheriaRuntimeGravityViewportDocument>> _gravityViewport;
private readonly Func<AetheriaRuntimeViewportBounds, CultMeshDocumentHandle<EveFieldsSplatsDocument>> _renderSplatsViewport;
private readonly Func<int, CultMeshDocumentHandle<AetheriaRuntimeZoneDetailsDocument>> _zoneDetails;
private readonly Func<int, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _zoneDetailsSurface;
private readonly Func<AetheriaRuntimeInventoryPanelSurfaceRequest, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _inventoryPanelSurface;
private readonly Func<AetheriaRuntimeInventoryDropdownSurfaceRequest, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _inventoryDropdownSurface;
private readonly Func<string, bool, bool, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _mainMenuSurfaceDocument;
private readonly Func<int, CultMeshDocumentHandle<AetheriaRuntimeSelectedObjectDocument>> _selectedObject;
private readonly Func<int, CultMeshDocumentHandle<AetheriaRuntimeInventoryDocument>> _inventory;
private readonly Func<string, CultMeshDocumentHandle<AetheriaRuntimeStarbridgePlayerSeatDocument>> _starbridgePlayerSeat;
private CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument>? _eveStateRefFrame;
private CultMeshReactiveDocument<AetheriaRuntimeDaemonHealthDocument>? _eveStateRefHealth;
private CultMeshReactiveDocument<AetheriaRuntimeDaemonCommandBoundaryDocument>? _eveStateRefCommandBoundary;
private CultMeshReactiveDocument<AetheriaRuntimeCatalogSnapshot>? _eveStateRefCatalog;
internal AetheriaClientState(
CultMeshDocumentHandle<AetheriaRuntimeDaemonProviderAdvertisementDocument> providerAdvertisement,
CultMeshDocumentHandle<AetheriaRuntimeDaemonHealthDocument> health,
CultMeshDocumentHandle<AetheriaRuntimeDaemonCommandBoundaryDocument> commandBoundary,
CultMeshDocumentHandle<AetheriaRuntimeAssetManifestDocument> assetManifest,
CultMeshDocumentHandle<AetheriaRuntimeVerseAuthorityPolicyDocument> authorityPolicy,
CultMeshDocumentHandle<AetheriaRuntimeDaemonFrameDocument> daemonFrame,
CultMeshDocumentHandle<AetheriaRuntimeDaemonSoaViewDocument> daemonSoaView,
CultMeshDocumentHandle<EveSurfaceDocument> gameSurface,
CultMeshDocumentHandle<EveSurfaceDocument> gameTuiSurface,
CultMeshDocumentHandle<EveSurfaceDocument> editorSurface,
CultMeshDocumentHandle<EveSurfaceDocument> editorTuiSurface,
CultMeshDocumentHandle<AetheriaRuntimeCatalogSnapshot> catalog,
CultMeshDocumentHandle<AetheriaRuntimeLoadoutTemplatesDocument> loadoutTemplates,
CultMeshDocumentHandle<AetheriaRuntimePlayerSettingsDocument> playerSettings,
CultMeshDocumentHandle<AetheriaRuntimeVerseHostSettingsDocument> verseHostSettings,
CultMeshDocumentHandle<AetheriaRuntimeCurrentZoneDocument> currentZone,
CultMeshDocumentHandle<AetheriaRuntimeCurrentEntityDocument> currentEntity,
CultMeshDocumentHandle<AetheriaRuntimeCurrentDockingDocument> currentDocking,
CultMeshDocumentHandle<AetheriaRuntimeZoneContactsDocument> zoneContacts,
CultMeshDocumentHandle<AetheriaRuntimeStationRefitDocument> stationRefit,
CultMeshDocumentHandle<AetheriaRuntimeSectorMapDocument> sectorMap,
CultMeshDocumentHandle<AetheriaRuntimeZoneRenderDocument> zoneRender,
Func<AetheriaRuntimeViewportBounds, CultMeshDocumentHandle<AetheriaRuntimeGameViewportDocument>> mapViewport,
Func<AetheriaRuntimeViewportBounds, CultMeshDocumentHandle<AetheriaRuntimeObjectsViewportDocument>> objectsViewport,
Func<AetheriaRuntimeViewportBounds, CultMeshDocumentHandle<AetheriaRuntimeGravityViewportDocument>> gravityViewport,
Func<AetheriaRuntimeViewportBounds, CultMeshDocumentHandle<EveFieldsSplatsDocument>> renderSplatsViewport,
Func<int, CultMeshDocumentHandle<AetheriaRuntimeZoneDetailsDocument>> zoneDetails,
Func<int, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> zoneDetailsSurface,
Func<AetheriaRuntimeInventoryPanelSurfaceRequest, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> inventoryPanelSurface,
Func<AetheriaRuntimeInventoryDropdownSurfaceRequest, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> inventoryDropdownSurface,
Func<string, bool, bool, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> mainMenuSurfaceDocument,
Func<int, CultMeshDocumentHandle<AetheriaRuntimeSelectedObjectDocument>> selectedObject,
Func<int, CultMeshDocumentHandle<AetheriaRuntimeInventoryDocument>> inventory,
CultMeshDocumentHandle<AetheriaRuntimeStarbridgeScenarioDocument> starbridgeScenario,
CultMeshDocumentHandle<AetheriaRuntimeStarbridgeSessionDocument> starbridgeSession,
CultMeshDocumentHandle<AetheriaRuntimeStarbridgeSessionSummaryDocument> starbridgeSummary,
Func<string, CultMeshDocumentHandle<AetheriaRuntimeStarbridgePlayerSeatDocument>> starbridgePlayerSeat)
{
ProviderAdvertisement = providerAdvertisement ?? throw new ArgumentNullException(nameof(providerAdvertisement));
Health = health ?? throw new ArgumentNullException(nameof(health));
CommandBoundary = commandBoundary ?? throw new ArgumentNullException(nameof(commandBoundary));
AssetManifest = assetManifest ?? throw new ArgumentNullException(nameof(assetManifest));
AuthorityPolicy = authorityPolicy ?? throw new ArgumentNullException(nameof(authorityPolicy));
DaemonFrame = daemonFrame ?? throw new ArgumentNullException(nameof(daemonFrame));
DaemonSoaView = daemonSoaView ?? throw new ArgumentNullException(nameof(daemonSoaView));
GameSurface = gameSurface ?? throw new ArgumentNullException(nameof(gameSurface));
GameTuiSurface = gameTuiSurface ?? throw new ArgumentNullException(nameof(gameTuiSurface));
EditorSurface = editorSurface ?? throw new ArgumentNullException(nameof(editorSurface));
EditorTuiSurface = editorTuiSurface ?? throw new ArgumentNullException(nameof(editorTuiSurface));
Catalog = catalog ?? throw new ArgumentNullException(nameof(catalog));
LoadoutTemplates = loadoutTemplates ?? throw new ArgumentNullException(nameof(loadoutTemplates));
PlayerSettings = playerSettings ?? throw new ArgumentNullException(nameof(playerSettings));
VerseHostSettings = verseHostSettings ?? throw new ArgumentNullException(nameof(verseHostSettings));
CurrentZone = currentZone ?? throw new ArgumentNullException(nameof(currentZone));
CurrentEntity = currentEntity ?? throw new ArgumentNullException(nameof(currentEntity));
CurrentDocking = currentDocking ?? throw new ArgumentNullException(nameof(currentDocking));
ZoneContacts = zoneContacts ?? throw new ArgumentNullException(nameof(zoneContacts));
StationRefit = stationRefit ?? throw new ArgumentNullException(nameof(stationRefit));
SectorMap = sectorMap ?? throw new ArgumentNullException(nameof(sectorMap));
ZoneRender = zoneRender ?? throw new ArgumentNullException(nameof(zoneRender));
_mapViewport = mapViewport ?? throw new ArgumentNullException(nameof(mapViewport));
_objectsViewport = objectsViewport ?? throw new ArgumentNullException(nameof(objectsViewport));
_gravityViewport = gravityViewport ?? throw new ArgumentNullException(nameof(gravityViewport));
_renderSplatsViewport = renderSplatsViewport ?? throw new ArgumentNullException(nameof(renderSplatsViewport));
_zoneDetails = zoneDetails ?? throw new ArgumentNullException(nameof(zoneDetails));
_zoneDetailsSurface = zoneDetailsSurface ?? throw new ArgumentNullException(nameof(zoneDetailsSurface));
_inventoryPanelSurface = inventoryPanelSurface ?? throw new ArgumentNullException(nameof(inventoryPanelSurface));
_inventoryDropdownSurface = inventoryDropdownSurface ?? throw new ArgumentNullException(nameof(inventoryDropdownSurface));
_mainMenuSurfaceDocument = mainMenuSurfaceDocument ?? throw new ArgumentNullException(nameof(mainMenuSurfaceDocument));
_selectedObject = selectedObject ?? throw new ArgumentNullException(nameof(selectedObject));
_inventory = inventory ?? throw new ArgumentNullException(nameof(inventory));
StarbridgeScenario = starbridgeScenario ?? throw new ArgumentNullException(nameof(starbridgeScenario));
StarbridgeSession = starbridgeSession ?? throw new ArgumentNullException(nameof(starbridgeSession));
StarbridgeSummary = starbridgeSummary ?? throw new ArgumentNullException(nameof(starbridgeSummary));
_starbridgePlayerSeat = starbridgePlayerSeat ?? throw new ArgumentNullException(nameof(starbridgePlayerSeat));
}
public CultMeshDocumentHandle<AetheriaRuntimeDaemonProviderAdvertisementDocument> ProviderAdvertisement { get; }
public CultMeshDocumentHandle<AetheriaRuntimeDaemonHealthDocument> Health { get; }
public CultMeshDocumentHandle<AetheriaRuntimeDaemonCommandBoundaryDocument> CommandBoundary { get; }
public CultMeshDocumentHandle<AetheriaRuntimeAssetManifestDocument> AssetManifest { get; }
public CultMeshDocumentHandle<AetheriaRuntimeVerseAuthorityPolicyDocument> AuthorityPolicy { get; }
public CultMeshDocumentHandle<AetheriaRuntimeDaemonFrameDocument> DaemonFrame { get; }
public CultMeshDocumentHandle<AetheriaRuntimeDaemonSoaViewDocument> DaemonSoaView { get; }
public CultMeshDocumentHandle<AetheriaRuntimeCatalogSnapshot> Catalog { get; }
public CultMeshDocumentHandle<AetheriaRuntimeLoadoutTemplatesDocument> LoadoutTemplates { get; }
public CultMeshDocumentHandle<EveSurfaceDocument> GameSurface { get; }
public CultMeshDocumentHandle<EveSurfaceDocument> GameTuiSurface { get; }
public CultMeshDocumentHandle<EveSurfaceDocument> EditorSurface { get; }
public CultMeshDocumentHandle<EveSurfaceDocument> EditorTuiSurface { get; }
public CultMeshDocumentHandle<AetheriaRuntimePlayerSettingsDocument> PlayerSettings { get; }
public CultMeshDocumentHandle<AetheriaRuntimeVerseHostSettingsDocument> VerseHostSettings { get; }
public CultMeshDocumentHandle<AetheriaRuntimeCurrentZoneDocument> CurrentZone { get; }
public CultMeshDocumentHandle<AetheriaRuntimeCurrentEntityDocument> CurrentEntity { get; }
public CultMeshDocumentHandle<AetheriaRuntimeCurrentDockingDocument> CurrentDocking { get; }
public CultMeshDocumentHandle<AetheriaRuntimeStarbridgeScenarioDocument> StarbridgeScenario { get; }
public CultMeshDocumentHandle<AetheriaRuntimeStarbridgeSessionDocument> StarbridgeSession { get; }
public CultMeshDocumentHandle<AetheriaRuntimeStarbridgeSessionSummaryDocument> StarbridgeSummary { get; }
public CultMeshDocumentHandle<AetheriaRuntimeZoneContactsDocument> ZoneContacts { get; }
public CultMeshDocumentHandle<AetheriaRuntimeStationRefitDocument> StationRefit { get; }
public CultMeshDocumentHandle<AetheriaRuntimeSectorMapDocument> SectorMap { get; }
public CultMeshDocumentHandle<AetheriaRuntimeZoneRenderDocument> ZoneRender { get; }
public static bool TryResolveEveSurface(
string? surfaceId,
out AetheriaClientEveSurface surface)
{
if (string.Equals(surfaceId, AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId, StringComparison.Ordinal))
{
surface = AetheriaClientEveSurface.Game;
return true;
}
if (string.Equals(surfaceId, AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId, StringComparison.Ordinal))
{
surface = AetheriaClientEveSurface.GameTui;
return true;
}
if (string.Equals(surfaceId, AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId, StringComparison.Ordinal))
{
surface = AetheriaClientEveSurface.Editor;
return true;
}
if (string.Equals(surfaceId, AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId, StringComparison.Ordinal))
{
surface = AetheriaClientEveSurface.EditorTui;
return true;
}
surface = default;
return false;
}
public CultMeshDocumentHandle<EveSurfaceDocument>? EveSurfaceDocument(
string? surfaceId)
{
if (!TryResolveEveSurface(surfaceId, out var surface))
return null;
return surface switch
{
AetheriaClientEveSurface.Game => GameSurface,
AetheriaClientEveSurface.GameTui => GameTuiSurface,
AetheriaClientEveSurface.Editor => EditorSurface,
AetheriaClientEveSurface.EditorTui => EditorTuiSurface,
_ => throw new ArgumentOutOfRangeException(nameof(surface), surface, null)
};
}
public CultMeshDocumentHandle<AetheriaRuntimeGameViewportDocument> GameViewport(
AetheriaRuntimeViewportBounds viewport)
{
return _mapViewport(viewport ?? new AetheriaRuntimeViewportBounds());
}
public CultMeshDocumentHandle<AetheriaRuntimeObjectsViewportDocument> ObjectsViewport(
AetheriaRuntimeViewportBounds viewport)
{
return _objectsViewport(viewport ?? new AetheriaRuntimeViewportBounds());
}
public CultMeshDocumentHandle<AetheriaRuntimeGravityViewportDocument> GravityViewport(
AetheriaRuntimeViewportBounds viewport)
{
return _gravityViewport(viewport ?? new AetheriaRuntimeViewportBounds());
}
public CultMeshDocumentHandle<EveFieldsSplatsDocument> RenderSplatsViewport(
AetheriaRuntimeViewportBounds viewport)
{
return _renderSplatsViewport(viewport ?? new AetheriaRuntimeViewportBounds());
}
public CultMeshDocumentHandle<AetheriaRuntimeZoneDetailsDocument> ZoneDetails(int zoneIndex)
{
if (zoneIndex < 0)
throw new ArgumentOutOfRangeException(nameof(zoneIndex), zoneIndex, "Aetheria zone index must be non-negative.");
return _zoneDetails(zoneIndex);
}
public CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument> ZoneDetailsSurface(int zoneIndex)
{
if (zoneIndex < 0)
throw new ArgumentOutOfRangeException(nameof(zoneIndex), zoneIndex, "Aetheria zone index must be non-negative.");
return _zoneDetailsSurface(zoneIndex);
}
public CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument> InventoryPanelSurface(
AetheriaRuntimeInventoryPanelSurfaceRequest request)
{
return _inventoryPanelSurface(request ?? new AetheriaRuntimeInventoryPanelSurfaceRequest());
}
public CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument> InventoryDropdownSurface(
AetheriaRuntimeInventoryDropdownSurfaceRequest request)
{
return _inventoryDropdownSurface(request ?? new AetheriaRuntimeInventoryDropdownSurfaceRequest());
}
public CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument> MainMenuSurface(
string surfaceId,
bool canOpenRuntimeInputScreen,
bool inGame)
{
if (string.IsNullOrWhiteSpace(surfaceId))
throw new ArgumentException("Main menu surface id must be non-empty.", nameof(surfaceId));
return _mainMenuSurfaceDocument(surfaceId, canOpenRuntimeInputScreen, inGame);
}
public CultMeshDocumentHandle<AetheriaRuntimeSelectedObjectDocument> SelectedObject(int entityIndex)
{
if (entityIndex < 0)
throw new ArgumentOutOfRangeException(nameof(entityIndex), entityIndex, "Aetheria entity index must be non-negative.");
return _selectedObject(entityIndex);
}
public CultMeshDocumentHandle<AetheriaRuntimeInventoryDocument> Inventory(int entityIndex)
{
if (entityIndex < 0)
throw new ArgumentOutOfRangeException(nameof(entityIndex), entityIndex, "Aetheria entity index must be non-negative.");
return _inventory(entityIndex);
}
public CultMeshDocumentHandle<AetheriaRuntimeStarbridgePlayerSeatDocument> StarbridgePlayerSeat(string seatId)
{
if (string.IsNullOrWhiteSpace(seatId))
throw new ArgumentException("Seat id must be non-empty.", nameof(seatId));
return _starbridgePlayerSeat(seatId);
}
public CultMeshStateRefResolver CreateEveSurfaceCultMeshStateRefResolver()
{
return AetheriaRuntimeStateRefResolver.CreateEveSurfaceCultMeshStateRefResolver(
() => ReadOptionalReactive(ref _eveStateRefFrame, DaemonFrame),
() => ReadOptionalReactive(ref _eveStateRefHealth, Health),
() => ReadOptionalReactive(ref _eveStateRefCommandBoundary, CommandBoundary),
() => ReadOptionalReactive(ref _eveStateRefCatalog, Catalog));
}
private static TDocument? ReadOptionalReactive<TDocument>(
ref CultMeshReactiveDocument<TDocument>? reactive,
CultMeshDocumentHandle<TDocument> handle)
where TDocument : class
{
try
{
reactive ??= handle.Reactive();
return reactive.Current;
}
catch (KeyNotFoundException)
{
return null;
}
catch (FormatterNotRegisteredException)
{
return null;
}
}
public void Dispose()
{
_eveStateRefFrame?.Dispose();
_eveStateRefHealth?.Dispose();
_eveStateRefCommandBoundary?.Dispose();
_eveStateRefCatalog?.Dispose();
_eveStateRefFrame = null;
_eveStateRefHealth = null;
_eveStateRefCommandBoundary = null;
_eveStateRefCatalog = null;
}
}
}

View file

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 80b87956450e46e4a9b7ed79b9d8aee8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,244 +0,0 @@
using System;
using GameCult.Eve.Surface;
using GameCult.Mesh;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public sealed class AetheriaControl
{
private readonly AetheriaRuntimeDaemonOperationsClient _operations;
internal AetheriaControl(AetheriaRuntimeDaemonOperationsClient operations)
{
_operations = operations ?? throw new ArgumentNullException(nameof(operations));
}
public AetheriaRuntimeDaemonCommandEnvelope SetTarget(string targetEntityKey) =>
_operations.SetTarget(targetEntityKey);
public CultMeshOperationReceipt ClearTarget() =>
_operations.ClearTarget();
public CultMeshOperationReceipt TargetNearest() =>
_operations.TargetNearest();
public CultMeshOperationReceipt TargetNext() =>
_operations.TargetNext();
public CultMeshOperationReceipt TargetPrevious() =>
_operations.TargetPrevious();
public CultMeshOperationReceipt TargetReticle(double directionX, double directionY, double directionZ) =>
_operations.TargetReticle(directionX, directionY, directionZ);
public AetheriaRuntimeDaemonCommandEnvelope SetMoveVector(
double directionX,
double directionY,
double scalarValue = 1.0) =>
_operations.SetMoveVector(directionX, directionY, scalarValue);
public CultMeshOperationReceipt SetLookDirection(double directionX, double directionY, double directionZ) =>
_operations.SetLookDirection(directionX, directionY, directionZ);
public CultMeshOperationReceipt SetTractorPower(double power) =>
_operations.SetTractorPower(power);
public CultMeshOperationReceipt FireWeaponGroup(int weaponGroup) =>
_operations.FireWeaponGroup(weaponGroup);
public CultMeshOperationReceipt SetWeaponGroupActive(int weaponGroup, bool active) =>
_operations.SetWeaponGroupActive(weaponGroup, active);
public CultMeshOperationReceipt SetWeaponGroupMembership(
string targetEntityKey,
int equipmentIndex,
int weaponGroup,
bool assigned) =>
_operations.SetWeaponGroupMembership(targetEntityKey, equipmentIndex, weaponGroup, assigned);
public CultMeshOperationReceipt SetBehaviorActive(int equipmentIndex, int behaviorIndex, bool active) =>
_operations.SetBehaviorActive(equipmentIndex, behaviorIndex, active);
public CultMeshOperationReceipt ActivateConsumable(string itemKey) =>
_operations.ActivateConsumable(itemKey);
public CultMeshOperationReceipt SensorPing() =>
_operations.SensorPing();
public CultMeshOperationReceipt SetHeatsinksEnabled(bool enabled) =>
_operations.SetHeatsinksEnabled(enabled);
public CultMeshOperationReceipt SetOverrideShutdown(bool enabled) =>
_operations.SetOverrideShutdown(enabled);
public CultMeshOperationReceipt SetEntityOverrideShutdown(string targetEntityKey, bool enabled) =>
_operations.SetEntityOverrideShutdown(targetEntityKey, enabled);
public CultMeshOperationReceipt SetItemEnabled(int equipmentIndex, bool enabled) =>
_operations.SetItemEnabled(equipmentIndex, enabled);
public CultMeshOperationReceipt ToggleShieldEnabled() =>
_operations.ToggleShieldEnabled();
public CultMeshOperationReceipt SetItemOverrideShutdown(
string targetEntityKey,
int equipmentIndex,
bool enabled) =>
_operations.SetItemOverrideShutdown(targetEntityKey, equipmentIndex, enabled);
public CultMeshOperationReceipt SetThermotoggleTargetTemperature(
string targetEntityKey,
int equipmentIndex,
int behaviorIndex,
double targetTemperature) =>
_operations.SetThermotoggleTargetTemperature(
targetEntityKey,
equipmentIndex,
behaviorIndex,
targetTemperature);
public CultMeshOperationReceipt SetShutdownPerformance(
string targetEntityKey,
double shutdownPerformance) =>
_operations.SetShutdownPerformance(targetEntityKey, shutdownPerformance);
public CultMeshOperationReceipt ToggleHullConductivity(
string targetEntityKey,
int x,
int y,
int axis) =>
_operations.ToggleHullConductivity(targetEntityKey, x, y, axis);
public CultMeshOperationReceipt SetEntityName(string targetEntityKey, string name) =>
_operations.SetEntityName(targetEntityKey, name);
public CultMeshOperationReceipt Dock(string targetEntityKey) =>
_operations.Dock(targetEntityKey);
public CultMeshOperationReceipt DockNearest() =>
_operations.DockNearest();
public CultMeshOperationReceipt Undock() =>
_operations.Undock();
public CultMeshOperationReceipt Interact() =>
_operations.Interact();
public CultMeshOperationReceipt SetDockedCurrentShip(string targetEntityKey) =>
_operations.SetDockedCurrentShip(targetEntityKey);
public CultMeshOperationReceipt EnterWormhole(
int targetZoneIndex,
double positionX,
double positionY) =>
_operations.EnterWormhole(targetZoneIndex, positionX, positionY);
public CultMeshOperationReceipt TowToStation(string stationEntityKey) =>
_operations.TowToStation(stationEntityKey);
public CultMeshOperationReceipt TransferCargoItem(
string originEntityKey,
int originCargoIndex,
string destinationEntityKey,
int destinationCargoIndex,
string itemKey,
int quantity,
int sourceX,
int sourceY,
int destinationX,
int destinationY,
bool hasDestinationPosition) =>
_operations.TransferCargoItem(
originEntityKey,
originCargoIndex,
destinationEntityKey,
destinationCargoIndex,
itemKey,
quantity,
sourceX,
sourceY,
destinationX,
destinationY,
hasDestinationPosition);
public CultMeshOperationReceipt TradePurchase(
string itemKey,
int quantity,
int stationCargoIndex,
int targetCargoIndex,
int sourceX,
int sourceY) =>
_operations.TradePurchase(
itemKey,
quantity,
stationCargoIndex,
targetCargoIndex,
sourceX,
sourceY);
public CultMeshOperationReceipt TradeSale(
string itemKey,
int quantity,
int sourceCargoIndex,
int sourceX,
int sourceY) =>
_operations.TradeSale(itemKey, quantity, sourceCargoIndex, sourceX, sourceY);
public CultMeshOperationReceipt RestoreLoadout(
string dockedEntityKey,
string templateName,
int price) =>
_operations.RestoreLoadout(dockedEntityKey, templateName, price);
public CultMeshOperationReceipt EquipItem(
string sourceKind,
string originEntityKey,
int originIndex,
string destinationEntityKey,
string itemKey,
int sourceX,
int sourceY,
int destinationX,
int destinationY,
bool hasDestinationPosition) =>
_operations.EquipItem(
sourceKind,
originEntityKey,
originIndex,
destinationEntityKey,
itemKey,
sourceX,
sourceY,
destinationX,
destinationY,
hasDestinationPosition);
public CultMeshOperationReceipt StoreItem(
string originEntityKey,
int sourceEquipmentIndex,
string destinationEntityKey,
int destinationCargoIndex,
string itemKey,
int destinationX,
int destinationY,
bool hasDestinationPosition,
string sourceKind = AetheriaRuntimeRefitSourceKinds.Equipment) =>
_operations.StoreItem(
originEntityKey,
sourceEquipmentIndex,
destinationEntityKey,
destinationCargoIndex,
itemKey,
destinationX,
destinationY,
hasDestinationPosition,
sourceKind);
public bool TrySubmitSurfaceCommand(
EveSurfaceCommandRequest request,
out AetheriaRuntimeDaemonCommandEnvelope? envelope) =>
_operations.TrySubmitSurfaceCommand(request, out envelope);
}
}

View file

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 7cbef1cb4f644e3aa0ed0475c7446d85
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: f9ad0b6b39cf40bf951dd16f17502991

View file

@ -1,5 +0,0 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Aetheria.State.Unity.Smoke")]
[assembly: InternalsVisibleTo("Aetheria.State.Smoke")]
[assembly: InternalsVisibleTo("Tests")]

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 8397d4d816796f24c9faf1c1c2df522a

View file

@ -1,119 +0,0 @@
using System;
using System.Collections.Generic;
using GameCult.Caching;
using MessagePack;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeAssetKinds
{
public const string Texture = "texture";
public const string Sprite = "sprite";
public const string Mesh = "mesh";
public const string Material = "material";
public const string Shader = "shader";
public const string ComputeShader = "compute-shader";
public const string Audio = "audio";
public const string Prefab = "prefab";
public const string VolumeProfile = "unity.volume-profile";
public const string Font = "font";
}
public static class AetheriaRuntimeAssetTransports
{
public const string CultMesh = "cultmesh";
}
[MessagePackObject]
public sealed class AetheriaRuntimeAssetRef
{
[Key(0)]
public string AssetKey { get; set; } = "";
[Key(1)]
public string Kind { get; set; } = "";
[Key(2)]
public string Uri { get; set; } = "";
[Key(3)]
public string Transport { get; set; } = AetheriaRuntimeAssetTransports.CultMesh;
[Key(4)]
public string ContentHash { get; set; } = "";
[Key(5)]
public string MimeType { get; set; } = "";
[Key(6)]
public IReadOnlyDictionary<string, string> Metadata { get; set; } =
new Dictionary<string, string>();
public static AetheriaRuntimeAssetRef Empty(string kind = "")
{
return new AetheriaRuntimeAssetRef { Kind = kind ?? "" };
}
public static AetheriaRuntimeAssetRef FromKey(
string key,
string kind,
string? uri = null,
string transport = AetheriaRuntimeAssetTransports.CultMesh,
string? mimeType = null)
{
key ??= "";
return new AetheriaRuntimeAssetRef
{
AssetKey = key,
Kind = kind ?? "",
Uri = string.IsNullOrWhiteSpace(uri) ? key : uri ?? "",
Transport = string.IsNullOrWhiteSpace(transport)
? AetheriaRuntimeAssetTransports.CultMesh
: transport,
MimeType = mimeType ?? ""
};
}
}
[CultDocument("gamecult.aetheria.asset_manifest", "gamecult.aetheria.asset_manifest.v1")]
[MessagePackObject]
public sealed class AetheriaRuntimeAssetManifestDocument
{
[Key(0)]
public string Schema { get; set; } = AetheriaRuntimeDaemonSchemas.AssetManifest;
[Key(1)]
public string PublishedAtUtc { get; set; } = "";
[Key(2)]
public string RunId { get; set; } = "";
[Key(3)]
public string BaseUri { get; set; } = "cultmesh://aetheria.local/assets";
[Key(4)]
public IReadOnlyList<AetheriaRuntimeAssetManifestEntry> Assets { get; set; } =
Array.Empty<AetheriaRuntimeAssetManifestEntry>();
}
[MessagePackObject]
public sealed class AetheriaRuntimeAssetManifestEntry
{
[Key(0)]
public AetheriaRuntimeAssetRef Ref { get; set; } = new AetheriaRuntimeAssetRef();
[Key(1)]
public long SizeBytes { get; set; } = -1;
[Key(2)]
public int Width { get; set; }
[Key(3)]
public int Height { get; set; }
[Key(4)]
public IReadOnlyList<string> Tags { get; set; } = Array.Empty<string>();
}
}

View file

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 9ff95c77b30d4b8c8d2df0f2a4de0ef8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,815 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeAssets
{
public const string PresentationHullItemTagPrefix = "presentation-hull-item:";
public const string UnityShaderBundle = "aetheria-shaders";
public const string UnityUiBundle = "aetheria-ui";
public const string UnityCoreBundle = "aetheria-core";
public static string ResolveUnityBundleName(AetheriaRuntimeAssetManifestEntry entry)
{
if (entry?.Ref == null)
throw new ArgumentNullException(nameof(entry));
var key = entry.Ref.AssetKey ?? "";
if (key.StartsWith("shader.", StringComparison.Ordinal) ||
key.StartsWith("compute.", StringComparison.Ordinal) ||
key == "material.environment.stardust" ||
key == "texture.environment.stardust-colors" ||
key == "texture.environment.volume-dither" ||
key == "profile.environment.flight")
return UnityShaderBundle;
if (key.StartsWith("map.", StringComparison.Ordinal) ||
key.StartsWith("material.sector-map.", StringComparison.Ordinal) ||
key.StartsWith("inventory.", StringComparison.Ordinal) ||
key.StartsWith("profile.thermal.", StringComparison.Ordinal) ||
key == "profile.death" ||
key.StartsWith("item.", StringComparison.Ordinal) && key.EndsWith(".icon", StringComparison.Ordinal) ||
key == "font.ui.primary")
return UnityUiBundle;
if (!string.Equals(entry.Ref.Kind, AetheriaRuntimeAssetKinds.Prefab, StringComparison.Ordinal))
return UnityCoreBundle;
var source = entry.Ref.Metadata.TryGetValue("unityAssetPath", out var unityAssetPath)
? unityAssetPath
: entry.Ref.Metadata.TryGetValue("resourcesPath", out var resourcesPath)
? resourcesPath
: key;
var stem = string.Concat((source ?? key).Trim().ToLowerInvariant().Select(character =>
char.IsLetterOrDigit(character) ? character : '-'));
while (stem.Contains("--")) stem = stem.Replace("--", "-");
stem = stem.Trim('-');
if (stem.StartsWith("assets-", StringComparison.Ordinal)) stem = stem.Substring("assets-".Length);
if (stem.StartsWith("resources-", StringComparison.Ordinal)) stem = stem.Substring("resources-".Length);
if (stem.EndsWith("-prefab", StringComparison.Ordinal)) stem = stem.Substring(0, stem.Length - "-prefab".Length);
const int maximumStemLength = 96;
if (stem.Length > maximumStemLength) stem = stem.Substring(stem.Length - maximumStemLength);
return $"aetheria-content-{stem}";
}
public static string ResolveEntityPrefabAssetRef(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot? catalog = null)
{
if (entity == null) return "";
var hull = catalog?.FindItem(entity.HullItemKey ?? "");
if (hull != null && !string.IsNullOrWhiteSpace(hull.HullPrefab))
return HullPrefabAssetKey(ResolvePresentationHull(hull, catalog!).ItemKey);
var kind = (entity.Kind ?? "").Trim().ToLowerInvariant();
if (kind.Contains("station")) return "prefab.entity.station";
if (kind.Contains("projectile")) return "prefab.entity.projectile";
if (kind.Contains("orbital")) return "prefab.entity.orbital";
return string.Equals(entity.FactionKey, "player", StringComparison.OrdinalIgnoreCase)
? "prefab.entity.player"
: "prefab.entity.ship";
}
public static string HullPrefabAssetKey(string hullItemKey) =>
string.IsNullOrWhiteSpace(hullItemKey) ? "" : $"prefab.hull.{hullItemKey.Trim()}";
public static AetheriaRuntimeAssetManifestDocument ProjectManifest(
AetheriaRuntimeCatalogSnapshot? catalog,
string runId = "",
string baseUri = "cultmesh://aetheria.local/assets")
{
var entries = new Dictionary<string, AetheriaRuntimeAssetManifestEntry>(StringComparer.Ordinal);
Add(entries, EnvironmentReflectionCubemap());
Add(entries, EnvironmentPostProcessProfile());
Add(entries, EnvironmentGravityFogShader());
Add(entries, EnvironmentDitherTexture());
Add(entries, EnvironmentStardustComputeShader());
Add(entries, EnvironmentStardustMaterial());
Add(entries, EnvironmentStardustColorTexture());
foreach (var coreAsset in CorePresentationResources()) Add(entries, coreAsset);
Add(entries, UiPrimaryFont());
foreach (var mapAsset in SectorMapAssets()) Add(entries, mapAsset);
Add(entries, MapIcon("entity.player", "Player", "Sprites/Icons/Stroked/Ship"));
Add(entries, MapIcon("entity.ship", "Ship", "Sprites/Icons/Stroked/Ship"));
Add(entries, MapIcon("entity.orbital", "Orbital", "Sprites/Icons/Stroked/orbital"));
Add(entries, MapIcon("entity.station", "Station", "Sprites/Icons/station1"));
Add(entries, MapIcon("entity.projectile", "Projectile", "Sprites/Icons/Lightning Bolt"));
Add(entries, MapIcon("body.planet", "Planet", "Sprites/Icons/Stroked/Planet"));
Add(entries, MapIcon("body.sun", "Sun", "Sprites/Icons/Stroked/Sun"));
Add(entries, MapIcon("body.asteroid", "Asteroid", "Sprites/Icons/Stroked/Planet"));
Add(entries, MapPrefab("prefab.entity.player", "Player Ship", "Prefabs/Ships/Djinni", "player"));
Add(entries, MapPrefab("prefab.entity.ship", "Ship", "Prefabs/Ships/Djinni", "ship"));
Add(entries, MapPrefab("prefab.entity.station", "Station", "Prefabs/Stations/AsteroidOutpost", "station"));
Add(entries, MapPrefab("prefab.entity.orbital", "Orbital", "Prefabs/Stations/Zenith", "orbital"));
Add(entries, MapPrefab("prefab.effect.shot.bolt", "Bolt", "Prefabs/Lightning", "effect.shot.bolt"));
Add(entries, MapPrefab("prefab.effect.impact.shield", "Shield impact", "Prefabs/Shield", "effect.impact.shield"));
Add(entries, MapPrefab("prefab.effect.beam.tractor", "Tractor beam", "Prefabs/Tractor Beam", "effect.beam.tractor"));
Add(entries, DestructionEffect());
Add(entries, MapProjectPrefab("prefab.entity.pickup", "Pickup", "Assets/Prefabs/RPG/Pickups/Tetrahedron.prefab"));
Add(entries, MapCelestialPrefab(
"prefab.body.planet", "Planet", "Assets/Prefabs/RPG/Planets/Planet.prefab", "Terrain Mesh", "celestial.planet"));
Add(entries, MapCelestialPrefab(
"prefab.body.gas-giant", "Gas Giant", "Assets/Prefabs/RPG/Planets/Gas Giant.prefab", "Sphere", "celestial.gas-giant"));
Add(entries, MapCelestialPrefab(
"prefab.body.sun", "Sun", "Assets/Prefabs/RPG/Planets/Sun.prefab", "Sphere", "celestial.sun"));
Add(entries, MapCelestialPrefab(
"prefab.body.asteroid", "Asteroid",
"Assets/Plugins/LowPoly_AsteroidsPack/Prefabs/Asteroid_Huge_v01.prefab", "", "celestial.asteroid"));
Add(entries, MinePrefab());
foreach (var profile in ThermalPresentationProfiles()) Add(entries, profile);
foreach (var inventoryAsset in InventoryUiAssets())
Add(entries, inventoryAsset);
foreach (var item in catalog?.Items ?? Array.Empty<AetheriaRuntimeCatalogItem>())
{
if (item == null || string.IsNullOrWhiteSpace(item.ItemKey))
continue;
if (!string.IsNullOrWhiteSpace(item.HullPrefab))
{
var presentationHull = ResolvePresentationHull(item, catalog!);
if (!string.Equals(presentationHull.ItemKey, item.ItemKey, StringComparison.Ordinal))
continue;
Add(entries, MapPrefab(
HullPrefabAssetKey(item.ItemKey),
string.IsNullOrWhiteSpace(item.Name) ? item.ItemKey : item.Name,
item.HullPrefab,
"entity.hull"));
}
var icon = AssetRefFromCatalogIcon(item.ActionBarIcon, $"item.{item.ItemKey}.icon");
if (!string.IsNullOrWhiteSpace(icon.AssetKey))
{
Add(entries, new AetheriaRuntimeAssetManifestEntry
{
Ref = icon,
Tags = new[] { "item", item.ItemKey, item.Category ?? "" }
.Where(tag => !string.IsNullOrWhiteSpace(tag))
.ToArray()
});
}
}
return new AetheriaRuntimeAssetManifestDocument
{
PublishedAtUtc = DateTime.UtcNow.ToString("O"),
RunId = runId ?? "",
BaseUri = string.IsNullOrWhiteSpace(baseUri) ? "cultmesh://aetheria.local/assets" : baseUri,
Assets = entries.Values
.OrderBy(entry => entry.Ref.AssetKey, StringComparer.Ordinal)
.ToArray()
};
}
private static AetheriaRuntimeCatalogItem ResolvePresentationHull(
AetheriaRuntimeCatalogItem hull,
AetheriaRuntimeCatalogSnapshot catalog)
{
var aliases = (hull.Tags ?? Array.Empty<string>())
.Where(tag => tag != null && tag.StartsWith(PresentationHullItemTagPrefix, StringComparison.Ordinal))
.Select(tag => tag.Substring(PresentationHullItemTagPrefix.Length).Trim())
.Where(itemKey => itemKey.Length > 0)
.Distinct(StringComparer.Ordinal)
.ToArray();
if (aliases.Length == 0)
return hull;
if (aliases.Length != 1)
throw new InvalidOperationException($"Hull '{hull.ItemKey}' advertises multiple presentation hulls.");
var presentationHull = catalog.FindItem(aliases[0]);
if (presentationHull == null || string.IsNullOrWhiteSpace(presentationHull.HullPrefab))
throw new InvalidOperationException(
$"Hull '{hull.ItemKey}' presentation hull '{aliases[0]}' is missing or has no provider prefab.");
return presentationHull;
}
public static AetheriaRuntimeAssetRef ResolveEntityIcon(AetheriaRuntimeViewportObject? obj)
{
if (obj?.Controlled == true)
return Sprite("map.entity.player", "Sprites/Icons/Stroked/Ship");
var kind = (obj?.Kind ?? "").Trim().ToLowerInvariant();
if (kind.IndexOf("station", StringComparison.Ordinal) >= 0)
return Sprite("map.entity.station", "Sprites/Icons/station1");
if (kind.IndexOf("orbital", StringComparison.Ordinal) >= 0)
return Sprite("map.entity.orbital", "Sprites/Icons/Stroked/orbital");
if (kind.IndexOf("projectile", StringComparison.Ordinal) >= 0)
return Sprite("map.entity.projectile", "Sprites/Icons/Lightning Bolt");
return Sprite("map.entity.ship", "Sprites/Icons/Stroked/Ship");
}
public static AetheriaRuntimeAssetRef ResolveBodyIcon(AetheriaRuntimeBodyView? body)
{
var kind = (body?.Kind ?? "").Trim().ToLowerInvariant();
if (kind.IndexOf("sun", StringComparison.Ordinal) >= 0 ||
kind.IndexOf("star", StringComparison.Ordinal) >= 0)
return Sprite("map.body.sun", "Sprites/Icons/Stroked/Sun");
if (kind.IndexOf("asteroid", StringComparison.Ordinal) >= 0)
return Sprite("map.body.asteroid", "Sprites/Icons/Stroked/Planet");
return Sprite("map.body.planet", "Sprites/Icons/Stroked/Planet");
}
public static AetheriaRuntimeAssetRef AssetRefFromCatalogIcon(
string? catalogPath,
string fallbackAssetKey)
{
if (string.IsNullOrWhiteSpace(catalogPath))
return AetheriaRuntimeAssetRef.Empty(AetheriaRuntimeAssetKinds.Texture);
var resourcePath = NormalizeResourcePath(catalogPath ?? "");
var assetKey = string.IsNullOrWhiteSpace(fallbackAssetKey)
? $"catalog.{resourcePath}".Replace('/', '.').Replace('\\', '.')
: fallbackAssetKey;
return CultMeshAssetRef(
assetKey,
AetheriaRuntimeAssetKinds.Texture,
resourcePath,
"image/*");
}
public static AetheriaRuntimeAssetRef InventoryCellBackgroundAtlas()
{
return Texture(
"inventory.cell.background_atlas",
"Sprites/Flat UI/Nodes/Nodes8BG");
}
public static AetheriaRuntimeAssetRef InventoryCellForegroundAtlas()
{
return Texture(
"inventory.cell.foreground_atlas",
"Sprites/Flat UI/Nodes/Nodes8");
}
public static AetheriaRuntimeAssetRef InventoryThermalLayerAtlas()
{
return Texture(
"inventory.cell.thermal_layer_atlas",
"Sprites/Flat UI/pipes");
}
public static IReadOnlyList<AetheriaRuntimeAssetManifestEntry> InventoryUiAssets()
{
return new[]
{
new AetheriaRuntimeAssetManifestEntry
{
Ref = InventoryCellBackgroundAtlas(),
Tags = new[] { "inventory", "cell", "background", "atlas" }
},
new AetheriaRuntimeAssetManifestEntry
{
Ref = InventoryCellForegroundAtlas(),
Tags = new[] { "inventory", "cell", "foreground", "atlas" }
},
new AetheriaRuntimeAssetManifestEntry
{
Ref = InventoryThermalLayerAtlas(),
Tags = new[] { "inventory", "thermal", "layer", "atlas" }
}
};
}
public static AetheriaRuntimeAssetManifestEntry EnvironmentReflectionCubemap()
{
return new AetheriaRuntimeAssetManifestEntry
{
Ref = new AetheriaRuntimeAssetRef
{
AssetKey = "texture.environment.reflection",
Kind = AetheriaRuntimeAssetKinds.Texture,
Uri = CultMeshAssetUri("texture.environment.reflection"),
MimeType = "image/vnd.radiance",
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["unityAssetPath"] = "Assets/Textures/studio2.hdr",
["presentationRole"] = "environment.reflection"
}
},
Tags = new[] { "presentation", "environment", "reflection", "pre-generated" }
};
}
public static AetheriaRuntimeAssetManifestEntry EnvironmentGravityFogShader()
{
return new AetheriaRuntimeAssetManifestEntry
{
Ref = new AetheriaRuntimeAssetRef
{
AssetKey = "shader.environment.gravity-fog",
Kind = AetheriaRuntimeAssetKinds.Shader,
Uri = CultMeshAssetUri("shader.environment.gravity-fog"),
MimeType = "application/vnd.unity.shader",
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["unityAssetPath"] = "Assets/Shaders/Raymarching/CloudShader.shader",
["presentationRole"] = "environment.gravity-fog.volume",
["unity.volume.pass.raymarch"] = "0",
["unity.volume.pass.composite"] = "2",
["unity.volume.quality.bootstrap"] = "ultra",
["unity.volume.quality.ultra.keyword"] = "ULTRA_QUALITY",
["unity.volume.quality.high.keyword"] = "HIGH_QUALITY",
["unity.volume.quality.normal.keyword"] = "MEDIUM_QUALITY",
["unity.volume.feature.flow.global.keyword"] = "FLOW_GLOBAL",
["unity.volume.feature.flow.value3d.keyword"] = "FLOW_VALUE3D",
["unity.volume.feature.noise.slope.keyword"] = "NOISE_SLOPE",
["unity.volume.texturePort.surfaceHeight"] = "_NebulaSurfaceHeight",
["unity.volume.texturePort.patchHeight"] = "_NebulaPatchHeight",
["unity.volume.texturePort.patch"] = "_NebulaPatch",
["unity.volume.texturePort.tint"] = "_NebulaTint",
["unity.volume.texturePort.dither"] = "_DitheringTex",
["unity.volume.texturePort.cloud"] = "_CloudTex",
["unity.volume.vectorPort.viewportTransform"] = "_GridTransform",
["unity.volume.matrixPort.cameraInverseViewProjection"] = "_CamInvProj",
["unity.volume.matrixPort.cameraToWorld"] = "_CamToWorld",
["unity.volume.vectorPort.cameraProjectionExtents"] = "_ProjectionExtents",
["unity.volume.floatPort.raymarchOffset"] = "_RaymarchOffset",
["unity.volume.vectorPort.ditherCoordinates"] = "_DitheringCoords",
["unity.volume.floatPort.fillDensity"] = "_NebulaFillDensity",
["unity.volume.floatPort.fillDistance"] = "_NebulaFillDistance",
["unity.volume.floatPort.fillExponent"] = "_NebulaFillExponent",
["unity.volume.floatPort.fillOffset"] = "_NebulaFillOffset",
["unity.volume.floatPort.patchDensity"] = "_NebulaPatchDensity",
["unity.volume.floatPort.floorOffset"] = "_NebulaFloorOffset",
["unity.volume.floatPort.floorBlend"] = "_NebulaFloorBlend",
["unity.volume.floatPort.patchBlend"] = "_NebulaPatchBlend",
["unity.volume.floatPort.luminance"] = "_NebulaLuminance",
["unity.volume.floatPort.extinction"] = "_ExtinctionCoefficient",
["unity.volume.floatPort.tintLodExponent"] = "_TintLodExponent",
["unity.volume.floatPort.safetyDistance"] = "_SafetyDistance",
["unity.volume.floatPort.flowScale"] = "_FlowScale",
["unity.volume.floatPort.flowAmplitude"] = "_FlowAmplitude",
["unity.volume.floatPort.flowScroll"] = "_FlowScroll",
["unity.volume.floatPort.flowPeriod"] = "_FlowPeriod",
["unity.volume.floatPort.flowSlopeAmplitude"] = "_FlowSlopeAmplitude",
["unity.volume.floatPort.flowSwirlAmplitude"] = "_FlowSwirlAmplitude",
["unity.volume.floatPort.noiseScale"] = "_NebulaNoiseScale",
["unity.volume.floatPort.noiseAmplitude"] = "_NebulaNoiseAmplitude",
["unity.volume.floatPort.noiseExponent"] = "_NebulaNoiseExponent",
["unity.volume.floatPort.noiseSpeed"] = "_NebulaNoiseSpeed",
["unity.volume.floatPort.noiseSlopeExponent"] = "_NebulaNoiseSlopeExponent",
["unity.volume.floatPort.dynamicSkyBoost"] = "_DynamicSkyBoost",
["unity.volume.floatPort.dynamicLodHigh"] = "_DynamicLodHigh",
["unity.volume.floatPort.dynamicLodLow"] = "_DynamicLodLow",
["unity.volume.floatPort.dynamicIntensity"] = "_DynamicIntensity",
["unity.volume.floatPort.compositeOpacity"] = "_CompositeOpacity"
}
},
Tags = new[] { "presentation", "environment", "gravity", "fog", "volume" }
};
}
public static AetheriaRuntimeAssetManifestEntry EnvironmentPostProcessProfile()
{
return new AetheriaRuntimeAssetManifestEntry
{
Ref = new AetheriaRuntimeAssetRef
{
AssetKey = "profile.environment.flight",
Kind = AetheriaRuntimeAssetKinds.VolumeProfile,
Uri = CultMeshAssetUri("profile.environment.flight"),
MimeType = "application/vnd.unity.volume-profile",
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["unityAssetPath"] = "Assets/Generated/Eve/Environment/Flight.asset",
["presentationRole"] = "environment.post-process.flight",
["sourceProfile"] = "Assets/Scenes/ARPG_Profiles/Postprocessing Profile.asset",
["profileSemantics"] = "aces;bloom;fixed-dark-scene-exposure;contrast;vignette;grain"
}
},
Tags = new[] { "presentation", "environment", "post-process", "flight" }
};
}
public static AetheriaRuntimeAssetManifestEntry EnvironmentStardustComputeShader()
{
return new AetheriaRuntimeAssetManifestEntry
{
Ref = new AetheriaRuntimeAssetRef
{
AssetKey = "compute.environment.stardust",
Kind = AetheriaRuntimeAssetKinds.ComputeShader,
Uri = CultMeshAssetUri("compute.environment.stardust"),
MimeType = "application/vnd.unity.compute-shader",
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["unityAssetPath"] = "Assets/Shaders/Compute/Stardust/Stardust.compute",
["presentationRole"] = "environment.stardust.update",
["unity.particles.kernel.update"] = "UpdateParticles",
["unity.particles.feature.flow.global.keyword"] = "FLOW_GLOBAL",
["unity.particles.feature.flow.value3d.keyword"] = "FLOW_VALUE3D",
["unity.particles.feature.noise.slope.keyword"] = "NOISE_SLOPE",
["unity.particles.texturePort.surfaceHeight"] = "_NebulaSurfaceHeight",
["unity.particles.texturePort.tint"] = "_NebulaTint",
["unity.particles.texturePort.hue"] = "HueTexture",
["unity.particles.bufferPort.particles"] = "particles",
["unity.particles.vectorPort.viewportTransform"] = "_GridTransform",
["unity.particles.vectorPort.timeVector"] = "_Time",
["unity.particles.floatPort.time"] = "time",
["unity.particles.floatPort.period"] = "period",
["unity.particles.floatPort.spacing"] = "spacing",
["unity.particles.floatPort.ceilingHeight"] = "ceilingHeight",
["unity.particles.floatPort.floorHeight"] = "floorHeight",
["unity.particles.floatPort.maximumSize"] = "maximumSize",
["unity.particles.floatPort.minimumSize"] = "minimumSize",
["unity.particles.floatPort.minHeadroom"] = "minHeadroom",
["unity.particles.floatPort.maxHeadroom"] = "maxHeadroom",
["unity.particles.floatPort.heightExponent"] = "heightExponent",
["unity.particles.intPort.span"] = "span",
["unity.particles.floatPort.fillDensity"] = "_NebulaFillDensity",
["unity.particles.floatPort.fillDistance"] = "_NebulaFillDistance",
["unity.particles.floatPort.fillExponent"] = "_NebulaFillExponent",
["unity.particles.floatPort.fillOffset"] = "_NebulaFillOffset",
["unity.particles.floatPort.floorOffset"] = "_NebulaFloorOffset",
["unity.particles.floatPort.floorBlend"] = "_NebulaFloorBlend",
["unity.particles.floatPort.luminance"] = "_NebulaLuminance",
["unity.particles.floatPort.tintLodExponent"] = "_TintLodExponent",
["unity.particles.floatPort.flowScale"] = "_FlowScale",
["unity.particles.floatPort.flowAmplitude"] = "_FlowAmplitude",
["unity.particles.floatPort.flowScroll"] = "_FlowScroll",
["unity.particles.floatPort.flowPeriod"] = "_FlowPeriod",
["unity.particles.floatPort.flowSlopeAmplitude"] = "_FlowSlopeAmplitude",
["unity.particles.floatPort.flowSwirlAmplitude"] = "_FlowSwirlAmplitude",
["unity.particles.floatPort.noiseScale"] = "_NebulaNoiseScale",
["unity.particles.floatPort.noiseAmplitude"] = "_NebulaNoiseAmplitude",
["unity.particles.floatPort.noiseExponent"] = "_NebulaNoiseExponent",
["unity.particles.floatPort.noiseSpeed"] = "_NebulaNoiseSpeed",
["unity.particles.floatPort.noiseSlopeExponent"] = "_NebulaNoiseSlopeExponent",
["unity.particles.floatPort.dynamicSkyBoost"] = "_DynamicSkyBoost",
["unity.particles.floatPort.dynamicLodHigh"] = "_DynamicLodHigh",
["unity.particles.floatPort.dynamicLodLow"] = "_DynamicLodLow",
["unity.particles.floatPort.dynamicIntensity"] = "_DynamicIntensity"
}
},
Tags = new[] { "presentation", "environment", "stardust", "particles", "compute" }
};
}
public static AetheriaRuntimeAssetManifestEntry EnvironmentStardustMaterial()
{
return new AetheriaRuntimeAssetManifestEntry
{
Ref = new AetheriaRuntimeAssetRef
{
AssetKey = "material.environment.stardust",
Kind = AetheriaRuntimeAssetKinds.Material,
Uri = CultMeshAssetUri("material.environment.stardust"),
MimeType = "application/vnd.unity.material",
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["unityAssetPath"] = "Assets/Shaders/Compute/Stardust/Stardust.mat",
["presentationRole"] = "environment.stardust.render",
["unity.particles.pass.render"] = "0",
["unity.particles.bufferPort.particles"] = "particles",
["unity.particles.bufferPort.quadPoints"] = "quadPoints",
["unity.particles.texturePort.dither"] = "_DitheringTex",
["unity.particles.vectorPort.ditherCoordinates"] = "_DitheringCoords",
["unity.particles.intPort.frameIndex"] = "_FrameNumber"
}
},
Tags = new[] { "presentation", "environment", "stardust", "particles", "material" }
};
}
public static AetheriaRuntimeAssetManifestEntry EnvironmentStardustColorTexture()
{
return new AetheriaRuntimeAssetManifestEntry
{
Ref = new AetheriaRuntimeAssetRef
{
AssetKey = "texture.environment.stardust-colors",
Kind = AetheriaRuntimeAssetKinds.Texture,
Uri = CultMeshAssetUri("texture.environment.stardust-colors"),
MimeType = "image/png",
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["unityAssetPath"] = "Assets/Resources/Gradients/blackbody.png",
["presentationRole"] = "environment.stardust.colors"
}
},
Tags = new[] { "presentation", "environment", "stardust", "pre-generated" }
};
}
public static AetheriaRuntimeAssetManifestEntry EnvironmentDitherTexture()
{
return new AetheriaRuntimeAssetManifestEntry
{
Ref = new AetheriaRuntimeAssetRef
{
AssetKey = "texture.environment.volume-dither",
Kind = AetheriaRuntimeAssetKinds.Texture,
Uri = CultMeshAssetUri("texture.environment.volume-dither"),
MimeType = "image/png",
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["unityAssetPath"] = "Assets/Resources/LDR_LLL1_0.png",
["presentationRole"] = "environment.volume.dither"
}
},
Tags = new[] { "presentation", "environment", "volume", "pre-generated" }
};
}
public static IReadOnlyList<AetheriaRuntimeAssetManifestEntry> CorePresentationResources()
{
return new[]
{
MapPresentationAsset("texture.core.sun.surface-flow", AetheriaRuntimeAssetKinds.Texture,
"Sun surface flow", "Assets/Resources/Sun Flow/sunsurface1offset.png", "core.sun.surface-flow"),
MapPresentationAsset("texture.core.sun.albedo", AetheriaRuntimeAssetKinds.Texture,
"Sun albedo", "Assets/Resources/Sun Albedo/sunsurface2map.png", "core.sun.albedo"),
MapPresentationAsset("texture.core.fx.explosion-emission", AetheriaRuntimeAssetKinds.Texture,
"Explosion emission", "Assets/Prefabs/Fire & Explosion Effects/Textures/ExplosionEmission.tif", "core.fx.explosion-emission"),
MapPresentationAsset("texture.core.fx.smoke-puff", AetheriaRuntimeAssetKinds.Texture,
"Smoke puff", "Assets/Prefabs/Fire & Explosion Effects/Textures/SmokePuff01.tif", "core.fx.smoke-puff"),
MapPresentationAsset("texture.core.fx.explosion", AetheriaRuntimeAssetKinds.Texture,
"Explosion", "Assets/Prefabs/Fire & Explosion Effects/Textures/Explosion.tif", "core.fx.explosion"),
MapPresentationAsset("texture.core.fx.shockwave", AetheriaRuntimeAssetKinds.Texture,
"Shockwave", "Assets/Prefabs/Fire & Explosion Effects/Textures/shockwave.tif", "core.fx.shockwave"),
MapPresentationAsset("texture.core.fx.sphere-normal", AetheriaRuntimeAssetKinds.Texture,
"Sphere normal", "Assets/Prefabs/Fire & Explosion Effects/Textures/SphereNormal.tif", "core.fx.sphere-normal"),
MapPresentationAsset("texture.core.fx.debris", AetheriaRuntimeAssetKinds.Texture,
"Debris", "Assets/Prefabs/Fire & Explosion Effects/Textures/Debris.png", "core.fx.debris")
};
}
public static AetheriaRuntimeAssetManifestEntry UiPrimaryFont()
{
return MapPresentationAsset(
"font.ui.primary",
AetheriaRuntimeAssetKinds.Font,
"Primary UI font",
"Assets/Fonts/Ubuntu/Ubuntu-L Small SDF.asset",
"ui.font.primary");
}
private static AetheriaRuntimeAssetManifestEntry MapIcon(
string key,
string label,
string resourcePath)
{
return new AetheriaRuntimeAssetManifestEntry
{
Ref = Sprite($"map.{key}", resourcePath),
Tags = new[] { "map", "icon", label }
};
}
private static AetheriaRuntimeAssetManifestEntry MapPrefab(
string key,
string label,
string resourcePath,
string presentationRole)
{
var asset = Prefab(key, resourcePath);
asset.Metadata = asset.Metadata
.Concat(new[]
{
new KeyValuePair<string, string>("presentationRole", presentationRole ?? ""),
new KeyValuePair<string, string>("bundleAssetPath", PresentationPrefabPath(key))
})
.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.Ordinal);
return new AetheriaRuntimeAssetManifestEntry
{
Ref = asset,
Tags = new[] { "world", "prefab", label }
};
}
private static AetheriaRuntimeAssetManifestEntry MapProjectPrefab(
string key,
string label,
string unityAssetPath)
{
return new AetheriaRuntimeAssetManifestEntry
{
Ref = new AetheriaRuntimeAssetRef
{
AssetKey = key,
Kind = AetheriaRuntimeAssetKinds.Prefab,
Uri = CultMeshAssetUri(key),
MimeType = "application/vnd.unity.prefab",
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["unityAssetPath"] = (unityAssetPath ?? "").Replace('\\', '/'),
["bundleAssetPath"] = PresentationPrefabPath(key)
}
},
Tags = new[] { "world", "prefab", label }
};
}
private static AetheriaRuntimeAssetManifestEntry MapCelestialPrefab(
string key,
string label,
string unityAssetPath,
string presentationVisualPath,
string presentationRole)
{
var entry = MapProjectPrefab(key, label, unityAssetPath);
var metadata = entry.Ref.Metadata.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.Ordinal);
metadata["presentationRole"] = presentationRole ?? "";
if (!string.IsNullOrWhiteSpace(presentationVisualPath))
metadata["presentationVisualPath"] = presentationVisualPath;
entry.Ref.Metadata = metadata;
entry.Tags = new[] { "world", "prefab", "celestial", label };
return entry;
}
private static AetheriaRuntimeAssetManifestEntry MapProjectAsset(
string key, string kind, string label, string unityAssetPath, string presentationRole)
{
return new AetheriaRuntimeAssetManifestEntry
{
Ref = new AetheriaRuntimeAssetRef
{
AssetKey = key, Kind = kind ?? "", Uri = CultMeshAssetUri(key),
MimeType = "application/vnd.unity.asset",
Metadata = new Dictionary<string, string>(StringComparer.Ordinal)
{
["unityAssetPath"] = (unityAssetPath ?? "").Replace('\\', '/'),
["presentationRole"] = presentationRole ?? ""
}
},
Tags = new[] { "presentation", "thermal", label ?? "" }
};
}
private static IReadOnlyList<AetheriaRuntimeAssetManifestEntry> SectorMapAssets() =>
new[]
{
MapPresentationAsset(
"material.sector-map.background",
AetheriaRuntimeAssetKinds.Material,
"Sector Map Galaxy",
"Assets/Materials/UI/Galaxy.mat",
"sector-map.background"),
MapPresentationAsset(
"material.sector-map.influence",
AetheriaRuntimeAssetKinds.Material,
"Sector Map Influence",
"Assets/Materials/UI/Influence Map.mat",
"sector-map.influence"),
MapIcon("sector-map.icon.start", "Start", "Sprites/Icons/enter"),
MapIcon("sector-map.icon.terminus", "Terminus", "Sprites/Icons/exit"),
MapIcon("sector-map.icon.home", "Home Zone", "Sprites/Icons/Home"),
MapIcon("sector-map.icon.executive", "Executive", "Sprites/Icons/boss")
};
private static AetheriaRuntimeAssetManifestEntry MapPresentationAsset(
string key,
string kind,
string label,
string unityAssetPath,
string presentationRole)
{
var entry = MapProjectAsset(key, kind, label, unityAssetPath, presentationRole);
entry.Tags = new[] { "presentation", "sector-map", label ?? "" };
return entry;
}
private static IReadOnlyList<AetheriaRuntimeAssetManifestEntry> ThermalPresentationProfiles() =>
new[]
{
MapProjectAsset("profile.thermal.heatstroke", AetheriaRuntimeAssetKinds.VolumeProfile,
"Heatstroke", "Assets/Generated/Eve/Thermal/Heatstroke.asset",
"post.thermal.heatstroke"),
MapProjectAsset("profile.thermal.severe-heatstroke", AetheriaRuntimeAssetKinds.VolumeProfile,
"Severe heatstroke", "Assets/Generated/Eve/Thermal/Severe Heatstroke.asset",
"post.thermal.severe-heatstroke"),
MapProjectAsset("profile.thermal.hypothermia", AetheriaRuntimeAssetKinds.VolumeProfile,
"Hypothermia", "Assets/Generated/Eve/Thermal/Hypothermia.asset",
"post.thermal.hypothermia"),
MapProjectAsset("profile.thermal.severe-hypothermia", AetheriaRuntimeAssetKinds.VolumeProfile,
"Severe hypothermia", "Assets/Generated/Eve/Thermal/Severe Hypothermia.asset",
"post.thermal.severe-hypothermia"),
MapProjectAsset("profile.death", AetheriaRuntimeAssetKinds.VolumeProfile,
"Death", "Assets/Generated/Eve/Thermal/Death.asset", "post.death")
};
private static AetheriaRuntimeAssetManifestEntry MinePrefab()
{
var entry = MapProjectPrefab(
"prefab.entity.mine",
"Mine",
"Assets/Prefabs/RPG/Effects/Mine.prefab");
entry.Ref.Metadata = new Dictionary<string, string>(entry.Ref.Metadata, StringComparer.Ordinal)
{
["presentationRole"] = "physical-payload.mine",
["activePulseSeconds"] = "1",
["triggeredPulseSeconds"] = "0.25",
["activeEmission"] = "100",
["triggeredEmission"] = "1000"
};
return entry;
}
private static AetheriaRuntimeAssetManifestEntry DestructionEffect()
{
var entry = MapProjectPrefab(
"prefab.effect.entity.destroyed",
"Entity destruction",
"Assets/Prefabs/Fire & Explosion Effects/Prefabs/BigExplosion.prefab");
entry.Ref.Metadata = new Dictionary<string, string>(entry.Ref.Metadata, StringComparer.Ordinal)
{
["presentationRole"] = "effect.feedback.entity.destroyed"
};
return entry;
}
private static AetheriaRuntimeAssetRef Sprite(string key, string resourcePath)
{
return CultMeshAssetRef(
key,
AetheriaRuntimeAssetKinds.Sprite,
resourcePath,
"image/*");
}
private static AetheriaRuntimeAssetRef Texture(string key, string resourcePath)
{
return CultMeshAssetRef(
key,
AetheriaRuntimeAssetKinds.Texture,
resourcePath,
"image/*");
}
private static AetheriaRuntimeAssetRef Prefab(string key, string resourcePath)
{
return CultMeshAssetRef(
key,
AetheriaRuntimeAssetKinds.Prefab,
resourcePath,
"application/vnd.unity.prefab");
}
private static AetheriaRuntimeAssetRef CultMeshAssetRef(
string key,
string kind,
string resourcePath,
string mimeType)
{
var normalizedResourcePath = NormalizeResourcePath(resourcePath);
return new AetheriaRuntimeAssetRef
{
AssetKey = key ?? "",
Kind = kind ?? "",
Uri = CultMeshAssetUri(key),
Transport = AetheriaRuntimeAssetTransports.CultMesh,
MimeType = mimeType ?? "",
Metadata = string.IsNullOrWhiteSpace(normalizedResourcePath)
? new Dictionary<string, string>()
: new Dictionary<string, string>(StringComparer.Ordinal)
{
["resourcesPath"] = normalizedResourcePath
}
};
}
private static string CultMeshAssetUri(string key)
{
var path = (key ?? "").Trim().Replace('.', '/').Replace('\\', '/').Trim('/');
return $"cultmesh://aetheria/assets/{path}";
}
private static string PresentationPrefabPath(string key)
{
var fileName = string.Concat((key ?? "").Select(character =>
char.IsLetterOrDigit(character) || character == '.' || character == '-'
? character
: '_'));
return $"Assets/Generated/Eve/ProviderPrefabs/{fileName}.prefab";
}
private static string NormalizeResourcePath(string path)
{
path ??= "";
const string resourcesPrefix = "Assets/Resources/";
if (path.StartsWith(resourcesPrefix, StringComparison.OrdinalIgnoreCase))
path = path.Substring(resourcesPrefix.Length);
var extensionIndex = path.LastIndexOf('.');
if (extensionIndex > 0)
path = path.Substring(0, extensionIndex);
return path.Replace('\\', '/').Trim('/');
}
private static void Add(
Dictionary<string, AetheriaRuntimeAssetManifestEntry> entries,
AetheriaRuntimeAssetManifestEntry entry)
{
var key = entry.Ref?.AssetKey ?? "";
if (key.Length == 0)
return;
entries[key] = entry;
}
}
}

View file

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 8e38efc9379f4a7ea4b5df5ca079b5c1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,41 +0,0 @@
using System;
using System.Collections.Generic;
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeBehaviorKinds
{
public const string StatModifier = "StatModifier";
public const string GuidedWeapon = "GuidedWeapon";
public const string Launcher = "Launcher";
public const string InstantWeapon = "InstantWeapon";
public const string ConstantWeapon = "ConstantWeapon";
public const string ChargedWeapon = "ChargedWeapon";
public const string AutoWeapon = "AutoWeapon";
public const string DeployableWeapon = "DeployableWeapon";
public const string AetherDrive = "AetherDrive";
public const string Thruster = "Thruster";
public const string VelocityConversion = "VelocityConversion";
public const string VelocityLimit = "VelocityLimit";
public const string Reflector = "Reflector";
public const string Visibility = "Visibility";
public const string Sensor = "Sensor";
public const string TurretController = "TurretController";
private static readonly ISet<string> WeaponKinds = new HashSet<string>(StringComparer.Ordinal)
{
GuidedWeapon,
Launcher,
InstantWeapon,
ConstantWeapon,
ChargedWeapon,
AutoWeapon,
DeployableWeapon
};
public static bool IsWeapon(string kind)
{
return !string.IsNullOrWhiteSpace(kind) && WeaponKinds.Contains(kind);
}
}
}

View file

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 3f03e4d7d6e841a4a9a37b4c9d25cc5a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,190 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace GameCult.Aetheria.State.Verse
{
public enum AetheriaRuntimeBehaviorFieldValueKind
{
Number,
Integer,
Boolean,
PerformanceStat,
Temperature
}
public sealed class AetheriaRuntimeBehaviorFieldMetadata
{
public AetheriaRuntimeBehaviorFieldMetadata(string name, int key, AetheriaRuntimeBehaviorFieldValueKind valueKind)
{
Name = name ?? "";
Key = key;
ValueKind = valueKind;
}
public string Name { get; }
public int Key { get; }
public AetheriaRuntimeBehaviorFieldValueKind ValueKind { get; }
}
public sealed class AetheriaRuntimeBehaviorMetadata
{
public AetheriaRuntimeBehaviorMetadata(
string kind,
string parentKind,
IReadOnlyList<AetheriaRuntimeBehaviorFieldMetadata> displayFields)
{
Kind = kind ?? "";
ParentKind = parentKind ?? "";
DisplayFields = displayFields ?? Array.Empty<AetheriaRuntimeBehaviorFieldMetadata>();
}
public string Kind { get; }
public string ParentKind { get; }
public IReadOnlyList<AetheriaRuntimeBehaviorFieldMetadata> DisplayFields { get; }
}
public static class AetheriaRuntimeBehaviorMetadataCatalog
{
private static readonly AetheriaRuntimeBehaviorFieldMetadata[] WeaponFields =
{
Stat("Damage", 2),
Stat("Penetration", 3),
Stat("DamageSpread", 4),
Stat("MinRange", 5),
Stat("Range", 6),
Stat("Energy", 9),
Stat("Heat", 10),
Stat("Visibility", 11),
Integer("MagazineSize", 13),
Number("ReloadTime", 14),
Stat("Spread", 15),
Stat("Velocity", 16)
};
private static readonly AetheriaRuntimeBehaviorFieldMetadata[] InstantWeaponFields =
With(WeaponFields, Stat("BurstCount", 17), Stat("BurstTime", 18), Stat("Cooldown", 19), Boolean("SingleAmmoBurst", 20));
private static readonly AetheriaRuntimeBehaviorFieldMetadata[] LockWeaponFields =
With(InstantWeaponFields, Stat("LockSpeed", 21), Stat("SensorImpact", 22), Stat("LockAngle", 23),
Stat("DirectionImpact", 24), Stat("Decay", 25));
private static readonly IReadOnlyDictionary<string, AetheriaRuntimeBehaviorMetadata> ByKind =
new[]
{
Behavior("AetherDrive", "", Stat("MaximumRpm", 3), Stat("CouplingEfficiency", 6), Stat("Torque", 7), Stat("EnergyDraw", 9), Stat("PassiveCoupling", 10)),
Behavior("Capacitor", "", Stat("Capacity", 1), Stat("Efficiency", 2)),
Behavior("ChargedWeapon", AetheriaRuntimeBehaviorKinds.InstantWeapon, With(InstantWeaponFields,
Stat("ChargeTime", 21), Stat("ChargeEnergy", 22), Stat("ChargeHeat", 23),
Boolean("CanFireEarly", 24), Number("FailureCharge", 25), Number("FailureDamage", 26),
Number("ChargeDamageMultiplier", 27), Number("ChargeSpreadMultiplier", 28),
Number("ChargeBurstCountMultiplier", 29), Number("ChargeVisibilityMultiplier", 30),
Number("ChargeVelocityMultiplier", 31), Number("ChargeHeatMultiplier", 32))),
Behavior("Cockpit", ""),
Behavior("ConstantWeapon", "Weapon", With(WeaponFields, Number("AmmoIntervalDuration", 17))),
Behavior(AetheriaRuntimeBehaviorKinds.GuidedWeapon, AetheriaRuntimeBehaviorKinds.InstantWeapon, With(InstantWeaponFields, Stat("MissileVelocity", 26))),
Behavior(AetheriaRuntimeBehaviorKinds.Launcher, "LockWeapon", With(LockWeaponFields, Stat("MissileVelocity", 31))),
Behavior("LockWeapon", AetheriaRuntimeBehaviorKinds.InstantWeapon, LockWeaponFields),
Behavior(AetheriaRuntimeBehaviorKinds.InstantWeapon, "Weapon", InstantWeaponFields),
Behavior(AetheriaRuntimeBehaviorKinds.AutoWeapon, AetheriaRuntimeBehaviorKinds.InstantWeapon, InstantWeaponFields),
Behavior(AetheriaRuntimeBehaviorKinds.DeployableWeapon, AetheriaRuntimeBehaviorKinds.InstantWeapon,
With(InstantWeaponFields, Number("ActivationDelay", 26), Number("Lifetime", 27),
Stat("TriggerRadius", 28), Number("DetonationDelay", 29), Stat("BlastRadius", 30))),
Behavior("Cooldown", "", Stat("Cooldown", 1)),
Behavior("EnergyDraw", "", Stat("EnergyDraw", 1)),
Behavior("Heat", "", Stat("Heat", 1)),
Behavior("HeatStorage", ""),
Behavior("ItemUsage", ""),
Behavior("MiningTool", "", Stat("DamagePerSecond", 1), Stat("Efficiency", 2), Stat("Penetration", 3), Stat("Range", 4)),
Behavior("Radiator", "", Stat("Emissivity", 1), Stat("PumpedHeat", 2), Temperature("TemperatureFloor", 3), Stat("WasteHeat", 4), Stat("EnergyUsage", 5), Stat("ThermalMass", 6)),
Behavior("Reactor", "", Stat("Charge", 1), Stat("Efficiency", 2), Stat("OverloadEfficiency", 3), Stat("ThrottlingFactor", 4)),
Behavior("Reflector", "", Stat("CrossSection", 1)),
Behavior("ResourceScanner", "", Stat("Range", 1), Stat("MinimumDensity", 2), Stat("ScanDuration", 3)),
Behavior("Sensor", "", Stat("Sensitivity", 3), Stat("PingBoost", 5), Stat("PingEnergy", 6), Stat("PingVisibility", 7), Stat("PingRange", 8), Stat("PingCooldown", 9)),
Behavior("Shield", "", Stat("Efficiency", 1), Stat("EnergyUsage", 2)),
Behavior("Switch", ""),
Behavior("Thermotoggle", "", Temperature("TargetTemperature", 1)),
Behavior("Thruster", "", Stat("Thrust", 1), Stat("Visibility", 2), Stat("Heat", 3), Stat("EnergyUsage", 4)),
Behavior("Trigger", ""),
Behavior("TurretController", ""),
Behavior("VelocityConversion", "", Stat("Lambda", 1)),
Behavior("VelocityLimit", "", Stat("TopSpeed", 1)),
Behavior("Visibility", "", Stat("Visibility", 1)),
Behavior("Weapon", "", WeaponFields),
Behavior("Wear", "")
}
.ToDictionary(metadata => metadata.Kind, StringComparer.Ordinal);
public static AetheriaRuntimeBehaviorMetadata Get(string kind)
{
return !string.IsNullOrWhiteSpace(kind) && ByKind.TryGetValue(kind, out var metadata)
? metadata
: null!;
}
public static IReadOnlyList<AetheriaRuntimeBehaviorMetadata> All => ByKind.Values.ToArray();
public static bool IsKindOrDescendant(string candidateKind, string expectedKind)
{
if (string.IsNullOrWhiteSpace(candidateKind) || string.IsNullOrWhiteSpace(expectedKind))
{
return false;
}
var currentKind = candidateKind;
while (!string.IsNullOrWhiteSpace(currentKind))
{
if (string.Equals(currentKind, expectedKind, StringComparison.Ordinal))
{
return true;
}
currentKind = ByKind.TryGetValue(currentKind, out var metadata)
? metadata.ParentKind
: "";
}
return false;
}
private static AetheriaRuntimeBehaviorMetadata Behavior(
string kind,
string parentKind,
params AetheriaRuntimeBehaviorFieldMetadata[] displayFields)
{
return new AetheriaRuntimeBehaviorMetadata(kind, parentKind, displayFields);
}
private static AetheriaRuntimeBehaviorFieldMetadata Stat(string name, int key)
{
return new AetheriaRuntimeBehaviorFieldMetadata(name, key, AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat);
}
private static AetheriaRuntimeBehaviorFieldMetadata Number(string name, int key)
{
return new AetheriaRuntimeBehaviorFieldMetadata(name, key, AetheriaRuntimeBehaviorFieldValueKind.Number);
}
private static AetheriaRuntimeBehaviorFieldMetadata Integer(string name, int key)
{
return new AetheriaRuntimeBehaviorFieldMetadata(name, key, AetheriaRuntimeBehaviorFieldValueKind.Integer);
}
private static AetheriaRuntimeBehaviorFieldMetadata Boolean(string name, int key)
{
return new AetheriaRuntimeBehaviorFieldMetadata(name, key, AetheriaRuntimeBehaviorFieldValueKind.Boolean);
}
private static AetheriaRuntimeBehaviorFieldMetadata Temperature(string name, int key)
{
return new AetheriaRuntimeBehaviorFieldMetadata(name, key, AetheriaRuntimeBehaviorFieldValueKind.Temperature);
}
private static AetheriaRuntimeBehaviorFieldMetadata[] With(
AetheriaRuntimeBehaviorFieldMetadata[] baseFields,
params AetheriaRuntimeBehaviorFieldMetadata[] extraFields)
{
return baseFields.Concat(extraFields).ToArray();
}
}
}

View file

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 8a6d7af26b164b8fa8629e2b8efbe9c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,495 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeBehaviorSimulation
{
public static void Step(
int zoneIndex,
IReadOnlyList<AetheriaRuntimeEntitySnapshotCommit> entities,
IEnumerable<AetheriaRuntimeDaemonBehaviorIntent>? intents,
AetheriaRuntimeCatalogSnapshot? catalog,
double deltaSeconds)
{
if (catalog == null || deltaSeconds <= 0)
return;
var intentArray = (intents ?? Enumerable.Empty<AetheriaRuntimeDaemonBehaviorIntent>()).ToArray();
foreach (var entity in entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
{
if (entity == null)
continue;
AetheriaRuntimeBehaviorStateProjector.EnsureEquipmentBehaviorStates(entity, catalog);
ApplyControlIntents(zoneIndex, entity, intentArray);
ExecuteEquipmentChains(entity, catalog, deltaSeconds);
ProjectCooldownProgress(entity);
}
}
public static double Apply(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot catalog,
int targetEquipmentIndex,
AetheriaRuntimeBehaviorPayload targetBehavior,
int targetFieldKey,
double baseline) =>
Apply(entity, catalog, targetEquipmentIndex, targetBehavior, targetFieldKey, baseline, null);
internal static double Apply(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot catalog,
int targetEquipmentIndex,
AetheriaRuntimeBehaviorPayload targetBehavior,
int targetFieldKey,
double baseline,
IReadOnlyList<AetheriaRuntimeEquippedBehavior>? equippedModifiers)
{
var targetStatName = StatName(targetBehavior?.Kind, targetFieldKey);
if (entity == null || catalog == null || targetEquipmentIndex < 0 ||
targetBehavior == null || string.IsNullOrWhiteSpace(targetStatName))
return baseline;
double multiplier = 1;
double constant = 0;
foreach (var modifier in ActiveEquippedModifiers(entity, catalog, equippedModifiers)
.Concat(ActiveConsumableModifiers(entity, catalog)))
{
if (!Targets(modifier.Payload, entity, catalog, targetEquipmentIndex, targetBehavior, targetStatName))
continue;
if (IsMultiplier(modifier.Payload))
multiplier *= modifier.Value;
else
constant += modifier.Value;
}
return baseline * multiplier + constant;
}
public static void ReportSpecializedResult(
AetheriaRuntimeEquippedBehavior behavior,
bool succeeded)
{
if (behavior?.State == null || !behavior.State.ChainReached)
return;
behavior.State.ChainSucceeded = succeeded;
}
public static void CompleteDeferredChains(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot? catalog,
double deltaSeconds)
{
if (entity == null || catalog == null || deltaSeconds <= 0)
return;
var states = EquipmentStates(entity);
var online = OnlineStates(entity);
var equipment = entity.Equipment ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>();
for (var equipmentIndex = 0; equipmentIndex < equipment.Count; equipmentIndex++)
{
var item = catalog.FindItem(equipment[equipmentIndex]?.Item?.ItemKey ?? "");
var payloads = item?.BehaviorPayloads ?? Array.Empty<AetheriaRuntimeBehaviorPayload>();
foreach (var group in Indexed(payloads).GroupBy(value => value.Payload.Group).OrderBy(value => value.Key))
{
var entries = group.ToArray();
var deferredOffset = Array.FindIndex(entries, entry =>
states.TryGetValue((equipmentIndex, entry.Index), out var state) &&
state.ChainReached && IsDeferred(entry.Payload.Kind));
if (deferredOffset < 0)
continue;
var deferredState = states[(equipmentIndex, entries[deferredOffset].Index)];
if (!deferredState.ChainSucceeded || deferredState.ChainCompleted)
continue;
deferredState.ChainCompleted = true;
foreach (var entry in entries.Skip(deferredOffset + 1))
{
if (!states.TryGetValue((equipmentIndex, entry.Index), out var state))
break;
state.ChainReached = true;
if (IsDeferred(entry.Payload.Kind) ||
!ExecuteInline(entity, catalog, online, equipmentIndex, entry, state, deltaSeconds))
break;
}
}
ProjectModifierState(entity, catalog, equipmentIndex, payloads, states);
}
ProjectCooldownProgress(entity);
}
public static int CountTargets(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot catalog,
AetheriaRuntimeBehaviorPayload modifier)
{
var count = 0;
var equipment = entity.Equipment ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>();
for (var equipmentIndex = 0; equipmentIndex < equipment.Count; equipmentIndex++)
{
var item = catalog.FindItem(equipment[equipmentIndex]?.Item?.ItemKey ?? "");
foreach (var behavior in item?.BehaviorPayloads ?? Array.Empty<AetheriaRuntimeBehaviorPayload>())
{
if (behavior == null)
continue;
foreach (var stat in PerformanceStats(behavior.Kind))
if (Targets(modifier, entity, catalog, equipmentIndex, behavior, stat.Name))
count++;
}
}
return count;
}
private static void ApplyControlIntents(
int zoneIndex,
AetheriaRuntimeEntitySnapshotCommit entity,
IReadOnlyList<AetheriaRuntimeDaemonBehaviorIntent> intents)
{
var states = entity.BehaviorStates ?? Array.Empty<AetheriaRuntimeBehaviorStateCommit>();
foreach (var intent in intents.Where(value => value != null &&
((AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(value.ActorEntityKey ?? "", out var actorZone, out var actorEntity) &&
actorZone == zoneIndex && actorEntity == entity.EntityIndex) ||
string.Equals(value.ActorEntityKey, entity.EntityId, StringComparison.Ordinal))))
{
var state = states.FirstOrDefault(value => value != null &&
string.Equals(value.OwnerKind, AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, StringComparison.Ordinal) &&
value.OwnerIndex == intent.EquipmentIndex && value.BehaviorIndex == intent.BehaviorIndex);
if (state == null)
continue;
if (string.Equals(state.BehaviorKind, "Switch", StringComparison.Ordinal))
state.SwitchActivated = intent.Active;
else if (string.Equals(state.BehaviorKind, "Trigger", StringComparison.Ordinal) && intent.Active)
state.TriggerPulled = true;
}
}
private static void ExecuteEquipmentChains(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot catalog,
double deltaSeconds)
{
var states = EquipmentStates(entity);
foreach (var state in states.Values)
{
state.ChainReached = false;
state.ChainSucceeded = false;
state.ChainCompleted = false;
}
var online = OnlineStates(entity);
var equipment = entity.Equipment ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>();
for (var equipmentIndex = 0; equipmentIndex < equipment.Count; equipmentIndex++)
{
var equipped = equipment[equipmentIndex]?.Item;
var item = catalog.FindItem(equipped?.ItemKey ?? "");
var operational = equipped != null && equipped.Enabled && equipped.Durability > 0.01 &&
(!online.TryGetValue(equipmentIndex, out var equipmentState) || equipmentState.Online);
var payloads = item?.BehaviorPayloads ?? Array.Empty<AetheriaRuntimeBehaviorPayload>();
foreach (var entry in Indexed(payloads))
if (entry.Payload != null && string.Equals(entry.Payload.Kind, "StatModifier", StringComparison.Ordinal) &&
states.TryGetValue((equipmentIndex, entry.Index), out var modifierState))
modifierState.StatModifierExecuted = false;
if (operational)
foreach (var group in Indexed(payloads).GroupBy(value => value.Payload.Group).OrderBy(value => value.Key))
foreach (var entry in group)
{
if (!states.TryGetValue((equipmentIndex, entry.Index), out var state))
break;
state.ChainReached = true;
if (IsDeferred(entry.Payload.Kind) ||
!ExecuteInline(entity, catalog, online, equipmentIndex, entry, state, deltaSeconds))
break;
}
ProjectModifierState(entity, catalog, equipmentIndex, payloads, states);
foreach (var entry in Indexed(payloads))
{
if (entry.Payload == null || !string.Equals(entry.Payload.Kind, "Cooldown", StringComparison.Ordinal) ||
!states.TryGetValue((equipmentIndex, entry.Index), out var state))
continue;
var duration = Evaluate(entity, catalog, equipmentIndex, entry.Index, entry.Payload, state, 1);
state.CooldownProgress -= deltaSeconds / Math.Max(double.Epsilon, duration);
}
}
}
private static bool ExecuteInline(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot catalog,
IReadOnlyDictionary<int, AetheriaRuntimeEquipmentStateCommit> online,
int equipmentIndex,
(AetheriaRuntimeBehaviorPayload Payload, int Index) entry,
AetheriaRuntimeBehaviorStateCommit state,
double deltaSeconds)
{
if (string.Equals(entry.Payload.Kind, "Switch", StringComparison.Ordinal) && !state.SwitchActivated)
return false;
if (string.Equals(entry.Payload.Kind, "Trigger", StringComparison.Ordinal))
{
if (!state.TriggerPulled)
return false;
state.TriggerPulled = false;
}
if (string.Equals(entry.Payload.Kind, "Thermotoggle", StringComparison.Ordinal))
{
var temperature = AetheriaRuntimeThermalSimulation.EquipmentTemperature(entity, catalog, equipmentIndex);
if (!((temperature < state.ThermotoggleTargetTemperature) ^ ReadBool(entry.Payload, 2)))
return false;
}
if (string.Equals(entry.Payload.Kind, "Cooldown", StringComparison.Ordinal))
{
if (state.CooldownProgress >= 0)
return false;
state.CooldownProgress = 1;
}
if (string.Equals(entry.Payload.Kind, "EnergyDraw", StringComparison.Ordinal))
{
var demand = Evaluate(entity, catalog, equipmentIndex, entry.Index, entry.Payload, state, 1) *
(ReadBool(entry.Payload, 2) ? deltaSeconds : 1);
if (!AetheriaRuntimeEnergySimulation.TryConsume(entity, catalog, demand))
return false;
}
if (string.Equals(entry.Payload.Kind, "Heat", StringComparison.Ordinal))
{
var heat = Evaluate(entity, catalog, equipmentIndex, entry.Index, entry.Payload, state, 1) *
(ReadBool(entry.Payload, 2) ? deltaSeconds : 1);
AetheriaRuntimeThermalSimulation.AddHeatToEquipment(entity, catalog, equipmentIndex, heat);
}
if (string.Equals(entry.Payload.Kind, "ItemUsage", StringComparison.Ordinal))
{
var itemKey = ReadItemKey(entry.Payload, 1);
if (string.IsNullOrWhiteSpace(itemKey) ||
!AetheriaRuntimeCargoTransactions.TryFind(entity, itemKey, out var cargoIndex, out var x, out var y) ||
!AetheriaRuntimeCargoTransactions.TryRemoveQuantity(entity, cargoIndex, itemKey, x, y, 1, out _))
return false;
}
if (string.Equals(entry.Payload.Kind, "Wear", StringComparison.Ordinal))
{
var wear = online.TryGetValue(equipmentIndex, out var wearState) ? wearState.Wear : 0;
AetheriaRuntimeThermalSimulation.ApplyWear(
entity, equipmentIndex, wear * (ReadBool(entry.Payload, 1, true) ? deltaSeconds : 1));
}
if (string.Equals(entry.Payload.Kind, "StatModifier", StringComparison.Ordinal))
state.StatModifierExecuted = true;
return true;
}
private static bool IsDeferred(string? kind) =>
string.Equals(kind, AetheriaRuntimeBehaviorKinds.Thruster, StringComparison.Ordinal) ||
string.Equals(kind, AetheriaRuntimeBehaviorKinds.AetherDrive, StringComparison.Ordinal) ||
string.Equals(kind, "Radiator", StringComparison.Ordinal) ||
string.Equals(kind, "MiningTool", StringComparison.Ordinal) ||
string.Equals(kind, "ResourceScanner", StringComparison.Ordinal);
private static IEnumerable<(AetheriaRuntimeBehaviorPayload Payload, int Index)> Indexed(
IReadOnlyList<AetheriaRuntimeBehaviorPayload> payloads) =>
payloads.Select((payload, index) => (Payload: payload, Index: index))
.Where(value => value.Payload != null);
private static Dictionary<(int OwnerIndex, int BehaviorIndex), AetheriaRuntimeBehaviorStateCommit>
EquipmentStates(AetheriaRuntimeEntitySnapshotCommit entity) =>
(entity.BehaviorStates ?? Array.Empty<AetheriaRuntimeBehaviorStateCommit>())
.Where(value => value != null && string.Equals(value.OwnerKind,
AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, StringComparison.Ordinal))
.ToDictionary(value => (value.OwnerIndex, value.BehaviorIndex));
private static Dictionary<int, AetheriaRuntimeEquipmentStateCommit> OnlineStates(
AetheriaRuntimeEntitySnapshotCommit entity) =>
(entity.EquipmentStates ?? Array.Empty<AetheriaRuntimeEquipmentStateCommit>())
.Where(value => value != null).ToDictionary(value => value.EquipmentIndex);
private static void ProjectModifierState(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot catalog,
int equipmentIndex,
IReadOnlyList<AetheriaRuntimeBehaviorPayload> payloads,
IReadOnlyDictionary<(int OwnerIndex, int BehaviorIndex), AetheriaRuntimeBehaviorStateCommit> states)
{
foreach (var entry in Indexed(payloads))
{
if (!string.Equals(entry.Payload.Kind, "StatModifier", StringComparison.Ordinal) ||
!states.TryGetValue((equipmentIndex, entry.Index), out var state))
continue;
state.StatModifierApplied = state.StatModifierExecuted;
state.StatModifierTargetStatCount = CountTargets(entity, catalog, entry.Payload);
}
}
private static double Evaluate(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot catalog,
int equipmentIndex,
int behaviorIndex,
AetheriaRuntimeBehaviorPayload payload,
AetheriaRuntimeBehaviorStateCommit state,
int fieldKey)
{
var equipment = entity.Equipment ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>();
if (equipmentIndex < 0 || equipmentIndex >= equipment.Count || equipment[equipmentIndex]?.Item == null)
return 0;
var item = equipment[equipmentIndex].Item;
var typed = catalog.FindItem(item.ItemKey ?? "");
if (typed == null)
return 0;
var behavior = new AetheriaRuntimeEquippedBehavior(
entity, catalog, equipmentIndex, behaviorIndex, equipment[equipmentIndex], item, typed, payload, state);
var thermal = (entity.EquipmentStates ?? Array.Empty<AetheriaRuntimeEquipmentStateCommit>())
.FirstOrDefault(value => value?.EquipmentIndex == equipmentIndex)?.ThermalPerformance ?? 1;
return behavior.EvaluateStat(fieldKey, thermal);
}
private static void ProjectCooldownProgress(AetheriaRuntimeEntitySnapshotCommit entity)
{
var retained = (entity.BehaviorProgress ?? Array.Empty<AetheriaRuntimeBehaviorProgressCommit>())
.Where(value => value != null &&
!(string.Equals(value.OwnerKind, AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, StringComparison.Ordinal) &&
string.Equals(value.BehaviorKind, "Cooldown", StringComparison.Ordinal)));
var cooldowns = (entity.BehaviorStates ?? Array.Empty<AetheriaRuntimeBehaviorStateCommit>())
.Where(value => value != null &&
string.Equals(value.OwnerKind, AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, StringComparison.Ordinal) &&
string.Equals(value.BehaviorKind, "Cooldown", StringComparison.Ordinal))
.Select(value => new AetheriaRuntimeBehaviorProgressCommit
{
OwnerKind = value.OwnerKind,
OwnerIndex = value.OwnerIndex,
BehaviorIndex = value.BehaviorIndex,
BehaviorKind = value.BehaviorKind,
Progress = Math.Max(0, Math.Min(1, value.CooldownProgress))
});
entity.BehaviorProgress = retained.Concat(cooldowns)
.OrderBy(value => value.OwnerKind, StringComparer.Ordinal)
.ThenBy(value => value.OwnerIndex)
.ThenBy(value => value.BehaviorIndex)
.ToArray();
}
private static IEnumerable<ModifierValue> ActiveEquippedModifiers(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot catalog,
IReadOnlyList<AetheriaRuntimeEquippedBehavior>? equippedModifiers = null)
{
foreach (var behavior in equippedModifiers ??
AetheriaRuntimeEquippedBehaviorQueries.FindOperational(entity, catalog, "StatModifier"))
{
if (!behavior.State.StatModifierApplied)
continue;
var value = Field(behavior.Payload, 2)?.Value;
var thermal = (entity.EquipmentStates ?? Array.Empty<AetheriaRuntimeEquipmentStateCommit>())
.FirstOrDefault(state => state?.EquipmentIndex == behavior.EquipmentIndex)?.ThermalPerformance ?? 1;
yield return new ModifierValue(behavior.Payload,
AetheriaRuntimeDaemonItemStatQueries.EvaluatePerformanceStat(value, behavior.Item, thermal));
}
}
private static IEnumerable<ModifierValue> ActiveConsumableModifiers(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot catalog)
{
foreach (var effect in entity.ActiveConsumables ?? Array.Empty<AetheriaRuntimeActiveConsumableCommit>())
{
var item = catalog.FindItem(effect?.ItemKey ?? "");
if (effect == null || item == null)
continue;
var elapsed = effect.Duration <= 0 ? 1 : Math.Max(0, Math.Min(1,
(effect.Duration - effect.RemainingDuration) / effect.Duration));
var effectiveness = item.EffectivenessCurveKeys == null || item.EffectivenessCurveKeys.Count == 0
? 1 : AetheriaRuntimeDaemonItemStatQueries.SampleCurve(item.EffectivenessCurveKeys, elapsed);
foreach (var entry in (item.BehaviorPayloads ?? Array.Empty<AetheriaRuntimeBehaviorPayload>())
.Select((payload, index) => (Payload: payload, Index: index)))
{
var state = (effect.BehaviorStates ?? Array.Empty<AetheriaRuntimeConsumableBehaviorStateCommit>())
.FirstOrDefault(value => value?.BehaviorIndex == entry.Index);
if (entry.Payload != null && string.Equals(entry.Payload.Kind, "StatModifier", StringComparison.Ordinal) &&
state?.StatModifierApplied == true)
yield return new ModifierValue(entry.Payload,
AetheriaRuntimeDaemonItemStatQueries.EvaluateConsumablePerformanceStat(
Field(entry.Payload, 2)?.Value, effect.Quality, effectiveness));
}
}
}
private static bool Targets(
AetheriaRuntimeBehaviorPayload modifier,
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot catalog,
int targetEquipmentIndex,
AetheriaRuntimeBehaviorPayload targetBehavior,
string targetStatName)
{
var reference = Field(modifier, 1)?.Value;
var expectedKind = NormalizeKind(ChildString(reference, 1));
if (!AetheriaRuntimeBehaviorMetadataCatalog.IsKindOrDescendant(targetBehavior?.Kind ?? "", expectedKind) ||
!string.Equals(ChildString(reference, 2), targetStatName, StringComparison.Ordinal))
return false;
var requiredKind = NormalizeKind(ReadString(Field(modifier, 4)?.Value));
if (string.IsNullOrWhiteSpace(requiredKind))
return true;
var targetItemKey = (entity.Equipment ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>())
.ElementAtOrDefault(targetEquipmentIndex)?.Item?.ItemKey ?? "";
return (catalog.FindItem(targetItemKey)?.BehaviorPayloads ?? Array.Empty<AetheriaRuntimeBehaviorPayload>())
.Any(value => AetheriaRuntimeBehaviorMetadataCatalog.IsKindOrDescendant(value?.Kind ?? "", requiredKind));
}
private static bool IsMultiplier(AetheriaRuntimeBehaviorPayload payload)
{
var value = Field(payload, 3)?.Value;
return string.Equals(ReadString(value), "Multiplier", StringComparison.OrdinalIgnoreCase) ||
(string.IsNullOrWhiteSpace(ReadString(value)) && Math.Round(value?.NumberValue ?? 0) == 1);
}
private static string NormalizeKind(string value) =>
!string.IsNullOrWhiteSpace(value) && value.EndsWith("Data", StringComparison.Ordinal)
? value.Substring(0, value.Length - 4) : value ?? "";
private static string StatName(string? behaviorKind, int fieldKey) =>
PerformanceStats(behaviorKind).FirstOrDefault(value => value.Key == fieldKey)?.Name ?? "";
private static IEnumerable<AetheriaRuntimeBehaviorFieldMetadata> PerformanceStats(string? behaviorKind) =>
AetheriaRuntimeBehaviorMetadataCatalog.Get(behaviorKind ?? "")?.DisplayFields?
.Where(value => value.ValueKind == AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat)
?? Enumerable.Empty<AetheriaRuntimeBehaviorFieldMetadata>();
private static AetheriaRuntimeBehaviorField? Field(AetheriaRuntimeBehaviorPayload payload, int key) =>
(payload?.Fields ?? Array.Empty<AetheriaRuntimeBehaviorField>()).FirstOrDefault(value => value?.Key == key);
private static bool ReadBool(AetheriaRuntimeBehaviorPayload payload, int key, bool fallback = false)
{
var field = Field(payload, key);
return field == null ? fallback : field.Value?.BoolValue ?? fallback;
}
private static string ReadItemKey(AetheriaRuntimeBehaviorPayload payload, int key)
{
var value = Field(payload, key)?.Value;
return !string.IsNullOrWhiteSpace(value?.ItemKeyValue)
? value.ItemKeyValue
: ReadString(value);
}
private static string ChildString(AetheriaRuntimeBehaviorValue? value, int index) =>
value != null && value.Children != null && value.Children.Count > index
? ReadString(value.Children[index]) : "";
private static string ReadString(AetheriaRuntimeBehaviorValue? value) =>
!string.IsNullOrWhiteSpace(value?.StringValue) ? value.StringValue :
!string.IsNullOrWhiteSpace(value?.LegacyIdValue) ? value.LegacyIdValue :
value?.ItemKeyValue ?? "";
private readonly struct ModifierValue
{
public ModifierValue(AetheriaRuntimeBehaviorPayload payload, double value)
{
Payload = payload;
Value = value;
}
public AetheriaRuntimeBehaviorPayload Payload { get; }
public double Value { get; }
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 5f127e58b7fe83d41aae3d2c07e92b93

View file

@ -1,133 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeBehaviorStateProjector
{
public const string EquipmentOwnerKind = "equipment";
public static void EnsureEquipmentBehaviorStates(
AetheriaRuntimeEntitySnapshotCommit? entity,
AetheriaRuntimeCatalogSnapshot? catalog)
{
if (entity == null)
return;
var existing = entity.BehaviorStates ?? Array.Empty<AetheriaRuntimeBehaviorStateCommit>();
if (EquipmentBehaviorStatesMatch(existing, entity.Equipment, catalog))
return;
var projected = CreateEquipmentBehaviorStates(entity.Equipment, catalog);
if (projected.Length == 0)
{
entity.BehaviorStates = existing
.Where(state => state != null && !string.Equals(state.OwnerKind, EquipmentOwnerKind, StringComparison.Ordinal))
.ToArray();
return;
}
var existingByKey = existing
.Where(state => state != null)
.GroupBy(StateKey, StringComparer.Ordinal)
.ToDictionary(group => group.Key, group => group.First(), StringComparer.Ordinal);
var nonEquipmentStates = existing
.Where(state => state != null && !string.Equals(state.OwnerKind, EquipmentOwnerKind, StringComparison.Ordinal));
var equipmentStates = projected
.Select(state => existingByKey.TryGetValue(StateKey(state), out var current) ? current : state);
entity.BehaviorStates = nonEquipmentStates
.Concat(equipmentStates)
.OrderBy(state => state.OwnerKind, StringComparer.Ordinal)
.ThenBy(state => state.OwnerIndex)
.ThenBy(state => state.BehaviorIndex)
.ToArray();
}
public static AetheriaRuntimeBehaviorStateCommit[] CreateEquipmentBehaviorStates(
IReadOnlyList<AetheriaRuntimeLoadoutItemSlotCommit>? equipment,
AetheriaRuntimeCatalogSnapshot? catalog)
{
catalog ??= new AetheriaRuntimeCatalogSnapshot();
return (equipment ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>())
.SelectMany((slot, equipmentIndex) => CreateEquipmentBehaviorStates(slot?.Item, equipmentIndex, catalog))
.ToArray();
}
private static IEnumerable<AetheriaRuntimeBehaviorStateCommit> CreateEquipmentBehaviorStates(
AetheriaRuntimeLoadoutItemCommit? item,
int equipmentIndex,
AetheriaRuntimeCatalogSnapshot catalog)
{
if (item == null || equipmentIndex < 0)
yield break;
var typedItem = catalog.FindItem(item.ItemKey ?? "");
var payloads = typedItem?.BehaviorPayloads ?? Array.Empty<AetheriaRuntimeBehaviorPayload>();
for (var behaviorIndex = 0; behaviorIndex < payloads.Count; behaviorIndex++)
{
var payload = payloads[behaviorIndex];
if (payload == null || string.IsNullOrWhiteSpace(payload.Kind))
continue;
var state = new AetheriaRuntimeBehaviorStateCommit
{
OwnerKind = EquipmentOwnerKind,
OwnerIndex = equipmentIndex,
BehaviorIndex = behaviorIndex,
BehaviorKind = payload.Kind
};
if (string.Equals(payload.Kind, "Thermotoggle", StringComparison.Ordinal))
state.ThermotoggleTargetTemperature = (payload.Fields ?? Array.Empty<AetheriaRuntimeBehaviorField>())
.FirstOrDefault(field => field != null && field.Key == 1)?.Value?.NumberValue ?? 0;
yield return state;
}
}
private static string StateKey(AetheriaRuntimeBehaviorStateCommit? state)
{
if (state == null)
return "";
return string.Join(
"/",
state.OwnerKind ?? "",
state.OwnerIndex.ToString(System.Globalization.CultureInfo.InvariantCulture),
state.BehaviorIndex.ToString(System.Globalization.CultureInfo.InvariantCulture),
state.BehaviorKind ?? "");
}
private static bool EquipmentBehaviorStatesMatch(
IReadOnlyList<AetheriaRuntimeBehaviorStateCommit> existing,
IReadOnlyList<AetheriaRuntimeLoadoutItemSlotCommit>? equipment,
AetheriaRuntimeCatalogSnapshot? catalog)
{
catalog ??= new AetheriaRuntimeCatalogSnapshot();
var matched = 0;
var slots = equipment ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>();
for (var equipmentIndex = 0; equipmentIndex < slots.Count; equipmentIndex++)
{
var item = slots[equipmentIndex]?.Item;
var payloads = catalog.FindItem(item?.ItemKey ?? "")?.BehaviorPayloads ??
Array.Empty<AetheriaRuntimeBehaviorPayload>();
for (var behaviorIndex = 0; behaviorIndex < payloads.Count; behaviorIndex++)
{
var payload = payloads[behaviorIndex];
if (payload == null || string.IsNullOrWhiteSpace(payload.Kind))
continue;
matched++;
if (!existing.Any(state => state != null &&
string.Equals(state.OwnerKind, EquipmentOwnerKind, StringComparison.Ordinal) &&
state.OwnerIndex == equipmentIndex &&
state.BehaviorIndex == behaviorIndex &&
string.Equals(state.BehaviorKind, payload.Kind, StringComparison.Ordinal)))
return false;
}
}
return existing.Count(state => state != null &&
string.Equals(state.OwnerKind, EquipmentOwnerKind, StringComparison.Ordinal)) == matched;
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: c014edb10e374064983c53c270e56b25

View file

@ -1,82 +0,0 @@
using System;
using System.Linq;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeCargoCapacityQueries
{
public static int Quantity(AetheriaRuntimeEntitySnapshotCommit? entity) =>
(entity?.CargoContents ?? Array.Empty<AetheriaRuntimeCargoBayLoadoutCommit>())
.Where(bay => bay != null)
.SelectMany(bay => bay.Items ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>())
.Where(slot => slot?.Item != null)
.Sum(slot => Math.Max(0, slot.Item.Quantity));
public static double Capacity(AetheriaRuntimeEntitySnapshotCommit? entity, AetheriaRuntimeCatalogSnapshot? catalog)
{
if (entity == null || catalog == null)
return 0;
var hull = catalog.FindItem(entity.HullItemKey ?? "");
if (hull == null)
return 0;
return Math.Max(0, AetheriaRuntimeDaemonItemStatQueries.EvaluatePerformanceStat(
hull.HullCapacity,
new AetheriaRuntimeLoadoutItemCommit { ItemKey = hull.ItemKey, Quality = 1, Durability = 1, Enabled = true },
1));
}
public static double Occupied(AetheriaRuntimeEntitySnapshotCommit? entity, AetheriaRuntimeCatalogSnapshot? catalog)
{
if (entity == null || catalog == null)
return 0;
var equipment = (entity.Equipment ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>())
.Sum(slot => Volume(slot?.Item, catalog));
var cargoBays = (entity.CargoBays ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>())
.Sum(slot => Volume(slot?.Item, catalog));
var dockingBays = (entity.DockingBays ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>())
.Sum(slot => Volume(slot?.Item, catalog));
var cargo = (entity.CargoContents ?? Array.Empty<AetheriaRuntimeCargoBayLoadoutCommit>())
.SelectMany(bay => bay?.Items ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>())
.Sum(slot => Volume(slot?.Item, catalog));
var dockingCargo = (entity.DockingBayContents ?? Array.Empty<AetheriaRuntimeCargoBayLoadoutCommit>())
.SelectMany(bay => bay?.Items ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>())
.Sum(slot => Volume(slot?.Item, catalog));
return equipment + cargoBays + dockingBays + cargo + dockingCargo;
}
public static double Available(AetheriaRuntimeEntitySnapshotCommit? entity, AetheriaRuntimeCatalogSnapshot? catalog) =>
Math.Max(0, Capacity(entity, catalog) - Occupied(entity, catalog));
public static int UnitsThatFit(AetheriaRuntimeEntitySnapshotCommit? entity, AetheriaRuntimeCatalogSnapshot? catalog, string itemKey)
{
return UnitsThatFit(entity, catalog, itemKey, 0);
}
public static int UnitsThatFit(
AetheriaRuntimeEntitySnapshotCommit? entity,
AetheriaRuntimeCatalogSnapshot? catalog,
string itemKey,
int cargoIndex)
{
var volume = catalog?.FindItem(itemKey ?? "")?.Volume ?? 0;
if (volume <= 0)
return int.MaxValue;
var cargoBays = (entity?.CargoBays ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>())
.Select(slot => catalog?.FindItem(slot?.Item?.ItemKey ?? ""))
.ToArray();
if (cargoIndex < 0 || cargoIndex >= cargoBays.Length)
return 0;
var capacity = cargoBays[cargoIndex]!.InteriorOccupiedCells;
var bay = (entity?.CargoContents ?? Array.Empty<AetheriaRuntimeCargoBayLoadoutCommit>())
.ElementAtOrDefault(cargoIndex);
var occupied = (bay?.Items ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>())
.Sum(slot => Volume(slot?.Item, catalog!));
return Math.Max(0, (int)Math.Floor((capacity - occupied) / volume));
}
private static double Volume(AetheriaRuntimeLoadoutItemCommit? item, AetheriaRuntimeCatalogSnapshot catalog) =>
item == null ? 0 : Math.Max(0, catalog.FindItem(item.ItemKey ?? "")?.Volume ?? 0) * Math.Max(1, item.Quantity);
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 65b37579114393648b8c881ae488399b

View file

@ -1,734 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using GameCult.Eve.Surface;
namespace GameCult.Aetheria.State.Verse
{
public sealed class AetheriaRuntimeCargoItemMetric
{
public AetheriaRuntimeCargoItemMetric(string id, string label, string value, string valueRef = "")
{
Id = id ?? "";
Label = label ?? "";
Value = value ?? "";
ValueRef = valueRef ?? "";
}
public string Id { get; }
public string Label { get; }
public string Value { get; }
public string ValueRef { get; }
}
public sealed class AetheriaRuntimeCargoItemSection
{
public AetheriaRuntimeCargoItemSection(
string id,
string title,
IReadOnlyList<AetheriaRuntimeCargoItemMetric> metrics)
{
Id = id ?? "";
Title = title ?? "";
Metrics = metrics ?? Array.Empty<AetheriaRuntimeCargoItemMetric>();
}
public string Id { get; }
public string Title { get; }
public IReadOnlyList<AetheriaRuntimeCargoItemMetric> Metrics { get; }
}
public sealed class AetheriaRuntimeCargoItemObservation
{
public AetheriaRuntimeCargoItemObservation(
string itemKey,
int quantity,
bool isEquippable,
double quality,
double durability,
double temperature,
bool overrideShutdown)
{
ItemKey = itemKey ?? "";
Quantity = quantity;
IsEquippable = isEquippable;
Quality = quality;
Durability = durability;
Temperature = temperature;
OverrideShutdown = overrideShutdown;
}
public string ItemKey { get; }
public int Quantity { get; }
public bool IsEquippable { get; }
public double Quality { get; }
public double Durability { get; }
public double Temperature { get; }
public bool OverrideShutdown { get; }
}
public static class AetheriaRuntimeCargoItemDetailsSurfaceBuilder
{
public const string SurfaceId = "aetheria.inventory.cargo_item_details";
public const string Close = "aetheria.inventory.cargo_item_details.close";
public static AetheriaRuntimeSurfaceDocument Build(
AetheriaRuntimeCatalogItem typedItem,
AetheriaRuntimeCargoItemObservation item,
string manufacturer,
string tier,
Func<float, string> formatValue,
Func<float, string> formatTemperature,
DateTime updatedAtUtc = default(DateTime),
long version = 1)
{
if (updatedAtUtc == default(DateTime))
updatedAtUtc = DateTime.UtcNow;
var itemName = typedItem?.Name ?? "";
var description = typedItem?.Description ?? "";
manufacturer ??= "";
var quantity = item?.Quantity ?? 0;
tier ??= "";
var mass = typedItem == null ? "" : FormatValue(Mass(typedItem, quantity), formatValue);
var price = typedItem?.Price ?? 0;
var durability = "";
var thermalRange = "";
var behaviorSections = Array.Empty<AetheriaRuntimeCargoItemSection>();
if (typedItem != null && item != null && item.IsEquippable)
{
durability = $"{(int)(item.Durability / MaxDurability(typedItem, item) * 100)}%";
thermalRange = FormatTemperatureRange(typedItem, formatTemperature);
behaviorSections = ProjectBehaviorSections(typedItem, item, formatValue, formatTemperature).ToArray();
}
var hasEquipmentStatus = !string.IsNullOrWhiteSpace(tier) ||
!string.IsNullOrWhiteSpace(durability) ||
!string.IsNullOrWhiteSpace(thermalRange);
var updated = updatedAtUtc.ToString("O", CultureInfo.InvariantCulture);
var children = new List<AetheriaRuntimeSurfaceComponent>
{
Card(
$"{SurfaceId}.summary",
itemName,
Text(
$"{SurfaceId}.description",
string.IsNullOrWhiteSpace(description)
? "No typed item description is available."
: description),
Text(
$"{SurfaceId}.note",
"The observing client supplies the selected item; the shared runtime surface owns cargo-item inspection layout."),
Metric($"{SurfaceId}.manufacturer", "Manufacturer", manufacturer),
Metric($"{SurfaceId}.mass", "Mass", mass))
};
if (price > 0)
{
children.Add(Card(
$"{SurfaceId}.market.card",
"Market",
Metric($"{SurfaceId}.price", "Price", price.ToString("N0"))));
}
if (quantity > 0)
{
children.Add(Card(
$"{SurfaceId}.quantity.card",
"Quantity",
Metric($"{SurfaceId}.quantity", "Units", quantity.ToString())));
}
if (hasEquipmentStatus)
{
children.Add(Card(
$"{SurfaceId}.status.card",
"Status",
Metric($"{SurfaceId}.tier", "Tier", tier),
Metric($"{SurfaceId}.durability", "Durability", durability),
Metric($"{SurfaceId}.temperature_range", "Thermal Range", thermalRange)));
}
foreach (var section in behaviorSections)
{
if (section?.Metrics == null || section.Metrics.Count == 0)
continue;
children.Add(Card(
string.IsNullOrWhiteSpace(section.Id)
? $"{SurfaceId}.behavior.{SafeId(section.Title)}"
: section.Id,
section.Title,
section.Metrics
.Select(metric => Metric(metric.Id, metric.Label, metric.Value, metric.ValueRef))
.ToArray()));
}
children.Add(ButtonRow(
$"{SurfaceId}.actions",
Button($"{SurfaceId}.close", "Close", Close)));
return new AetheriaRuntimeSurfaceDocument(
providerId: "aetheria",
providerKind: "inventory.menu",
title: "Inventory Cargo Item Details",
version: version,
updatedAtUtc: updated,
surface: new AetheriaRuntimeSurfaceTree(
SurfaceId,
Node(
$"{SurfaceId}.root",
"surface",
Array.Empty<(string Key, string Value)>(),
children.ToArray()),
Array.Empty<AetheriaRuntimeSurfaceStyleToken>()),
commands: new[]
{
new AetheriaRuntimeSurfaceCommandTemplate(Close, "Close", AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport)
});
}
private static IEnumerable<AetheriaRuntimeCargoItemSection> ProjectBehaviorSections(
AetheriaRuntimeCatalogItem typedItem,
AetheriaRuntimeCargoItemObservation item,
Func<float, string> formatValue,
Func<float, string> formatTemperature)
{
foreach (var behavior in typedItem.BehaviorPayloads ?? Array.Empty<AetheriaRuntimeBehaviorPayload>())
{
if (string.Equals(behavior.Kind, AetheriaRuntimeBehaviorKinds.StatModifier, StringComparison.Ordinal))
{
var statReference = ReadStatReference(FindTypedBehaviorField(behavior, 1)?.Value);
var modifier = ReadPerformanceStat(FindTypedBehaviorField(behavior, 2)?.Value);
var modifierType = ReadEnum(
FindTypedBehaviorField(behavior, 3)?.Value,
AetheriaRuntimeCargoItemStatModifierType.Constant);
yield return new AetheriaRuntimeCargoItemSection(
$"{SurfaceId}.behavior.{behavior.Kind}.stat_modifier",
"Stat Modifier",
new[]
{
new AetheriaRuntimeCargoItemMetric(
$"{SurfaceId}.behavior.{behavior.Kind}.target",
$"{SplitCamelCase(statReference.Target)}:{SplitCamelCase(statReference.Stat)}",
$"{(modifierType == AetheriaRuntimeCargoItemStatModifierType.Constant ? "+" : "x")}{FormatCurrentItemStat(modifier, item, formatValue)}",
AetheriaRuntimeDaemonItemStatQueries.ItemStatRef(
item.ItemKey,
behavior.Kind,
behavior.Group,
2))
});
continue;
}
var metadata = AetheriaRuntimeBehaviorMetadataCatalog.Get(behavior.Kind);
if (metadata == null)
continue;
var fields = metadata.DisplayFields
.Select(field => ProjectBehaviorMetric(behavior, field, item, formatValue, formatTemperature))
.Where(metric => metric != null)
.ToArray();
if (fields.Length == 0)
continue;
yield return new AetheriaRuntimeCargoItemSection(
$"{SurfaceId}.behavior.{behavior.Kind}",
FormatTypeName(behavior.Kind),
fields);
}
}
private static AetheriaRuntimeCargoItemMetric ProjectBehaviorMetric(
AetheriaRuntimeBehaviorPayload behavior,
AetheriaRuntimeBehaviorFieldMetadata field,
AetheriaRuntimeCargoItemObservation item,
Func<float, string> formatValue,
Func<float, string> formatTemperature)
{
var payloadField = FindTypedBehaviorField(behavior, field.Key);
if (payloadField == null)
return null;
string value;
switch (field.ValueKind)
{
case AetheriaRuntimeBehaviorFieldValueKind.Number:
value = FormatValue(payloadField.Value.NumberValue, formatValue);
break;
case AetheriaRuntimeBehaviorFieldValueKind.Temperature:
value = FormatTemperature(payloadField.Value.NumberValue, formatTemperature);
break;
case AetheriaRuntimeBehaviorFieldValueKind.Integer:
value = ((int)payloadField.Value.NumberValue).ToString(CultureInfo.InvariantCulture);
break;
case AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat:
value = FormatCurrentItemStat(payloadField.Value, item, formatValue);
break;
default:
return null;
}
return new AetheriaRuntimeCargoItemMetric(
$"{SurfaceId}.behavior.{behavior.Kind}.{field.Key}",
SplitCamelCase(field.Name),
value,
field.ValueKind == AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat
? AetheriaRuntimeDaemonItemStatQueries.ItemStatRef(
item.ItemKey,
behavior.Kind,
behavior.Group,
field.Key)
: "");
}
private static string FormatCurrentItemStat(
AetheriaRuntimePerformanceStat stat,
AetheriaRuntimeCargoItemObservation item,
Func<float, string> formatValue)
{
return FormatValue(
AetheriaRuntimeDaemonItemStatQueries.EvaluatePerformanceStat(
ToBehaviorValue(stat),
ToLoadoutItem(item),
item?.Temperature ?? 0),
formatValue);
}
private static string FormatCurrentItemStat(
AetheriaRuntimeBehaviorValue stat,
AetheriaRuntimeCargoItemObservation item,
Func<float, string> formatValue)
{
return FormatValue(
AetheriaRuntimeDaemonItemStatQueries.EvaluatePerformanceStat(
stat,
ToLoadoutItem(item),
item?.Temperature ?? 0),
formatValue);
}
private static AetheriaRuntimeLoadoutItemCommit ToLoadoutItem(AetheriaRuntimeCargoItemObservation item)
{
return AetheriaRuntimeDaemonItemStatQueries.ItemCommit(
item?.ItemKey ?? "",
item?.Quality ?? 1,
item?.Durability ?? 1,
enabled: true,
overrideShutdown: item != null && item.OverrideShutdown,
temperature: item?.Temperature ?? 0);
}
private static double Mass(AetheriaRuntimeCatalogItem typedItem, int quantity)
{
if (typedItem == null)
return 0;
return quantity > 0
? typedItem.Mass * quantity
: typedItem.Mass;
}
private static double MaxDurability(
AetheriaRuntimeCatalogItem typedItem,
AetheriaRuntimeCargoItemObservation item)
{
if (typedItem != null && typedItem.Durability > 0)
return typedItem.Durability;
return Math.Max(item?.Durability ?? 1, 1);
}
private static string FormatTemperatureRange(
AetheriaRuntimeCatalogItem item,
Func<float, string> formatTemperature)
{
if (item.MaximumTemperature > item.MinimumTemperature)
{
return
$"{FormatTemperature(item.MinimumTemperature, formatTemperature)} to " +
$"{FormatTemperature(item.MaximumTemperature, formatTemperature)}";
}
return "No typed thermal range";
}
private static AetheriaRuntimeBehaviorField FindTypedBehaviorField(
AetheriaRuntimeBehaviorPayload behavior,
int? key)
{
return key == null
? null
: behavior?.Fields?.FirstOrDefault(field => field.Key == key.Value);
}
private static AetheriaRuntimePerformanceStat ReadPerformanceStat(AetheriaRuntimeBehaviorValue value)
{
return new AetheriaRuntimePerformanceStat(
ChildNumber(value, 0),
ChildNumber(value, 1),
ChildNumber(value, 2),
ChildNumber(value, 3),
ChildNumber(value, 4),
ReadStatRecipe(ChildValue(value, 5)));
}
private static AetheriaRuntimeStatRecipe ReadStatRecipe(AetheriaRuntimeBehaviorValue value)
{
if (value == null || value.Children.Count == 0)
return null;
var modifiers = ChildValue(value, 1)?.Children
.Select(ReadStatRecipeModifier)
.Where(modifier => modifier != null)
.ToArray() ?? Array.Empty<AetheriaRuntimeStatRecipeModifier>();
return new AetheriaRuntimeStatRecipe(ChildNumber(value, 0), modifiers);
}
private static AetheriaRuntimeStatRecipeModifier ReadStatRecipeModifier(AetheriaRuntimeBehaviorValue value)
{
if (value == null)
return null;
return new AetheriaRuntimeStatRecipeModifier(
ChildString(value, 0),
ChildString(value, 1),
ChildNumber(value, 2),
ReadCurveKeys(ChildValue(value, 3)),
value.Children.Count <= 4 || ChildValue(value, 4)?.BoolValue == true);
}
private static IReadOnlyList<AetheriaRuntimeCurveKey> ReadCurveKeys(AetheriaRuntimeBehaviorValue value)
{
if (value?.Children == null || value.Children.Count == 0)
return Array.Empty<AetheriaRuntimeCurveKey>();
return value.Children
.Where(key => key.Children.Count >= 4)
.Select(key => new AetheriaRuntimeCurveKey(
ChildNumber(key, 0),
ChildNumber(key, 1),
ChildNumber(key, 2),
ChildNumber(key, 3)))
.ToArray();
}
private static AetheriaRuntimeCargoItemStatReference ReadStatReference(AetheriaRuntimeBehaviorValue value)
{
return new AetheriaRuntimeCargoItemStatReference(
ChildString(value, 1),
ChildString(value, 2));
}
private static T ReadEnum<T>(AetheriaRuntimeBehaviorValue value, T fallback) where T : struct
{
if (!string.IsNullOrWhiteSpace(value?.StringValue) && Enum.TryParse(value.StringValue, true, out T parsed))
return parsed;
return value != null && Enum.IsDefined(typeof(T), (int)value.NumberValue)
? (T)Enum.ToObject(typeof(T), (int)value.NumberValue)
: fallback;
}
private static AetheriaRuntimeBehaviorValue ToBehaviorValue(AetheriaRuntimePerformanceStat stat)
{
return new AetheriaRuntimeBehaviorValue(
"performance-stat",
"",
0,
false,
"",
"",
new[]
{
Number(stat?.Min ?? 0),
Number(stat?.Max ?? 0),
Number(stat?.HeatExponentMultiplier ?? 0),
Number(stat?.DurabilityExponentMultiplier ?? 0),
Number(stat?.QualityExponent ?? 0),
StatRecipeValue(stat?.Recipe)
},
EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue StatRecipeValue(AetheriaRuntimeStatRecipe recipe)
{
if (recipe == null)
return EmptyValue("stat-recipe");
return new AetheriaRuntimeBehaviorValue(
"stat-recipe",
"",
0,
false,
"",
"",
new[]
{
Number(recipe.BaseValue),
new AetheriaRuntimeBehaviorValue(
"stat-recipe-modifiers",
"",
0,
false,
"",
"",
(recipe.Modifiers ?? Array.Empty<AetheriaRuntimeStatRecipeModifier>())
.Select(StatRecipeModifierValue)
.ToArray(),
EmptyMapEntries())
},
EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue StatRecipeModifierValue(AetheriaRuntimeStatRecipeModifier modifier)
{
if (modifier == null)
return EmptyValue("stat-recipe-modifier");
return new AetheriaRuntimeBehaviorValue(
"stat-recipe-modifier",
"",
0,
false,
"",
"",
new[]
{
TextValue(modifier.Condition),
TextValue(modifier.Operation),
Number(modifier.Amount),
CurveValue(modifier.CurveKeys),
BoolValue(modifier.Enabled)
},
EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue CurveValue(IReadOnlyList<AetheriaRuntimeCurveKey> keys)
{
return new AetheriaRuntimeBehaviorValue(
"curve",
"",
0,
false,
"",
"",
(keys ?? Array.Empty<AetheriaRuntimeCurveKey>())
.Select(key => new AetheriaRuntimeBehaviorValue(
"curve-key",
"",
0,
false,
"",
"",
new[]
{
Number(key.Time),
Number(key.Value),
Number(key.InTangent),
Number(key.OutTangent)
},
EmptyMapEntries()))
.ToArray(),
EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue Number(double value)
{
return new AetheriaRuntimeBehaviorValue("", "", value, false, "", "", Array.Empty<AetheriaRuntimeBehaviorValue>(), EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue TextValue(string value)
{
return new AetheriaRuntimeBehaviorValue("", value ?? "", 0, false, "", "", Array.Empty<AetheriaRuntimeBehaviorValue>(), EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue BoolValue(bool value)
{
return new AetheriaRuntimeBehaviorValue("", "", 0, value, "", "", Array.Empty<AetheriaRuntimeBehaviorValue>(), EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue EmptyValue(string kind)
{
return new AetheriaRuntimeBehaviorValue(kind ?? "", "", 0, false, "", "", Array.Empty<AetheriaRuntimeBehaviorValue>(), EmptyMapEntries());
}
private static IReadOnlyList<AetheriaRuntimeBehaviorMapEntry> EmptyMapEntries()
{
return Array.Empty<AetheriaRuntimeBehaviorMapEntry>();
}
private static double ChildNumber(AetheriaRuntimeBehaviorValue value, int index)
{
return value != null && value.Children.Count > index ? value.Children[index].NumberValue : 0;
}
private static string ChildString(AetheriaRuntimeBehaviorValue value, int index)
{
return value != null && value.Children.Count > index ? value.Children[index].StringValue ?? "" : "";
}
private static AetheriaRuntimeBehaviorValue ChildValue(AetheriaRuntimeBehaviorValue value, int index)
{
return value != null && value.Children.Count > index ? value.Children[index] : null;
}
private static string FormatValue(
double value,
Func<float, string> formatValue)
{
return formatValue == null
? value.ToString("0.###", CultureInfo.InvariantCulture)
: formatValue((float)value);
}
private static string FormatTemperature(
double value,
Func<float, string> formatTemperature)
{
return formatTemperature == null
? value.ToString("0.###", CultureInfo.InvariantCulture)
: formatTemperature((float)value);
}
private static string FormatTypeName(string typeName)
{
if (string.IsNullOrEmpty(typeName))
return "";
return SplitCamelCase(typeName.StartsWith("I", StringComparison.Ordinal) && typeName.Length > 1
? typeName.Substring(1)
: typeName);
}
private static string SplitCamelCase(string value)
{
if (string.IsNullOrEmpty(value))
return "";
return string.Concat(value.Select((character, index) =>
index > 0 && char.IsUpper(character) ? " " + character : character.ToString()));
}
private static string SafeId(string value)
{
if (string.IsNullOrWhiteSpace(value))
return "empty";
return new string(value
.Select(character => char.IsLetterOrDigit(character) ? char.ToLowerInvariant(character) : '-')
.ToArray()).Trim('-');
}
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, string valueRef = "")
{
var props = new List<(string Key, string Value)>
{
("label", label ?? ""),
("value", value ?? "")
};
if (!string.IsNullOrWhiteSpace(valueRef))
props.Add(AetheriaRuntimeSurfaceStateRefs.ValueRef(valueRef));
return Node(id, "metric", props);
}
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 Node(
string id,
string kind,
IEnumerable<(string Key, string Value)> props,
params AetheriaRuntimeSurfaceComponent[] children)
{
return new AetheriaRuntimeSurfaceComponent(
id,
kind,
props.ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal),
children ?? Array.Empty<AetheriaRuntimeSurfaceComponent>());
}
}
public enum AetheriaRuntimeCargoItemDetailsCommandKind
{
Unknown = 0,
Close = 1
}
internal enum AetheriaRuntimeCargoItemStatModifierType
{
Constant = 0,
Multiplier = 1
}
internal readonly struct AetheriaRuntimeCargoItemStatReference
{
public AetheriaRuntimeCargoItemStatReference(string target, string stat)
{
Target = target ?? "";
Stat = stat ?? "";
}
public string Target { get; }
public string Stat { get; }
}
public readonly struct AetheriaRuntimeCargoItemDetailsCommand
{
public AetheriaRuntimeCargoItemDetailsCommand(AetheriaRuntimeCargoItemDetailsCommandKind kind)
{
Kind = kind;
}
public AetheriaRuntimeCargoItemDetailsCommandKind Kind { get; }
}
public static class AetheriaRuntimeCargoItemDetailsSurfaceCommands
{
public static bool TryRead(
EveSurfaceCommandRequest request,
out AetheriaRuntimeCargoItemDetailsCommand command)
{
command = default;
if (request == null ||
!string.Equals(request.SurfaceId, AetheriaRuntimeCargoItemDetailsSurfaceBuilder.SurfaceId, StringComparison.Ordinal))
return false;
if (string.Equals(request.Operation?.OperationId, AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Close, StringComparison.Ordinal))
{
command = new AetheriaRuntimeCargoItemDetailsCommand(AetheriaRuntimeCargoItemDetailsCommandKind.Close);
return true;
}
return false;
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: e9160e97be310004cb18268ccd436a3e

View file

@ -1,68 +0,0 @@
using System;
using System.Linq;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeCargoTransactions
{
public static bool TryRemoveQuantity(
AetheriaRuntimeEntitySnapshotCommit entity,
int cargoIndex,
string itemKey,
int x,
int y,
int quantity,
out AetheriaRuntimeLoadoutItemSlotCommit slot)
{
slot = null!;
var cargoContents = (entity?.CargoContents ?? Array.Empty<AetheriaRuntimeCargoBayLoadoutCommit>()).ToArray();
if (cargoIndex < 0 || cargoIndex >= cargoContents.Length || cargoContents[cargoIndex] == null)
return false;
var items = (cargoContents[cargoIndex].Items ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>()).ToList();
var itemIndex = items.FindIndex(candidate => candidate?.Item != null &&
string.Equals(candidate.Item.ItemKey, itemKey, StringComparison.Ordinal) &&
candidate.X == x && candidate.Y == y);
if (itemIndex < 0) return false;
var source = items[itemIndex];
var sourceQuantity = Math.Max(1, source.Item.Quantity);
if (quantity <= 0 || quantity > sourceQuantity) return false;
slot = Clone(source, quantity);
if (quantity == sourceQuantity) items.RemoveAt(itemIndex);
else source.Item.Quantity = sourceQuantity - quantity;
cargoContents[cargoIndex] = new AetheriaRuntimeCargoBayLoadoutCommit { Items = items.ToArray() };
entity!.CargoContents = cargoContents;
return true;
}
public static bool TryFind(
AetheriaRuntimeEntitySnapshotCommit entity,
string itemKey,
out int cargoIndex,
out int x,
out int y)
{
var cargo = entity?.CargoContents ?? Array.Empty<AetheriaRuntimeCargoBayLoadoutCommit>();
for (var bay = 0; bay < cargo.Count; bay++)
foreach (var slot in cargo[bay]?.Items ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>())
{
if (slot?.Item == null || !string.Equals(slot.Item.ItemKey, itemKey, StringComparison.Ordinal)) continue;
cargoIndex = bay; x = slot.X; y = slot.Y; return true;
}
cargoIndex = x = y = -1;
return false;
}
private static AetheriaRuntimeLoadoutItemSlotCommit Clone(AetheriaRuntimeLoadoutItemSlotCommit source, int quantity) => new()
{
X = source.X, Y = source.Y, Rotation = source.Rotation ?? "None",
Item = new AetheriaRuntimeLoadoutItemCommit
{
ItemKey = source.Item.ItemKey, Quality = source.Item.Quality, Durability = source.Item.Durability,
Quantity = quantity, Enabled = source.Item.Enabled, OverrideShutdown = source.Item.OverrideShutdown,
Temperature = source.Item.Temperature
}
};
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: aeffd152980489d42a76727ed45cadcb

View file

@ -1,13 +0,0 @@
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeCatalogCommands
{
public const string SurfaceId = "aetheria.catalog.operator";
public const string Refresh = "aetheria.catalog.refresh";
public static bool IsKnown(string command)
{
return command == Refresh;
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: b149e8e695183924fb74e86cdb871be4

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 521f121b64367a548837968dda720594

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 4ae4a16968aa48e4a9244a3bfec4bd16

View file

@ -1,218 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GameCult.Eve.Surface;
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeClientTargetCommands
{
public const string SurfaceId = "aetheria.client_target";
public const string Refresh = "aetheria.client_target.refresh";
public const string CycleTargetKind = "aetheria.client_target.kind.cycle";
public const string SetTitle = "aetheria.client_target.title.set";
public const string SetVerseId = "aetheria.client_target.verse_id.set";
public const string SetRuntimeId = "aetheria.client_target.runtime_id.set";
public const string SetCultMeshAddress = "aetheria.client_target.cultmesh_address.set";
public const string SetStateFilePath = "aetheria.client_target.state_file_path.set";
public const string SetDiscoveryEndpoints = "aetheria.client_target.discovery_endpoints.set";
public const string DiscoverVerses = "aetheria.client_target.discovery.refresh";
public const string SelectDiscoveredVerse = "aetheria.client_target.discovery.select";
public const string SyncReplica = "aetheria.client_target.replica.sync";
public static bool IsKnown(string command)
{
return command == Refresh ||
command == CycleTargetKind ||
command == SetTitle ||
command == SetVerseId ||
command == SetRuntimeId ||
command == SetCultMeshAddress ||
command == SetStateFilePath ||
command == SetDiscoveryEndpoints ||
command == DiscoverVerses ||
command == SelectDiscoveredVerse ||
command == SyncReplica;
}
}
#if UNITY_5_3_OR_NEWER
public static class AetheriaRuntimeClientTargetSurfaceCommands
{
public static bool TryRequest(
AetheriaClientTarget target,
EveSurfaceCommandRequest request,
out AetheriaRuntimeClientTargetDocument? document)
{
document = null;
if (!TryRead(request, out var operation))
return false;
document = Request(target, operation);
return true;
}
private static bool TryRead(
EveSurfaceCommandRequest request,
out AetheriaClientTargetOperation operation)
{
operation = default;
if (request == null)
return false;
var command = request.Operation?.OperationId ?? "";
switch (command)
{
case AetheriaRuntimeClientTargetCommands.Refresh:
operation = new AetheriaClientTargetOperation(AetheriaClientTargetOperationKind.Refresh);
return true;
case AetheriaRuntimeClientTargetCommands.CycleTargetKind:
operation = new AetheriaClientTargetOperation(AetheriaClientTargetOperationKind.CycleTransport);
return true;
case AetheriaRuntimeClientTargetCommands.SetTitle:
operation = new AetheriaClientTargetOperation(
AetheriaClientTargetOperationKind.SetTitle,
value: ReadPayloadValue(request, "value"));
return true;
case AetheriaRuntimeClientTargetCommands.SetVerseId:
operation = new AetheriaClientTargetOperation(
AetheriaClientTargetOperationKind.SetVerseId,
value: ReadPayloadValue(request, "value"));
return true;
case AetheriaRuntimeClientTargetCommands.SetRuntimeId:
operation = new AetheriaClientTargetOperation(
AetheriaClientTargetOperationKind.SetRuntimeId,
value: ReadPayloadValue(request, "value"));
return true;
case AetheriaRuntimeClientTargetCommands.SetCultMeshAddress:
operation = new AetheriaClientTargetOperation(
AetheriaClientTargetOperationKind.SetCultMeshAddress,
value: ReadPayloadValue(request, "value"));
return true;
case AetheriaRuntimeClientTargetCommands.SetStateFilePath:
operation = new AetheriaClientTargetOperation(
AetheriaClientTargetOperationKind.SetStateFilePath,
value: ReadPayloadValue(request, "value"));
return true;
case AetheriaRuntimeClientTargetCommands.SetDiscoveryEndpoints:
operation = new AetheriaClientTargetOperation(
AetheriaClientTargetOperationKind.SetDiscoveryEndpoints,
discoveryEndpoints: ParseDiscoveryEndpoints(ReadPayloadValue(request, "value")));
return true;
case AetheriaRuntimeClientTargetCommands.DiscoverVerses:
operation = new AetheriaClientTargetOperation(AetheriaClientTargetOperationKind.DiscoverVerses);
return true;
case AetheriaRuntimeClientTargetCommands.SelectDiscoveredVerse:
operation = new AetheriaClientTargetOperation(
AetheriaClientTargetOperationKind.SelectDiscoveredVerse,
verseId: ReadPayloadValue(request, "verseId"),
title: ReadPayloadValue(request, "title"),
cultMeshAddress: ReadPayloadValue(request, "cultMeshAddress"),
discoveryEndpoints: ParseDiscoveryEndpoints(ReadPayloadValue(request, "discoveryEndpoints")));
return true;
case AetheriaRuntimeClientTargetCommands.SyncReplica:
operation = new AetheriaClientTargetOperation(AetheriaClientTargetOperationKind.SyncReplica);
return true;
default:
return false;
}
}
private static AetheriaRuntimeClientTargetDocument Request(
AetheriaClientTarget target,
AetheriaClientTargetOperation operation)
{
switch (operation.Kind)
{
case AetheriaClientTargetOperationKind.Refresh:
return target.Refresh();
case AetheriaClientTargetOperationKind.CycleTransport:
return target.CycleTransport();
case AetheriaClientTargetOperationKind.SetTitle:
return target.RequestTitle(operation.Value);
case AetheriaClientTargetOperationKind.SetVerseId:
return target.RequestVerseId(operation.Value);
case AetheriaClientTargetOperationKind.SetRuntimeId:
return target.RequestRuntimeId(operation.Value);
case AetheriaClientTargetOperationKind.SetCultMeshAddress:
return target.RequestCultMeshAddress(operation.Value);
case AetheriaClientTargetOperationKind.SetStateFilePath:
return target.RequestStateFilePath(operation.Value);
case AetheriaClientTargetOperationKind.SetDiscoveryEndpoints:
return target.RequestDiscoveryEndpoints(operation.DiscoveryEndpoints);
case AetheriaClientTargetOperationKind.DiscoverVerses:
return target.DiscoverVerses();
case AetheriaClientTargetOperationKind.SelectDiscoveredVerse:
return target.SelectDiscoveredVerse(
operation.VerseId,
operation.Title,
operation.CultMeshAddress,
operation.DiscoveryEndpoints);
case AetheriaClientTargetOperationKind.SyncReplica:
return target.SyncReplica();
default:
throw new ArgumentException(
$"Unknown Aetheria client-target operation '{operation.Kind}'.",
nameof(operation));
}
}
private static string ReadPayloadValue(EveSurfaceCommandRequest request, string key)
{
return request.Payload.GetString(key);
}
private static string[] ParseDiscoveryEndpoints(string value)
{
return (value ?? "")
.Split(new[] { ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(endpoint => endpoint.Trim())
.Where(endpoint => !string.IsNullOrWhiteSpace(endpoint))
.Distinct(StringComparer.Ordinal)
.ToArray();
}
private enum AetheriaClientTargetOperationKind
{
Unknown = 0,
Refresh = 1,
CycleTransport = 2,
SetTitle = 3,
SetVerseId = 4,
SetRuntimeId = 5,
SetCultMeshAddress = 6,
SetStateFilePath = 7,
SetDiscoveryEndpoints = 8,
DiscoverVerses = 9,
SelectDiscoveredVerse = 10,
SyncReplica = 11
}
private readonly struct AetheriaClientTargetOperation
{
public AetheriaClientTargetOperation(
AetheriaClientTargetOperationKind kind,
string value = "",
string verseId = "",
string title = "",
string cultMeshAddress = "",
IReadOnlyList<string>? discoveryEndpoints = null)
{
Kind = kind;
Value = value ?? "";
VerseId = verseId ?? "";
Title = title ?? "";
CultMeshAddress = cultMeshAddress ?? "";
DiscoveryEndpoints = discoveryEndpoints?.ToArray() ?? Array.Empty<string>();
}
public AetheriaClientTargetOperationKind Kind { get; }
public string Value { get; }
public string VerseId { get; }
public string Title { get; }
public string CultMeshAddress { get; }
public IReadOnlyList<string> DiscoveryEndpoints { get; }
}
}
#endif
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 6bd3516801c897b40b02efef67e17fe9

View file

@ -1,181 +0,0 @@
using System;
using System.IO;
using GameCult.Caching;
using GameCult.Mesh;
using MessagePack;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeClientTargetKinds
{
public const string StateFile = "state-file";
public const string CultMeshVerse = "cultmesh-verse";
}
[MessagePackObject]
public sealed class AetheriaRuntimeClientTargetDocument
{
public const string SchemaId = "gamecult.aetheria.runtime_client_target.v1";
public const string SchemaName = "aetheria.runtime_client_target";
public const string SchemaVersion = "aetheria.runtime_client_target.v1";
public const string DocumentKey = "local:aetheria.runtime_client_target.v1";
[Key(0)] public string Schema { get; set; } = SchemaId;
[Key(1)] public string TargetKind { get; set; } = AetheriaRuntimeClientTargetKinds.StateFile;
[Key(2)] public string Title { get; set; } = "Local Aetheria";
[Key(3)] public string VerseId { get; set; } = "aetheria.local";
[Key(4)] public string CultMeshAddress { get; set; } = "asgard.local.aetheria/eve";
[Key(5)] public string StateFilePath { get; set; } = "";
[Key(6)] public string UpdatedAtUtc { get; set; } = "";
[Key(7)] public string[] DiscoveryEndpoints { get; set; } = Array.Empty<string>();
[Key(8)] public AetheriaRuntimeDiscoveredVerse[] DiscoveredVerses { get; set; } = Array.Empty<AetheriaRuntimeDiscoveredVerse>();
[Key(9)] public string LastDiscoveryAtUtc { get; set; } = "";
[Key(10)] public string LastDiscoveryError { get; set; } = "";
[Key(11)] public string ReplicaStateFilePath { get; set; } = "";
[Key(12)] public string LastReplicaSyncAtUtc { get; set; } = "";
[Key(13)] public string LastReplicaSyncError { get; set; } = "";
[Key(14)] public string RuntimeId { get; set; } = AetheriaRuntimeStateBoundary.DefaultClientRuntimeId;
}
[MessagePackObject]
public sealed class AetheriaRuntimeDiscoveredVerse
{
[Key(0)] public string VerseId { get; set; } = "";
[Key(1)] public string DisplayName { get; set; } = "";
[Key(2)] public string AuthorityModel { get; set; } = "";
[Key(3)] public string TransportVersion { get; set; } = "";
[Key(4)] public string RulesHash { get; set; } = "";
[Key(5)] public string Description { get; set; } = "";
[Key(6)] public string[] DiscoveryEndpoints { get; set; } = Array.Empty<string>();
[Key(7)] public string[] AuthorityRuntimeIds { get; set; } = Array.Empty<string>();
[Key(8)] public string ParentVerseId { get; set; } = "";
}
public static class AetheriaRuntimeClientTargetStore
{
public static AetheriaRuntimeClientTargetDocument ReadOrInitialize(
string clientTargetPath,
string defaultStateFilePath)
{
if (File.Exists(clientTargetPath))
return Read(clientTargetPath);
var document = CreateDefault(defaultStateFilePath);
Write(clientTargetPath, document);
return document;
}
public static AetheriaRuntimeClientTargetDocument CreateDefault(string defaultStateFilePath)
{
var gameDataDirectory = ResolveGameDataDirectory(defaultStateFilePath);
return new AetheriaRuntimeClientTargetDocument
{
Schema = AetheriaRuntimeClientTargetDocument.SchemaId,
TargetKind = AetheriaRuntimeClientTargetKinds.StateFile,
Title = "Local Aetheria",
VerseId = "aetheria.local",
CultMeshAddress = "asgard.local.aetheria/eve",
RuntimeId = AetheriaRuntimeStateBoundary.DefaultClientRuntimeId,
StateFilePath = defaultStateFilePath ?? "",
ReplicaStateFilePath = AetheriaRuntimeStateBoundary.GetReplicaStateFilePath(gameDataDirectory, "aetheria.local"),
UpdatedAtUtc = DateTime.UtcNow.ToString("O")
};
}
public static void Write(string clientTargetPath, AetheriaRuntimeClientTargetDocument document)
{
if (document == null) throw new ArgumentNullException(nameof(document));
document.Schema = AetheriaRuntimeClientTargetDocument.SchemaId;
document.DiscoveryEndpoints ??= Array.Empty<string>();
document.DiscoveredVerses ??= Array.Empty<AetheriaRuntimeDiscoveredVerse>();
document.LastDiscoveryAtUtc ??= "";
document.LastDiscoveryError ??= "";
document.ReplicaStateFilePath = NormalizeReplicaStateFilePath(document, clientTargetPath, document.ReplicaStateFilePath);
document.LastReplicaSyncAtUtc ??= "";
document.LastReplicaSyncError ??= "";
if (string.IsNullOrWhiteSpace(document.RuntimeId))
document.RuntimeId = AetheriaRuntimeStateBoundary.DefaultClientRuntimeId;
foreach (var verse in document.DiscoveredVerses)
{
if (verse == null)
continue;
verse.VerseId ??= "";
verse.DisplayName ??= "";
verse.AuthorityModel ??= "";
verse.TransportVersion ??= "";
verse.RulesHash ??= "";
verse.Description ??= "";
verse.DiscoveryEndpoints ??= Array.Empty<string>();
verse.AuthorityRuntimeIds ??= Array.Empty<string>();
verse.ParentVerseId ??= "";
}
document.UpdatedAtUtc = string.IsNullOrWhiteSpace(document.UpdatedAtUtc)
? DateTime.UtcNow.ToString("O")
: document.UpdatedAtUtc;
CultMesh.WriteSingleFileDocumentPayload(
clientTargetPath,
new CultRecordKey(AetheriaRuntimeClientTargetDocument.DocumentKey),
new CultMeshSingleFileDocumentSchema(
AetheriaRuntimeClientTargetDocument.SchemaId,
AetheriaRuntimeClientTargetDocument.SchemaName,
AetheriaRuntimeClientTargetDocument.SchemaVersion),
document.UpdatedAtUtc,
MessagePackSerializer.Serialize(document));
}
public static AetheriaRuntimeClientTargetDocument Update(
string clientTargetPath,
string defaultStateFilePath,
Action<AetheriaRuntimeClientTargetDocument> mutate)
{
if (mutate == null) throw new ArgumentNullException(nameof(mutate));
var document = ReadOrInitialize(clientTargetPath, defaultStateFilePath);
mutate(document);
document.ReplicaStateFilePath = NormalizeReplicaStateFilePath(document, clientTargetPath, document.ReplicaStateFilePath);
Write(clientTargetPath, document);
return document;
}
public static AetheriaRuntimeClientTargetDocument Read(string clientTargetPath)
{
return MessagePackSerializer.Deserialize<AetheriaRuntimeClientTargetDocument>(
CultMesh.ReadSingleFileDocumentPayload(
clientTargetPath,
new CultRecordKey(AetheriaRuntimeClientTargetDocument.DocumentKey),
AetheriaRuntimeClientTargetDocument.SchemaId));
}
private static string NormalizeReplicaStateFilePath(
AetheriaRuntimeClientTargetDocument document,
string clientTargetPath,
string configuredPath)
{
if (!string.IsNullOrWhiteSpace(configuredPath))
return Path.GetFullPath(configuredPath);
var gameDataDirectory = ResolveGameDataDirectoryFromClientTarget(clientTargetPath);
return AetheriaRuntimeStateBoundary.GetReplicaStateFilePath(gameDataDirectory, document?.VerseId ?? "");
}
private static DirectoryInfo ResolveGameDataDirectory(string defaultStateFilePath)
{
var fullPath = Path.GetFullPath(string.IsNullOrWhiteSpace(defaultStateFilePath) ? "." : defaultStateFilePath);
var directory = Path.GetDirectoryName(fullPath);
return new DirectoryInfo(string.IsNullOrWhiteSpace(directory) ? "." : directory);
}
private static DirectoryInfo ResolveGameDataDirectoryFromClientTarget(string clientTargetPath)
{
var fullPath = Path.GetFullPath(string.IsNullOrWhiteSpace(clientTargetPath) ? "." : clientTargetPath);
var directory = Path.GetDirectoryName(fullPath);
return new DirectoryInfo(string.IsNullOrWhiteSpace(directory) ? "." : directory);
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 3aca91844413c7d46afaf6168a04ca8d

View file

@ -1,365 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeClientTargetSurfaceBuilder
{
public static AetheriaRuntimeSurfaceDocument Build(
AetheriaRuntimeStateBootReport stateBoot,
AetheriaRuntimeVerseHostSettingsDocument verseHost,
string updatedAtUtc,
long version = 1)
{
var targetKind = stateBoot.TargetKind ?? AetheriaRuntimeClientTargetKinds.StateFile;
var targetTitle = stateBoot.Title ?? "";
var targetVerseId = stateBoot.VerseId ?? "";
var targetRuntimeId = string.IsNullOrWhiteSpace(stateBoot.RuntimeId) ? AetheriaRuntimeStateBoundary.DefaultClientRuntimeId : stateBoot.RuntimeId;
var targetCultMeshAddress = stateBoot.CultMeshAddress ?? "";
var targetStateFilePath = stateBoot.StateFilePath ?? "";
var targetReplicaStateFilePath = stateBoot.ReplicaStateFilePath ?? "";
var discoveryEndpointsText = string.Join(", ", stateBoot.DiscoveryEndpoints ?? Array.Empty<string>());
var discoveredVerses = stateBoot.DiscoveredVerses ?? Array.Empty<AetheriaRuntimeDiscoveredVerse>();
var targetSource = stateBoot.TargetSource ?? "";
var bootFailureMessage = stateBoot.FailureMessage ?? "";
var hostTitle = verseHost?.Title ?? targetTitle;
var hostVerseId = verseHost?.VerseId ?? targetVerseId;
var hostVisibility = verseHost?.Visibility ?? "unknown";
var hostCultMeshAddress = verseHost?.CultMeshAddress ?? targetCultMeshAddress;
updatedAtUtc ??= "";
var targetKindLabel = string.Equals(targetKind, AetheriaRuntimeClientTargetKinds.CultMeshVerse, StringComparison.Ordinal)
? "CultMesh Verse"
: "Local State File";
var visibilityLabel = string.IsNullOrWhiteSpace(hostVisibility) ? "unknown" : hostVisibility;
var visibilityActionLabel = string.Equals(visibilityLabel, "public", StringComparison.OrdinalIgnoreCase)
? "Make Private"
: "Make Public";
var lastDiscoveryLabel = string.IsNullOrWhiteSpace(stateBoot.LastDiscoveryAtUtc) ? "never" : stateBoot.LastDiscoveryAtUtc;
var lastReplicaSyncLabel = string.IsNullOrWhiteSpace(stateBoot.LastReplicaSyncAtUtc) ? "never" : stateBoot.LastReplicaSyncAtUtc;
var summaryNote = !stateBoot.SupportsLocalStateFileRead && !string.IsNullOrWhiteSpace(bootFailureMessage)
? bootFailureMessage
: string.Equals(targetSource, "state-path-override", StringComparison.Ordinal)
? "AETHERIA_STATE_PATH is overriding the persisted client target. Update the environment if you want boot to follow the saved target again."
: "Client target edits persist in aetheria-client.cc. Verse discovery and selection mutate the same typed owner. Verse visibility changes append provider-owned Eve requests for the daemon bridge. Remote Verse targets hydrate a cache-only local replica before observers read them.";
var discoveryChildren = new List<AetheriaRuntimeSurfaceComponent>
{
TextInput(
"aetheria.clientTarget.discovery.endpoints",
"Discovery Endpoints",
discoveryEndpointsText,
AetheriaRuntimeClientTargetCommands.SetDiscoveryEndpoints),
Metric("aetheria.clientTarget.discovery.lastScan", "Last Scan", lastDiscoveryLabel)
};
if (!string.IsNullOrWhiteSpace(stateBoot.LastDiscoveryError))
{
discoveryChildren.Add(Text(
"aetheria.clientTarget.discovery.error",
stateBoot.LastDiscoveryError));
}
if (discoveredVerses.Length == 0)
{
discoveryChildren.Add(Text(
"aetheria.clientTarget.discovery.empty",
string.IsNullOrWhiteSpace(discoveryEndpointsText)
? "Add one or more cultnet:// discovery endpoints to scan for public or federated Aetheria Verses."
: "No Verse descriptors are cached yet for the configured discovery endpoints."));
}
else
{
discoveryChildren.Add(Metric(
"aetheria.clientTarget.discovery.count",
"Known Verses",
discoveredVerses.Length.ToString()));
discoveryChildren.Add(Row(
"aetheria.clientTarget.discovery.list",
discoveredVerses
.Select((verse, index) => BuildDiscoveredVerseNode(targetKind, targetVerseId, verse, index))
.ToArray()));
}
discoveryChildren.Add(ButtonRow(
"aetheria.clientTarget.discovery.actions",
Button(
"aetheria.clientTarget.discovery.scan",
"Discover",
AetheriaRuntimeClientTargetCommands.DiscoverVerses),
Button(
"aetheria.clientTarget.discovery.refresh",
"Refresh",
AetheriaRuntimeClientTargetCommands.Refresh)));
return new AetheriaRuntimeSurfaceDocument(
providerId: "aetheria",
providerKind: "game.menu",
title: "Aetheria Verse Settings",
version: version,
updatedAtUtc: updatedAtUtc,
surface: new AetheriaRuntimeSurfaceTree(
AetheriaRuntimeClientTargetCommands.SurfaceId,
Node(
"aetheria.clientTarget.root",
"surface",
Array.Empty<(string Key, string Value)>(),
Node(
"aetheria.clientTarget.summary",
"card",
new[] { ("title", "Client Target") },
Metric("aetheria.clientTarget.summary.target", "Target", stateBoot.TargetLabel),
Metric("aetheria.clientTarget.summary.transport", "Transport", targetKindLabel),
Metric("aetheria.clientTarget.summary.source", "Target Source", targetSource),
Text("aetheria.clientTarget.summary.note", summaryNote)),
Node(
"aetheria.clientTarget.target",
"card",
new[] { ("title", "Target Fields") },
TextInput(
"aetheria.clientTarget.target.title",
"Title",
targetTitle,
AetheriaRuntimeClientTargetCommands.SetTitle),
TextInput(
"aetheria.clientTarget.target.verseId",
"Verse Id",
targetVerseId,
AetheriaRuntimeClientTargetCommands.SetVerseId),
TextInput(
"aetheria.clientTarget.target.runtimeId",
"Runtime Id",
targetRuntimeId,
AetheriaRuntimeClientTargetCommands.SetRuntimeId),
TextInput(
"aetheria.clientTarget.target.cultMeshAddress",
"CultMesh Address",
targetCultMeshAddress,
AetheriaRuntimeClientTargetCommands.SetCultMeshAddress),
TextInput(
"aetheria.clientTarget.target.stateFilePath",
"State File Path",
targetStateFilePath,
AetheriaRuntimeClientTargetCommands.SetStateFilePath),
Metric(
"aetheria.clientTarget.target.replicaStateFilePath",
"Replica State File",
targetReplicaStateFilePath),
Metric(
"aetheria.clientTarget.target.replicaSyncAt",
"Replica Sync",
lastReplicaSyncLabel),
Text(
"aetheria.clientTarget.target.replicaSyncError",
stateBoot.LastReplicaSyncError),
ButtonRow(
"aetheria.clientTarget.target.actions",
Button(
"aetheria.clientTarget.target.cycleTransport",
string.Equals(targetKind, AetheriaRuntimeClientTargetKinds.CultMeshVerse, StringComparison.Ordinal)
? "Use Local State File"
: "Use CultMesh Verse",
AetheriaRuntimeClientTargetCommands.CycleTargetKind),
Button(
"aetheria.clientTarget.target.syncReplica",
"Sync Replica",
AetheriaRuntimeClientTargetCommands.SyncReplica),
Button(
"aetheria.clientTarget.target.refresh",
"Refresh",
AetheriaRuntimeClientTargetCommands.Refresh))),
Node(
"aetheria.clientTarget.discovery",
"card",
new[] { ("title", "Verse Discovery") },
discoveryChildren.ToArray()),
Node(
"aetheria.clientTarget.host",
"card",
new[] { ("title", "Daemon Verse Host") },
Metric("aetheria.clientTarget.host.verse", "Verse", BuildVerseLabel(hostTitle, hostVerseId)),
Metric("aetheria.clientTarget.host.visibility", "Visibility", visibilityLabel),
Metric("aetheria.clientTarget.host.cultMesh", "CultMesh", hostCultMeshAddress),
ButtonRow(
"aetheria.clientTarget.host.actions",
Button(
"aetheria.clientTarget.host.toggleVisibility",
visibilityActionLabel,
AetheriaRuntimeVerseHostCommands.CycleVisibility),
Button(
"aetheria.clientTarget.host.refresh",
"Refresh Host",
AetheriaRuntimeVerseHostCommands.Refresh))),
Node(
"aetheria.clientTarget.host.noteCard",
"card",
new[] { ("title", "Ownership") },
Text(
"aetheria.clientTarget.host.note",
"Client target edits persist through the managed target document. Visibility changes append provider-owned Eve requests against the selected Verse target."))),
Array.Empty<AetheriaRuntimeSurfaceStyleToken>()),
commands: new[]
{
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeClientTargetCommands.Refresh,
"Refresh",
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeClientTargetCommands.CycleTargetKind,
"Cycle Transport",
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeClientTargetCommands.SetTitle,
"Set Title",
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeClientTargetCommands.SetVerseId,
"Set Verse Id",
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeClientTargetCommands.SetRuntimeId,
"Set Runtime Id",
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeClientTargetCommands.SetCultMeshAddress,
"Set CultMesh Address",
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeClientTargetCommands.SetStateFilePath,
"Set State File Path",
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeClientTargetCommands.SetDiscoveryEndpoints,
"Set Discovery Endpoints",
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeClientTargetCommands.DiscoverVerses,
"Discover Verses",
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeClientTargetCommands.SelectDiscoveredVerse,
"Select Discovered Verse",
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeClientTargetCommands.SyncReplica,
"Sync Replica",
AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport),
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeVerseHostCommands.CycleVisibility,
"Toggle Visibility",
"cultmesh"),
new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeVerseHostCommands.Refresh,
"Refresh Host",
"cultmesh")
});
}
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 TextInput(string id, string label, string value, string command)
{
return Node(
id,
"control.text",
new[] { ("label", label ?? ""), ("value", value ?? ""), ("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 Row(
string id,
params AetheriaRuntimeSurfaceComponent[] children)
{
return Node(id, "inspector.kv", 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.ToDictionary(prop => prop.Key, prop => prop.Value, StringComparer.Ordinal),
children ?? Array.Empty<AetheriaRuntimeSurfaceComponent>());
}
private static AetheriaRuntimeSurfaceComponent BuildDiscoveredVerseNode(
string targetKind,
string targetVerseId,
AetheriaRuntimeDiscoveredVerse verse,
int index)
{
var verseLabel = BuildVerseLabel(verse);
var address = verse.DiscoveryEndpoints.FirstOrDefault() ?? "";
var isSelected = string.Equals(targetKind, AetheriaRuntimeClientTargetKinds.CultMeshVerse, StringComparison.Ordinal) &&
string.Equals(targetVerseId, verse.VerseId, StringComparison.Ordinal);
var buttonLabel = isSelected ? "Selected" : "Select";
return Node(
$"aetheria.clientTarget.discovery.verse.{index}",
"inspector.kv",
Array.Empty<(string Key, string Value)>(),
Metric($"aetheria.clientTarget.discovery.verse.{index}.label", "Verse", verseLabel),
Metric($"aetheria.clientTarget.discovery.verse.{index}.authority", "Authority", verse.AuthorityModel),
Metric($"aetheria.clientTarget.discovery.verse.{index}.transport", "Transport", verse.TransportVersion),
Metric($"aetheria.clientTarget.discovery.verse.{index}.endpoint", "Endpoint", address),
Text(
$"aetheria.clientTarget.discovery.verse.{index}.description",
string.IsNullOrWhiteSpace(verse.Description)
? "No public description."
: verse.Description),
Node(
$"aetheria.clientTarget.discovery.verse.{index}.action",
"control.button",
new[]
{
("label", buttonLabel),
("command", AetheriaRuntimeClientTargetCommands.SelectDiscoveredVerse),
("verseId", verse.VerseId ?? ""),
("title", verse.DisplayName ?? ""),
("cultMeshAddress", address),
("discoveryEndpoints", string.Join(", ", verse.DiscoveryEndpoints ?? Array.Empty<string>()))
}));
}
private static string BuildVerseLabel(AetheriaRuntimeDiscoveredVerse verse)
{
return verse == null
? "Unknown Verse"
: BuildVerseLabel(verse.DisplayName, verse.VerseId);
}
private static string BuildVerseLabel(string displayName, string verseId)
{
if (string.IsNullOrWhiteSpace(displayName))
return string.IsNullOrWhiteSpace(verseId) ? "Unknown Verse" : verseId;
return string.IsNullOrWhiteSpace(verseId) || string.Equals(displayName, verseId, StringComparison.Ordinal)
? displayName
: $"{displayName} ({verseId})";
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 7c51ae1e0547a6c45b454932a285a555

View file

@ -1,185 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public sealed class AetheriaRuntimeCommittedFactImportResult
{
public AetheriaRuntimeCommittedFactImportResult(
AetheriaRuntimeDaemonTickResult tick,
IReadOnlyList<string> acceptedFactIds,
IReadOnlyList<string> rejectedFactIds,
IReadOnlyList<string> duplicateFactIds)
{
Tick = tick ?? throw new ArgumentNullException(nameof(tick));
AcceptedFactIds = acceptedFactIds ?? Array.Empty<string>();
RejectedFactIds = rejectedFactIds ?? Array.Empty<string>();
DuplicateFactIds = duplicateFactIds ?? Array.Empty<string>();
}
public AetheriaRuntimeDaemonTickResult Tick { get; }
public IReadOnlyList<string> AcceptedFactIds { get; }
public IReadOnlyList<string> RejectedFactIds { get; }
public IReadOnlyList<string> DuplicateFactIds { get; }
public AetheriaRuntimeRunCheckpointCommit Run => Tick.Run;
public AetheriaRuntimeDaemonFrameDocument Frame => Tick.Frame;
}
public static class AetheriaRuntimeCommittedFactImporter
{
public static AetheriaRuntimeCommittedFactImportResult ImportIntoFrame(
string stateFilePath,
AetheriaRuntimeDaemonFrameDocument? currentFrame,
IEnumerable<AetheriaRuntimeCommittedCommandFactDocument> facts,
AetheriaRuntimeVerseAuthorityPolicyDocument? policy,
IEnumerable<AetheriaRuntimeAuthorityLeaseDocument>? leases,
string localRuntimeId,
string daemonId,
string sessionId,
string verseId,
IAetheriaRuntimeWorldPhysics worldPhysics,
IEnumerable<string>? importedFactIds = null,
AetheriaRuntimeCatalogSnapshot? catalog = null)
{
if (worldPhysics == null)
throw new ArgumentNullException(nameof(worldPhysics));
currentFrame ??= new AetheriaRuntimeDaemonFrameDocument
{
Run = new AetheriaRuntimeRunCheckpointCommit(),
FixedDeltaSeconds = 0.02
};
var alreadyImported = new HashSet<string>(
importedFactIds ?? Array.Empty<string>(),
StringComparer.Ordinal);
var acceptedFacts = new List<string>();
var rejectedFacts = new List<string>();
var duplicateFacts = new List<string>();
var commands = new List<AetheriaRuntimeDaemonCommandDocument>();
var acceptedCommands = new List<AcceptedCommandFact>();
foreach (var fact in facts ?? Enumerable.Empty<AetheriaRuntimeCommittedCommandFactDocument>())
{
if (fact == null || string.IsNullOrWhiteSpace(fact.FactId))
continue;
if (!alreadyImported.Add(fact.FactId))
{
duplicateFacts.Add(fact.FactId);
continue;
}
if (!string.Equals(fact.Outcome, AetheriaRuntimeCommandFactOutcomes.Applied, StringComparison.Ordinal))
{
rejectedFacts.Add(fact.FactId);
continue;
}
var command = NormalizeCommand(fact);
var decision = AetheriaRuntimeAuthorityRouter.Authorize(
command,
policy,
leases,
localRuntimeId);
if (!decision.Authorized)
{
rejectedFacts.Add(fact.FactId);
continue;
}
acceptedFacts.Add(fact.FactId);
commands.Add(command);
acceptedCommands.Add(new AcceptedCommandFact(fact.FactId, command.CommandId));
}
var fixedDeltaSeconds = currentFrame.FixedDeltaSeconds > 0
? currentFrame.FixedDeltaSeconds
: 0.02;
var frameId = currentFrame.FrameId + 1;
var simulationTimeSeconds = currentFrame.SimulationTimeSeconds + fixedDeltaSeconds;
var tick = AetheriaRuntimeDaemonTickRunner.Tick(
stateFilePath,
currentFrame.Run ?? new AetheriaRuntimeRunCheckpointCommit(),
new AetheriaRuntimeDaemonTickOptions
{
DaemonId = string.IsNullOrWhiteSpace(daemonId) ? localRuntimeId : daemonId,
SessionId = string.IsNullOrWhiteSpace(sessionId) ? currentFrame.SessionId : sessionId,
VerseId = string.IsNullOrWhiteSpace(verseId) ? "aetheria.local" : verseId,
FrameId = frameId,
SimulationTimeSeconds = simulationTimeSeconds,
FixedDeltaSeconds = fixedDeltaSeconds,
ObservedCommands = commands,
AccountedCommandIds = currentFrame.AccountedCommandIds,
CumulativeAppliedCommandIds = currentFrame.CumulativeAppliedCommandIds,
CumulativeRejectedCommandIds = currentFrame.CumulativeRejectedCommandIds,
Catalog = catalog ?? EmptyCatalog(),
WorldPhysics = worldPhysics,
BuildPublications = false
});
var operationRejectedCommandIds = new HashSet<string>(
tick.OperationResult.RejectedCommandIds ?? Array.Empty<string>(),
StringComparer.Ordinal);
if (operationRejectedCommandIds.Count > 0)
{
acceptedFacts.Clear();
foreach (var item in acceptedCommands)
{
if (!string.IsNullOrWhiteSpace(item.CommandId) && operationRejectedCommandIds.Contains(item.CommandId))
rejectedFacts.Add(item.FactId);
else
acceptedFacts.Add(item.FactId);
}
}
return new AetheriaRuntimeCommittedFactImportResult(
tick,
acceptedFacts.Distinct(StringComparer.Ordinal).ToArray(),
rejectedFacts.Distinct(StringComparer.Ordinal).ToArray(),
duplicateFacts.Distinct(StringComparer.Ordinal).ToArray());
}
private static AetheriaRuntimeDaemonCommandDocument NormalizeCommand(
AetheriaRuntimeCommittedCommandFactDocument fact)
{
var command = fact.Command ?? new AetheriaRuntimeDaemonCommandDocument();
command.Schema = AetheriaRuntimeDaemonSchemas.Command;
if (string.IsNullOrWhiteSpace(command.CommandId))
command.CommandId = fact.CommandId;
if (string.IsNullOrWhiteSpace(command.AuthorRuntimeId))
command.AuthorRuntimeId = fact.SourceRuntimeId;
if (string.IsNullOrWhiteSpace(command.ClientId))
command.ClientId = fact.SourceRuntimeId;
if (string.IsNullOrWhiteSpace(command.SubjectKey))
command.SubjectKey = fact.SubjectKey;
if (string.IsNullOrWhiteSpace(command.ClaimKind))
command.ClaimKind = fact.ClaimKind;
return command;
}
private static AetheriaRuntimeCatalogSnapshot EmptyCatalog()
{
return new AetheriaRuntimeCatalogSnapshot(
Array.Empty<AetheriaRuntimeCatalogItem>(),
Array.Empty<AetheriaRuntimeCorporation>(),
Array.Empty<AetheriaRuntimeNameFile>());
}
private sealed class AcceptedCommandFact
{
public AcceptedCommandFact(string factId, string commandId)
{
FactId = factId ?? "";
CommandId = commandId ?? "";
}
public string FactId { get; }
public string CommandId { get; }
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: ec790b3f056eeb343813a139ea4fea8f

View file

@ -1,442 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeConsumableSimulation
{
public static void Step(
AetheriaRuntimeRunCheckpointCommit run,
IEnumerable<AetheriaRuntimeDaemonConsumableIntent> intents,
AetheriaRuntimeCatalogSnapshot? catalog,
long frameId,
double deltaSeconds)
{
if (run == null || deltaSeconds <= 0)
return;
foreach (var zone in run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
StepZone(run, zone, zone?.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>(), intents, catalog, frameId, deltaSeconds);
}
public static void StepZone(
AetheriaRuntimeRunCheckpointCommit run,
AetheriaRuntimeZoneSnapshotCommit? zone,
IReadOnlyList<AetheriaRuntimeEntitySnapshotCommit> entities,
IEnumerable<AetheriaRuntimeDaemonConsumableIntent> intents,
AetheriaRuntimeCatalogSnapshot? catalog,
long frameId,
double deltaSeconds)
{
if (run == null || zone == null || deltaSeconds <= 0)
return;
var intentIndex = 0;
foreach (var intent in intents ?? Enumerable.Empty<AetheriaRuntimeDaemonConsumableIntent>())
{
if (AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(intent?.ActorEntityKey ?? "", out var intentZone, out _) &&
intentZone == zone.ZoneIndex)
Activate(run, intent, catalog, frameId, intentIndex);
intentIndex++;
}
foreach (var entity in entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
{
if (entity == null)
continue;
var active = (entity.ActiveConsumables ?? Array.Empty<AetheriaRuntimeActiveConsumableCommit>())
.Where(effect => effect != null)
.ToArray();
for (var index = 0; index < active.Length; index++)
{
EnsureEffectIdentity(zone, entity, active[index], index);
ExecuteBehaviors(run, zone, entity, active[index], catalog, frameId, deltaSeconds);
active[index].RemainingDuration -= deltaSeconds;
if (active[index].RemainingDuration < 0)
{
AetheriaRuntimeGameEvents.Append(run, new AetheriaRuntimeGameEventCommit
{
EventId = $"frame:{frameId}:zone:{zone.ZoneIndex}:entity:{entity.EntityIndex}:consumable:{active[index].EffectId}:expired",
Kind = "consumable.expired",
FrameId = frameId,
ZoneIndex = zone.ZoneIndex,
SourceEntityIndex = entity.EntityIndex,
ItemKey = active[index].ItemKey
});
}
}
entity.ActiveConsumables = active.Where(effect => effect.RemainingDuration >= 0).ToArray();
}
}
private static void ExecuteBehaviors(
AetheriaRuntimeRunCheckpointCommit run,
AetheriaRuntimeZoneSnapshotCommit zone,
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeActiveConsumableCommit effect,
AetheriaRuntimeCatalogSnapshot? catalog,
long frameId,
double deltaSeconds)
{
var item = catalog?.FindItem(effect.ItemKey);
if (item == null)
{
BehaviorStopped(run, zone, entity, effect, frameId, -1, "missing-catalog-item");
return;
}
var elapsed = effect.Duration <= 0
? 1
: Math.Max(0, Math.Min(1, (effect.Duration - effect.RemainingDuration) / effect.Duration));
var effectiveness = item.EffectivenessCurveKeys == null || item.EffectivenessCurveKeys.Count == 0
? 1
: AetheriaRuntimeDaemonItemStatQueries.SampleCurve(item.EffectivenessCurveKeys, elapsed);
var payloads = item.BehaviorPayloads ?? Array.Empty<AetheriaRuntimeBehaviorPayload>();
if (!ReconcileBehaviorStates(effect, payloads))
{
BehaviorStopped(run, zone, entity, effect, frameId, -1, "duplicate-behavior-id");
return;
}
for (var behaviorIndex = 0; behaviorIndex < payloads.Count; behaviorIndex++)
if (payloads[behaviorIndex] != null &&
string.Equals(payloads[behaviorIndex].Kind, "StatModifier", StringComparison.Ordinal))
FindBehaviorState(effect, behaviorIndex, payloads[behaviorIndex]).StatModifierTargetStatCount =
AetheriaRuntimeBehaviorSimulation.CountTargets(entity, catalog!, payloads[behaviorIndex]);
UpdateAlwaysUpdatedBehaviors(effect, payloads, effectiveness, deltaSeconds);
for (var behaviorIndex = 0; behaviorIndex < payloads.Count; behaviorIndex++)
{
var payload = payloads[behaviorIndex];
if (payload == null)
continue;
if (ExecuteBehavior(
entity, effect, payload, behaviorIndex, catalog, effectiveness, deltaSeconds, out var reason))
continue;
BehaviorStopped(run, zone, entity, effect, frameId, behaviorIndex, reason);
break;
}
}
private static bool ExecuteBehavior(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeActiveConsumableCommit effect,
AetheriaRuntimeBehaviorPayload payload,
int behaviorIndex,
AetheriaRuntimeCatalogSnapshot? catalog,
double effectiveness,
double deltaSeconds,
out string reason)
{
reason = "";
switch (payload.Kind)
{
case "StatModifier":
FindBehaviorState(effect, behaviorIndex, payload).StatModifierExecuted = true;
return true;
case "Cooldown":
{
var state = FindBehaviorState(effect, behaviorIndex, payload);
if (state.ScalarState < 0)
{
state.ScalarState = 1;
return true;
}
reason = "cooldown";
return false;
}
case "EnergyDraw":
{
var demand = Evaluate(payload, 1, effect, effectiveness) * (ReadBool(payload, 2) ? deltaSeconds : 1);
if (AetheriaRuntimeEnergySimulation.TryConsume(entity, catalog, demand))
return true;
reason = "insufficient-energy";
return false;
}
case "Heat":
// The fossil only added heat to equipped items; consumable heat was intentionally a no-op.
return true;
case "ItemUsage":
{
var itemKey = ReadItemKey(payload, 1);
if (!string.IsNullOrWhiteSpace(itemKey) &&
AetheriaRuntimeCargoTransactions.TryFind(entity, itemKey, out var cargoIndex, out var x, out var y) &&
AetheriaRuntimeCargoTransactions.TryRemoveQuantity(entity, cargoIndex, itemKey, x, y, 1, out _))
return true;
reason = "missing-required-item";
return false;
}
case "Wear":
return true;
default:
reason = "unsupported-behavior:" + (payload.Kind ?? "");
return false;
}
}
private static bool ReconcileBehaviorStates(
AetheriaRuntimeActiveConsumableCommit effect,
IReadOnlyList<AetheriaRuntimeBehaviorPayload> payloads)
{
var identities = payloads
.Select((payload, behaviorIndex) => BehaviorIdentity(payload, behaviorIndex))
.ToArray();
if (identities.Distinct(StringComparer.Ordinal).Count() != identities.Length)
return false;
var previousStates = (effect.BehaviorStates ?? Array.Empty<AetheriaRuntimeConsumableBehaviorStateCommit>())
.Where(state => state != null)
.ToArray();
var previousById = previousStates
.Where(state => !string.IsNullOrWhiteSpace(state.BehaviorId))
.GroupBy(state => state.BehaviorId, StringComparer.Ordinal)
.ToDictionary(group => group.Key, group => group.First(), StringComparer.Ordinal);
effect.BehaviorStates = payloads
.Select((payload, behaviorIndex) =>
{
var kind = payload?.Kind ?? "";
var behaviorId = identities[behaviorIndex];
if (!previousById.TryGetValue(behaviorId, out var state))
state = previousStates.FirstOrDefault(candidate =>
string.IsNullOrWhiteSpace(candidate.BehaviorId) &&
candidate.BehaviorIndex == behaviorIndex &&
string.Equals(candidate.BehaviorKind, kind, StringComparison.Ordinal));
state ??= new AetheriaRuntimeConsumableBehaviorStateCommit
{
ScalarState = 0
};
state.BehaviorId = behaviorId;
state.BehaviorIndex = behaviorIndex;
state.BehaviorKind = kind;
return state;
})
.ToArray();
return true;
}
private static string BehaviorIdentity(AetheriaRuntimeBehaviorPayload? payload, int behaviorIndex) =>
!string.IsNullOrWhiteSpace(payload?.BehaviorId)
? payload.BehaviorId
: $"legacy:{behaviorIndex}:{payload?.Kind ?? ""}";
private static void UpdateAlwaysUpdatedBehaviors(
AetheriaRuntimeActiveConsumableCommit effect,
IReadOnlyList<AetheriaRuntimeBehaviorPayload> payloads,
double effectiveness,
double deltaSeconds)
{
for (var behaviorIndex = 0; behaviorIndex < payloads.Count; behaviorIndex++)
{
var payload = payloads[behaviorIndex];
if (payload == null)
continue;
if (string.Equals(payload.Kind, "StatModifier", StringComparison.Ordinal))
{
var state = FindBehaviorState(effect, behaviorIndex, payload);
state.StatModifierApplied = state.StatModifierExecuted;
state.StatModifierExecuted = false;
continue;
}
if (!string.Equals(payload.Kind, "Cooldown", StringComparison.Ordinal))
continue;
var duration = Evaluate(payload, 1, effect, effectiveness);
FindBehaviorState(effect, behaviorIndex, payload).ScalarState -= deltaSeconds / duration;
}
}
private static AetheriaRuntimeConsumableBehaviorStateCommit FindBehaviorState(
AetheriaRuntimeActiveConsumableCommit effect,
int behaviorIndex,
AetheriaRuntimeBehaviorPayload payload) =>
(effect.BehaviorStates ?? Array.Empty<AetheriaRuntimeConsumableBehaviorStateCommit>())
.Single(state => state != null &&
string.Equals(state.BehaviorId, BehaviorIdentity(payload, behaviorIndex),
StringComparison.Ordinal));
private static void EnsureEffectIdentity(
AetheriaRuntimeZoneSnapshotCommit zone,
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeActiveConsumableCommit effect,
int legacyIndex)
{
if (!string.IsNullOrWhiteSpace(effect.EffectId))
return;
effect.EffectId = $"legacy:zone:{zone.ZoneIndex}:entity:{entity.EntityIndex}:effect:{legacyIndex}";
}
private static double Evaluate(
AetheriaRuntimeBehaviorPayload payload,
int fieldKey,
AetheriaRuntimeActiveConsumableCommit effect,
double effectiveness) =>
AetheriaRuntimeDaemonItemStatQueries.EvaluateConsumablePerformanceStat(
Field(payload, fieldKey)?.Value,
effect.Quality,
effectiveness);
private static bool ReadBool(AetheriaRuntimeBehaviorPayload payload, int fieldKey) =>
Field(payload, fieldKey)?.Value?.BoolValue ?? false;
private static string ReadItemKey(AetheriaRuntimeBehaviorPayload payload, int fieldKey)
{
var value = Field(payload, fieldKey)?.Value;
return value?.ItemKeyValue ?? value?.StringValue ?? "";
}
private static AetheriaRuntimeBehaviorField? Field(AetheriaRuntimeBehaviorPayload payload, int fieldKey) =>
(payload.Fields ?? Array.Empty<AetheriaRuntimeBehaviorField>())
.FirstOrDefault(field => field != null && field.Key == fieldKey);
private static void BehaviorStopped(
AetheriaRuntimeRunCheckpointCommit run,
AetheriaRuntimeZoneSnapshotCommit zone,
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeActiveConsumableCommit effect,
long frameId,
int behaviorIndex,
string reason)
{
AetheriaRuntimeGameEvents.Append(run, new AetheriaRuntimeGameEventCommit
{
EventId = $"frame:{frameId}:zone:{zone.ZoneIndex}:entity:{entity.EntityIndex}:consumable:{effect.EffectId}:behavior:{behaviorIndex}:stopped",
Kind = "consumable.behavior.stopped",
FrameId = frameId,
ZoneIndex = zone.ZoneIndex,
SourceEntityIndex = entity.EntityIndex,
ItemKey = effect.ItemKey,
ScalarValue = behaviorIndex,
Reason = reason
});
}
public static bool CanActivate(
AetheriaRuntimeEntitySnapshotCommit entity,
AetheriaRuntimeCatalogSnapshot? catalog,
string itemKey)
{
var item = catalog?.FindItem(itemKey);
return entity != null && item != null &&
string.Equals(item.Category, AetheriaRuntimeItemCategories.Consumable, StringComparison.Ordinal) &&
item.Duration > 0 &&
(item.Stackable || !(entity.ActiveConsumables ?? Array.Empty<AetheriaRuntimeActiveConsumableCommit>())
.Any(active => active != null && string.Equals(active.ItemKey, itemKey, StringComparison.Ordinal))) &&
AetheriaRuntimeCargoTransactions.TryFind(entity, itemKey, out _, out _, out _);
}
private static void Activate(
AetheriaRuntimeRunCheckpointCommit run,
AetheriaRuntimeDaemonConsumableIntent? intent,
AetheriaRuntimeCatalogSnapshot? catalog,
long frameId,
int intentIndex)
{
var itemKey = intent?.ItemKey ?? "";
if (!TryResolveActor(run, intent?.ActorEntityKey, out var zone, out var entity))
{
Refuse(run, frameId, intentIndex, zone, entity, itemKey, "actor-unavailable");
return;
}
var item = catalog?.FindItem(itemKey);
if (item == null || !string.Equals(item.Category, AetheriaRuntimeItemCategories.Consumable, StringComparison.Ordinal))
{
Refuse(run, frameId, intentIndex, zone, entity, itemKey, "not-consumable");
return;
}
if (item.Duration <= 0)
{
Refuse(run, frameId, intentIndex, zone, entity, itemKey, "invalid-duration");
return;
}
if (!item.Stackable && (entity.ActiveConsumables ?? Array.Empty<AetheriaRuntimeActiveConsumableCommit>())
.Any(active => active != null && string.Equals(active.ItemKey, itemKey, StringComparison.Ordinal)))
{
Refuse(run, frameId, intentIndex, zone, entity, itemKey, "already-active");
return;
}
if (!AetheriaRuntimeCargoTransactions.TryFind(entity, itemKey, out var cargoIndex, out var x, out var y) ||
!AetheriaRuntimeCargoTransactions.TryRemoveQuantity(entity, cargoIndex, itemKey, x, y, 1, out var consumed))
{
Refuse(run, frameId, intentIndex, zone, entity, itemKey, "missing-cargo");
return;
}
entity.ActiveConsumables = (entity.ActiveConsumables ?? Array.Empty<AetheriaRuntimeActiveConsumableCommit>())
.Where(active => active != null)
.Append(new AetheriaRuntimeActiveConsumableCommit
{
EffectId = $"effect:frame:{frameId}:zone:{zone.ZoneIndex}:entity:{entity.EntityIndex}:intent:{intentIndex}",
ItemKey = item.ItemKey,
Quality = consumed.Item.Quality,
Duration = item.Duration,
RemainingDuration = item.Duration
})
.ToArray();
AetheriaRuntimeGameEvents.Append(run, new AetheriaRuntimeGameEventCommit
{
EventId = $"frame:{frameId}:zone:{zone.ZoneIndex}:entity:{entity.EntityIndex}:consumable:{intentIndex}:activated",
Kind = "consumable.activated",
FrameId = frameId,
ZoneIndex = zone.ZoneIndex,
SourceEntityIndex = entity.EntityIndex,
ItemKey = item.ItemKey,
ScalarValue = consumed.Item.Quality,
AuxiliaryValue = item.Duration
});
}
private static bool TryResolveActor(
AetheriaRuntimeRunCheckpointCommit run,
string? actorEntityKey,
out AetheriaRuntimeZoneSnapshotCommit? zone,
out AetheriaRuntimeEntitySnapshotCommit? entity)
{
AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(actorEntityKey ?? "", out var actorZoneIndex, out var actorEntityIndex);
foreach (var candidateZone in run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
foreach (var candidate in candidateZone?.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
{
var canonicalKey = candidate == null
? ""
: run.EntityRecordKey(candidateZone.ZoneIndex, candidate.EntityIndex);
if (candidate != null &&
(string.Equals(canonicalKey, actorEntityKey, StringComparison.Ordinal) ||
(candidateZone.ZoneIndex == actorZoneIndex && candidate.EntityIndex == actorEntityIndex)))
{
zone = candidateZone;
entity = candidate;
return true;
}
}
zone = null;
entity = null;
return false;
}
private static void Refuse(
AetheriaRuntimeRunCheckpointCommit run,
long frameId,
int intentIndex,
AetheriaRuntimeZoneSnapshotCommit? zone,
AetheriaRuntimeEntitySnapshotCommit? entity,
string itemKey,
string reason)
{
AetheriaRuntimeGameEvents.Append(run, new AetheriaRuntimeGameEventCommit
{
EventId = $"frame:{frameId}:consumable:{intentIndex}:refused",
Kind = "consumable.activation.refused",
FrameId = frameId,
ZoneIndex = zone?.ZoneIndex ?? -1,
SourceEntityIndex = entity?.EntityIndex ?? -1,
ItemKey = itemKey,
Reason = reason
});
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 76ddd6e0004f0a247a16d1a393d768d1

View file

@ -1,526 +0,0 @@
using System;
using System.Buffers;
using System.IO;
using GameCult.Caching;
using GameCult.Mesh;
using MessagePack;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
internal static class AetheriaRuntimeCultCacheDocumentStore
{
public static void WriteEveCommand(string path, AetheriaRuntimeEveCommandDocument document)
{
WriteDocument(
path,
$"command:gamecult.eve.command.{document.CommandId}.v1",
AetheriaRuntimeEveCommandDocument.SchemaId,
"gamecult.eve.command",
"gamecult.eve.command.v1",
document.IssuedAtUtc,
MessagePackSerializer.Serialize(document));
}
public static AetheriaRuntimeEveCommandDocument ReadEveCommand(string path)
{
return MessagePackSerializer.Deserialize<AetheriaRuntimeEveCommandDocument>(
CultMesh.ReadSingleFileDocumentPayload(path, AetheriaRuntimeEveCommandDocument.SchemaId));
}
public static void WriteDaemonCommand(string path, AetheriaRuntimeDaemonCommandDocument document)
{
WriteDocument(
path,
$"command:gamecult.aetheria.daemon_command.{document.CommandId}.v1",
AetheriaRuntimeDaemonSchemas.Command,
"gamecult.aetheria.daemon_command",
"gamecult.aetheria.daemon_command.v1",
document.IssuedAtUtc,
MessagePackSerializer.Serialize(document));
}
public static AetheriaRuntimeDaemonCommandDocument ReadDaemonCommand(string path)
{
return MessagePackSerializer.Deserialize<AetheriaRuntimeDaemonCommandDocument>(
CultMesh.ReadSingleFileDocumentPayload(path, AetheriaRuntimeDaemonSchemas.Command));
}
private static void WriteDocument(
string path,
string key,
string schemaId,
string schemaName,
string schemaVersion,
string storedAt,
byte[] payload)
{
CultMesh.WriteSingleFileDocumentPayload(
path,
new CultRecordKey(key),
new CultMeshSingleFileDocumentSchema(schemaId, schemaName, schemaVersion),
storedAt,
payload);
}
internal static byte[] WriteRuntimeSurfaceDocument(AetheriaRuntimeSurfaceDocument document)
{
var buffer = new ArrayBufferWriter<byte>();
var writer = new MessagePackWriter(buffer);
writer.WriteArrayHeader(7);
writer.Write(document.ProviderId ?? "");
writer.Write(document.ProviderKind ?? "");
writer.Write(document.Title ?? "");
writer.Write(document.Version);
writer.Write(document.UpdatedAtUtc ?? "");
WriteSurfaceTree(ref writer, document.Surface);
WriteSurfaceCommands(ref writer, document.Commands);
writer.Flush();
return buffer.WrittenSpan.ToArray();
}
private static void WriteSurfaceTree(ref MessagePackWriter writer, AetheriaRuntimeSurfaceTree tree)
{
tree ??= new AetheriaRuntimeSurfaceTree(
"",
new AetheriaRuntimeSurfaceComponent(
"",
"surface",
new System.Collections.Generic.Dictionary<string, string>(StringComparer.Ordinal),
Array.Empty<AetheriaRuntimeSurfaceComponent>()),
Array.Empty<AetheriaRuntimeSurfaceStyleToken>());
writer.WriteArrayHeader(3);
writer.Write(tree.Id ?? "");
WriteSurfaceComponent(ref writer, tree.Root);
writer.WriteArrayHeader(tree.Styles?.Count ?? 0);
foreach (var style in tree.Styles ?? Array.Empty<AetheriaRuntimeSurfaceStyleToken>())
{
writer.WriteArrayHeader(2);
writer.Write(style.Name ?? "");
writer.Write(style.Value ?? "");
}
}
private static void WriteSurfaceComponent(ref MessagePackWriter writer, AetheriaRuntimeSurfaceComponent component)
{
component ??= new AetheriaRuntimeSurfaceComponent(
"",
"",
new System.Collections.Generic.Dictionary<string, string>(StringComparer.Ordinal),
Array.Empty<AetheriaRuntimeSurfaceComponent>());
writer.WriteArrayHeader(8);
writer.Write(component.Id ?? "");
writer.Write(component.Kind ?? "");
writer.WriteMapHeader(component.Props?.Count ?? 0);
foreach (var prop in component.Props ?? new System.Collections.Generic.Dictionary<string, string>(StringComparer.Ordinal))
{
writer.Write(prop.Key ?? "");
writer.Write(prop.Value ?? "");
}
writer.WriteArrayHeader(component.Children?.Count ?? 0);
foreach (var child in component.Children ?? Array.Empty<AetheriaRuntimeSurfaceComponent>())
{
WriteSurfaceComponent(ref writer, child);
}
writer.WriteArrayHeader(component.StateBindings?.Count ?? 0);
foreach (var binding in component.StateBindings ?? Array.Empty<CultMeshStateBindingDescriptor>())
{
var record = CultMesh.StateBindingRecord(binding);
writer.WriteArrayHeader(6);
writer.Write(record.TargetProp);
writer.Write(record.PointerId);
writer.Write(record.SourceId);
writer.Write(record.SchemaId);
writer.Write(record.RouteKind);
writer.Write(record.RouteDescription);
}
writer.WriteArrayHeader(component.EmbeddedDocuments?.Count ?? 0);
foreach (var slot in component.EmbeddedDocuments ?? Array.Empty<AetheriaRuntimeEmbeddedDocumentSlot>())
{
var route = slot.RouteHint ?? CultMeshRouteHint.Automatic;
writer.WriteArrayHeader(6);
writer.Write(slot.SlotId ?? "");
writer.Write(slot.DocumentId ?? "");
writer.Write(slot.SchemaId ?? "");
writer.Write(slot.PresentationKind ?? "");
writer.Write(route.Kind.ToString());
writer.Write(route.Description ?? "");
}
WriteStringMap(ref writer, component.Layout);
WriteStringMap(ref writer, component.Style);
}
private static void WriteSurfaceCommands(
ref MessagePackWriter writer,
System.Collections.Generic.IReadOnlyList<AetheriaRuntimeSurfaceCommandTemplate> commands)
{
writer.WriteArrayHeader(commands?.Count ?? 0);
foreach (var command in commands ?? Array.Empty<AetheriaRuntimeSurfaceCommandTemplate>())
{
var record = CultMesh.OperationBindingRecord(command.Operation);
writer.WriteArrayHeader(5);
writer.Write(record.OperationId);
writer.Write(record.Label);
writer.Write(record.SchemaId);
writer.Write(record.RouteKind);
writer.Write(record.RouteDescription);
}
}
private static void WriteStringMap(
ref MessagePackWriter writer,
System.Collections.Generic.IReadOnlyDictionary<string, string> values)
{
writer.WriteMapHeader(values?.Count ?? 0);
foreach (var value in values ?? new System.Collections.Generic.Dictionary<string, string>(StringComparer.Ordinal))
{
writer.Write(value.Key ?? "");
writer.Write(value.Value ?? "");
}
}
internal static AetheriaRuntimeSurfaceDocument ReadRuntimeSurfaceDocument(byte[] payload)
{
var reader = new MessagePackReader(payload);
var fields = reader.ReadArrayHeader();
var providerId = ReadFieldString(ref reader, fields, 0, "");
var providerKind = ReadFieldString(ref reader, fields, 1, "");
var title = ReadFieldString(ref reader, fields, 2, "");
var version = ReadFieldInt64(ref reader, fields, 3, 0);
var updatedAtUtc = ReadFieldString(ref reader, fields, 4, "");
var surface = ReadFieldSurfaceTree(ref reader, fields, 5);
var commands = ReadFieldSurfaceCommands(ref reader, fields, 6);
for (var index = 7; index < fields; index++)
{
reader.Skip();
}
return new AetheriaRuntimeSurfaceDocument(
providerId,
providerKind,
title,
version,
updatedAtUtc,
surface,
commands);
}
private static AetheriaRuntimeSurfaceTree ReadFieldSurfaceTree(
ref MessagePackReader reader,
int fields,
int index)
{
if (index >= fields || reader.NextMessagePackType == MessagePackType.Nil)
{
if (index < fields)
reader.ReadNil();
return new AetheriaRuntimeSurfaceTree(
"",
new AetheriaRuntimeSurfaceComponent(
"",
"surface",
new System.Collections.Generic.Dictionary<string, string>(StringComparer.Ordinal),
Array.Empty<AetheriaRuntimeSurfaceComponent>()),
Array.Empty<AetheriaRuntimeSurfaceStyleToken>());
}
var treeFields = reader.ReadArrayHeader();
var id = ReadFieldString(ref reader, treeFields, 0, "");
var root = ReadFieldSurfaceComponent(ref reader, treeFields, 1);
var styles = ReadFieldSurfaceStyles(ref reader, treeFields, 2);
for (var field = 3; field < treeFields; field++)
{
reader.Skip();
}
return new AetheriaRuntimeSurfaceTree(id, root, styles);
}
private static AetheriaRuntimeSurfaceComponent ReadFieldSurfaceComponent(
ref MessagePackReader reader,
int fields,
int index)
{
if (index >= fields || reader.NextMessagePackType == MessagePackType.Nil)
{
if (index < fields)
reader.ReadNil();
return new AetheriaRuntimeSurfaceComponent(
"",
"",
new System.Collections.Generic.Dictionary<string, string>(StringComparer.Ordinal),
Array.Empty<AetheriaRuntimeSurfaceComponent>());
}
var componentFields = reader.ReadArrayHeader();
var id = ReadFieldString(ref reader, componentFields, 0, "");
var kind = ReadFieldString(ref reader, componentFields, 1, "");
var props = ReadFieldStringMap(ref reader, componentFields, 2);
var children = ReadFieldSurfaceComponents(ref reader, componentFields, 3);
var stateBindings = ReadFieldSurfaceStateBindings(ref reader, componentFields, 4);
var embeddedDocuments = ReadFieldEmbeddedDocumentSlots(ref reader, componentFields, 5);
var layout = ReadFieldStringMap(ref reader, componentFields, 6);
var style = ReadFieldStringMap(ref reader, componentFields, 7);
for (var field = 8; field < componentFields; field++)
{
reader.Skip();
}
return new AetheriaRuntimeSurfaceComponent(id, kind, props, children, stateBindings, embeddedDocuments, layout, style);
}
private static System.Collections.Generic.IReadOnlyList<AetheriaRuntimeEmbeddedDocumentSlot> ReadFieldEmbeddedDocumentSlots(
ref MessagePackReader reader,
int fields,
int index)
{
if (index >= fields || reader.NextMessagePackType == MessagePackType.Nil)
{
if (index < fields)
reader.ReadNil();
return Array.Empty<AetheriaRuntimeEmbeddedDocumentSlot>();
}
var count = reader.ReadArrayHeader();
var slots = new AetheriaRuntimeEmbeddedDocumentSlot[count];
for (var item = 0; item < count; item++)
{
var slotFields = reader.ReadArrayHeader();
var slotId = ReadFieldString(ref reader, slotFields, 0, "");
var documentId = ReadFieldString(ref reader, slotFields, 1, "");
var schemaId = ReadFieldString(ref reader, slotFields, 2, "");
var presentationKind = ReadFieldString(ref reader, slotFields, 3, "");
var routeKind = ReadFieldString(ref reader, slotFields, 4, nameof(CultMeshLocalityKind.Automatic));
var routeDescription = ReadFieldString(ref reader, slotFields, 5, "");
for (var field = 6; field < slotFields; field++)
{
reader.Skip();
}
slots[item] = new AetheriaRuntimeEmbeddedDocumentSlot(
slotId,
documentId,
schemaId,
presentationKind,
new CultMeshRouteHint(
Enum.TryParse<CultMeshLocalityKind>(routeKind, true, out var locality)
? locality
: CultMeshLocalityKind.Automatic,
routeDescription));
}
return slots;
}
private static System.Collections.Generic.IReadOnlyList<CultMeshStateBindingDescriptor> ReadFieldSurfaceStateBindings(
ref MessagePackReader reader,
int fields,
int index)
{
if (index >= fields || reader.NextMessagePackType == MessagePackType.Nil)
{
if (index < fields)
reader.ReadNil();
return Array.Empty<CultMeshStateBindingDescriptor>();
}
var count = reader.ReadArrayHeader();
var bindings = new CultMeshStateBindingDescriptor[count];
for (var item = 0; item < count; item++)
{
var bindingFields = reader.ReadArrayHeader();
var targetProp = ReadFieldString(ref reader, bindingFields, 0, "value");
var pointerId = ReadFieldString(ref reader, bindingFields, 1, "");
var sourceId = ReadFieldString(ref reader, bindingFields, 2, "");
var schemaId = ReadFieldString(ref reader, bindingFields, 3, "");
var routeKind = ReadFieldString(ref reader, bindingFields, 4, "");
var routeDescription = ReadFieldString(ref reader, bindingFields, 5, "");
for (var field = 6; field < bindingFields; field++)
{
reader.Skip();
}
bindings[item] = CultMesh.StateBindingRecord(
targetProp,
pointerId,
sourceId,
schemaId,
routeKind,
routeDescription).ToBinding();
}
return bindings;
}
private static System.Collections.Generic.IReadOnlyList<AetheriaRuntimeSurfaceComponent> ReadFieldSurfaceComponents(
ref MessagePackReader reader,
int fields,
int index)
{
if (index >= fields || reader.NextMessagePackType == MessagePackType.Nil)
{
if (index < fields)
reader.ReadNil();
return Array.Empty<AetheriaRuntimeSurfaceComponent>();
}
var count = reader.ReadArrayHeader();
var children = new AetheriaRuntimeSurfaceComponent[count];
for (var child = 0; child < count; child++)
{
children[child] = ReadFieldSurfaceComponent(ref reader, 1, 0);
}
return children;
}
private static System.Collections.Generic.IReadOnlyDictionary<string, string> ReadFieldStringMap(
ref MessagePackReader reader,
int fields,
int index)
{
var values = new System.Collections.Generic.Dictionary<string, string>(StringComparer.Ordinal);
if (index >= fields || reader.NextMessagePackType == MessagePackType.Nil)
{
if (index < fields)
reader.ReadNil();
return values;
}
var count = reader.ReadMapHeader();
for (var item = 0; item < count; item++)
{
values[reader.ReadString() ?? ""] = reader.ReadString() ?? "";
}
return values;
}
private static System.Collections.Generic.IReadOnlyList<AetheriaRuntimeSurfaceStyleToken> ReadFieldSurfaceStyles(
ref MessagePackReader reader,
int fields,
int index)
{
if (index >= fields || reader.NextMessagePackType == MessagePackType.Nil)
{
if (index < fields)
reader.ReadNil();
return Array.Empty<AetheriaRuntimeSurfaceStyleToken>();
}
var count = reader.ReadArrayHeader();
var styles = new AetheriaRuntimeSurfaceStyleToken[count];
for (var style = 0; style < count; style++)
{
var styleFields = reader.ReadArrayHeader();
var name = ReadFieldString(ref reader, styleFields, 0, "");
var value = ReadFieldString(ref reader, styleFields, 1, "");
for (var field = 2; field < styleFields; field++)
{
reader.Skip();
}
styles[style] = new AetheriaRuntimeSurfaceStyleToken(name, value);
}
return styles;
}
private static System.Collections.Generic.IReadOnlyList<AetheriaRuntimeSurfaceCommandTemplate> ReadFieldSurfaceCommands(
ref MessagePackReader reader,
int fields,
int index)
{
if (index >= fields || reader.NextMessagePackType == MessagePackType.Nil)
{
if (index < fields)
reader.ReadNil();
return Array.Empty<AetheriaRuntimeSurfaceCommandTemplate>();
}
var count = reader.ReadArrayHeader();
var commands = new AetheriaRuntimeSurfaceCommandTemplate[count];
for (var command = 0; command < count; command++)
{
var commandFields = reader.ReadArrayHeader();
var name = ReadFieldString(ref reader, commandFields, 0, "");
var label = ReadFieldString(ref reader, commandFields, 1, "");
var schemaId = ReadFieldString(ref reader, commandFields, 2, "");
var routeKind = commandFields > 3
? ReadFieldString(ref reader, commandFields, 3, "")
: nameof(CultMeshLocalityKind.Automatic);
var routeDescription = commandFields > 4
? ReadFieldString(ref reader, commandFields, 4, "")
: "";
for (var field = 5; field < commandFields; field++)
{
reader.Skip();
}
commands[command] = new AetheriaRuntimeSurfaceCommandTemplate(
CultMesh.OperationBindingRecord(
name,
label,
schemaId,
routeKind,
routeDescription).ToBinding());
}
return commands;
}
private static string ReadFieldString(ref MessagePackReader reader, int fields, int index, string fallback)
{
return index >= fields ? fallback : reader.ReadString() ?? fallback;
}
private static int ReadFieldInt32(ref MessagePackReader reader, int fields, int index, int fallback)
{
if (index >= fields)
{
return fallback;
}
if (reader.NextMessagePackType == MessagePackType.Nil)
{
reader.ReadNil();
return fallback;
}
return reader.ReadInt32();
}
private static long ReadFieldInt64(ref MessagePackReader reader, int fields, int index, long fallback)
{
if (index >= fields)
{
return fallback;
}
if (reader.NextMessagePackType == MessagePackType.Nil)
{
reader.ReadNil();
return fallback;
}
return reader.ReadInt64();
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 730416bfa990947479bc2eb704361bd8

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 89b7a1cbc494a264ba4130e65485ee5b

View file

@ -1,214 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeDaemonEditorSurfaceBuilder
{
public const string SurfaceId = "aetheria.daemon.editor";
public const string TuiSurfaceId = "aetheria.daemon.editor.tui";
public static AetheriaRuntimeSurfaceDocument Build(
AetheriaRuntimeDaemonProviderAdvertisementDocument provider,
AetheriaRuntimeDaemonHealthDocument health,
AetheriaRuntimeDaemonCommandBoundaryDocument commandBoundary,
IReadOnlyList<AetheriaRuntimeSurfaceDocument>? designerSurfaces = null)
{
provider ??= new AetheriaRuntimeDaemonProviderAdvertisementDocument();
health ??= new AetheriaRuntimeDaemonHealthDocument();
commandBoundary ??= AetheriaRuntimeDaemonCommandBoundaryDocument.Create(provider.DaemonId);
designerSurfaces ??= Array.Empty<AetheriaRuntimeSurfaceDocument>();
return new AetheriaRuntimeSurfaceDocument(
providerId: AetheriaRuntimeProviderIdentity.ProviderId,
providerKind: "editor.daemon",
title: "Aetheria Daemon Editor",
version: health.FrameId,
updatedAtUtc: string.IsNullOrWhiteSpace(health.PublishedAtUtc)
? provider.PublishedAtUtc
: health.PublishedAtUtc,
surface: new AetheriaRuntimeSurfaceTree(
SurfaceId,
Node(
"aetheria.daemon.editor.root",
"surface",
Array.Empty<(string Key, string Value)>(),
Node(
"aetheria.daemon.editor.provider",
"card",
new[] { ("title", "Verse Provider") },
Metric("aetheria.daemon.editor.provider.verse", "Verse", provider.VerseId),
Metric("aetheria.daemon.editor.provider.provider", "Provider", provider.ProviderId),
Metric("aetheria.daemon.editor.provider.daemon", "Daemon", provider.DaemonId),
Metric("aetheria.daemon.editor.provider.transport", "CultMesh", provider.CultMeshAddress),
Metric("aetheria.daemon.editor.provider.state", "State", provider.StateRecordRef)),
Node(
"aetheria.daemon.editor.records",
"card",
new[] { ("title", "Managed Records") },
Metric("aetheria.daemon.editor.records.frame", "Frame", provider.FrameRecordRef),
Metric("aetheria.daemon.editor.records.soa", "SoA", provider.SoaViewRecordRef),
Metric("aetheria.daemon.editor.records.health", "Health", provider.HealthRecordRef),
Metric("aetheria.daemon.editor.records.commands", "Command Boundary", provider.CommandBoundaryRecordRef),
Metric("aetheria.daemon.editor.records.game", "Game Surface", provider.EveGuiSurfaceRecordRef),
Metric("aetheria.daemon.editor.records.editor", "Editor Surface", provider.EditorGuiSurfaceRecordRef)),
Node(
"aetheria.daemon.editor.health",
"card",
new[] { ("title", "Health") },
Metric("aetheria.daemon.editor.health.status", "Status", health.Status),
Metric("aetheria.daemon.editor.health.frame", "Frame", health.FrameId.ToString(CultureInfo.InvariantCulture)),
Metric("aetheria.daemon.editor.health.observed_commands", "Observed Commands", health.ObservedCommandCount.ToString(CultureInfo.InvariantCulture)),
Metric("aetheria.daemon.editor.health.applied", "Applied", health.AppliedCommandCount.ToString(CultureInfo.InvariantCulture)),
Metric("aetheria.daemon.editor.health.rejected", "Rejected", health.RejectedCommandCount.ToString(CultureInfo.InvariantCulture)),
Metric("aetheria.daemon.editor.health.source", "Source", health.PublicationSource),
Metric("aetheria.daemon.editor.health.transport", "Transport", health.Transport)),
Node(
"aetheria.daemon.editor.schemas",
"card",
new[] { ("title", "Published Schemas") },
Row(
"aetheria.daemon.editor.schemas.rows",
provider.PublishedSchemas
.Select((schema, index) => Metric(
$"aetheria.daemon.editor.schemas.{index}",
"Schema",
schema))
.ToArray())),
Node(
"aetheria.daemon.editor.commands",
"card",
new[] { ("title", "Typed Commands") },
Metric(
"aetheria.daemon.editor.commands.boundary",
"Boundary",
commandBoundary.BoundaryId),
Metric(
"aetheria.daemon.editor.commands.count",
"Command Count",
commandBoundary.Commands.Count.ToString(CultureInfo.InvariantCulture)),
Row(
"aetheria.daemon.editor.commands.rows",
commandBoundary.Commands
.Take(16)
.Select((entry, index) => CommandRow(index, entry))
.ToArray())),
Node(
"aetheria.daemon.editor.designer_surfaces",
"card",
new[] { ("title", "Designer Surfaces") },
Row(
"aetheria.daemon.editor.designer_surfaces.rows",
designerSurfaces
.Where(surface => surface != null)
.Select((surface, index) => DesignerSurfaceRow(index, surface))
.ToArray())),
Node(
"aetheria.daemon.editor.eve_surfaces",
"card",
new[] { ("title", "Advertised Eve Surfaces") },
Row(
"aetheria.daemon.editor.eve_surfaces.rows",
provider.EveSurfaces
.Where(surface => surface != null)
.Select((surface, index) => AdvertisedSurfaceRow(index, surface))
.ToArray()))),
Array.Empty<AetheriaRuntimeSurfaceStyleToken>()),
commands: commandBoundary.Commands
.Where(entry => AetheriaRuntimeDaemonSurfaceCommandCatalog.IsArgumentlessCommand(entry.Kind))
.Select(entry => new AetheriaRuntimeSurfaceCommandTemplate(
AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandName(entry.Kind),
AetheriaRuntimeDaemonSurfaceCommandCatalog.Label(entry.Kind),
"cultmesh"))
.ToArray());
}
private static AetheriaRuntimeSurfaceComponent DesignerSurfaceRow(
int index,
AetheriaRuntimeSurfaceDocument surface)
{
return Node(
$"aetheria.daemon.editor.designer_surfaces.{index}",
"inspector.kv",
new[]
{
("title", surface.Title),
("surfaceId", surface.Surface.Id),
("provider", surface.ProviderId),
("kind", surface.ProviderKind),
("commands", surface.Commands.Count.ToString(CultureInfo.InvariantCulture))
});
}
private static AetheriaRuntimeSurfaceComponent AdvertisedSurfaceRow(
int index,
AetheriaRuntimeEveSurfaceAdvertisement surface)
{
return Node(
$"aetheria.daemon.editor.eve_surfaces.{index}",
"inspector.kv",
new[]
{
("title", surface.Title),
("surfaceId", surface.SurfaceId),
("record", surface.RecordRef),
("kind", surface.SurfaceKind),
("projection", surface.WorldInteraction?.ProjectionKind ?? ""),
("commandBoundary", surface.WorldInteraction?.CommandBoundary ?? ""),
("status", surface.Status),
("audience", surface.Audience),
("provider", surface.ProviderId)
});
}
private static AetheriaRuntimeSurfaceComponent CommandRow(
int index,
AetheriaRuntimeDaemonCommandBoundaryEntry entry)
{
return Node(
$"aetheria.daemon.editor.commands.{index}",
"inspector.kv",
new[]
{
("kind", entry.Kind.ToString()),
("body", entry.CommandBody),
("authority", entry.Authority),
("receipt", entry.Receipt)
});
}
private static string CommandName(AetheriaRuntimeDaemonCommandKinds kind)
{
return AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandName(kind);
}
private static AetheriaRuntimeSurfaceComponent Metric(string id, string label, string value)
{
return Node(id, "metric", new[] { ("label", label), ("value", value ?? "") });
}
private static AetheriaRuntimeSurfaceComponent Row(
string id,
params AetheriaRuntimeSurfaceComponent[] children)
{
return Node(id, "row", 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.ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal),
children ?? Array.Empty<AetheriaRuntimeSurfaceComponent>());
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 17918ab9d67f6304c96767532bdf3d11

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 09207ef539b22e348a582894cc9396ec

View file

@ -1,92 +0,0 @@
using System;
using System.Collections.Generic;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public sealed class AetheriaRuntimeDaemonIntentState
{
public List<AetheriaRuntimeDaemonMovementIntent> Movements { get; } =
new List<AetheriaRuntimeDaemonMovementIntent>();
public List<AetheriaRuntimeDaemonWeaponGroupIntent> WeaponGroups { get; } =
new List<AetheriaRuntimeDaemonWeaponGroupIntent>();
public List<AetheriaRuntimeDaemonBehaviorIntent> Behaviors { get; } =
new List<AetheriaRuntimeDaemonBehaviorIntent>();
public List<AetheriaRuntimeDaemonConsumableIntent> Consumables { get; } =
new List<AetheriaRuntimeDaemonConsumableIntent>();
public List<AetheriaRuntimeDaemonDockingIntent> Docking { get; } =
new List<AetheriaRuntimeDaemonDockingIntent>();
public List<AetheriaRuntimeDaemonWormholeIntent> Wormholes { get; } =
new List<AetheriaRuntimeDaemonWormholeIntent>();
public List<AetheriaRuntimeDaemonSensorPingIntent> SensorPings { get; } =
new List<AetheriaRuntimeDaemonSensorPingIntent>();
public bool HasAny =>
Movements.Count > 0 ||
SensorPings.Count > 0 ||
WeaponGroups.Count > 0 ||
Behaviors.Count > 0 ||
Consumables.Count > 0 ||
Docking.Count > 0 ||
Wormholes.Count > 0;
}
public sealed class AetheriaRuntimeDaemonMovementIntent
{
public string ActorEntityKey { get; set; } = "";
public double DirectionX { get; set; }
public double DirectionY { get; set; }
public double Magnitude { get; set; }
}
public sealed class AetheriaRuntimeDaemonWeaponGroupIntent
{
public string ActorEntityKey { get; set; } = "";
public int WeaponGroup { get; set; } = -1;
public bool Fire { get; set; }
public bool Active { get; set; }
}
public sealed class AetheriaRuntimeDaemonSensorPingIntent
{
public string ActorEntityKey { get; set; } = "";
}
public sealed class AetheriaRuntimeDaemonBehaviorIntent
{
public string ActorEntityKey { get; set; } = "";
public int EquipmentIndex { get; set; } = -1;
public int BehaviorIndex { get; set; } = -1;
public bool Active { get; set; }
public string TargetBodyKey { get; set; } = "";
public int TargetAsteroidIndex { get; set; } = -1;
}
public sealed class AetheriaRuntimeDaemonConsumableIntent
{
public string ActorEntityKey { get; set; } = "";
public string ItemKey { get; set; } = "";
}
public sealed class AetheriaRuntimeDaemonDockingIntent
{
public string ActorEntityKey { get; set; } = "";
public string TargetEntityKey { get; set; } = "";
public bool Dock { get; set; }
public bool Undock { get; set; }
}
public sealed class AetheriaRuntimeDaemonWormholeIntent
{
public string ActorEntityKey { get; set; } = "";
public int SourceZoneIndex { get; set; } = -1;
public int TargetZoneIndex { get; set; } = -1;
public double EntryWormholeX { get; set; }
public double EntryWormholeZ { get; set; }
public double ExitWormholeX { get; set; }
public double ExitWormholeZ { get; set; }
public string CommandId { get; set; } = "";
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: f16b722f9a3746340af404d50b13cc66

View file

@ -1,366 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public readonly struct AetheriaRuntimeDaemonItemStatValue
{
public AetheriaRuntimeDaemonItemStatValue(
string itemKey,
string behaviorKind,
int behaviorGroup,
int fieldKey,
string label,
double value)
{
ItemKey = itemKey ?? "";
BehaviorKind = behaviorKind ?? "";
BehaviorGroup = behaviorGroup;
FieldKey = fieldKey;
Label = label ?? "";
Value = value;
}
public string ItemKey { get; }
public string BehaviorKind { get; }
public int BehaviorGroup { get; }
public int FieldKey { get; }
public string Label { get; }
public double Value { get; }
public string ValueRef => AetheriaRuntimeDaemonItemStatQueries.ItemStatRef(
ItemKey,
BehaviorKind,
BehaviorGroup,
FieldKey);
}
public static class AetheriaRuntimeDaemonItemStatQueries
{
public const string StateRefPrefix = "aetheria.state/items";
public static string ItemStatRef(
string itemKey,
string behaviorKind,
int behaviorGroup,
int fieldKey)
{
return $"{StateRefPrefix}/{Token(itemKey)}/behaviors/{Token(behaviorKind)}/{behaviorGroup}/stats/{fieldKey}";
}
public static bool TryReadItemStatRef(
string stateRef,
out string itemKey,
out string behaviorKind,
out int behaviorGroup,
out int fieldKey)
{
itemKey = "";
behaviorKind = "";
behaviorGroup = -1;
fieldKey = -1;
var parts = (stateRef ?? "").Split('/');
if (parts.Length != 8 ||
!string.Equals(parts[0], "aetheria.state", StringComparison.Ordinal) ||
!string.Equals(parts[1], "items", StringComparison.Ordinal) ||
!string.Equals(parts[3], "behaviors", StringComparison.Ordinal) ||
!string.Equals(parts[6], "stats", StringComparison.Ordinal) ||
!int.TryParse(parts[5], NumberStyles.Integer, CultureInfo.InvariantCulture, out behaviorGroup) ||
!int.TryParse(parts[7], NumberStyles.Integer, CultureInfo.InvariantCulture, out fieldKey))
{
return false;
}
itemKey = DecodeToken(parts[2]);
behaviorKind = DecodeToken(parts[4]);
return !string.IsNullOrWhiteSpace(itemKey) && !string.IsNullOrWhiteSpace(behaviorKind);
}
public static AetheriaRuntimeStatRecipePreviewState ConditionsFor(
AetheriaRuntimeLoadoutItemCommit? item,
double heat = 0,
double charge = 1,
double ammo = 1,
double range = 1,
double integrity = 1,
double pilotSkill = 0,
double environment = 0)
{
return new AetheriaRuntimeStatRecipePreviewState(
quality: Clamp01(item?.Quality ?? 1),
durability: Clamp01(item?.Durability ?? 1),
heat: Clamp01(heat),
charge: Clamp01(charge),
ammo: Clamp01(ammo),
range: Clamp01(range),
integrity: Clamp01(integrity),
pilotSkill: Clamp01(pilotSkill),
environment: Clamp01(environment));
}
public static double EvaluatePerformanceStat(
AetheriaRuntimeBehaviorValue? value,
AetheriaRuntimeLoadoutItemCommit? item,
double heat = 0)
{
return EvaluatePerformanceStat(
ReadPerformanceStat(value),
ConditionsFor(item, heat));
}
public static double EvaluatePerformanceStat(
AetheriaRuntimeBehaviorValue? value,
AetheriaRuntimeStatRecipePreviewState conditions)
{
return EvaluatePerformanceStat(ReadPerformanceStat(value), conditions);
}
public static double EvaluateConsumablePerformanceStat(
AetheriaRuntimeBehaviorValue? value,
double quality,
double effectiveness)
{
var stat = ReadPerformanceStat(value);
var weightedQuality = stat.QualityExponent == 0
? 1
: Math.Pow(Clamp01(quality), stat.QualityExponent);
var result = Lerp(stat.Min, stat.Max, Clamp01(effectiveness) * weightedQuality);
return double.IsNaN(result) ? stat.Min : result;
}
public static double SampleCurve(IReadOnlyList<AetheriaRuntimeCurveKey>? keys, double value)
{
if (keys == null || keys.Count == 0)
return Clamp01(value);
var ordered = keys.OrderBy(key => key.Time).ToArray();
if (value <= ordered[0].Time)
return Clamp01(ordered[0].Value);
for (var index = 1; index < ordered.Length; index++)
{
var next = ordered[index];
var previous = ordered[index - 1];
if (value > next.Time)
continue;
var span = next.Time - previous.Time;
var t = span <= double.Epsilon ? 1 : Clamp01((value - previous.Time) / span);
var t2 = t * t;
var t3 = t2 * t;
var h00 = 2 * t3 - 3 * t2 + 1;
var h10 = t3 - 2 * t2 + t;
var h01 = -2 * t3 + 3 * t2;
var h11 = t3 - t2;
return Clamp01(
h00 * previous.Value +
h10 * span * previous.OutTangent +
h01 * next.Value +
h11 * span * next.InTangent);
}
return Clamp01(ordered[ordered.Length - 1].Value);
}
public static double EvaluatePerformanceStat(
AetheriaRuntimePerformanceStat? stat,
AetheriaRuntimeStatRecipePreviewState conditions)
{
if (stat == null)
return 0;
conditions ??= AetheriaRuntimeStatRecipePreviewState.Default;
if (stat.Recipe != null)
return EvaluateRecipe(stat.Recipe, conditions);
var heat = stat.HeatExponentMultiplier == 0
? 1
: Math.Pow(Clamp01(conditions.Heat), stat.HeatExponentMultiplier);
var durability = stat.DurabilityExponentMultiplier == 0
? 1
: Math.Pow(Clamp01(conditions.Durability), stat.DurabilityExponentMultiplier);
var quality = stat.QualityExponent == 0
? 1
: Math.Pow(Clamp01(conditions.Quality), stat.QualityExponent);
return Lerp(stat.Min, stat.Max, durability * quality * heat);
}
public static IReadOnlyList<AetheriaRuntimeDaemonItemStatValue> QueryPerformanceStats(
AetheriaRuntimeCatalogItem? catalogItem,
AetheriaRuntimeLoadoutItemCommit? item,
double heat = 0)
{
if (catalogItem?.BehaviorPayloads == null || catalogItem.BehaviorPayloads.Count == 0)
return Array.Empty<AetheriaRuntimeDaemonItemStatValue>();
var conditions = ConditionsFor(item, heat);
var stats = new List<AetheriaRuntimeDaemonItemStatValue>();
foreach (var behavior in catalogItem.BehaviorPayloads)
{
var metadata = AetheriaRuntimeBehaviorMetadataCatalog.Get(behavior.Kind);
foreach (var field in behavior.Fields ?? Array.Empty<AetheriaRuntimeBehaviorField>())
{
var fieldMetadata = metadata?.DisplayFields.FirstOrDefault(candidate => candidate.Key == field.Key);
if (fieldMetadata?.ValueKind != AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat)
continue;
stats.Add(new AetheriaRuntimeDaemonItemStatValue(
item?.ItemKey ?? catalogItem.ItemKey,
behavior.Kind,
behavior.Group,
field.Key,
fieldMetadata.Name,
EvaluatePerformanceStat(ReadPerformanceStat(field.Value), conditions)));
}
}
return stats.Count == 0 ? Array.Empty<AetheriaRuntimeDaemonItemStatValue>() : stats;
}
public static AetheriaRuntimeLoadoutItemCommit ItemCommit(
string itemKey,
double quality,
double durability,
int quantity = 1,
bool enabled = true,
bool overrideShutdown = false,
double temperature = 0)
{
return new AetheriaRuntimeLoadoutItemCommit
{
ItemKey = itemKey ?? "",
Quality = quality,
Durability = durability,
Quantity = quantity,
Enabled = enabled,
OverrideShutdown = overrideShutdown,
Temperature = temperature
};
}
private static AetheriaRuntimePerformanceStat ReadPerformanceStat(AetheriaRuntimeBehaviorValue? value)
{
return new AetheriaRuntimePerformanceStat(
ChildNumber(value, 0),
ChildNumber(value, 1),
ChildNumber(value, 2),
ChildNumber(value, 3),
ChildNumber(value, 4),
ReadStatRecipe(ChildValue(value, 5)));
}
private static AetheriaRuntimeStatRecipe? ReadStatRecipe(AetheriaRuntimeBehaviorValue? value)
{
if (value == null || value.Children.Count == 0)
return null;
var modifiers = ChildValue(value, 1)?.Children
.Select(ReadStatRecipeModifier)
.Where(modifier => modifier != null)
.Cast<AetheriaRuntimeStatRecipeModifier>()
.ToArray() ?? Array.Empty<AetheriaRuntimeStatRecipeModifier>();
return new AetheriaRuntimeStatRecipe(ChildNumber(value, 0), modifiers);
}
private static AetheriaRuntimeStatRecipeModifier? ReadStatRecipeModifier(AetheriaRuntimeBehaviorValue? value)
{
if (value == null)
return null;
return new AetheriaRuntimeStatRecipeModifier(
ChildString(value, 0),
ChildString(value, 1),
ChildNumber(value, 2),
ReadCurveKeys(ChildValue(value, 3)),
value.Children.Count <= 4 || ChildValue(value, 4)?.BoolValue == true);
}
private static IReadOnlyList<AetheriaRuntimeCurveKey> ReadCurveKeys(AetheriaRuntimeBehaviorValue? value)
{
if (value?.Children == null || value.Children.Count == 0)
return Array.Empty<AetheriaRuntimeCurveKey>();
return value.Children
.Where(key => key.Children.Count >= 4)
.Select(key => new AetheriaRuntimeCurveKey(
ChildNumber(key, 0),
ChildNumber(key, 1),
ChildNumber(key, 2),
ChildNumber(key, 3)))
.ToArray();
}
private static double EvaluateRecipe(
AetheriaRuntimeStatRecipe recipe,
AetheriaRuntimeStatRecipePreviewState conditions)
{
var value = recipe.BaseValue;
foreach (var modifier in recipe.Modifiers ?? Array.Empty<AetheriaRuntimeStatRecipeModifier>())
{
if (modifier == null || !modifier.Enabled || string.IsNullOrWhiteSpace(modifier.Condition))
continue;
var sample = SampleCurve(modifier.CurveKeys, conditions.GetConditionValue(modifier.Condition));
switch (modifier.Operation ?? "")
{
case AetheriaRuntimeStatRecipeOperations.Multiply:
value *= 1 + ((modifier.Amount - 1) * sample);
break;
case AetheriaRuntimeStatRecipeOperations.Override:
value = Lerp(value, modifier.Amount, sample);
break;
default:
value += modifier.Amount * sample;
break;
}
}
return value;
}
private static double ChildNumber(AetheriaRuntimeBehaviorValue? value, int index)
{
return ChildValue(value, index)?.NumberValue ?? 0;
}
private static string ChildString(AetheriaRuntimeBehaviorValue? value, int index)
{
return ChildValue(value, index)?.StringValue ?? "";
}
private static AetheriaRuntimeBehaviorValue? ChildValue(AetheriaRuntimeBehaviorValue? value, int index)
{
return value != null && value.Children.Count > index ? value.Children[index] : null;
}
private static double Clamp01(double value)
{
if (value < 0)
return 0;
return value > 1 ? 1 : value;
}
private static double Lerp(double from, double to, double t)
{
return from + ((to - from) * t);
}
private static string Token(string value)
{
return string.IsNullOrWhiteSpace(value)
? "_"
: value.Trim().Replace("/", "%2F");
}
private static string DecodeToken(string value)
{
return (value ?? "").Replace("%2F", "/", StringComparison.Ordinal);
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 740ed3acb051bb549b5f82a57eb31e3a

View file

@ -1,83 +0,0 @@
using System;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public sealed class AetheriaRuntimeDaemonObservationResult
{
public AetheriaRuntimeDaemonObservationResult(
bool observed,
bool frameChanged,
bool soaViewChanged,
long frameId,
long soaGeneration,
bool isAuthoritative)
{
Observed = observed;
FrameChanged = frameChanged;
SoaViewChanged = soaViewChanged;
FrameId = frameId;
SoaGeneration = soaGeneration;
IsAuthoritative = isAuthoritative;
}
public bool Observed { get; }
public bool FrameChanged { get; }
public bool SoaViewChanged { get; }
public bool Changed => FrameChanged || SoaViewChanged;
public long FrameId { get; }
public long SoaGeneration { get; }
public bool IsAuthoritative { get; }
}
public sealed class AetheriaRuntimeDaemonObservationCursor
{
public long LastFrameId { get; private set; } = -1;
public long LastSoaGeneration { get; private set; } = -1;
public string LastDaemonId { get; private set; } = "";
public string LastSessionId { get; private set; } = "";
public AetheriaRuntimeDaemonObservationResult Observe(AetheriaRuntimeDaemonRenderView? observed)
{
if (observed == null)
{
return new AetheriaRuntimeDaemonObservationResult(
false,
false,
false,
LastFrameId,
LastSoaGeneration,
false);
}
var frame = observed.Frame;
var soaGeneration = observed.SoaView?.Generation ?? -1;
var frameChanged = frame.FrameId != LastFrameId ||
!string.Equals(frame.DaemonId, LastDaemonId, StringComparison.Ordinal) ||
!string.Equals(frame.SessionId, LastSessionId, StringComparison.Ordinal);
var soaViewChanged = soaGeneration != LastSoaGeneration;
LastFrameId = frame.FrameId;
LastSoaGeneration = soaGeneration;
LastDaemonId = frame.DaemonId ?? "";
LastSessionId = frame.SessionId ?? "";
return new AetheriaRuntimeDaemonObservationResult(
true,
frameChanged,
soaViewChanged,
frame.FrameId,
soaGeneration,
frame.IsAuthoritative);
}
public void Reset()
{
LastFrameId = -1;
LastSoaGeneration = -1;
LastDaemonId = "";
LastSessionId = "";
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: bd46bb16aafbc684fa18d590767c9f9f

View file

@ -1,600 +0,0 @@
using System;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
internal sealed class AetheriaRuntimeDaemonOperationClient
{
public const string DefaultClientId = "aetheria-daemon-client";
private readonly Func<AetheriaRuntimeDaemonCommandDocument, AetheriaRuntimeDaemonCommandEnvelope>? _submit;
internal AetheriaRuntimeDaemonOperationClient(
string stateFilePath,
string clientId = DefaultClientId,
string sessionId = "local")
{
if (string.IsNullOrWhiteSpace(stateFilePath))
throw new ArgumentException("State file path must be non-empty.", nameof(stateFilePath));
StateFilePath = stateFilePath;
ClientId = string.IsNullOrWhiteSpace(clientId) ? DefaultClientId : clientId;
SessionId = string.IsNullOrWhiteSpace(sessionId) ? "local" : sessionId;
}
internal AetheriaRuntimeDaemonOperationClient(
string stateFilePath,
string clientId,
string sessionId,
Func<AetheriaRuntimeDaemonCommandDocument, AetheriaRuntimeDaemonCommandEnvelope> submit)
: this(stateFilePath, clientId, sessionId)
{
_submit = submit ?? throw new ArgumentNullException(nameof(submit));
}
public string StateFilePath { get; }
public string ClientId { get; }
public string SessionId { get; }
private AetheriaRuntimeDaemonCommandEnvelope Send(AetheriaRuntimeDaemonCommandDocument command)
{
if (_submit != null)
return _submit(command);
if (!TrySend(command, out var envelope, out var error))
{
throw new InvalidOperationException(
$"Failed to submit Aetheria daemon operation {command.Kind}: {error}");
}
return envelope!;
}
private bool TrySend(
AetheriaRuntimeDaemonCommandDocument command,
out AetheriaRuntimeDaemonCommandEnvelope? envelope,
out string error)
{
envelope = null;
error = "";
try
{
using var client = AetheriaClient
.OpenAsync(StateFilePath, ClientId, startServer: false, pullOnOpen: true)
.GetAwaiter()
.GetResult();
envelope = client
.SubmitDaemonCommandDocument(command);
return true;
}
catch (Exception ex)
{
error = ex.ToString();
return false;
}
}
private AetheriaRuntimeDaemonCommandDocument Create(
AetheriaRuntimeDaemonCommandKinds kind,
AetheriaRuntimeDaemonFrameDocument? frame)
{
return AetheriaRuntimeDaemonCommandDocument.Create(
kind,
ClientId,
frame?.SessionId ?? SessionId,
frame?.FrameId ?? -1,
frame?.Run.CurrentEntityKey ?? "");
}
internal static AetheriaRuntimeDaemonCommandEnvelope ToEnvelope(AetheriaRuntimeDaemonCommandDocument command)
{
return new AetheriaRuntimeDaemonCommandEnvelope(
command.Schema ?? "",
command.CommandId ?? "",
command.ClientId ?? "",
command.IssuedAtUtc ?? "",
command.SessionId ?? "",
command.ObservedFrameId,
command.Kind,
command.ActorEntityKey ?? "",
"");
}
internal AetheriaRuntimeDaemonCommandEnvelope SetTarget(
AetheriaRuntimeDaemonFrameDocument? frame,
string targetEntityKey)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetTarget, frame);
command.TargetEntityKey = targetEntityKey ?? "";
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope ClearTarget(AetheriaRuntimeDaemonFrameDocument? frame)
{
return Send(Create(AetheriaRuntimeDaemonCommandKinds.ClearTarget, frame));
}
internal AetheriaRuntimeDaemonCommandEnvelope TargetNearest(AetheriaRuntimeDaemonFrameDocument? frame)
{
return Send(Create(AetheriaRuntimeDaemonCommandKinds.TargetNearest, frame));
}
internal AetheriaRuntimeDaemonCommandEnvelope TargetNext(AetheriaRuntimeDaemonFrameDocument? frame)
{
return Send(Create(AetheriaRuntimeDaemonCommandKinds.TargetNext, frame));
}
internal AetheriaRuntimeDaemonCommandEnvelope TargetPrevious(AetheriaRuntimeDaemonFrameDocument? frame)
{
return Send(Create(AetheriaRuntimeDaemonCommandKinds.TargetPrevious, frame));
}
internal AetheriaRuntimeDaemonCommandEnvelope TargetReticle(
AetheriaRuntimeDaemonFrameDocument? frame,
double directionX,
double directionY,
double directionZ)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.TargetReticle, frame);
command.DirectionX = directionX;
command.DirectionY = directionY;
command.PositionZ = directionZ;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetMoveVector(
AetheriaRuntimeDaemonFrameDocument? frame,
double directionX,
double directionY,
double scalarValue = 1.0)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetMoveVector, frame);
command.DirectionX = directionX;
command.DirectionY = directionY;
command.ScalarValue = scalarValue;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetSimulationRate(
AetheriaRuntimeDaemonFrameDocument? frame,
double rate)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetSimulationRate, frame);
command.ScalarValue = rate;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope AdvanceSimulationStep(
AetheriaRuntimeDaemonFrameDocument? frame)
{
return Send(Create(AetheriaRuntimeDaemonCommandKinds.AdvanceSimulationStep, frame));
}
internal AetheriaRuntimeDaemonCommandEnvelope SetLookDirection(
AetheriaRuntimeDaemonFrameDocument? frame,
double directionX,
double directionY,
double directionZ)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetLookDirection, frame);
command.DirectionX = directionX;
command.DirectionY = directionY;
command.PositionZ = directionZ;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetTractorPower(
AetheriaRuntimeDaemonFrameDocument? frame,
double power)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetTractorPower, frame);
command.ScalarValue = power;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope FireWeaponGroup(
AetheriaRuntimeDaemonFrameDocument? frame,
int weaponGroup)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup, frame);
command.WeaponGroup = weaponGroup;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetWeaponGroupActive(
AetheriaRuntimeDaemonFrameDocument? frame,
int weaponGroup,
bool active)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetWeaponGroupActive, frame);
command.WeaponGroup = weaponGroup;
command.ScalarValue = active ? 1.0 : 0.0;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetWeaponGroupMembership(
AetheriaRuntimeDaemonFrameDocument? frame,
string targetEntityKey,
int equipmentIndex,
int weaponGroup,
bool assigned)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetWeaponGroupMembership, frame);
command.TargetEntityKey = targetEntityKey ?? "";
command.EquipmentIndex = equipmentIndex;
command.WeaponGroup = weaponGroup;
command.ScalarValue = assigned ? 1.0 : 0.0;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetBehaviorActive(
AetheriaRuntimeDaemonFrameDocument? frame,
int equipmentIndex,
int behaviorIndex,
bool active)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetBehaviorActive, frame);
command.EquipmentIndex = equipmentIndex;
command.BehaviorIndex = behaviorIndex;
command.ScalarValue = active ? 1.0 : 0.0;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope ActivateConsumable(
AetheriaRuntimeDaemonFrameDocument? frame,
string itemKey)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.ActivateConsumable, frame);
command.TextValue = itemKey ?? "";
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SensorPing(AetheriaRuntimeDaemonFrameDocument? frame)
{
return Send(Create(AetheriaRuntimeDaemonCommandKinds.SensorPing, frame));
}
internal AetheriaRuntimeDaemonCommandEnvelope SetHeatsinksEnabled(
AetheriaRuntimeDaemonFrameDocument? frame,
bool enabled)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetHeatsinksEnabled, frame);
command.ScalarValue = enabled ? 1.0 : 0.0;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetOverrideShutdown(
AetheriaRuntimeDaemonFrameDocument? frame,
bool enabled)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetOverrideShutdown, frame);
command.ScalarValue = enabled ? 1.0 : 0.0;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetOverrideShutdown(
AetheriaRuntimeDaemonFrameDocument? frame,
string targetEntityKey,
bool enabled)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetOverrideShutdown, frame);
command.TargetEntityKey = targetEntityKey ?? "";
command.ScalarValue = enabled ? 1.0 : 0.0;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetItemEnabled(
AetheriaRuntimeDaemonFrameDocument? frame,
int equipmentIndex,
bool enabled)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetItemEnabled, frame);
command.EquipmentIndex = equipmentIndex;
command.ScalarValue = enabled ? 1.0 : 0.0;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope ToggleShieldEnabled(AetheriaRuntimeDaemonFrameDocument? frame)
{
return Send(Create(AetheriaRuntimeDaemonCommandKinds.ToggleShieldEnabled, frame));
}
internal AetheriaRuntimeDaemonCommandEnvelope SetItemOverrideShutdown(
AetheriaRuntimeDaemonFrameDocument? frame,
string targetEntityKey,
int equipmentIndex,
bool enabled)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetItemOverrideShutdown, frame);
command.TargetEntityKey = targetEntityKey ?? "";
command.EquipmentIndex = equipmentIndex;
command.ScalarValue = enabled ? 1.0 : 0.0;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetThermotoggleTargetTemperature(
AetheriaRuntimeDaemonFrameDocument? frame,
string targetEntityKey,
int equipmentIndex,
int behaviorIndex,
double targetTemperature)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetThermotoggleTargetTemperature, frame);
command.TargetEntityKey = targetEntityKey ?? "";
command.EquipmentIndex = equipmentIndex;
command.BehaviorIndex = behaviorIndex;
command.ScalarValue = targetTemperature;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetShutdownPerformance(
AetheriaRuntimeDaemonFrameDocument? frame,
string targetEntityKey,
double shutdownPerformance)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetShutdownPerformance, frame);
command.TargetEntityKey = targetEntityKey ?? "";
command.ScalarValue = shutdownPerformance;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope ToggleHullConductivity(
AetheriaRuntimeDaemonFrameDocument? frame,
string targetEntityKey,
int x,
int y,
int axis)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.ToggleHullConductivity, frame);
command.TargetEntityKey = targetEntityKey ?? "";
command.PositionX = x;
command.PositionY = y;
command.ScalarValue = axis;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetEntityName(
AetheriaRuntimeDaemonFrameDocument? frame,
string targetEntityKey,
string name)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetEntityName, frame);
command.TargetEntityKey = targetEntityKey ?? "";
command.TextValue = name ?? "";
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope Dock(AetheriaRuntimeDaemonFrameDocument? frame, string targetEntityKey)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.Dock, frame);
command.TargetEntityKey = targetEntityKey ?? "";
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope DockNearest(AetheriaRuntimeDaemonFrameDocument? frame)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.DockNearest, frame);
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope Undock(AetheriaRuntimeDaemonFrameDocument? frame)
{
return Send(Create(AetheriaRuntimeDaemonCommandKinds.Undock, frame));
}
internal AetheriaRuntimeDaemonCommandEnvelope Interact(AetheriaRuntimeDaemonFrameDocument? frame)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.Interact, frame);
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope SetDockedCurrentShip(
AetheriaRuntimeDaemonFrameDocument? frame,
string targetEntityKey)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.SetDockedCurrentShip, frame);
command.TargetEntityKey = targetEntityKey ?? "";
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope EnterWormhole(
AetheriaRuntimeDaemonFrameDocument? frame,
int targetZoneIndex,
double positionX,
double positionY)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.EnterWormhole, frame);
command.TargetZoneIndex = targetZoneIndex;
command.PositionX = positionX;
command.PositionY = positionY;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope TowToStation(
AetheriaRuntimeDaemonFrameDocument? frame,
string stationEntityKey)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.TowToStation, frame);
command.TargetEntityKey = stationEntityKey ?? "";
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope TransferCargoItem(
AetheriaRuntimeDaemonFrameDocument? frame,
string originEntityKey,
int originCargoIndex,
string destinationEntityKey,
int destinationCargoIndex,
string itemKey,
int quantity,
int sourceX,
int sourceY,
int destinationX,
int destinationY,
bool hasDestinationPosition)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.TransferCargoItem, frame);
command.TargetEntityKey = destinationEntityKey ?? "";
command.EquipmentIndex = destinationCargoIndex;
command.PositionX = destinationX;
command.PositionY = destinationY;
command.ScalarValue = quantity;
command.TextValue = itemKey ?? "";
command.CargoTransfer.OriginEntityKey = originEntityKey ?? "";
command.CargoTransfer.OriginCargoIndex = originCargoIndex;
command.CargoTransfer.DestinationEntityKey = destinationEntityKey ?? "";
command.CargoTransfer.DestinationCargoIndex = destinationCargoIndex;
command.CargoTransfer.SourceX = sourceX;
command.CargoTransfer.SourceY = sourceY;
command.CargoTransfer.DestinationX = destinationX;
command.CargoTransfer.DestinationY = destinationY;
command.CargoTransfer.HasDestinationPosition = hasDestinationPosition;
command.CargoTransfer.Quantity = quantity;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope TradePurchase(
AetheriaRuntimeDaemonFrameDocument? frame,
string itemKey,
int quantity,
int stationCargoIndex,
int targetCargoIndex,
int sourceX,
int sourceY)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.TradePurchase, frame);
command.EquipmentIndex = targetCargoIndex;
command.PositionX = sourceX;
command.PositionY = sourceY;
command.TextValue = itemKey ?? "";
command.TradePurchase.ItemKey = itemKey ?? "";
command.TradePurchase.Quantity = quantity;
command.TradePurchase.StationCargoIndex = stationCargoIndex;
command.TradePurchase.TargetCargoIndex = targetCargoIndex;
command.TradePurchase.SourceX = sourceX;
command.TradePurchase.SourceY = sourceY;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope TradeSale(
AetheriaRuntimeDaemonFrameDocument? frame,
string itemKey,
int quantity,
int sourceCargoIndex,
int sourceX,
int sourceY)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.TradeSale, frame);
command.TextValue = itemKey ?? "";
command.EquipmentIndex = sourceCargoIndex;
command.PositionX = sourceX;
command.PositionY = sourceY;
command.ScalarValue = quantity;
command.TradeSale.ItemKey = itemKey ?? "";
command.TradeSale.Quantity = quantity;
command.TradeSale.SourceCargoIndex = sourceCargoIndex;
command.TradeSale.SourceX = sourceX;
command.TradeSale.SourceY = sourceY;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope RestoreLoadout(
AetheriaRuntimeDaemonFrameDocument? frame,
string dockedEntityKey,
string templateName,
int price)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.RestoreLoadout, frame);
command.TargetEntityKey = dockedEntityKey ?? "";
command.TextValue = templateName ?? "";
command.ScalarValue = price;
command.LoadoutRestore.DockedEntityKey = dockedEntityKey ?? "";
command.LoadoutRestore.TemplateName = templateName ?? "";
command.LoadoutRestore.Price = price;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope EquipItem(
AetheriaRuntimeDaemonFrameDocument? frame,
string sourceKind,
string originEntityKey,
int originIndex,
string destinationEntityKey,
string itemKey,
int sourceX,
int sourceY,
int destinationX,
int destinationY,
bool hasDestinationPosition)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.EquipItem, frame);
command.TargetEntityKey = destinationEntityKey ?? "";
command.PositionX = destinationX;
command.PositionY = destinationY;
command.TextValue = itemKey ?? "";
command.EquipmentTransfer.SourceKind = sourceKind ?? "";
command.EquipmentTransfer.OriginEntityKey = originEntityKey ?? "";
command.EquipmentTransfer.OriginIndex = originIndex;
command.EquipmentTransfer.DestinationEntityKey = destinationEntityKey ?? "";
command.EquipmentTransfer.SourceX = sourceX;
command.EquipmentTransfer.SourceY = sourceY;
command.EquipmentTransfer.DestinationX = destinationX;
command.EquipmentTransfer.DestinationY = destinationY;
command.EquipmentTransfer.HasDestinationPosition = hasDestinationPosition;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope StoreItem(
AetheriaRuntimeDaemonFrameDocument? frame,
string originEntityKey,
int sourceEquipmentIndex,
string destinationEntityKey,
int destinationCargoIndex,
string itemKey,
int destinationX,
int destinationY,
bool hasDestinationPosition,
string sourceKind = AetheriaRuntimeRefitSourceKinds.Equipment)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.StoreItem, frame);
command.TargetEntityKey = destinationEntityKey ?? "";
command.EquipmentIndex = sourceEquipmentIndex;
command.PositionX = destinationX;
command.PositionY = destinationY;
command.TextValue = itemKey ?? "";
command.StoreItem.OriginEntityKey = originEntityKey ?? "";
command.StoreItem.SourceEquipmentIndex = sourceEquipmentIndex;
command.StoreItem.DestinationEntityKey = destinationEntityKey ?? "";
command.StoreItem.DestinationCargoIndex = destinationCargoIndex;
command.StoreItem.DestinationX = destinationX;
command.StoreItem.DestinationY = destinationY;
command.StoreItem.HasDestinationPosition = hasDestinationPosition;
command.StoreItem.SourceKind = sourceKind ?? AetheriaRuntimeRefitSourceKinds.Equipment;
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope IssueAgentTask(
AetheriaRuntimeDaemonFrameDocument? frame,
AetheriaRuntimeAgentTaskCommand task)
{
task ??= new AetheriaRuntimeAgentTaskCommand();
var command = Create(AetheriaRuntimeDaemonCommandKinds.IssueAgentTask, frame);
command.AgentTask = task;
command.SubjectKey = string.IsNullOrWhiteSpace(task.CorporationKey)
? "aetheria.tasks"
: $"aetheria.tasks.{task.CorporationKey}";
return Send(command);
}
internal AetheriaRuntimeDaemonCommandEnvelope CancelAgentTask(
AetheriaRuntimeDaemonFrameDocument? frame,
string taskId)
{
var command = Create(AetheriaRuntimeDaemonCommandKinds.CancelAgentTask, frame);
command.AgentTask.TaskId = taskId ?? "";
command.SubjectKey = "aetheria.tasks";
return Send(command);
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: ce9502e0eb3bd5749b54057cb266a717

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: e5491470c689e0b40a098462d4ab1fe5

View file

@ -1,645 +0,0 @@
using System;
using System.Linq;
using GameCult.Eve.Surface;
using GameCult.Mesh;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public sealed class AetheriaRuntimeDaemonOperationsClient
{
private readonly Func<Func<AetheriaRuntimeDaemonOperationClient, AetheriaRuntimeDaemonFrameDocument?, AetheriaRuntimeDaemonCommandEnvelope>, AetheriaRuntimeDaemonCommandEnvelope> _submit;
internal AetheriaRuntimeDaemonOperationsClient(
Func<Func<AetheriaRuntimeDaemonOperationClient, AetheriaRuntimeDaemonFrameDocument?, AetheriaRuntimeDaemonCommandEnvelope>, AetheriaRuntimeDaemonCommandEnvelope> submit)
{
_submit = submit ?? throw new ArgumentNullException(nameof(submit));
}
public static bool TryCreateSurfaceCommandDocument(
EveSurfaceCommandRequest request,
AetheriaRuntimeDaemonFrameDocument? frame,
string stateFilePath,
string clientId,
string sessionId,
out AetheriaRuntimeDaemonCommandDocument? command)
{
command = null;
if (request == null)
return false;
AetheriaRuntimeDaemonCommandDocument? translated = null;
var operations = new AetheriaRuntimeDaemonOperationsClient(submit =>
{
var client = new AetheriaRuntimeDaemonOperationClient(
string.IsNullOrWhiteSpace(stateFilePath) ? "." : stateFilePath,
clientId,
sessionId,
document =>
{
translated = document;
return AetheriaRuntimeDaemonOperationClient.ToEnvelope(document);
});
return submit(client, frame);
});
var accepted = operations.TrySubmitSurfaceCommand(request, out _);
var actorEntityKey = ReadPayloadString(
request,
"entityId",
ReadPayloadString(request, "sourceEntityId", ""));
if (translated != null && !string.IsNullOrWhiteSpace(actorEntityKey))
translated.ActorEntityKey = actorEntityKey;
if (string.Equals(Environment.GetEnvironmentVariable("AETHERIA_TRACE_EVE_SNAPSHOTS"), "1", StringComparison.Ordinal))
Console.WriteLine(
$"Eve command translated command={request.Command} actor={translated?.ActorEntityKey ?? "missing"} " +
$"direction={translated?.DirectionX},{translated?.DirectionY} scalar={translated?.ScalarValue}");
command = translated;
return accepted && command != null;
}
public AetheriaRuntimeDaemonCommandEnvelope SetTarget(string targetEntityKey)
{
return Submit((client, frame) => client.SetTarget(frame, targetEntityKey));
}
public CultMeshOperationReceipt ClearTarget()
{
return Send((client, frame) => client.ClearTarget(frame));
}
public CultMeshOperationReceipt TargetNearest()
{
return Send((client, frame) => client.TargetNearest(frame));
}
public CultMeshOperationReceipt TargetNext()
{
return Send((client, frame) => client.TargetNext(frame));
}
public CultMeshOperationReceipt TargetPrevious()
{
return Send((client, frame) => client.TargetPrevious(frame));
}
public CultMeshOperationReceipt TargetReticle(double directionX, double directionY, double directionZ)
{
return Send((client, frame) => client.TargetReticle(frame, directionX, directionY, directionZ));
}
public AetheriaRuntimeDaemonCommandEnvelope SetMoveVector(
double directionX,
double directionY,
double scalarValue = 1.0)
{
return Submit((client, frame) => client.SetMoveVector(frame, directionX, directionY, scalarValue));
}
public AetheriaRuntimeDaemonCommandEnvelope IssueAgentTask(AetheriaRuntimeAgentTaskCommand task)
{
return Submit((client, frame) => client.IssueAgentTask(frame, task));
}
public AetheriaRuntimeDaemonCommandEnvelope CancelAgentTask(string taskId)
{
return Submit((client, frame) => client.CancelAgentTask(frame, taskId));
}
public CultMeshOperationReceipt SetLookDirection(double directionX, double directionY, double directionZ)
{
return Send((client, frame) => client.SetLookDirection(frame, directionX, directionY, directionZ));
}
public CultMeshOperationReceipt SetTractorPower(double power)
{
return Send((client, frame) => client.SetTractorPower(frame, power));
}
public CultMeshOperationReceipt FireWeaponGroup(int weaponGroup)
{
return Send((client, frame) => client.FireWeaponGroup(frame, weaponGroup));
}
public CultMeshOperationReceipt SetWeaponGroupActive(int weaponGroup, bool active)
{
return Send((client, frame) => client.SetWeaponGroupActive(frame, weaponGroup, active));
}
public CultMeshOperationReceipt SetWeaponGroupMembership(
string targetEntityKey,
int equipmentIndex,
int weaponGroup,
bool assigned)
{
return Send((client, frame) => client.SetWeaponGroupMembership(
frame,
targetEntityKey,
equipmentIndex,
weaponGroup,
assigned));
}
public CultMeshOperationReceipt SetBehaviorActive(int equipmentIndex, int behaviorIndex, bool active)
{
return Send((client, frame) => client.SetBehaviorActive(frame, equipmentIndex, behaviorIndex, active));
}
public CultMeshOperationReceipt ActivateConsumable(string itemKey)
{
return Send((client, frame) => client.ActivateConsumable(frame, itemKey));
}
public CultMeshOperationReceipt SensorPing()
{
return Send((client, frame) => client.SensorPing(frame));
}
public CultMeshOperationReceipt SetHeatsinksEnabled(bool enabled)
{
return Send((client, frame) => client.SetHeatsinksEnabled(frame, enabled));
}
public CultMeshOperationReceipt SetOverrideShutdown(bool enabled)
{
return Send((client, frame) => client.SetOverrideShutdown(frame, enabled));
}
public CultMeshOperationReceipt SetEntityOverrideShutdown(string targetEntityKey, bool enabled)
{
return Send((client, frame) => client.SetOverrideShutdown(frame, targetEntityKey, enabled));
}
public CultMeshOperationReceipt SetItemEnabled(int equipmentIndex, bool enabled)
{
return Send((client, frame) => client.SetItemEnabled(frame, equipmentIndex, enabled));
}
public CultMeshOperationReceipt ToggleShieldEnabled()
{
return Send((client, frame) => client.ToggleShieldEnabled(frame));
}
public CultMeshOperationReceipt SetItemOverrideShutdown(
string targetEntityKey,
int equipmentIndex,
bool enabled)
{
return Send((client, frame) => client.SetItemOverrideShutdown(
frame,
targetEntityKey,
equipmentIndex,
enabled));
}
public CultMeshOperationReceipt SetThermotoggleTargetTemperature(
string targetEntityKey,
int equipmentIndex,
int behaviorIndex,
double targetTemperature)
{
return Send((client, frame) => client.SetThermotoggleTargetTemperature(
frame,
targetEntityKey,
equipmentIndex,
behaviorIndex,
targetTemperature));
}
public CultMeshOperationReceipt SetShutdownPerformance(
string targetEntityKey,
double shutdownPerformance)
{
return Send((client, frame) => client.SetShutdownPerformance(
frame,
targetEntityKey,
shutdownPerformance));
}
public CultMeshOperationReceipt ToggleHullConductivity(
string targetEntityKey,
int x,
int y,
int axis)
{
return Send((client, frame) => client.ToggleHullConductivity(frame, targetEntityKey, x, y, axis));
}
public CultMeshOperationReceipt SetEntityName(string targetEntityKey, string name)
{
return Send((client, frame) => client.SetEntityName(frame, targetEntityKey, name));
}
public CultMeshOperationReceipt Dock(string targetEntityKey)
{
return Send((client, frame) => client.Dock(frame, targetEntityKey));
}
public CultMeshOperationReceipt DockNearest()
{
return Send((client, frame) => client.DockNearest(frame));
}
public CultMeshOperationReceipt Undock()
{
return Send((client, frame) => client.Undock(frame));
}
public CultMeshOperationReceipt Interact()
{
return Send((client, frame) => client.Interact(frame));
}
public CultMeshOperationReceipt SetDockedCurrentShip(string targetEntityKey)
{
return Send((client, frame) => client.SetDockedCurrentShip(frame, targetEntityKey));
}
public CultMeshOperationReceipt EnterWormhole(
int targetZoneIndex,
double positionX,
double positionY)
{
return Send((client, frame) => client.EnterWormhole(frame, targetZoneIndex, positionX, positionY));
}
public CultMeshOperationReceipt TowToStation(string stationEntityKey)
{
return Send((client, frame) => client.TowToStation(frame, stationEntityKey));
}
public CultMeshOperationReceipt TransferCargoItem(
string originEntityKey,
int originCargoIndex,
string destinationEntityKey,
int destinationCargoIndex,
string itemKey,
int quantity,
int sourceX,
int sourceY,
int destinationX,
int destinationY,
bool hasDestinationPosition)
{
return Send((client, frame) => client.TransferCargoItem(
frame,
originEntityKey,
originCargoIndex,
destinationEntityKey,
destinationCargoIndex,
itemKey,
quantity,
sourceX,
sourceY,
destinationX,
destinationY,
hasDestinationPosition));
}
public CultMeshOperationReceipt TradePurchase(
string itemKey,
int quantity,
int stationCargoIndex,
int targetCargoIndex,
int sourceX,
int sourceY)
{
return Send((client, frame) => client.TradePurchase(
frame,
itemKey,
quantity,
stationCargoIndex,
targetCargoIndex,
sourceX,
sourceY));
}
public CultMeshOperationReceipt TradeSale(
string itemKey,
int quantity,
int sourceCargoIndex,
int sourceX,
int sourceY)
{
return Send((client, frame) => client.TradeSale(
frame,
itemKey,
quantity,
sourceCargoIndex,
sourceX,
sourceY));
}
public CultMeshOperationReceipt RestoreLoadout(
string dockedEntityKey,
string templateName,
int price)
{
return Send((client, frame) => client.RestoreLoadout(frame, dockedEntityKey, templateName, price));
}
public CultMeshOperationReceipt EquipItem(
string sourceKind,
string originEntityKey,
int originIndex,
string destinationEntityKey,
string itemKey,
int sourceX,
int sourceY,
int destinationX,
int destinationY,
bool hasDestinationPosition)
{
return Send((client, frame) => client.EquipItem(
frame,
sourceKind,
originEntityKey,
originIndex,
destinationEntityKey,
itemKey,
sourceX,
sourceY,
destinationX,
destinationY,
hasDestinationPosition));
}
public CultMeshOperationReceipt StoreItem(
string originEntityKey,
int sourceEquipmentIndex,
string destinationEntityKey,
int destinationCargoIndex,
string itemKey,
int destinationX,
int destinationY,
bool hasDestinationPosition,
string sourceKind = AetheriaRuntimeRefitSourceKinds.Equipment)
{
return Send((client, frame) => client.StoreItem(
frame,
originEntityKey,
sourceEquipmentIndex,
destinationEntityKey,
destinationCargoIndex,
itemKey,
destinationX,
destinationY,
hasDestinationPosition,
sourceKind));
}
public bool TrySubmitSurfaceCommand(
EveSurfaceCommandRequest request,
out AetheriaRuntimeDaemonCommandEnvelope? envelope)
{
envelope = null;
if (request == null ||
!string.Equals(request.ProviderId, AetheriaRuntimeProviderIdentity.ProviderId, StringComparison.Ordinal) ||
!TryResolveSurfaceCommandKind(request, out var kind))
{
return false;
}
try
{
envelope = Submit((client, frame) =>
TrySubmitSurfaceCommand(client, frame, kind, request, out var submitted)
? submitted!
: throw new UnsupportedSurfaceCommandException());
return true;
}
catch (UnsupportedSurfaceCommandException)
{
envelope = null;
return false;
}
}
private CultMeshOperationReceipt Send(
Func<AetheriaRuntimeDaemonOperationClient, AetheriaRuntimeDaemonFrameDocument?, AetheriaRuntimeDaemonCommandEnvelope> submit)
{
return Submit(submit);
}
private AetheriaRuntimeDaemonCommandEnvelope Submit(
Func<AetheriaRuntimeDaemonOperationClient, AetheriaRuntimeDaemonFrameDocument?, AetheriaRuntimeDaemonCommandEnvelope> submit)
{
return _submit(submit);
}
private static bool TryResolveSurfaceCommandKind(
EveSurfaceCommandRequest request,
out AetheriaRuntimeDaemonCommandKinds kind)
{
kind = AetheriaRuntimeDaemonCommandKinds.None;
var command = NormalizeSurfaceCommandName(request.Operation?.OperationId ?? "");
if (!Enum.TryParse(command, ignoreCase: false, out kind) ||
kind == AetheriaRuntimeDaemonCommandKinds.None)
{
command = NormalizeSurfaceCommandName(ReadPayloadString(request, "commandId", ""));
}
return Enum.TryParse(command, ignoreCase: false, out kind) &&
kind != AetheriaRuntimeDaemonCommandKinds.None;
}
private static string NormalizeSurfaceCommandName(string command)
{
if (command.StartsWith(AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandPrefix, StringComparison.Ordinal))
command = command.Substring(AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandPrefix.Length);
return command;
}
private static bool TrySubmitSurfaceCommand(
AetheriaRuntimeDaemonOperationClient client,
AetheriaRuntimeDaemonFrameDocument? frame,
AetheriaRuntimeDaemonCommandKinds kind,
EveSurfaceCommandRequest request,
out AetheriaRuntimeDaemonCommandEnvelope? envelope)
{
envelope = null;
if (client == null)
return false;
envelope = kind switch
{
AetheriaRuntimeDaemonCommandKinds.SetMoveVector => client.SetMoveVector(
frame,
ReadPayloadDouble(request, "directionX", 0.0),
ReadPayloadDouble(request, "directionY", 0.0),
ReadPayloadDouble(request, "scalarValue", 1.0)),
AetheriaRuntimeDaemonCommandKinds.SetSimulationRate => client.SetSimulationRate(
frame,
ReadPayloadDouble(request, "scalarValue", 0.0)),
AetheriaRuntimeDaemonCommandKinds.AdvanceSimulationStep => client.AdvanceSimulationStep(frame),
AetheriaRuntimeDaemonCommandKinds.SetLookDirection => client.SetLookDirection(
frame,
ReadPayloadDouble(request, "directionX", 0.0),
ReadPayloadDouble(request, "directionY", 1.0),
ReadPayloadDouble(request, "directionZ", 0.0)),
AetheriaRuntimeDaemonCommandKinds.TargetReticle => client.TargetReticle(
frame,
ReadPayloadDouble(request, "directionX", 0.0),
ReadPayloadDouble(request, "directionY", 0.0),
ReadPayloadDouble(request, "directionZ", 0.0)),
AetheriaRuntimeDaemonCommandKinds.SetTractorPower => client.SetTractorPower(
frame,
ReadPayloadDouble(request, "scalarValue", 1.0)),
AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup => client.FireWeaponGroup(
frame,
ReadWeaponGroup(request)),
AetheriaRuntimeDaemonCommandKinds.SetWeaponGroupActive => client.SetWeaponGroupActive(
frame,
ReadWeaponGroup(request),
ReadPayloadBool(request, "active", true)),
AetheriaRuntimeDaemonCommandKinds.SetBehaviorActive => client.SetBehaviorActive(
frame,
(int)ReadPayloadDouble(request, "equipmentIndex", -1),
(int)ReadPayloadDouble(request, "behaviorIndex", -1),
ReadPayloadBool(request, "active", true)),
AetheriaRuntimeDaemonCommandKinds.SetThermotoggleTargetTemperature =>
client.SetThermotoggleTargetTemperature(
frame,
ReadPayloadString(request, "targetEntityKey",
ReadPayloadString(request, "entityId", "")),
(int)ReadPayloadDouble(request, "equipmentIndex", -1),
(int)ReadPayloadDouble(request, "behaviorIndex", -1),
ReadPayloadDouble(request, "scalarValue", double.NaN)),
AetheriaRuntimeDaemonCommandKinds.SetTarget => client.SetTarget(
frame,
ReadPayloadString(request, "targetEntityId", ReadPayloadString(request, "entityId", ""))),
AetheriaRuntimeDaemonCommandKinds.IssueAgentTask => client.IssueAgentTask(
frame,
new AetheriaRuntimeAgentTaskCommand
{
TaskId = ReadPayloadString(request, "taskId", ""),
CorporationKey = ReadPayloadString(request, "corporation", ""),
TaskType = ReadPayloadString(request, "taskType", ""),
Priority = (int)ReadPayloadDouble(request, "priority", 0),
ZoneIndex = (int)ReadPayloadDouble(request, "zoneIndex", -1),
TargetEntityIndex = (int)ReadPayloadDouble(request, "targetEntityIndex", -1),
TargetPositionX = ReadPayloadDouble(request, "targetPositionX", 0),
TargetPositionZ = ReadPayloadDouble(request, "targetPositionZ", 0),
CompletionRadius = ReadPayloadDouble(request, "completionRadius", 10),
WeaponGroup = (int)ReadPayloadDouble(request, "weaponGroup", 0),
OriginEntityIndex = (int)ReadPayloadDouble(request, "originEntityIndex", -1),
ItemKey = ReadPayloadString(request, "itemKey", ""),
Quantity = (int)ReadPayloadDouble(request, "quantity", 0),
TargetBodyKeys = ReadPayloadString(request, "targetBodyKeys", "")
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(key => key.Trim())
.Where(key => !string.IsNullOrWhiteSpace(key))
.ToArray(),
OrbitParentKey = ReadPayloadString(request, "orbitParentKey", ""),
OrbitDistance = ReadPayloadDouble(request, "orbitDistance", 0)
}),
AetheriaRuntimeDaemonCommandKinds.CancelAgentTask => client.CancelAgentTask(
frame,
ReadPayloadString(request, "taskId", "")),
AetheriaRuntimeDaemonCommandKinds.TradePurchase => client.TradePurchase(
frame,
ReadPayloadString(request, "itemKey", ""),
(int)ReadPayloadDouble(request, "quantity", 1),
(int)ReadPayloadDouble(request, "stationCargoIndex", -1),
(int)ReadPayloadDouble(request, "targetCargoIndex", -1),
(int)ReadPayloadDouble(request, "sourceX", int.MinValue),
(int)ReadPayloadDouble(request, "sourceY", int.MinValue)),
AetheriaRuntimeDaemonCommandKinds.TradeSale => client.TradeSale(
frame,
ReadPayloadString(request, "itemKey", ""),
(int)ReadPayloadDouble(request, "quantity", 1),
(int)ReadPayloadDouble(request, "sourceCargoIndex", -1),
(int)ReadPayloadDouble(request, "sourceX", int.MinValue),
(int)ReadPayloadDouble(request, "sourceY", int.MinValue)),
AetheriaRuntimeDaemonCommandKinds.TransferCargoItem => client.TransferCargoItem(
frame,
ReadPayloadString(request, "originEntityKey", ""),
(int)ReadPayloadDouble(request, "originCargoIndex", -1),
ReadPayloadString(request, "destinationEntityKey", ""),
(int)ReadPayloadDouble(request, "destinationCargoIndex", -1),
ReadPayloadString(request, "itemKey", ""),
(int)ReadPayloadDouble(request, "quantity", 1),
(int)ReadPayloadDouble(request, "sourceX", int.MinValue),
(int)ReadPayloadDouble(request, "sourceY", int.MinValue),
(int)ReadPayloadDouble(request, "destinationX", int.MinValue),
(int)ReadPayloadDouble(request, "destinationY", int.MinValue),
ReadPayloadBool(request, "hasDestinationPosition", false)),
AetheriaRuntimeDaemonCommandKinds.EquipItem => client.EquipItem(
frame,
ReadPayloadString(request, "sourceKind", AetheriaRuntimeRefitSourceKinds.Cargo),
ReadPayloadString(request, "originEntityKey", ""),
(int)ReadPayloadDouble(request, "originIndex", -1),
ReadPayloadString(request, "destinationEntityKey", ""),
ReadPayloadString(request, "itemKey", ""),
(int)ReadPayloadDouble(request, "sourceX", int.MinValue),
(int)ReadPayloadDouble(request, "sourceY", int.MinValue),
(int)ReadPayloadDouble(request, "destinationX", int.MinValue),
(int)ReadPayloadDouble(request, "destinationY", int.MinValue),
ReadPayloadBool(request, "hasDestinationPosition", false)),
AetheriaRuntimeDaemonCommandKinds.StoreItem => client.StoreItem(
frame,
ReadPayloadString(request, "originEntityKey", ""),
(int)ReadPayloadDouble(request, "originIndex", -1),
ReadPayloadString(request, "destinationEntityKey", ""),
(int)ReadPayloadDouble(request, "destinationCargoIndex", -1),
ReadPayloadString(request, "itemKey", ""),
(int)ReadPayloadDouble(request, "destinationX", int.MinValue),
(int)ReadPayloadDouble(request, "destinationY", int.MinValue),
ReadPayloadBool(request, "hasDestinationPosition", false),
ReadPayloadString(request, "sourceKind", AetheriaRuntimeRefitSourceKinds.Equipment)),
_ => AetheriaRuntimeDaemonSurfaceCommandCatalog.TrySubmitArgumentless(
client,
frame,
kind,
out var submitted)
? submitted
: null
};
return envelope != null;
}
private static string ReadPayloadString(
EveSurfaceCommandRequest request,
string key,
string defaultValue)
{
var value = request.Payload.GetString(key);
return string.IsNullOrWhiteSpace(value) ? defaultValue : value;
}
private static double ReadPayloadDouble(
EveSurfaceCommandRequest request,
string key,
double defaultValue)
{
return request.Payload.GetDouble(key, defaultValue);
}
private static bool ReadPayloadBool(
EveSurfaceCommandRequest request,
string key,
bool defaultValue)
{
var value = request.Payload.GetString(key);
return string.IsNullOrWhiteSpace(value)
? defaultValue
: string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) ||
string.Equals(value, "1", StringComparison.Ordinal);
}
private static int ReadWeaponGroup(EveSurfaceCommandRequest request)
{
var value = request.Payload.GetString("weaponGroup");
if (int.TryParse(value, out var parsed))
return parsed;
value = request.Payload.GetString("actionId");
return int.TryParse(value, out parsed) ? parsed : 0;
}
private sealed class UnsupportedSurfaceCommandException : Exception
{
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 28d1fca8a55115f4db34e4bf2be8c072

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 8d4ea4a3ec7b5e14aa476dbceb212451

View file

@ -1,60 +0,0 @@
using System;
using GameCult.Mesh;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public sealed class AetheriaRuntimeDaemonRenderView
{
public AetheriaRuntimeDaemonRenderView(
AetheriaRuntimeDaemonFrameDocument frame,
AetheriaRuntimeDaemonSoaViewDocument? soaView,
AetheriaRuntimeZoneRenderDocument zoneRender)
{
Frame = frame ?? throw new ArgumentNullException(nameof(frame));
SoaView = soaView;
SoaIndex = AetheriaRuntimeDaemonSoaViewIndex.Build(soaView);
ZoneRender = zoneRender ?? throw new ArgumentNullException(nameof(zoneRender));
}
public AetheriaRuntimeDaemonFrameDocument Frame { get; }
public AetheriaRuntimeDaemonSoaViewDocument? SoaView { get; }
public AetheriaRuntimeDaemonSoaViewIndex SoaIndex { get; }
public AetheriaRuntimeZoneRenderDocument ZoneRender { get; }
public bool HasSoaView => SoaView != null && SoaIndex.IsValid;
public bool IsAuthoritative => Frame.IsAuthoritative;
public AetheriaRuntimeRunCheckpointCommit Run => Frame.Run;
public static bool TryCreateCurrent(
CultMeshReactiveDocument<AetheriaRuntimeDaemonFrameDocument> frame,
CultMeshReactiveDocument<AetheriaRuntimeDaemonSoaViewDocument>? soaView,
CultMeshReactiveDocument<AetheriaRuntimeZoneRenderDocument> zoneRender,
out AetheriaRuntimeDaemonRenderView? observed)
{
observed = null;
try
{
var currentFrame = frame?.Current;
var currentZoneRender = zoneRender?.Current;
if (currentFrame == null || currentZoneRender == null)
return false;
var currentSoaView = soaView?.Current;
if (currentSoaView == null ||
!string.Equals(currentSoaView.Schema, AetheriaRuntimeDaemonSchemas.SoaView, StringComparison.Ordinal))
{
currentSoaView = null;
}
observed = new AetheriaRuntimeDaemonRenderView(currentFrame, currentSoaView, currentZoneRender);
return true;
}
catch
{
return false;
}
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: faf4bdd4c3ff79548932e1e85d115eed

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 479d0ef759b333b4dae8052bad70eca7

View file

@ -1,303 +0,0 @@
using MessagePack;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
[MessagePackObject(true)]
public readonly struct AetheriaRuntimeDaemonSimulationSettings
{
public static AetheriaRuntimeDaemonSimulationSettings AetheriaDefault { get; } =
new AetheriaRuntimeDaemonSimulationSettings(
pawnSpeed: 84.0,
raiderSpeed: 68.0,
attackRange: 145.0,
attackHoldRatio: 0.82,
pawnProjectileDamage: 18.0,
raiderProjectileDamage: 12.0,
weaponCooldownSeconds: 0.55,
projectileSpeed: 330.0,
projectileRadius: 18.0,
projectileLifetimeSeconds: 2.2,
projectileSpawnOffset: 18.0,
projectileHeatScale: 0.18,
heatDissipationPerSecond: 8.0,
stationSensorRange: 720.0,
entitySensorRange: 520.0,
playerStationHull: 420.0,
hostileStationHull: 240.0,
playerEntityHull: 120.0,
raiderEntityHull: 80.0,
stationShield: 120.0,
entityShield: 45.0,
weaponLockSpeed: 2.0,
weaponLockSensorImpact: 1.0,
weaponLockAngleDegrees: 45.0,
weaponLockDirectionImpact: 1.0,
weaponLockDecayPerSecond: 1.0,
lootDropProbability: 0.25,
lootDropVelocity: 25.0,
pickupLifetimeSeconds: 30.0,
severeThermalRiskThreshold: 0.25,
heatstrokeTemperature: 330.0,
heatstrokeMultiplier: 0.00001,
heatstrokeExponent: 2.0,
heatstrokeRecoveryPerSecond: 0.2,
hypothermiaTemperature: 273.0,
hypothermiaMultiplier: 0.00001,
hypothermiaExponent: 2.0,
hypothermiaRecoveryPerSecond: 0.2,
agentRangeExponent: 0.25,
agentForwardLerp: 0.5,
agentMaxForwardDistance: 50,
agentDpsSampleCount: 32,
wormholeAnimationDuration: 4,
wormholeExitCurveStart: 0.8,
wormholeExitVelocity: 20,
wormholeExitRadius: 50,
wormholeDepth: 1000);
public AetheriaRuntimeDaemonSimulationSettings(
double pawnSpeed,
double raiderSpeed,
double attackRange,
double attackHoldRatio,
double pawnProjectileDamage,
double raiderProjectileDamage,
double weaponCooldownSeconds,
double projectileSpeed,
double projectileRadius,
double projectileLifetimeSeconds,
double projectileSpawnOffset,
double projectileHeatScale,
double heatDissipationPerSecond,
double stationSensorRange,
double entitySensorRange,
double playerStationHull,
double hostileStationHull,
double playerEntityHull,
double raiderEntityHull,
double stationShield,
double entityShield,
double weaponLockSpeed = AetheriaDefaultRaw.WeaponLockSpeed,
double weaponLockSensorImpact = AetheriaDefaultRaw.WeaponLockSensorImpact,
double weaponLockAngleDegrees = AetheriaDefaultRaw.WeaponLockAngleDegrees,
double weaponLockDirectionImpact = AetheriaDefaultRaw.WeaponLockDirectionImpact,
double weaponLockDecayPerSecond = AetheriaDefaultRaw.WeaponLockDecayPerSecond,
double lootDropProbability = AetheriaDefaultRaw.LootDropProbability,
double lootDropVelocity = AetheriaDefaultRaw.LootDropVelocity,
double pickupLifetimeSeconds = AetheriaDefaultRaw.PickupLifetimeSeconds,
double severeThermalRiskThreshold = AetheriaDefaultRaw.SevereThermalRiskThreshold,
double heatstrokeTemperature = AetheriaDefaultRaw.HeatstrokeTemperature,
double heatstrokeMultiplier = AetheriaDefaultRaw.HeatstrokeMultiplier,
double heatstrokeExponent = AetheriaDefaultRaw.HeatstrokeExponent,
double heatstrokeRecoveryPerSecond = AetheriaDefaultRaw.HeatstrokeRecoveryPerSecond,
double hypothermiaTemperature = AetheriaDefaultRaw.HypothermiaTemperature,
double hypothermiaMultiplier = AetheriaDefaultRaw.HypothermiaMultiplier,
double hypothermiaExponent = AetheriaDefaultRaw.HypothermiaExponent,
double hypothermiaRecoveryPerSecond = AetheriaDefaultRaw.HypothermiaRecoveryPerSecond,
double aetherTorqueMultiplier = AetheriaDefaultRaw.AetherTorqueMultiplier,
double aetherHeatMultiplier = AetheriaDefaultRaw.AetherHeatMultiplier,
double torqueFloor = AetheriaDefaultRaw.TorqueFloor,
double torqueMultiplier = AetheriaDefaultRaw.TorqueMultiplier,
double agentRangeExponent = AetheriaDefaultRaw.AgentRangeExponent,
double agentForwardLerp = AetheriaDefaultRaw.AgentForwardLerp,
double agentMaxForwardDistance = AetheriaDefaultRaw.AgentMaxForwardDistance,
int agentDpsSampleCount = AetheriaDefaultRaw.AgentDpsSampleCount,
double wormholeAnimationDuration = AetheriaDefaultRaw.WormholeAnimationDuration,
double wormholeExitCurveStart = AetheriaDefaultRaw.WormholeExitCurveStart,
double wormholeExitVelocity = AetheriaDefaultRaw.WormholeExitVelocity,
double wormholeExitRadius = AetheriaDefaultRaw.WormholeExitRadius,
double wormholeDepth = AetheriaDefaultRaw.WormholeDepth,
double visibilityDecay = AetheriaDefaultRaw.VisibilityDecay,
double targetInfoDecay = AetheriaDefaultRaw.TargetInfoDecay,
double secureAreaRadiusMultiplier = AetheriaDefaultRaw.SecureAreaRadiusMultiplier)
{
PawnSpeed = PositiveOr(pawnSpeed, AetheriaDefaultRaw.PawnSpeed);
RaiderSpeed = PositiveOr(raiderSpeed, AetheriaDefaultRaw.RaiderSpeed);
AttackRange = PositiveOr(attackRange, AetheriaDefaultRaw.AttackRange);
AttackHoldRatio = Clamp01Or(attackHoldRatio, AetheriaDefaultRaw.AttackHoldRatio);
PawnProjectileDamage = PositiveOr(pawnProjectileDamage, AetheriaDefaultRaw.PawnProjectileDamage);
RaiderProjectileDamage = PositiveOr(raiderProjectileDamage, AetheriaDefaultRaw.RaiderProjectileDamage);
WeaponCooldownSeconds = PositiveOr(weaponCooldownSeconds, AetheriaDefaultRaw.WeaponCooldownSeconds);
ProjectileSpeed = PositiveOr(projectileSpeed, AetheriaDefaultRaw.ProjectileSpeed);
ProjectileRadius = PositiveOr(projectileRadius, AetheriaDefaultRaw.ProjectileRadius);
ProjectileLifetimeSeconds = PositiveOr(projectileLifetimeSeconds, AetheriaDefaultRaw.ProjectileLifetimeSeconds);
ProjectileSpawnOffset = PositiveOr(projectileSpawnOffset, AetheriaDefaultRaw.ProjectileSpawnOffset);
ProjectileHeatScale = NonNegativeOr(projectileHeatScale, AetheriaDefaultRaw.ProjectileHeatScale);
HeatDissipationPerSecond = NonNegativeOr(heatDissipationPerSecond, AetheriaDefaultRaw.HeatDissipationPerSecond);
StationSensorRange = PositiveOr(stationSensorRange, AetheriaDefaultRaw.StationSensorRange);
EntitySensorRange = PositiveOr(entitySensorRange, AetheriaDefaultRaw.EntitySensorRange);
PlayerStationHull = PositiveOr(playerStationHull, AetheriaDefaultRaw.PlayerStationHull);
HostileStationHull = PositiveOr(hostileStationHull, AetheriaDefaultRaw.HostileStationHull);
PlayerEntityHull = PositiveOr(playerEntityHull, AetheriaDefaultRaw.PlayerEntityHull);
RaiderEntityHull = PositiveOr(raiderEntityHull, AetheriaDefaultRaw.RaiderEntityHull);
StationShield = NonNegativeOr(stationShield, AetheriaDefaultRaw.StationShield);
EntityShield = NonNegativeOr(entityShield, AetheriaDefaultRaw.EntityShield);
WeaponLockSpeed = PositiveOr(weaponLockSpeed, AetheriaDefaultRaw.WeaponLockSpeed);
WeaponLockSensorImpact = NonNegativeOr(weaponLockSensorImpact, AetheriaDefaultRaw.WeaponLockSensorImpact);
WeaponLockAngleDegrees = PositiveOr(weaponLockAngleDegrees, AetheriaDefaultRaw.WeaponLockAngleDegrees);
WeaponLockDirectionImpact = NonNegativeOr(weaponLockDirectionImpact, AetheriaDefaultRaw.WeaponLockDirectionImpact);
WeaponLockDecayPerSecond = NonNegativeOr(weaponLockDecayPerSecond, AetheriaDefaultRaw.WeaponLockDecayPerSecond);
LootDropProbability = Clamp01Or(lootDropProbability, AetheriaDefaultRaw.LootDropProbability);
LootDropVelocity = NonNegativeOr(lootDropVelocity, AetheriaDefaultRaw.LootDropVelocity);
PickupLifetimeSeconds = PositiveOr(pickupLifetimeSeconds, AetheriaDefaultRaw.PickupLifetimeSeconds);
SevereThermalRiskThreshold = Clamp01Or(severeThermalRiskThreshold, AetheriaDefaultRaw.SevereThermalRiskThreshold);
HeatstrokeTemperature = PositiveOr(heatstrokeTemperature, AetheriaDefaultRaw.HeatstrokeTemperature);
HeatstrokeMultiplier = NonNegativeOr(heatstrokeMultiplier, AetheriaDefaultRaw.HeatstrokeMultiplier);
HeatstrokeExponent = PositiveOr(heatstrokeExponent, AetheriaDefaultRaw.HeatstrokeExponent);
HeatstrokeRecoveryPerSecond = NonNegativeOr(heatstrokeRecoveryPerSecond, AetheriaDefaultRaw.HeatstrokeRecoveryPerSecond);
HypothermiaTemperature = PositiveOr(hypothermiaTemperature, AetheriaDefaultRaw.HypothermiaTemperature);
HypothermiaMultiplier = NonNegativeOr(hypothermiaMultiplier, AetheriaDefaultRaw.HypothermiaMultiplier);
HypothermiaExponent = PositiveOr(hypothermiaExponent, AetheriaDefaultRaw.HypothermiaExponent);
HypothermiaRecoveryPerSecond = NonNegativeOr(hypothermiaRecoveryPerSecond, AetheriaDefaultRaw.HypothermiaRecoveryPerSecond);
AetherTorqueMultiplier = NonNegativeOr(aetherTorqueMultiplier, AetheriaDefaultRaw.AetherTorqueMultiplier);
AetherHeatMultiplier = NonNegativeOr(aetherHeatMultiplier, AetheriaDefaultRaw.AetherHeatMultiplier);
TorqueFloor = NonNegativeOr(torqueFloor, AetheriaDefaultRaw.TorqueFloor);
TorqueMultiplier = NonNegativeOr(torqueMultiplier, AetheriaDefaultRaw.TorqueMultiplier);
AgentRangeExponent = NonNegativeOr(agentRangeExponent, AetheriaDefaultRaw.AgentRangeExponent);
AgentForwardLerp = Clamp01Or(agentForwardLerp, AetheriaDefaultRaw.AgentForwardLerp);
AgentMaxForwardDistance = PositiveOr(agentMaxForwardDistance, AetheriaDefaultRaw.AgentMaxForwardDistance);
AgentDpsSampleCount = agentDpsSampleCount > 0 ? agentDpsSampleCount : AetheriaDefaultRaw.AgentDpsSampleCount;
WormholeAnimationDuration = PositiveOr(wormholeAnimationDuration, AetheriaDefaultRaw.WormholeAnimationDuration);
WormholeExitCurveStart = Clamp01Or(wormholeExitCurveStart, AetheriaDefaultRaw.WormholeExitCurveStart);
WormholeExitVelocity = PositiveOr(wormholeExitVelocity, AetheriaDefaultRaw.WormholeExitVelocity);
WormholeExitRadius = PositiveOr(wormholeExitRadius, AetheriaDefaultRaw.WormholeExitRadius);
WormholeDepth = PositiveOr(wormholeDepth, AetheriaDefaultRaw.WormholeDepth);
VisibilityDecay = NonNegativeOr(visibilityDecay, AetheriaDefaultRaw.VisibilityDecay);
TargetInfoDecay = NonNegativeOr(targetInfoDecay, AetheriaDefaultRaw.TargetInfoDecay);
SecureAreaRadiusMultiplier = PositiveOr(
secureAreaRadiusMultiplier, AetheriaDefaultRaw.SecureAreaRadiusMultiplier);
}
public double PawnSpeed { get; }
public double RaiderSpeed { get; }
public double AttackRange { get; }
public double AttackHoldRatio { get; }
public double PawnProjectileDamage { get; }
public double RaiderProjectileDamage { get; }
public double WeaponCooldownSeconds { get; }
public double ProjectileSpeed { get; }
public double ProjectileRadius { get; }
public double ProjectileLifetimeSeconds { get; }
public double ProjectileSpawnOffset { get; }
public double ProjectileHeatScale { get; }
public double HeatDissipationPerSecond { get; }
[System.Obsolete("Sensor reach derives from installed equipment. Retained only for snapshot compatibility.")]
public double StationSensorRange { get; }
public double FallbackSensorRange => EntitySensorRange;
public double EntitySensorRange { get; }
public double PlayerStationHull { get; }
public double HostileStationHull { get; }
public double PlayerEntityHull { get; }
public double RaiderEntityHull { get; }
public double StationShield { get; }
public double EntityShield { get; }
public double WeaponLockSpeed { get; }
public double WeaponLockSensorImpact { get; }
public double WeaponLockAngleDegrees { get; }
public double WeaponLockDirectionImpact { get; }
public double WeaponLockDecayPerSecond { get; }
public double LootDropProbability { get; }
public double LootDropVelocity { get; }
public double PickupLifetimeSeconds { get; }
public double SevereThermalRiskThreshold { get; }
public double HeatstrokeTemperature { get; }
public double HeatstrokeMultiplier { get; }
public double HeatstrokeExponent { get; }
public double HeatstrokeRecoveryPerSecond { get; }
public double HypothermiaTemperature { get; }
public double HypothermiaMultiplier { get; }
public double HypothermiaExponent { get; }
public double HypothermiaRecoveryPerSecond { get; }
public double AetherTorqueMultiplier { get; }
public double AetherHeatMultiplier { get; }
public double TorqueFloor { get; }
public double TorqueMultiplier { get; }
public double AgentRangeExponent { get; }
public double AgentForwardLerp { get; }
public double AgentMaxForwardDistance { get; }
public int AgentDpsSampleCount { get; }
public double WormholeAnimationDuration { get; }
public double WormholeExitCurveStart { get; }
public double WormholeExitVelocity { get; }
public double WormholeExitRadius { get; }
public double WormholeDepth { get; }
public double VisibilityDecay { get; }
public double TargetInfoDecay { get; }
public double SecureAreaRadiusMultiplier { get; }
private static double PositiveOr(double value, double fallback)
{
return double.IsFinite(value) && value > 0 ? value : fallback;
}
private static double NonNegativeOr(double value, double fallback)
{
return double.IsFinite(value) && value >= 0 ? value : fallback;
}
private static double Clamp01Or(double value, double fallback)
{
return double.IsFinite(value) ? value < 0 ? 0 : value > 1 ? 1 : value : fallback;
}
private static class AetheriaDefaultRaw
{
public const double PawnSpeed = 84.0;
public const double RaiderSpeed = 68.0;
public const double AttackRange = 145.0;
public const double AttackHoldRatio = 0.82;
public const double PawnProjectileDamage = 18.0;
public const double RaiderProjectileDamage = 12.0;
public const double WeaponCooldownSeconds = 0.55;
public const double ProjectileSpeed = 330.0;
public const double ProjectileRadius = 18.0;
public const double ProjectileLifetimeSeconds = 2.2;
public const double ProjectileSpawnOffset = 18.0;
public const double ProjectileHeatScale = 0.18;
public const double HeatDissipationPerSecond = 8.0;
public const double StationSensorRange = 720.0;
public const double EntitySensorRange = 520.0;
public const double PlayerStationHull = 420.0;
public const double HostileStationHull = 240.0;
public const double PlayerEntityHull = 120.0;
public const double RaiderEntityHull = 80.0;
public const double StationShield = 120.0;
public const double EntityShield = 45.0;
public const double WeaponLockSpeed = 2.0;
public const double WeaponLockSensorImpact = 1.0;
public const double WeaponLockAngleDegrees = 45.0;
public const double WeaponLockDirectionImpact = 1.0;
public const double WeaponLockDecayPerSecond = 1.0;
public const double LootDropProbability = 0.25;
public const double LootDropVelocity = 25.0;
public const double PickupLifetimeSeconds = 30.0;
public const double SevereThermalRiskThreshold = 0.25;
public const double HeatstrokeTemperature = 330.0;
public const double HeatstrokeMultiplier = 0.00001;
public const double HeatstrokeExponent = 2.0;
public const double HeatstrokeRecoveryPerSecond = 0.2;
public const double HypothermiaTemperature = 273.0;
public const double HypothermiaMultiplier = 0.00001;
public const double HypothermiaExponent = 2.0;
public const double HypothermiaRecoveryPerSecond = 0.2;
public const double AetherTorqueMultiplier = 0.1;
public const double AetherHeatMultiplier = 0.25;
public const double TorqueFloor = 0.5;
public const double TorqueMultiplier = 0.1;
public const double AgentRangeExponent = 0.25;
public const double AgentForwardLerp = 0.5;
public const double AgentMaxForwardDistance = 50.0;
public const int AgentDpsSampleCount = 32;
public const double WormholeAnimationDuration = 4.0;
public const double WormholeExitCurveStart = 0.8;
public const double WormholeExitVelocity = 20.0;
public const double WormholeExitRadius = 50.0;
public const double WormholeDepth = 1000.0;
public const double VisibilityDecay = 0.5;
public const double TargetInfoDecay = 0.5;
public const double SecureAreaRadiusMultiplier = 0.425;
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 7a04293019e69804188e0fd89165928e

View file

@ -1,298 +0,0 @@
using System;
using System.Collections.Generic;
using GameCult.Caching;
using MessagePack;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public static class AetheriaRuntimeDaemonSoaBackends
{
public const string CultCache = "cultcache";
public const string CultMesh = "cultmesh";
public const string MemoryMappedFile = "memory_mapped_file";
public const string SharedNativeMemory = "shared_native_memory";
}
public static class AetheriaRuntimeDaemonSoaSynchronizationModes
{
public const string ImmutableFrame = "immutable_frame";
public const string DoubleBuffered = "double_buffered";
public const string TripleBuffered = "triple_buffered";
}
public static class AetheriaRuntimeDaemonSoaColumnKinds
{
public const string EntityIndex = "entity.index";
public const string EntityKey = "entity.key";
public const string ZoneIndex = "entity.zone_index";
public const string Position = "transform.position";
public const string RotationRadians = "transform.rotation.radians";
public const string Velocity = "motion.velocity";
public const string PhysicsBodyRadius = "physics.body.radius";
public const string PhysicsBodyMass = "physics.body.mass";
public const string PhysicsBodyInverseMass = "physics.body.inverse_mass";
public const string Hull = "stats.hull";
public const string Heat = "stats.heat";
public const string Quality = "stats.quality";
public const string Durability = "stats.durability";
public const string RenderGroupId = "render.group.id";
public const string RenderScale = "render.scale";
public const string RenderVisibility = "render.visibility";
public const string RenderLod = "render.lod";
public const string CargoQuantity = "inventory.cargo.quantity";
public const string BeamPower = "effect.beam.power";
}
public static class AetheriaRuntimeDaemonRenderShadowModes
{
public const string Off = "off";
public const string On = "on";
public const string TwoSided = "two_sided";
public const string ShadowsOnly = "shadows_only";
}
[CultDocument("gamecult.aetheria.daemon_soa_view", "gamecult.aetheria.daemon_soa_view.v1")]
[MessagePackObject]
public sealed class AetheriaRuntimeDaemonSoaViewDocument
{
[Key(0)]
public string Schema { get; set; } = AetheriaRuntimeDaemonSchemas.SoaView;
[Key(1)]
public string DaemonId { get; set; } = "local";
[Key(2)]
public string SessionId { get; set; } = "local";
[Key(3)]
public long FrameId { get; set; }
[Key(4)]
public long Generation { get; set; }
[Key(5)]
public string PublishedAtUtc { get; set; } = "";
[Key(6)]
public bool IsAuthoritative { get; set; } = true;
[Key(7)]
public string Backend { get; set; } = AetheriaRuntimeDaemonSoaBackends.CultCache;
[Key(8)]
public string SynchronizationMode { get; set; } = AetheriaRuntimeDaemonSoaSynchronizationModes.ImmutableFrame;
[Key(9)]
public IReadOnlyList<AetheriaRuntimeDaemonSoaBufferDocument> Buffers { get; set; } =
Array.Empty<AetheriaRuntimeDaemonSoaBufferDocument>();
[Key(10)]
public IReadOnlyList<AetheriaRuntimeDaemonSoaColumnDocument> Columns { get; set; } =
Array.Empty<AetheriaRuntimeDaemonSoaColumnDocument>();
[Key(11)]
public IReadOnlyList<AetheriaRuntimeDaemonSoaDirtyRangeDocument> DirtyRanges { get; set; } =
Array.Empty<AetheriaRuntimeDaemonSoaDirtyRangeDocument>();
[Key(12)]
public IReadOnlyList<AetheriaRuntimeDaemonRenderGroupDocument> RenderGroups { get; set; } =
Array.Empty<AetheriaRuntimeDaemonRenderGroupDocument>();
[Key(13)]
public IReadOnlyList<AetheriaRuntimeDaemonSoaIdentityDocument> Identities { get; set; } =
Array.Empty<AetheriaRuntimeDaemonSoaIdentityDocument>();
public static AetheriaRuntimeDaemonSoaViewDocument Create(
string daemonId,
string sessionId,
long frameId,
long generation,
IReadOnlyList<AetheriaRuntimeDaemonSoaBufferDocument>? buffers,
IReadOnlyList<AetheriaRuntimeDaemonSoaColumnDocument>? columns,
IReadOnlyList<AetheriaRuntimeDaemonSoaDirtyRangeDocument>? dirtyRanges = null,
string backend = AetheriaRuntimeDaemonSoaBackends.CultCache,
string synchronizationMode = AetheriaRuntimeDaemonSoaSynchronizationModes.ImmutableFrame,
bool isAuthoritative = true,
IReadOnlyList<AetheriaRuntimeDaemonRenderGroupDocument>? renderGroups = null,
IReadOnlyList<AetheriaRuntimeDaemonSoaIdentityDocument>? identities = null)
{
return new AetheriaRuntimeDaemonSoaViewDocument
{
DaemonId = string.IsNullOrWhiteSpace(daemonId) ? "local" : daemonId,
SessionId = string.IsNullOrWhiteSpace(sessionId) ? "local" : sessionId,
FrameId = frameId,
Generation = generation,
PublishedAtUtc = DateTime.UtcNow.ToString("O"),
IsAuthoritative = isAuthoritative,
Backend = string.IsNullOrWhiteSpace(backend) ? AetheriaRuntimeDaemonSoaBackends.CultCache : backend,
SynchronizationMode = string.IsNullOrWhiteSpace(synchronizationMode)
? AetheriaRuntimeDaemonSoaSynchronizationModes.ImmutableFrame
: synchronizationMode,
Buffers = buffers ?? Array.Empty<AetheriaRuntimeDaemonSoaBufferDocument>(),
Columns = columns ?? Array.Empty<AetheriaRuntimeDaemonSoaColumnDocument>(),
DirtyRanges = dirtyRanges ?? Array.Empty<AetheriaRuntimeDaemonSoaDirtyRangeDocument>(),
RenderGroups = renderGroups ?? Array.Empty<AetheriaRuntimeDaemonRenderGroupDocument>(),
Identities = identities ?? Array.Empty<AetheriaRuntimeDaemonSoaIdentityDocument>()
};
}
}
[MessagePackObject]
public sealed class AetheriaRuntimeDaemonSoaIdentityDocument
{
[Key(0)] public int EntityIndex { get; set; } = -1;
[Key(1)] public string EntityId { get; set; } = "";
[Key(2)] public string Kind { get; set; } = "";
[Key(3)] public string Label { get; set; } = "";
[Key(4)] public string Faction { get; set; } = "";
[Key(5)] public bool Selectable { get; set; } = true;
[Key(6)] public bool Controllable { get; set; }
[Key(7)] public string AssetRef { get; set; } = "";
}
[MessagePackObject]
public sealed class AetheriaRuntimeDaemonRenderGroupDocument
{
[Key(0)]
public int GroupId { get; set; }
[Key(1)]
public string MeshKey { get; set; } = "";
[Key(2)]
public string MaterialKey { get; set; } = "";
[Key(3)]
public int SubMeshIndex { get; set; }
[Key(4)]
public int Layer { get; set; }
[Key(5)]
public string ShaderKey { get; set; } = "";
[Key(6)]
public string DisplayName { get; set; } = "";
[Key(7)]
public int InstanceCount { get; set; } = -1;
[Key(8)]
public float BoundsCenterX { get; set; }
[Key(9)]
public float BoundsCenterY { get; set; }
[Key(10)]
public float BoundsCenterZ { get; set; }
[Key(11)]
public float BoundsSizeX { get; set; } = -1.0f;
[Key(12)]
public float BoundsSizeY { get; set; } = -1.0f;
[Key(13)]
public float BoundsSizeZ { get; set; } = -1.0f;
[Key(14)]
public string ShadowMode { get; set; } = AetheriaRuntimeDaemonRenderShadowModes.On;
[Key(15)]
public bool ReceiveShadows { get; set; } = true;
[Key(16)]
public float DefaultScale { get; set; } = 1.0f;
[Key(17)]
public int Lod { get; set; } = -1;
[Key(18)]
public AetheriaRuntimeAssetRef MeshAsset { get; set; } =
AetheriaRuntimeAssetRef.Empty(AetheriaRuntimeAssetKinds.Mesh);
[Key(19)]
public AetheriaRuntimeAssetRef MaterialAsset { get; set; } =
AetheriaRuntimeAssetRef.Empty(AetheriaRuntimeAssetKinds.Material);
}
[MessagePackObject]
public sealed class AetheriaRuntimeDaemonSoaBufferDocument
{
[Key(0)]
public string BufferId { get; set; } = "";
[Key(1)]
public string DisplayName { get; set; } = "";
[Key(2)]
public string Backend { get; set; } = AetheriaRuntimeDaemonSoaBackends.CultCache;
[Key(3)]
public string Location { get; set; } = "";
[Key(4)]
public long ByteOffset { get; set; }
[Key(5)]
public long ByteLength { get; set; }
[Key(6)]
public long Generation { get; set; }
[Key(7)]
public bool DaemonWritable { get; set; } = true;
[Key(8)]
public bool ObserverWritable { get; set; }
}
[MessagePackObject]
public sealed class AetheriaRuntimeDaemonSoaColumnDocument
{
[Key(0)]
public string ColumnId { get; set; } = "";
[Key(1)]
public string Kind { get; set; } = "";
[Key(2)]
public string BufferId { get; set; } = "";
[Key(3)]
public string ScalarType { get; set; } = "";
[Key(4)]
public long ByteOffset { get; set; }
[Key(5)]
public int ElementStride { get; set; }
[Key(6)]
public int ElementCount { get; set; }
[Key(7)]
public string Unit { get; set; } = "";
[Key(8)]
public string CoordinateSpace { get; set; } = "";
}
[MessagePackObject]
public sealed class AetheriaRuntimeDaemonSoaDirtyRangeDocument
{
[Key(0)]
public string ColumnId { get; set; } = "";
[Key(1)]
public int StartIndex { get; set; }
[Key(2)]
public int Count { get; set; }
[Key(3)]
public long Generation { get; set; }
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: aaba35482c606b241adcf479cbfa4298

View file

@ -1,953 +0,0 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using GameCult.Eve.Surface;
using GameCult.Mesh;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
public sealed class AetheriaRuntimeDaemonSoaFrame : IDisposable
{
private CultMeshFrameBodyWriteLease? _write;
public AetheriaRuntimeDaemonSoaFrame(
AetheriaRuntimeDaemonSoaViewDocument view,
CultMeshFrameBodyWriteLease write,
int byteLength)
{
View = view ?? throw new ArgumentNullException(nameof(view));
_write = write ?? throw new ArgumentNullException(nameof(write));
ByteLength = byteLength;
}
public AetheriaRuntimeDaemonSoaViewDocument View { get; }
public int ByteLength { get; }
public CultMeshBodyDescriptor Commit(DateTimeOffset nowUtc)
{
var write = _write ?? throw new ObjectDisposedException(nameof(AetheriaRuntimeDaemonSoaFrame));
var descriptor = write.Commit(ByteLength, nowUtc);
_write = null;
return descriptor;
}
public void Dispose()
{
_write?.Dispose();
_write = null;
}
}
public sealed class AetheriaRuntimeDaemonSoaPublication
{
public AetheriaRuntimeDaemonSoaPublication(
CultMeshBodyPublicationDocument body,
EveEntitySoaViewDocument view,
ReadOnlyMemory<byte> realtimePayload)
{
Body = body ?? throw new ArgumentNullException(nameof(body));
View = view ?? throw new ArgumentNullException(nameof(view));
RealtimePayload = realtimePayload;
}
public CultMeshBodyPublicationDocument Body { get; }
public EveEntitySoaViewDocument View { get; }
public ReadOnlyMemory<byte> RealtimePayload { get; }
}
public sealed class AetheriaRuntimeDaemonSoaFramePublisher : IDisposable
{
public const string BodyId = "eve:entity-soa:aetheria.daemon:pilot";
public const string ProducerId = "aetheria.daemon";
public const string BodySchemaId = "gamecult.eve.entity_soa.body.v2";
public const int LayoutVersion = 3;
private const int Capacity = 4096;
private const int EntityRenderGroupId = 1;
private const int FloatStride = 4;
private const int Float3Stride = 12;
private const int IntStride = 4;
private const int ByteStride = 1;
private const string EntityProxyMeshAssetKey = "daemon.entity_proxy.mesh";
private const string EntityProxyMaterialAssetKey = "daemon.entity_proxy.material";
private const string EntityProxyMeshUri = "cultmesh://aetheria/assets/daemon/entity_proxy/mesh";
private const string EntityProxyMaterialUri = "cultmesh://aetheria/assets/daemon/entity_proxy/material";
private readonly CultMeshFrameBodyPublisher _localPublisher;
private readonly CultMeshBodyDemandTracker? _demand;
private readonly Dictionary<string, int> _syntheticEntityIndices =
new Dictionary<string, int>(StringComparer.Ordinal);
private int _nextSyntheticEntityIndex = -2;
public AetheriaRuntimeDaemonSoaFramePublisher(
long producerEpoch,
CultMeshBodyDemandTracker? demand = null)
{
_localPublisher = new CultMeshFrameBodyPublisher(
BodyId, BodySchemaId, LayoutVersion, Capacity, producerEpoch,
checked((int)EntityHotSlabLayout.Create(Capacity).TotalByteLength),
TimeSpan.FromSeconds(30));
_demand = demand;
}
public CultMeshBodyDemandPlan? CurrentDemandPlan() => _demand?.Plan(BodyId);
public AetheriaRuntimeDaemonSoaFrame? BuildCurrentZoneEntities(
AetheriaRuntimeDaemonFrameDocument frame,
AetheriaRuntimeCatalogSnapshot? catalog = null,
bool realtimeDemand = false)
{
if (frame == null)
throw new ArgumentNullException(nameof(frame));
var demand = _demand?.Plan(BodyId);
if (demand != null &&
(!demand.HasConsumers ||
(!demand.RequiresSharedMemory && !demand.RequiresSharedFileMapping && !realtimeDemand)))
return null;
var run = frame.Run ?? new AetheriaRuntimeRunCheckpointCommit();
var zone = (run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == run.CurrentZoneIndex);
AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(
run.CurrentEntityKey,
out var controlledZoneIndex,
out var controlledEntityIndex);
var controlled = controlledZoneIndex == run.CurrentZoneIndex
? (zone?.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
.FirstOrDefault(entity => entity != null && entity.EntityIndex == controlledEntityIndex)
: null;
var dockParent = (zone?.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
.FirstOrDefault(entity => entity != null && controlled != null &&
(entity.DockingBayAssignments ?? Array.Empty<int>()).Contains(controlled.EntityIndex));
var visibleEntityIndices = AetheriaRuntimeDaemonRenderQueries
.QueryEffectiveContacts(zone, controlled?.EntityIndex ?? -1)
.Where(contact => contact.Contact.Visible)
.Select(contact => contact.Contact.TargetEntityIndex)
.Append(controlled?.EntityIndex ?? -1)
.ToHashSet();
if (dockParent != null)
visibleEntityIndices.Add(dockParent.EntityIndex);
var fieldContributorIndices = (zone?.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
.Where(AetheriaRuntimeGameDocuments.HasActiveFogFieldEmitter)
.Select(entity => entity.EntityIndex)
.ToHashSet();
var entities = (zone?.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
.Where(entity => entity != null &&
(visibleEntityIndices.Contains(entity.EntityIndex) ||
fieldContributorIndices.Contains(entity.EntityIndex)))
.OrderBy(entity => entity.EntityIndex)
.ToArray();
var pickups = (zone?.DroppedPickups ?? Array.Empty<AetheriaRuntimeDroppedPickupCommit>())
.Where(pickup => pickup != null)
.OrderBy(pickup => pickup.PickupIndex)
.ToArray();
var payloads = (zone?.PhysicalPayloads ?? Array.Empty<AetheriaRuntimePhysicalPayloadCommit>())
.Where(payload => payload != null && payload.Active)
.OrderBy(payload => payload.PayloadId, StringComparer.Ordinal)
.ToArray();
var celestialBodies = BuildCelestialBodies(frame, zone);
var asteroidInstances = BuildAsteroidInstances(frame, zone);
var unsupportedPayload = payloads.FirstOrDefault(payload =>
!string.Equals(payload.PayloadKind, "mine", StringComparison.Ordinal));
if (unsupportedPayload != null)
throw new InvalidOperationException(
$"Physical payload '{unsupportedPayload.PayloadId}' uses unsupported provider asset kind '{unsupportedPayload.PayloadKind}'.");
var pickupEntityIds = pickups
.Select(pickup => $"pickup:{run.CurrentZoneIndex}:{pickup.PickupIndex}")
.ToArray();
var payloadEntityIds = payloads
.Select(payload => $"{run.RunId}:zone:{run.CurrentZoneIndex}:physical-payload:{payload.PayloadId}")
.ToArray();
var syntheticEntityIds = pickupEntityIds
.Concat(payloadEntityIds)
.Concat(celestialBodies.Select(value => value.EntityId))
.Concat(asteroidInstances.Select(value => value.EntityId))
.ToArray();
if (syntheticEntityIds.Distinct(StringComparer.Ordinal).Count() != syntheticEntityIds.Length)
throw new InvalidOperationException("Current-zone synthetic SoA entity identities are not unique.");
var syntheticEntityIndices = syntheticEntityIds.ToDictionary(
entityId => entityId,
GetOrAllocateSyntheticEntityIndex,
StringComparer.Ordinal);
var count = entities.Length + pickups.Length + payloads.Length +
celestialBodies.Count + asteroidInstances.Count;
if (count > Capacity)
throw new InvalidOperationException($"Aetheria entity SoA capacity {Capacity} was exceeded by {count} rows.");
var generation = Math.Max(frame.FrameId, 0);
// LayoutVersion owns byte interpretation. Keep offsets and body extent fixed for the
// publisher capacity so a mapped frame may advance before reactive row metadata
// without causing the same layout version to reinterpret its bytes.
var layout = EntityHotSlabLayout.Create(Capacity);
if (!_localPublisher.TryAcquireWrite(out var write))
throw new InvalidOperationException("CultMesh has no unleased frame slot for the Aetheria SoA generation.");
try
{
var bytes = write.Span[..checked((int)layout.TotalByteLength)];
bytes.Clear();
WriteEntities(bytes, layout, entities, visibleEntityIndices);
WritePickups(bytes, layout, pickups, pickupEntityIds, syntheticEntityIndices, entities.Length);
WritePayloads(bytes, layout, payloads, payloadEntityIds, syntheticEntityIndices, entities.Length + pickups.Length);
WriteCelestialBodies(
bytes,
layout,
celestialBodies,
syntheticEntityIndices,
entities.Length + pickups.Length + payloads.Length);
WriteAsteroidInstances(
bytes,
layout,
asteroidInstances,
syntheticEntityIndices,
entities.Length + pickups.Length + payloads.Length + celestialBodies.Count);
var view = AetheriaRuntimeDaemonSoaViewDocument.Create(
string.IsNullOrWhiteSpace(frame.DaemonId) ? "aetheria-daemon" : frame.DaemonId,
string.IsNullOrWhiteSpace(frame.SessionId) ? "local" : frame.SessionId,
frame.FrameId,
generation,
new[]
{
new AetheriaRuntimeDaemonSoaBufferDocument
{
BufferId = BodyId,
DisplayName = "Current zone daemon entity hot slab",
ByteOffset = 0,
ByteLength = layout.TotalByteLength,
Generation = generation,
DaemonWritable = true,
ObserverWritable = false
}
},
layout.CreateColumns(count),
layout.CreateDirtyRanges(count, generation),
backend: AetheriaRuntimeDaemonSoaBackends.CultMesh,
synchronizationMode: AetheriaRuntimeDaemonSoaSynchronizationModes.ImmutableFrame,
renderGroups: CreateRenderGroups(entities, pickups, payloads, celestialBodies, asteroidInstances),
identities: entities.Select(entity => new AetheriaRuntimeDaemonSoaIdentityDocument
{
EntityIndex = entity.EntityIndex,
EntityId = entity.EntityId,
Kind = entity.Kind,
Label = entity.Name,
Faction = entity.FactionKey,
Selectable = visibleEntityIndices.Contains(entity.EntityIndex) &&
entity.EntityIndex != controlled?.EntityIndex,
Controllable = visibleEntityIndices.Contains(entity.EntityIndex) &&
entity.EntityIndex == controlled?.EntityIndex,
AssetRef = AetheriaRuntimeAssets.ResolveEntityPrefabAssetRef(entity, catalog)
})
.Concat(pickups.Select((pickup, pickupRow) => new AetheriaRuntimeDaemonSoaIdentityDocument
{
EntityIndex = syntheticEntityIndices[pickupEntityIds[pickupRow]],
EntityId = pickupEntityIds[pickupRow],
Kind = "pickup",
Label = string.IsNullOrWhiteSpace(pickup.Item?.ItemKey) ? "Pickup" : pickup.Item.ItemKey,
Faction = "",
Selectable = true,
Controllable = false,
AssetRef = "prefab.entity.pickup"
}))
.Concat(payloads.Select((payload, payloadRow) => new AetheriaRuntimeDaemonSoaIdentityDocument
{
EntityIndex = syntheticEntityIndices[payloadEntityIds[payloadRow]],
EntityId = payloadEntityIds[payloadRow],
Kind = "physical-payload",
Label = string.IsNullOrWhiteSpace(payload.PayloadKind) ? "Physical payload" : payload.PayloadKind,
Faction = payload.FactionKey ?? "",
Selectable = false,
Controllable = false,
AssetRef = "prefab.entity.mine"
}))
.Concat(celestialBodies.Select(value => new AetheriaRuntimeDaemonSoaIdentityDocument
{
EntityIndex = syntheticEntityIndices[value.EntityId],
EntityId = value.EntityId,
Kind = value.EntityKind,
Label = value.Label,
Faction = "",
Selectable = false,
Controllable = false,
AssetRef = value.AssetRef
}))
.Concat(asteroidInstances.Select(value => new AetheriaRuntimeDaemonSoaIdentityDocument
{
EntityIndex = syntheticEntityIndices[value.EntityId],
EntityId = value.EntityId,
Kind = "celestial.asteroid",
Label = "Asteroid",
Faction = "",
Selectable = false,
Controllable = false,
AssetRef = "prefab.body.asteroid"
}))
.ToArray());
return new AetheriaRuntimeDaemonSoaFrame(
view,
write,
checked((int)layout.TotalByteLength));
}
catch
{
write.Dispose();
throw;
}
}
public Task<AetheriaRuntimeDaemonSoaPublication> PublishAsync(
AetheriaRuntimeDaemonSoaFrame frame,
bool includeRealtimePayload = false)
{
if (frame == null) throw new ArgumentNullException(nameof(frame));
var now = DateTimeOffset.UtcNow;
var local = frame.Commit(now);
var generation = new CultMeshBodyGeneration
{
BodyId = BodyId,
ProducerId = ProducerId,
SchemaId = BodySchemaId,
LayoutVersion = LayoutVersion,
Capacity = Capacity,
ProducerEpoch = local.ProducerEpoch,
Sequence = local.Sequence,
Synchronization = local.Synchronization,
LeaseExpiresAtUnixMs = local.LeaseExpiresAtUnixMs
};
var publication = new CultMeshBodyPublicationDocument
{
BodyId = BodyId,
ProducerId = ProducerId,
SchemaId = BodySchemaId,
LayoutVersion = LayoutVersion,
ByteSize = local.ByteSize,
Capacity = Capacity,
ProducerEpoch = local.ProducerEpoch,
Sequence = local.Sequence,
Synchronization = local.Synchronization,
LivenessExpiresAtUnixMs = local.LeaseExpiresAtUnixMs,
Representations = new[] { local }
};
new CultMeshBodyPublicationHandle(BodyId, publication.ProducerEpoch, publication.Sequence)
.Validate(publication);
var view = AetheriaRuntimeEveEntitySoaProjection.Project(frame.View, generation);
if (view.Buffers.Length != 1 || !string.Equals(view.Buffers[0].BufferId, publication.BodyId, StringComparison.Ordinal) ||
view.Columns.Any(column => !string.Equals(column.BufferId, publication.BodyId, StringComparison.Ordinal)))
throw new InvalidOperationException("Aetheria Eve SoA layout does not reference its published logical body identity.");
var realtimePayload = includeRealtimePayload
? CopyRealtimePayload(local, now)
: ReadOnlyMemory<byte>.Empty;
return Task.FromResult(new AetheriaRuntimeDaemonSoaPublication(publication, view, realtimePayload));
}
private static ReadOnlyMemory<byte> CopyRealtimePayload(CultMeshBodyDescriptor descriptor, DateTimeOffset now)
{
var bytes = new byte[checked((int)descriptor.ByteSize)];
using var lease = new CultMeshSharedMemoryBodyAdapter().OpenReadOnly(
descriptor,
new CultMeshBodyValidationRequest
{
BodyId = descriptor.BodyId,
SchemaId = descriptor.SchemaId,
LayoutVersion = descriptor.LayoutVersion,
ProducerEpoch = descriptor.ProducerEpoch,
Sequence = descriptor.Sequence,
Capacity = descriptor.Capacity,
AccessMode = CultMeshBodyAccessMode.ReadOnly,
NowUtc = now
});
var copied = lease.CopyTo(0, bytes, 0, bytes.Length);
if (copied != bytes.Length)
throw new EndOfStreamException($"CultMesh copied {copied} of {bytes.Length} realtime SoA bytes.");
return bytes;
}
public void Dispose() => _localPublisher.Dispose();
private static IReadOnlyList<AetheriaRuntimeDaemonRenderGroupDocument> CreateRenderGroups(
IReadOnlyList<AetheriaRuntimeEntitySnapshotCommit> entities,
IReadOnlyList<AetheriaRuntimeDroppedPickupCommit> pickups,
IReadOnlyList<AetheriaRuntimePhysicalPayloadCommit> payloads,
IReadOnlyList<CelestialBodyPresentation> celestialBodies,
IReadOnlyList<AsteroidPresentation> asteroidInstances)
{
if (entities.Count == 0 && pickups.Count == 0 && payloads.Count == 0 &&
celestialBodies.Count == 0 && asteroidInstances.Count == 0)
return Array.Empty<AetheriaRuntimeDaemonRenderGroupDocument>();
var positions = entities.Select(entity => (entity.PositionX, entity.PositionY, entity.PositionZ))
.Concat(pickups.Select(pickup => (pickup.PositionX, pickup.PositionY, pickup.PositionZ)))
.Concat(payloads.Select(payload => (payload.PositionX, PositionY: payload.PositionY, payload.PositionZ)))
.Concat(celestialBodies.Select(value => (value.PositionX, value.PositionY, value.PositionZ)))
.Concat(asteroidInstances.Select(value => (value.PositionX, value.PositionY, value.PositionZ)))
.ToArray();
var minX = positions.Min(position => (float)position.PositionX);
var minY = positions.Min(position => (float)position.PositionY);
var minZ = positions.Min(position => (float)position.PositionZ);
var maxX = positions.Max(position => (float)position.PositionX);
var maxY = positions.Max(position => (float)position.PositionY);
var maxZ = positions.Max(position => (float)position.PositionZ);
const float boundsCell = 1024.0f;
var boundsMinX = MathF.Floor((minX - 16.0f) / boundsCell) * boundsCell;
var boundsMinY = MathF.Floor((minY - 16.0f) / boundsCell) * boundsCell;
var boundsMinZ = MathF.Floor((minZ - 16.0f) / boundsCell) * boundsCell;
var boundsMaxX = MathF.Ceiling((maxX + 16.0f) / boundsCell) * boundsCell;
var boundsMaxY = MathF.Ceiling((maxY + 16.0f) / boundsCell) * boundsCell;
var boundsMaxZ = MathF.Ceiling((maxZ + 16.0f) / boundsCell) * boundsCell;
return new[]
{
new AetheriaRuntimeDaemonRenderGroupDocument
{
GroupId = EntityRenderGroupId,
MeshKey = EntityProxyMeshUri,
MaterialKey = EntityProxyMaterialUri,
MeshAsset = AetheriaRuntimeAssetRef.FromKey(
EntityProxyMeshAssetKey,
AetheriaRuntimeAssetKinds.Mesh,
EntityProxyMeshUri,
AetheriaRuntimeAssetTransports.CultMesh),
MaterialAsset = AetheriaRuntimeAssetRef.FromKey(
EntityProxyMaterialAssetKey,
AetheriaRuntimeAssetKinds.Material,
EntityProxyMaterialUri,
AetheriaRuntimeAssetTransports.CultMesh),
SubMeshIndex = 0,
Layer = 0,
ShaderKey = "aetheria.daemon.entity-proxy",
DisplayName = "Daemon current-zone entities",
InstanceCount = entities.Count + pickups.Count + payloads.Count +
celestialBodies.Count + asteroidInstances.Count,
BoundsCenterX = (boundsMinX + boundsMaxX) * 0.5f,
BoundsCenterY = (boundsMinY + boundsMaxY) * 0.5f,
BoundsCenterZ = (boundsMinZ + boundsMaxZ) * 0.5f,
BoundsSizeX = Math.Max(boundsMaxX - boundsMinX, boundsCell),
BoundsSizeY = Math.Max(boundsMaxY - boundsMinY, boundsCell),
BoundsSizeZ = Math.Max(boundsMaxZ - boundsMinZ, boundsCell),
ShadowMode = AetheriaRuntimeDaemonRenderShadowModes.On,
ReceiveShadows = true,
DefaultScale = 1.0f,
Lod = -1
}
};
}
private static void WriteEntities(
Span<byte> bytes,
EntityHotSlabLayout layout,
IReadOnlyList<AetheriaRuntimeEntitySnapshotCommit> entities,
ISet<int> visibleEntityIndices)
{
for (var index = 0; index < entities.Count; index++)
{
var entity = entities[index];
WriteInt32(bytes, layout.EntityIndex + index * IntStride, entity.EntityIndex);
WriteInt32(bytes, layout.CargoQuantity + index * IntStride, AetheriaRuntimeCargoCapacityQueries.Quantity(entity));
WriteFloat(bytes, layout.BeamPower, index, entity.TractorPower);
WriteFloat3(bytes, layout.Position, index, entity.PositionX, entity.PositionY, entity.PositionZ);
WriteFloat(bytes, layout.RotationRadians, index, Math.Atan2(entity.DirectionX, entity.DirectionY));
WriteFloat3(bytes, layout.Velocity, index, entity.VelocityX, 0.0, entity.VelocityY);
WriteFloat(bytes, layout.PhysicsBodyRadius, index, 1.0);
WriteFloat(bytes, layout.PhysicsBodyMass, index, 1.0);
WriteFloat(bytes, layout.PhysicsBodyInverseMass, index, 1.0);
WriteFloat(bytes, layout.RenderScale, index, 1.0);
bytes[checked((int)(layout.RenderVisibility + index * ByteStride))] =
(byte)(entity.IsActive && visibleEntityIndices.Contains(entity.EntityIndex) ? 1 : 0);
WriteInt32(bytes, layout.RenderLod + index * IntStride, 0);
WriteUInt32(bytes, layout.RenderGroupId + index * IntStride, EntityRenderGroupId);
}
}
private static void WritePickups(
Span<byte> bytes,
EntityHotSlabLayout layout,
IReadOnlyList<AetheriaRuntimeDroppedPickupCommit> pickups,
IReadOnlyList<string> pickupEntityIds,
IReadOnlyDictionary<string, int> syntheticEntityIndices,
int rowOffset)
{
for (var pickupRow = 0; pickupRow < pickups.Count; pickupRow++)
{
var pickup = pickups[pickupRow];
var row = rowOffset + pickupRow;
WriteInt32(bytes, layout.EntityIndex + row * IntStride, syntheticEntityIndices[pickupEntityIds[pickupRow]]);
WriteInt32(bytes, layout.CargoQuantity + row * IntStride, 0);
WriteFloat3(bytes, layout.Position, row, pickup.PositionX, pickup.PositionY, pickup.PositionZ);
WriteFloat(bytes, layout.RotationRadians, row, 0.0);
WriteFloat3(bytes, layout.Velocity, row, pickup.VelocityX, pickup.VelocityY, pickup.VelocityZ);
WriteFloat(bytes, layout.PhysicsBodyRadius, row, 0.5);
WriteFloat(bytes, layout.PhysicsBodyMass, row, 0.25);
WriteFloat(bytes, layout.PhysicsBodyInverseMass, row, 4.0);
WriteFloat(bytes, layout.RenderScale, row, 1.0);
bytes[checked((int)(layout.RenderVisibility + row * ByteStride))] = 1;
WriteInt32(bytes, layout.RenderLod + row * IntStride, 0);
WriteUInt32(bytes, layout.RenderGroupId + row * IntStride, EntityRenderGroupId);
}
}
private static void WritePayloads(
Span<byte> bytes,
EntityHotSlabLayout layout,
IReadOnlyList<AetheriaRuntimePhysicalPayloadCommit> payloads,
IReadOnlyList<string> payloadEntityIds,
IReadOnlyDictionary<string, int> syntheticEntityIndices,
int rowOffset)
{
for (var payloadRow = 0; payloadRow < payloads.Count; payloadRow++)
{
var payload = payloads[payloadRow];
var row = rowOffset + payloadRow;
WriteInt32(bytes, layout.EntityIndex + row * IntStride, syntheticEntityIndices[payloadEntityIds[payloadRow]]);
WriteInt32(bytes, layout.CargoQuantity + row * IntStride, 0);
WriteFloat3(bytes, layout.Position, row, payload.PositionX, payload.PositionY, payload.PositionZ);
WriteFloat(bytes, layout.RotationRadians, row, Math.Atan2(payload.DirectionX, payload.DirectionY));
WriteFloat3(bytes, layout.Velocity, row, payload.VelocityX, 0.0, payload.VelocityY);
WriteFloat(bytes, layout.PhysicsBodyRadius, row, Math.Max(0.01, payload.Radius));
WriteFloat(bytes, layout.PhysicsBodyMass, row, payload.Stationary ? 0.0 : 1.0);
WriteFloat(bytes, layout.PhysicsBodyInverseMass, row, payload.Stationary ? 0.0 : 1.0);
WriteFloat(bytes, layout.RenderScale, row, 1.0);
bytes[checked((int)(layout.RenderVisibility + row * ByteStride))] = 1;
WriteInt32(bytes, layout.RenderLod + row * IntStride, 0);
WriteUInt32(bytes, layout.RenderGroupId + row * IntStride, EntityRenderGroupId);
}
}
private static IReadOnlyList<CelestialBodyPresentation> BuildCelestialBodies(
AetheriaRuntimeDaemonFrameDocument frame,
AetheriaRuntimeZoneSnapshotCommit? zone)
{
if (zone == null)
return Array.Empty<CelestialBodyPresentation>();
var poses = AetheriaRuntimeDaemonRenderQueries.QueryBodyPoses(zone)
.ToDictionary(pose => pose.BodyKey, StringComparer.Ordinal);
var values = new List<CelestialBodyPresentation>();
foreach (var body in zone.Bodies ?? Array.Empty<AetheriaRuntimeBodySnapshotCommit>())
{
if (body == null ||
string.Equals(body.Kind, "asteroid_belt", StringComparison.OrdinalIgnoreCase) ||
string.IsNullOrWhiteSpace(body.BodyKey) ||
!poses.TryGetValue(body.BodyKey, out var pose))
continue;
var scale = Math.Max(
0.1,
frame.RenderSettings.ResolveBodyRadius(body.Mass) * Math.Max(0, body.BodyRadiusMultiplier));
var terrainHeight = AetheriaRuntimeDaemonRenderQueries.EvaluateGravityTerrainHeight(
zone,
pose.CenterX,
pose.CenterZ,
frame.SimulationTimeSeconds);
var kind = NormalizeCelestialKind(body.Kind);
values.Add(new CelestialBodyPresentation(
$"{frame.Run.RunId}:zone:{zone.ZoneIndex}:body:{body.BodyKey}",
kind,
string.IsNullOrWhiteSpace(body.Name) ? body.BodyKey : body.Name,
ResolveCelestialAssetRef(kind),
pose.CenterX,
terrainHeight + scale * 2.0,
pose.CenterZ,
frame.SimulationTimeSeconds * frame.RenderSettings.PlanetRotationSpeed * Math.PI / 180.0,
scale));
}
return values;
}
private static IReadOnlyList<AsteroidPresentation> BuildAsteroidInstances(
AetheriaRuntimeDaemonFrameDocument frame,
AetheriaRuntimeZoneSnapshotCommit? zone)
{
if (zone == null)
return Array.Empty<AsteroidPresentation>();
var values = new List<AsteroidPresentation>();
foreach (var belt in zone.Bodies ?? Array.Empty<AetheriaRuntimeBodySnapshotCommit>())
{
if (belt == null ||
!string.Equals(belt.Kind, "asteroid_belt", StringComparison.OrdinalIgnoreCase) ||
string.IsNullOrWhiteSpace(belt.BodyKey))
continue;
foreach (var pose in AetheriaRuntimeDaemonRenderQueries.QueryAsteroidInstancePoses(
zone,
belt.BodyKey,
frame.SimulationTimeSeconds))
{
if (pose.Size <= 0)
continue;
var terrainHeight = AetheriaRuntimeDaemonRenderQueries.EvaluateGravityTerrainHeight(
zone,
pose.PositionX,
pose.PositionZ,
frame.SimulationTimeSeconds);
values.Add(new AsteroidPresentation(
$"{frame.Run.RunId}:zone:{zone.ZoneIndex}:asteroid:{belt.BodyKey}:{pose.AsteroidIndex}",
pose.PositionX,
terrainHeight + frame.RenderSettings.AsteroidVerticalOffset,
pose.PositionZ,
pose.Rotation,
pose.Size));
}
}
return values;
}
private static string NormalizeCelestialKind(string? kind)
{
var normalized = (kind ?? "").Trim().ToLowerInvariant().Replace('_', '-');
return normalized switch
{
"sun" => "sun",
"gas-giant" => "gas-giant",
_ => "planet"
};
}
private static string ResolveCelestialAssetRef(string kind)
{
return kind switch
{
"sun" => "prefab.body.sun",
"gas-giant" => "prefab.body.gas-giant",
_ => "prefab.body.planet"
};
}
private static void WriteCelestialBodies(
Span<byte> bytes,
EntityHotSlabLayout layout,
IReadOnlyList<CelestialBodyPresentation> values,
IReadOnlyDictionary<string, int> syntheticEntityIndices,
int rowOffset)
{
for (var valueIndex = 0; valueIndex < values.Count; valueIndex++)
{
var value = values[valueIndex];
var row = rowOffset + valueIndex;
WritePresentationRow(
bytes,
layout,
row,
syntheticEntityIndices[value.EntityId],
value.PositionX,
value.PositionY,
value.PositionZ,
value.RotationRadians,
value.Scale);
}
}
private static void WriteAsteroidInstances(
Span<byte> bytes,
EntityHotSlabLayout layout,
IReadOnlyList<AsteroidPresentation> values,
IReadOnlyDictionary<string, int> syntheticEntityIndices,
int rowOffset)
{
for (var valueIndex = 0; valueIndex < values.Count; valueIndex++)
{
var value = values[valueIndex];
var row = rowOffset + valueIndex;
WritePresentationRow(
bytes,
layout,
row,
syntheticEntityIndices[value.EntityId],
value.PositionX,
value.PositionY,
value.PositionZ,
value.RotationRadians,
value.Scale);
}
}
private static void WritePresentationRow(
Span<byte> bytes,
EntityHotSlabLayout layout,
int row,
int entityIndex,
double positionX,
double positionY,
double positionZ,
double rotationRadians,
double scale)
{
WriteInt32(bytes, layout.EntityIndex + row * IntStride, entityIndex);
WriteInt32(bytes, layout.CargoQuantity + row * IntStride, 0);
WriteFloat3(bytes, layout.Position, row, positionX, positionY, positionZ);
WriteFloat(bytes, layout.RotationRadians, row, rotationRadians);
WriteFloat3(bytes, layout.Velocity, row, 0, 0, 0);
WriteFloat(bytes, layout.PhysicsBodyRadius, row, Math.Max(0.01, scale));
WriteFloat(bytes, layout.PhysicsBodyMass, row, 0);
WriteFloat(bytes, layout.PhysicsBodyInverseMass, row, 0);
WriteFloat(bytes, layout.RenderScale, row, Math.Max(0.01, scale));
bytes[checked((int)(layout.RenderVisibility + row * ByteStride))] = 1;
WriteInt32(bytes, layout.RenderLod + row * IntStride, 0);
WriteUInt32(bytes, layout.RenderGroupId + row * IntStride, EntityRenderGroupId);
}
private int GetOrAllocateSyntheticEntityIndex(string entityId)
{
if (_syntheticEntityIndices.TryGetValue(entityId, out var existing))
return existing;
if (_nextSyntheticEntityIndex == int.MinValue)
throw new InvalidOperationException("Aetheria SoA synthetic entity index space is exhausted.");
var allocated = _nextSyntheticEntityIndex--;
_syntheticEntityIndices.Add(entityId, allocated);
return allocated;
}
private sealed class CelestialBodyPresentation
{
public CelestialBodyPresentation(
string entityId,
string kind,
string label,
string assetRef,
double positionX,
double positionY,
double positionZ,
double rotationRadians,
double scale)
{
EntityId = entityId;
EntityKind = "celestial." + kind;
Label = label;
AssetRef = assetRef;
PositionX = positionX;
PositionY = positionY;
PositionZ = positionZ;
RotationRadians = rotationRadians;
Scale = scale;
}
public string EntityId { get; }
public string EntityKind { get; }
public string Label { get; }
public string AssetRef { get; }
public double PositionX { get; }
public double PositionY { get; }
public double PositionZ { get; }
public double RotationRadians { get; }
public double Scale { get; }
}
private sealed class AsteroidPresentation
{
public AsteroidPresentation(
string entityId,
double positionX,
double positionY,
double positionZ,
double rotationRadians,
double scale)
{
EntityId = entityId;
PositionX = positionX;
PositionY = positionY;
PositionZ = positionZ;
RotationRadians = rotationRadians;
Scale = scale;
}
public string EntityId { get; }
public double PositionX { get; }
public double PositionY { get; }
public double PositionZ { get; }
public double RotationRadians { get; }
public double Scale { get; }
}
private static void WriteFloat(Span<byte> bytes, long byteOffset, int index, double value)
{
var scalar = IsFinite(value) ? (float)value : 0.0f;
BinaryPrimitives.WriteInt32LittleEndian(
bytes.Slice(checked((int)(byteOffset + index * FloatStride)), FloatStride),
BitConverter.SingleToInt32Bits(scalar));
}
private static void WriteFloat3(
Span<byte> bytes,
long byteOffset,
int index,
double x,
double y,
double z)
{
var elementOffset = byteOffset + index * Float3Stride;
WriteFloat(bytes, elementOffset, 0, x);
WriteFloat(bytes, elementOffset + FloatStride, 0, y);
WriteFloat(bytes, elementOffset + FloatStride * 2, 0, z);
}
private static void WriteInt32(Span<byte> bytes, long offset, int value) =>
BinaryPrimitives.WriteInt32LittleEndian(bytes.Slice(checked((int)offset), IntStride), value);
private static void WriteUInt32(Span<byte> bytes, long offset, int value) =>
BinaryPrimitives.WriteUInt32LittleEndian(bytes.Slice(checked((int)offset), IntStride), (uint)value);
private static bool IsFinite(double value)
{
return !double.IsNaN(value) && !double.IsInfinity(value);
}
private readonly struct EntityHotSlabLayout
{
private EntityHotSlabLayout(
long entityIndex,
long cargoQuantity,
long beamPower,
long position,
long rotationRadians,
long velocity,
long physicsBodyRadius,
long physicsBodyMass,
long physicsBodyInverseMass,
long renderScale,
long renderVisibility,
long renderLod,
long renderGroupId,
long totalByteLength)
{
EntityIndex = entityIndex;
CargoQuantity = cargoQuantity;
BeamPower = beamPower;
Position = position;
RotationRadians = rotationRadians;
Velocity = velocity;
PhysicsBodyRadius = physicsBodyRadius;
PhysicsBodyMass = physicsBodyMass;
PhysicsBodyInverseMass = physicsBodyInverseMass;
RenderScale = renderScale;
RenderVisibility = renderVisibility;
RenderLod = renderLod;
RenderGroupId = renderGroupId;
TotalByteLength = totalByteLength;
}
public long EntityIndex { get; }
public long CargoQuantity { get; }
public long BeamPower { get; }
public long Position { get; }
public long RotationRadians { get; }
public long Velocity { get; }
public long PhysicsBodyRadius { get; }
public long PhysicsBodyMass { get; }
public long PhysicsBodyInverseMass { get; }
public long RenderScale { get; }
public long RenderVisibility { get; }
public long RenderLod { get; }
public long RenderGroupId { get; }
public long TotalByteLength { get; }
public static EntityHotSlabLayout Create(int count)
{
count = Math.Max(0, count);
var offset = 0L;
var entityIndex = Take(ref offset, count, IntStride);
var cargoQuantity = Take(ref offset, count, IntStride);
var beamPower = Take(ref offset, count, FloatStride);
var position = Take(ref offset, count, Float3Stride);
var rotationRadians = Take(ref offset, count, FloatStride);
var velocity = Take(ref offset, count, Float3Stride);
var physicsBodyRadius = Take(ref offset, count, FloatStride);
var physicsBodyMass = Take(ref offset, count, FloatStride);
var physicsBodyInverseMass = Take(ref offset, count, FloatStride);
var renderScale = Take(ref offset, count, FloatStride);
var renderVisibility = Take(ref offset, count, ByteStride);
offset = Align4(offset);
var renderLod = Take(ref offset, count, IntStride);
var renderGroupId = Take(ref offset, count, IntStride);
return new EntityHotSlabLayout(
entityIndex,
cargoQuantity,
beamPower,
position,
rotationRadians,
velocity,
physicsBodyRadius,
physicsBodyMass,
physicsBodyInverseMass,
renderScale,
renderVisibility,
renderLod,
renderGroupId,
Math.Max(1, offset));
}
public AetheriaRuntimeDaemonSoaColumnDocument[] CreateColumns(int count)
{
return new[]
{
Column("entity-index", AetheriaRuntimeDaemonSoaColumnKinds.EntityIndex, "int32", EntityIndex, IntStride, count, "index", "world"),
Column("cargo-quantity", AetheriaRuntimeDaemonSoaColumnKinds.CargoQuantity, "int32", CargoQuantity, IntStride, count, "items", "entity"),
Column("beam-power", AetheriaRuntimeDaemonSoaColumnKinds.BeamPower, "float32", BeamPower, FloatStride, count, "normalized", "entity"),
Column("position", AetheriaRuntimeDaemonSoaColumnKinds.Position, "float3", Position, Float3Stride, count, "world_units", "world"),
Column("rotation-radians", AetheriaRuntimeDaemonSoaColumnKinds.RotationRadians, "float32", RotationRadians, FloatStride, count, "radians", "world"),
Column("velocity", AetheriaRuntimeDaemonSoaColumnKinds.Velocity, "float3", Velocity, Float3Stride, count, "world_units_per_second", "world"),
Column("physics-body-radius", AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyRadius, "float32", PhysicsBodyRadius, FloatStride, count, "world_units", "world"),
Column("physics-body-mass", AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyMass, "float32", PhysicsBodyMass, FloatStride, count, "mass_units", "world"),
Column("physics-body-inverse-mass", AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyInverseMass, "float32", PhysicsBodyInverseMass, FloatStride, count, "inverse_mass_units", "world"),
Column("render-scale", AetheriaRuntimeDaemonSoaColumnKinds.RenderScale, "float32", RenderScale, FloatStride, count, "scale", "world"),
Column("render-visibility", AetheriaRuntimeDaemonSoaColumnKinds.RenderVisibility, "uint8", RenderVisibility, ByteStride, count, "bool", "world"),
Column("render-lod", AetheriaRuntimeDaemonSoaColumnKinds.RenderLod, "int32", RenderLod, IntStride, count, "lod", "world"),
Column("render-group-id", AetheriaRuntimeDaemonSoaColumnKinds.RenderGroupId, "uint32", RenderGroupId, IntStride, count, "id", "world")
};
}
public AetheriaRuntimeDaemonSoaDirtyRangeDocument[] CreateDirtyRanges(int count, long generation)
{
return CreateColumns(count)
.Select(column => new AetheriaRuntimeDaemonSoaDirtyRangeDocument
{
ColumnId = column.ColumnId,
StartIndex = 0,
Count = count,
Generation = generation
})
.ToArray();
}
private static AetheriaRuntimeDaemonSoaColumnDocument Column(
string columnId,
string kind,
string scalarType,
long byteOffset,
int stride,
int count,
string unit,
string coordinateSpace)
{
return new AetheriaRuntimeDaemonSoaColumnDocument
{
ColumnId = columnId,
Kind = kind,
BufferId = BodyId,
ScalarType = scalarType,
ByteOffset = byteOffset,
ElementStride = stride,
ElementCount = count,
Unit = unit,
CoordinateSpace = coordinateSpace
};
}
private static long Take(ref long offset, int count, int stride)
{
var current = offset;
offset += (long)count * stride;
return current;
}
private static long Align4(long value)
{
var remainder = value % 4;
return remainder == 0 ? value : value + 4 - remainder;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more