diff --git a/Aetheria.State.Daemon/Aetheria.State.Daemon.csproj b/Aetheria.State.Daemon/Aetheria.State.Daemon.csproj new file mode 100644 index 000000000..ca788873d --- /dev/null +++ b/Aetheria.State.Daemon/Aetheria.State.Daemon.csproj @@ -0,0 +1,13 @@ + + + Exe + net10.0 + enable + latest + enable + + + + + + diff --git a/Aetheria.State.Daemon/Program.cs b/Aetheria.State.Daemon/Program.cs new file mode 100644 index 000000000..7185e7c4e --- /dev/null +++ b/Aetheria.State.Daemon/Program.cs @@ -0,0 +1,402 @@ +using Aetheria.State; +using Aetheria.State.Documents; +using GameCult.Aetheria.State.Unity; + +var options = AetheriaDaemonHostOptions.Parse(args); +var startedAtUtc = DateTimeOffset.UtcNow.ToString("O"); + +Console.WriteLine($"Aetheria Verse daemon starting: {options.StatePath}"); + +await using var node = await AetheriaStateNode.OpenAsync( + options.StatePath, + runtimeId: options.DaemonId, + startServer: true).ConfigureAwait(false); +using var discoveryHost = new AetheriaVerseDiscoveryHost(node); + +await EnsureWorldDocumentAsync(node).ConfigureAwait(false); +var verseHost = await EnsureVerseHostSettingsAsync(node, options, startedAtUtc).ConfigureAwait(false); +discoveryHost.Update(verseHost); +await PublishRuntimeSessionAsync(node, options, startedAtUtc, "starting").ConfigureAwait(false); +await PublishStateSurfacesAsync(node, options, startedAtUtc).ConfigureAwait(false); +var firstTick = await TickAsync(node, options).ConfigureAwait(false); +Console.WriteLine($"Aetheria Verse daemon published frame {firstTick.Frame.FrameId}."); + +if (options.Once) +{ + await PublishRuntimeSessionAsync(node, options, startedAtUtc, "completed").ConfigureAwait(false); + return; +} + +var stopped = new TaskCompletionSource(); +Console.CancelKeyPress += (_, eventArgs) => +{ + eventArgs.Cancel = true; + stopped.TrySetResult(null); +}; + +await PublishRuntimeSessionAsync(node, options, startedAtUtc, "running").ConfigureAwait(false); +Console.WriteLine("Aetheria Verse daemon is running. Press Ctrl+C to stop."); + +while (!stopped.Task.IsCompleted) +{ + var completed = await Task.WhenAny(stopped.Task, Task.Delay(options.TickInterval)).ConfigureAwait(false); + if (completed == stopped.Task) + break; + + var tick = await TickAsync(node, options).ConfigureAwait(false); + discoveryHost.Update(await node.GetVerseHostSettingsAsync().ConfigureAwait(false)); + await PublishRuntimeSessionAsync(node, options, startedAtUtc, "running").ConfigureAwait(false); + Console.WriteLine($"Aetheria Verse daemon published frame {tick.Frame.FrameId}."); +} + +await PublishRuntimeSessionAsync(node, options, startedAtUtc, "stopping").ConfigureAwait(false); +Console.WriteLine("Aetheria Verse daemon stopping."); + +static async Task TickAsync( + AetheriaStateNode node, + AetheriaDaemonHostOptions options) +{ + await AcceptEveCommandsAsync(node, options).ConfigureAwait(false); + + var currentFrame = AetheriaRuntimeDaemonFrameStore.TryReadFrame(node.StatePath, out var frame) + ? frame + : null; + var fixedDeltaSeconds = currentFrame?.FixedDeltaSeconds > 0 + ? currentFrame.FixedDeltaSeconds + : options.FixedDeltaSeconds; + var nextFrameId = (currentFrame?.FrameId ?? -1) + 1; + var simulationTimeSeconds = (currentFrame?.SimulationTimeSeconds ?? 0) + fixedDeltaSeconds; + var sessionId = string.IsNullOrWhiteSpace(currentFrame?.SessionId) + ? options.SessionId + : currentFrame.SessionId; + var run = currentFrame?.Run ?? new AetheriaRuntimeRunCheckpointCommit(); + + var loadoutTemplates = node.Cache + .GetAll() + .Select(ToLoadoutTemplateCommit) + .ToArray(); + var observedCommands = node.ReadObservedDaemonCommands(); + + var result = AetheriaRuntimeDaemonTickRunner.Tick( + node.StatePath, + run, + new AetheriaRuntimeDaemonTickOptions + { + DaemonId = options.DaemonId, + SessionId = sessionId, + VerseId = options.VerseId, + CultMeshAddress = options.CultMeshAddress, + FrameId = nextFrameId, + SimulationTimeSeconds = simulationTimeSeconds, + FixedDeltaSeconds = fixedDeltaSeconds, + ObservedCommands = observedCommands, + AccountedCommandIds = currentFrame?.AccountedCommandIds ?? Array.Empty(), + OperationContext = new AetheriaRuntimeDaemonOperationContext + { + LoadoutTemplates = loadoutTemplates + } + }); + + await PublishDaemonApiDocumentsAsync(node, result).ConfigureAwait(false); + await PublishStateSurfacesAsync(node, options, result.Frame.PublishedAtUtc).ConfigureAwait(false); + return result; +} + +static async Task PublishDaemonApiDocumentsAsync( + AetheriaStateNode node, + AetheriaRuntimeDaemonTickResult result) +{ + await node.PutDaemonFrameAsync(result.Frame).ConfigureAwait(false); + if (AetheriaRuntimeDaemonSoaViewStore.TryReadView(node.StatePath, out var soaView) && + string.Equals(soaView.Schema, AetheriaRuntimeDaemonSchemas.SoaView, StringComparison.Ordinal)) + { + await node.PutDaemonSoaViewAsync(soaView).ConfigureAwait(false); + } + + if (AetheriaRuntimeDaemonPublicationStore.TryReadProviderAdvertisement(node.StatePath, out var provider)) + await node.PutDaemonProviderAdvertisementAsync(provider).ConfigureAwait(false); + if (AetheriaRuntimeDaemonPublicationStore.TryReadHealth(node.StatePath, out var health)) + await node.PutDaemonHealthAsync(health).ConfigureAwait(false); + if (AetheriaRuntimeDaemonPublicationStore.TryReadCommandBoundary(node.StatePath, out var commandBoundary)) + await node.PutDaemonCommandBoundaryAsync(commandBoundary).ConfigureAwait(false); + if (AetheriaRuntimeDaemonPublicationStore.TryReadGameSurface(node.StatePath, out var gameSurface)) + await node.PutDaemonGameSurfaceAsync(AetheriaRuntimeEveSurfaceStateProjector.ToState(gameSurface)).ConfigureAwait(false); + if (AetheriaRuntimeDaemonPublicationStore.TryReadGameTuiSurface(node.StatePath, out var gameTuiSurface)) + await node.PutDaemonGameTuiSurfaceAsync(AetheriaRuntimeEveSurfaceStateProjector.ToState(gameTuiSurface)).ConfigureAwait(false); + if (AetheriaRuntimeDaemonPublicationStore.TryReadEditorSurface(node.StatePath, out var editorSurface)) + await node.PutDaemonEditorSurfaceAsync(AetheriaRuntimeEveSurfaceStateProjector.ToState(editorSurface)).ConfigureAwait(false); + if (AetheriaRuntimeDaemonPublicationStore.TryReadEditorTuiSurface(node.StatePath, out var editorTuiSurface)) + await node.PutDaemonEditorTuiSurfaceAsync(AetheriaRuntimeEveSurfaceStateProjector.ToState(editorTuiSurface)).ConfigureAwait(false); +} + +static async Task AcceptEveCommandsAsync(AetheriaStateNode node, AetheriaDaemonHostOptions options) +{ + var commandCountBefore = node.ReadObservedEveCommands().Count; + var now = DateTimeOffset.UtcNow.ToString("O"); + try + { + var existingStatus = await node.GetEveCommandAcceptanceStatusAsync().ConfigureAwait(false); + var report = await AetheriaEveCommandBridge.AcceptObservedAsync( + node, + existingStatus?.AccountedCommandIds) + .ConfigureAwait(false); + await node.PutEveCommandAcceptanceStatusAsync(new AetheriaEveCommandAcceptanceStatus + { + RuntimeId = options.DaemonId, + StatePath = node.StatePath, + LastPollAtUtc = now, + LastAcceptedAtUtc = report.AcceptedCommandIds.Length > 0 ? now : "", + ObservedBeforeAccept = commandCountBefore, + CommandsAccepted = report.AcceptedCommandIds.Length, + CommandsRejected = report.RejectedCommands, + AppliedCatalogRefreshes = report.AcceptedCatalogRefreshes, + AppliedOperationsRefreshes = report.AcceptedOperationsRefreshes, + AppliedPlayerSettingsCommands = report.AcceptedPlayerSettingsCommands, + AppliedInputSettingsCommands = report.AcceptedInputSettingsCommands, + AppliedLoadoutTemplateCommands = report.AcceptedLoadoutTemplateCommands, + AppliedVerseHostCommands = report.AcceptedVerseHostCommands, + AccountedCommandIds = report.AccountedCommandIds, + LastRejectedCommand = report.LastRejectedCommand, + LastRejectedReason = report.LastRejectedReason, + ConsecutiveFailures = 0, + Status = report.RejectedCommands > 0 ? "rejected" : "ok" + }).ConfigureAwait(false); + } + catch (Exception ex) + { + var existing = await node.GetEveCommandAcceptanceStatusAsync().ConfigureAwait(false); + await node.PutEveCommandAcceptanceStatusAsync(new AetheriaEveCommandAcceptanceStatus + { + RuntimeId = options.DaemonId, + StatePath = node.StatePath, + LastPollAtUtc = now, + LastAcceptedAtUtc = existing?.LastAcceptedAtUtc ?? "", + ObservedBeforeAccept = commandCountBefore, + AccountedCommandIds = existing?.AccountedCommandIds ?? [], + ConsecutiveFailures = (existing?.ConsecutiveFailures ?? 0) + 1, + LastError = ex.ToString(), + Status = "error" + }).ConfigureAwait(false); + throw; + } +} + +static async Task PublishStateSurfacesAsync( + AetheriaStateNode node, + AetheriaDaemonHostOptions options, + string updatedAtUtc) +{ + var verseHost = await EnsureVerseHostSettingsAsync(node, options, updatedAtUtc).ConfigureAwait(false); + var eveStatus = await node.GetEveCommandAcceptanceStatusAsync().ConfigureAwait(false); + var runtimeSession = await node.GetRuntimeSessionAsync(options.DaemonId).ConfigureAwait(false); + var playerSettings = await node.GetPlayerSettingsAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings(); + var playerSettingsUpdatedAt = string.IsNullOrWhiteSpace(playerSettings.LastUpdatedAtUtc) + ? updatedAtUtc + : playerSettings.LastUpdatedAtUtc; + + await node.PutOperationsSurfaceAsync( + AetheriaOperationsSurfaceProjector.Build(eveStatus, verseHost, runtimeSession)).ConfigureAwait(false); + await node.PutPlayerSettingsSurfaceAsync( + AetheriaPlayerSettingsSurfaceProjector.Build(playerSettings, playerSettingsUpdatedAt)).ConfigureAwait(false); + await node.PutProviderAdvertisementAsync( + AetheriaProviderAdvertisementProjector.Build(verseHost, node.StatePath, updatedAtUtc)).ConfigureAwait(false); + await node.FlushAsync().ConfigureAwait(false); +} + +static async Task PublishRuntimeSessionAsync( + AetheriaStateNode node, + AetheriaDaemonHostOptions options, + string startedAtUtc, + string status) +{ + var now = DateTimeOffset.UtcNow.ToString("O"); + await node.PutRuntimeSessionAsync(new AetheriaRuntimeSession + { + RuntimeId = options.DaemonId, + Role = "verse-daemon", + StartedAtUtc = startedAtUtc, + LastSeenAtUtc = now, + Status = status + }).ConfigureAwait(false); + await PublishStateSurfacesAsync(node, options, now).ConfigureAwait(false); +} + +static async Task EnsureWorldDocumentAsync(AetheriaStateNode node) +{ + var now = DateTimeOffset.UtcNow.ToString("O"); + var existing = await node.GetWorldAsync().ConfigureAwait(false); + if (existing != null) + { + existing.UpdatedAtUtc = now; + await node.PutWorldAsync(existing).ConfigureAwait(false); + return; + } + + await node.PutWorldAsync(new AetheriaWorldState + { + Name = "Aetheria", + WorldId = "aetheria", + SchemaEpoch = 1, + CreatedAtUtc = now, + UpdatedAtUtc = now + }).ConfigureAwait(false); +} + +static async Task EnsureVerseHostSettingsAsync( + AetheriaStateNode node, + AetheriaDaemonHostOptions options, + string now) +{ + var existing = await node.GetVerseHostSettingsAsync().ConfigureAwait(false); + var normalized = AetheriaVerseHostSettingsNormalizer.Normalize(existing); + normalized.ServiceId = options.DaemonId; + normalized.VerseId = options.VerseId; + normalized.CultMeshAddress = options.CultMeshAddress; + + if (existing == null || + string.IsNullOrWhiteSpace(existing.LastUpdatedAtUtc) || + !AetheriaVerseHostSettingsNormalizer.Equivalent(existing, normalized)) + { + normalized.LastUpdatedAtUtc = now; + await node.PutVerseHostSettingsAsync(normalized).ConfigureAwait(false); + } + + return normalized; +} + +static AetheriaRuntimeLoadoutTemplateCommit ToLoadoutTemplateCommit(AetheriaLoadoutTemplate template) +{ + return new AetheriaRuntimeLoadoutTemplateCommit + { + Name = template.Name ?? "", + OwnerPlayerKey = template.OwnerPlayerKey ?? "", + RootEntity = ToEntityLoadoutCommit(template.RootEntity) + }; +} + +static AetheriaRuntimeEntityLoadoutCommit ToEntityLoadoutCommit(AetheriaEntityLoadout? entity) +{ + entity ??= new AetheriaEntityLoadout(); + return new AetheriaRuntimeEntityLoadoutCommit + { + Name = entity.Name ?? "", + Kind = entity.Kind ?? "", + FactionKey = entity.FactionKey ?? "", + Hull = ToLoadoutItemCommit(entity.Hull), + Equipment = ToSlotCommits(entity.Equipment), + CargoBays = ToSlotCommits(entity.CargoBays), + DockingBays = ToSlotCommits(entity.DockingBays), + CargoContents = ToCargoBayCommits(entity.CargoContents), + DockingBayContents = ToCargoBayCommits(entity.DockingBayContents), + DockingBayAssignments = (entity.DockingBayAssignments ?? Array.Empty()).ToArray(), + WeaponGroups = (entity.WeaponGroups ?? Array.Empty>()) + .Select(group => (group ?? Array.Empty()).ToArray()) + .ToArray(), + Children = (entity.Children ?? Array.Empty()) + .Select(ToEntityLoadoutCommit) + .ToArray() + }; +} + +static AetheriaRuntimeLoadoutItemSlotCommit[] ToSlotCommits( + IReadOnlyList? slots) +{ + return (slots ?? Array.Empty()) + .Select(slot => new AetheriaRuntimeLoadoutItemSlotCommit + { + X = slot.Position?.X ?? 0, + Y = slot.Position?.Y ?? 0, + Item = ToLoadoutItemCommit(slot.Item) + }) + .ToArray(); +} + +static AetheriaRuntimeCargoBayLoadoutCommit[] ToCargoBayCommits( + IReadOnlyList? cargoBays) +{ + return (cargoBays ?? Array.Empty()) + .Select(cargoBay => new AetheriaRuntimeCargoBayLoadoutCommit + { + Items = ToSlotCommits(cargoBay.Items) + }) + .ToArray(); +} + +static AetheriaRuntimeLoadoutItemCommit ToLoadoutItemCommit(AetheriaLoadoutItem? item) +{ + return new AetheriaRuntimeLoadoutItemCommit + { + ItemKey = item?.ItemKey ?? "", + Quality = item?.Quality ?? 0, + Durability = item?.Durability ?? 0, + Quantity = item?.Quantity ?? 0, + Enabled = item?.Enabled ?? false, + OverrideShutdown = item?.OverrideShutdown ?? false + }; +} + +internal sealed class AetheriaDaemonHostOptions +{ + public string StatePath { get; init; } = ""; + public string DaemonId { get; init; } = "aetheria-daemon"; + public string SessionId { get; init; } = "local"; + public string VerseId { get; init; } = "aetheria.local"; + public string CultMeshAddress { get; init; } = "cultmesh://aetheria.local/eve/providers/aetheria.daemon"; + public TimeSpan TickInterval { get; init; } = TimeSpan.FromMilliseconds(50); + public double FixedDeltaSeconds { get; init; } = 0.02; + public bool Once { get; init; } + + public static AetheriaDaemonHostOptions Parse(IReadOnlyList args) + { + var root = ReadOption(args, "--root"); + if (string.IsNullOrWhiteSpace(root)) + root = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..")); + + var state = ReadOption(args, "--state"); + var intervalMs = ReadPositiveInt(args, "--tick-interval-ms") ?? 50; + var fixedDeltaMs = ReadPositiveInt(args, "--fixed-delta-ms") ?? 20; + var daemonId = ReadOption(args, "--daemon-id"); + var verseId = ReadOption(args, "--verse-id"); + var cultMeshAddress = ReadOption(args, "--cultmesh-address"); + var sessionId = ReadOption(args, "--session-id"); + + return new AetheriaDaemonHostOptions + { + StatePath = string.IsNullOrWhiteSpace(state) + ? AetheriaStatePaths.ResolveDefaultStatePath(root) + : Path.GetFullPath(state), + DaemonId = string.IsNullOrWhiteSpace(daemonId) ? "aetheria-daemon" : daemonId, + SessionId = string.IsNullOrWhiteSpace(sessionId) ? "local" : sessionId, + VerseId = string.IsNullOrWhiteSpace(verseId) ? "aetheria.local" : verseId, + CultMeshAddress = string.IsNullOrWhiteSpace(cultMeshAddress) + ? "cultmesh://aetheria.local/eve/providers/aetheria.daemon" + : cultMeshAddress, + TickInterval = TimeSpan.FromMilliseconds(intervalMs), + FixedDeltaSeconds = fixedDeltaMs / 1000.0, + Once = HasFlag(args, "--once") + }; + } + + private static string ReadOption(IReadOnlyList args, string name) + { + for (var i = 0; i < args.Count - 1; i++) + { + if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase)) + return args[i + 1]; + } + + return ""; + } + + private static int? ReadPositiveInt(IReadOnlyList args, string name) + { + return int.TryParse(ReadOption(args, name), out var value) && value > 0 + ? value + : null; + } + + private static bool HasFlag(IReadOnlyList args, string name) + { + return args.Any(arg => string.Equals(arg, name, StringComparison.OrdinalIgnoreCase)); + } +} diff --git a/Aetheria.State.Smoke/Aetheria.State.Smoke.csproj b/Aetheria.State.Smoke/Aetheria.State.Smoke.csproj index e9e2dc0f7..ca788873d 100644 --- a/Aetheria.State.Smoke/Aetheria.State.Smoke.csproj +++ b/Aetheria.State.Smoke/Aetheria.State.Smoke.csproj @@ -9,6 +9,5 @@ - diff --git a/Aetheria.State.Smoke/Program.cs b/Aetheria.State.Smoke/Program.cs index c4cab143b..3ebc1ffac 100644 --- a/Aetheria.State.Smoke/Program.cs +++ b/Aetheria.State.Smoke/Program.cs @@ -3,7 +3,6 @@ using Aetheria.State.Documents; using Aetheria.State.Migration; using GameCult.Caching; using GameCult.Aetheria.State.Unity; -using GameCult.Eve.Surface; var root = args.Length > 0 ? args[0] : Directory.GetCurrentDirectory(); var stateDirectory = Path.Combine(Path.GetTempPath(), "aetheria-state-smoke", Guid.NewGuid().ToString("N")); @@ -93,27 +92,73 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s await node.PutCatalogSurfaceAsync( AetheriaCatalogSurfaceProjector.Build(node.ReadCatalogSnapshot(), now)); - var drainStatus = new AetheriaRuntimeCommitDrainStatus - { - RuntimeId = "smoke-runtime", - StatePath = statePath, - LastPollAtUtc = now, - LastAppliedAtUtc = now, - PendingBeforeApply = 1, - CommandsApplied = 1, - AppliedPlayerSettings = 1, - Status = "ok" - }; - await node.PutRuntimeCommitDrainStatusAsync(drainStatus); var verseHostSettings = AetheriaVerseHostSettingsNormalizer.Normalize(new AetheriaVerseHostSettings { LastUpdatedAtUtc = now }); await node.PutVerseHostSettingsAsync(verseHostSettings); await node.PutOperationsSurfaceAsync( - AetheriaOperationsSurfaceProjector.Build(drainStatus, verseHostSettings: verseHostSettings)); + AetheriaOperationsSurfaceProjector.Build(verseHostSettings: verseHostSettings)); await node.PutProviderAdvertisementAsync( AetheriaProviderAdvertisementProjector.Build(verseHostSettings, statePath, now)); + var daemonProvider = AetheriaRuntimeDaemonProviderAdvertisementDocument.Create( + statePath, + "smoke-daemon", + "aetheria.local", + "cultmesh://aetheria.local/eve/providers/aetheria.daemon"); + var daemonHealth = new AetheriaRuntimeDaemonHealthDocument + { + DaemonId = "smoke-daemon", + VerseId = "aetheria.local", + PublishedAtUtc = now, + StatePath = statePath, + FrameId = 7, + ObservedCommandCount = 1, + AppliedCommandCount = 2, + RejectedCommandCount = 0, + Status = "healthy", + CommandBoundaryPath = AetheriaRuntimeStateBoundary.GetDaemonCommandBoundaryPath(statePath) + }; + var daemonCommandBoundary = AetheriaRuntimeDaemonCommandBoundaryDocument.Create("smoke-daemon"); + var daemonFrame = AetheriaRuntimeDaemonFrameDocument.Create( + new AetheriaRuntimeRunCheckpointCommit + { + RunId = "smoke-run", + CurrentZoneIndex = 0, + CurrentEntityKey = entityKey.ToString() + }, + "smoke-daemon", + "smoke-session", + 7, + 0.14, + 0.02); + var daemonGameSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( + daemonFrame, + daemonHealth, + daemonCommandBoundary); + await node.PutDaemonProviderAdvertisementAsync(daemonProvider); + await node.PutDaemonHealthAsync(daemonHealth); + await node.PutDaemonCommandBoundaryAsync(daemonCommandBoundary); + using var daemonCommandPort = await AetheriaCommandPort.OpenAsync( + statePath, + "aetheria-state-smoke-command-client"); + var daemonSubmitEnvelope = await daemonCommandPort.SubmitDaemonCommandAsync( + AetheriaRuntimeDaemonCommandDocument.Create( + AetheriaRuntimeDaemonCommandKinds.SensorPing, + "aetheria-state-smoke", + "smoke-session", + daemonFrame.FrameId, + entityKey.ToString())); + + await using (var commandVerifyNode = await AetheriaStateNode.OpenAsync( + statePath, + "aetheria-state-smoke-command-check")) + { + if (commandVerifyNode.ReadObservedDaemonCommands().All(command => command.CommandId != daemonSubmitEnvelope!.CommandId)) + throw new InvalidOperationException("Runtime command port daemon submission did not appear as a typed state record."); + } + await node.PutDaemonFrameAsync(daemonFrame); + await node.PutDaemonGameSurfaceAsync(AetheriaRuntimeEveSurfaceStateProjector.ToState(daemonGameSurface)); await node.PutRuntimeSessionAsync(new AetheriaRuntimeSession { RuntimeId = "smoke-runtime", @@ -123,7 +168,6 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s Status = "running" }); await node.PutOperationsSurfaceAsync(AetheriaOperationsSurfaceProjector.Build( - drainStatus, verseHostSettings: verseHostSettings, runtimeSession: await node.GetRuntimeSessionAsync("smoke-runtime"))); @@ -488,62 +532,64 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s await node.GetPlayerSettingsAsync(), now)); - AetheriaRuntimeEveCommandLog.QueueCommand( + using var eveCommandPort = await AetheriaCommandPort.OpenAsync( statePath, - new EveSurfaceCommandRequest( - AetheriaProviderAdvertisementProjector.ProviderId, - AetheriaCatalogSurfaceProjector.SurfaceId, - "aetheria.catalog.refresh", - new Dictionary { ["source"] = "state-smoke" }, - DateTimeOffset.UtcNow, - "aetheria-state-smoke")); - AetheriaRuntimeEveCommandLog.QueueCommand( - statePath, - new EveSurfaceCommandRequest( - AetheriaProviderAdvertisementProjector.ProviderId, - AetheriaPlayerSettingsSurfaceProjector.SurfaceId, - "aetheria.player_settings.gameplay.significant_digits.increment", - new Dictionary { ["source"] = "state-smoke" }, - DateTimeOffset.UtcNow, - "aetheria-state-smoke")); - AetheriaRuntimeEveCommandLog.QueueCommand( - statePath, - new EveSurfaceCommandRequest( - AetheriaProviderAdvertisementProjector.ProviderId, - AetheriaPlayerSettingsSurfaceProjector.SurfaceId, - "aetheria.player_settings.graphics.show_asteroids.toggle", - new Dictionary { ["source"] = "state-smoke" }, - DateTimeOffset.UtcNow, - "aetheria-state-smoke")); - AetheriaRuntimeEveCommandLog.QueueCommand( - statePath, - new EveSurfaceCommandRequest( - AetheriaProviderAdvertisementProjector.ProviderId, - AetheriaCatalogSurfaceProjector.SurfaceId, + "aetheria-state-smoke-eve-command-client"); + var eveSubmitEnvelope = await eveCommandPort.SubmitEveCommandAsync( + AetheriaRuntimeEveCommandClient.ToDocument( + AetheriaRuntimeEveCommands.SubmitCatalogCommand( + statePath, + AetheriaRuntimeCatalogCommands.Refresh, + "aetheria-state-smoke"))); + await using (var commandVerifyNode = await AetheriaStateNode.OpenAsync( + statePath, + "aetheria-state-smoke-eve-command-check")) + { + if (commandVerifyNode.ReadObservedEveCommands().All(command => command.CommandId != eveSubmitEnvelope!.CommandId)) + throw new InvalidOperationException("Runtime command port Eve submission did not appear as a typed state record."); + } + await node.SubmitEveCommandAsync(AetheriaRuntimeEveCommandClient.ToDocument( + AetheriaRuntimeEveCommands.SubmitCatalogCommand( + statePath, + AetheriaRuntimeCatalogCommands.Refresh, + "aetheria-state-smoke"))); + await node.SubmitEveCommandAsync(AetheriaRuntimeEveCommandClient.ToDocument( + AetheriaRuntimeEveCommands.SubmitPlayerSettingsCommand( + statePath, + AetheriaRuntimePlayerSettingsCommands.IncrementSignificantDigits, + new AetheriaRuntimePlayerSettingsCommandBody(), + "aetheria-state-smoke"))); + await node.SubmitEveCommandAsync(AetheriaRuntimeEveCommandClient.ToDocument( + AetheriaRuntimeEveCommands.SubmitPlayerSettingsCommand( + statePath, + AetheriaRuntimePlayerSettingsCommands.ToggleShowAsteroidsInMinimap, + new AetheriaRuntimePlayerSettingsCommandBody(), + "aetheria-state-smoke"))); + await node.SubmitEveCommandAsync(AetheriaRuntimeEveCommandClient.ToDocument( + AetheriaRuntimeEveCommands.SubmitCatalogCommand( + statePath, "aetheria.catalog.unknown", - new Dictionary { ["source"] = "state-smoke" }, - DateTimeOffset.UtcNow, - "aetheria-state-smoke")); - var eveCommandReport = await AetheriaEveCommandBridge.ApplyPendingAsync(node); - var eveCommandStatus = new AetheriaEveCommandDrainStatus + "aetheria-state-smoke"))); + var eveCommandReport = await AetheriaEveCommandBridge.AcceptObservedAsync(node); + var eveCommandStatus = new AetheriaEveCommandAcceptanceStatus { RuntimeId = "smoke-runtime", StatePath = statePath, LastPollAtUtc = now, LastAcceptedAtUtc = now, - PendingBeforeApply = 4, - CommandsAccepted = eveCommandReport.AcceptedPaths.Length, + ObservedBeforeAccept = 4, + CommandsAccepted = eveCommandReport.AcceptedCommandIds.Length, CommandsRejected = eveCommandReport.RejectedCommands, - AppliedCatalogRefreshes = eveCommandReport.AppliedCatalogRefreshes, - AppliedOperationsRefreshes = eveCommandReport.AppliedOperationsRefreshes, - AppliedPlayerSettingsCommands = eveCommandReport.AppliedPlayerSettingsCommands, + AppliedCatalogRefreshes = eveCommandReport.AcceptedCatalogRefreshes, + AppliedOperationsRefreshes = eveCommandReport.AcceptedOperationsRefreshes, + AppliedPlayerSettingsCommands = eveCommandReport.AcceptedPlayerSettingsCommands, + AccountedCommandIds = eveCommandReport.AccountedCommandIds, LastRejectedCommand = eveCommandReport.LastRejectedCommand, LastRejectedReason = eveCommandReport.LastRejectedReason, Status = eveCommandReport.RejectedCommands > 0 ? "rejected" : "ok" }; - await node.PutEveCommandDrainStatusAsync(eveCommandStatus); + await node.PutEveCommandAcceptanceStatusAsync(eveCommandStatus); await node.PutOperationsSurfaceAsync(AetheriaOperationsSurfaceProjector.Build( - drainStatus, eveCommandStatus, verseHostSettings: await node.GetVerseHostSettingsAsync(), runtimeSession: await node.GetRuntimeSessionAsync("smoke-runtime"))); @@ -559,11 +605,15 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether var nameFile = await reopened.GetNameFileByLegacyIdAsync(nameFileLegacyId); var quarantine = await reopened.GetLegacyCatalogQuarantineAsync(); var catalogSurface = await reopened.GetCatalogSurfaceAsync(); - var drainStatus = await reopened.GetRuntimeCommitDrainStatusAsync(); - var eveCommandStatus = await reopened.GetEveCommandDrainStatusAsync(); + var eveCommandStatus = await reopened.GetEveCommandAcceptanceStatusAsync(); var operationsSurface = await reopened.GetOperationsSurfaceAsync(); var playerSettingsSurface = await reopened.GetPlayerSettingsSurfaceAsync(); var advertisement = await reopened.GetProviderAdvertisementAsync(); + var daemonProvider = await reopened.GetDaemonProviderAdvertisementAsync(); + var daemonHealth = await reopened.GetDaemonHealthAsync(); + var daemonCommandBoundary = await reopened.GetDaemonCommandBoundaryAsync(); + var daemonFrame = await reopened.GetDaemonFrameAsync(); + var daemonGameSurface = await reopened.GetDaemonGameSurfaceAsync(); var runtimeSession = await reopened.GetRuntimeSessionAsync("smoke-runtime"); var playerSettings = await reopened.GetPlayerSettingsAsync(); var loadout = await reopened.GetLoadoutTemplateAsync(loadoutKey); @@ -602,32 +652,71 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether throw new InvalidOperationException("Eve catalog surface did not survive flush/reopen."); } - if (drainStatus?.RuntimeId != "smoke-runtime" || - drainStatus.CommandsApplied != 1 || - eveCommandStatus?.CommandsAccepted != 3 || + if (eveCommandStatus?.CommandsAccepted != 3 || eveCommandStatus.CommandsRejected != 1 || eveCommandStatus.AppliedCatalogRefreshes != 1 || eveCommandStatus.AppliedPlayerSettingsCommands != 2 || !eveCommandStatus.LastRejectedReason.Contains("not advertised", StringComparison.Ordinal) || operationsSurface?.Surface.Id != AetheriaOperationsSurfaceProjector.SurfaceId || !operationsSurface.Surface.Root.Children.Any(child => child.Id == "aetheria.operations.eveCommandDrain") || + operationsSurface.Surface.Root.Children.Any(child => child.Id == "aetheria.operations.commitDrain") || !operationsSurface.Surface.Root.Children.Any(child => child.Id == "aetheria.operations.runtimeSession")) { - throw new InvalidOperationException("Runtime commit/Eve command drain status or operations surface did not survive flush/reopen."); + throw new InvalidOperationException("Eve request acceptance status or operations surface did not survive flush/reopen."); } if (advertisement?.ProviderId != AetheriaProviderAdvertisementProjector.ProviderId || - advertisement.Surfaces.Length < 3 || + advertisement.Surfaces.Length < 7 || !advertisement.Surfaces.Any(surface => surface.SurfaceId == AetheriaCatalogSurfaceProjector.SurfaceId) || !advertisement.Surfaces.Any(surface => surface.SurfaceId == AetheriaOperationsSurfaceProjector.SurfaceId) || !advertisement.Surfaces.Any(surface => surface.SurfaceId == AetheriaPlayerSettingsSurfaceProjector.SurfaceId) || + !advertisement.Surfaces.Any(surface => + surface.SurfaceId == AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId && + surface.Key == AetheriaProviderAdvertisementProjector.DaemonGameSurfaceKey) || + !advertisement.Surfaces.Any(surface => + surface.SurfaceId == AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId && + surface.Key == AetheriaProviderAdvertisementProjector.DaemonGameTuiSurfaceKey) || + !advertisement.Surfaces.Any(surface => + surface.SurfaceId == AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId && + surface.Key == AetheriaProviderAdvertisementProjector.DaemonEditorSurfaceKey) || + !advertisement.Surfaces.Any(surface => + surface.SurfaceId == AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId && + surface.Key == AetheriaProviderAdvertisementProjector.DaemonEditorTuiSurfaceKey) || !advertisement.Schemas.Contains("aetheria.runtime_session.v1") || + !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.ProviderAdvertisement) || + !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.Frame) || + !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.SoaView) || + !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.Health) || + !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.CommandBoundary) || + !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.GameSurface) || + !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.EditorSurface) || + !advertisement.Witnesses.Any(witness => + witness.Kind == "cultcache-witness" && + witness.Ref == AetheriaRuntimeStateBoundary.GetDaemonProviderPath(statePath)) || + !advertisement.Witnesses.Any(witness => + witness.Kind == "cultcache-witness" && + witness.Ref == AetheriaRuntimeStateBoundary.GetDaemonCommandBoundaryPath(statePath)) || + !advertisement.Commands.Any(command => + command.Command == "aetheria.daemon.commands" && + command.Transport == "cultcache-witness") || !advertisement.Commands.Any(command => command.Command == "aetheria.player_settings.graphics.show_asteroids.toggle") || !advertisement.Schemas.Contains(AetheriaEveCommandBridge.CommandSchema)) { throw new InvalidOperationException("Aetheria Eve provider advertisement did not survive flush/reopen."); } + if (daemonProvider?.DaemonId != "smoke-daemon" || + daemonHealth?.FrameId != 7 || + daemonCommandBoundary?.BoundaryId != "aetheria.daemon.commands" || + !daemonCommandBoundary.Commands.Any(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup) || + daemonFrame?.FrameId != 7 || + daemonFrame.SessionId != "smoke-session" || + daemonGameSurface?.Surface.Id != AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId || + daemonGameSurface.Surface.Root.Id != "aetheria.daemon.game.root") + { + throw new InvalidOperationException("Daemon Verse API documents did not survive flush/reopen as typed CultCache records."); + } + if (runtimeSession?.RuntimeId != "smoke-runtime" || runtimeSession.Role != "state-smoke" || runtimeSession.Status != "running") diff --git a/Aetheria.State.Unity.Smoke/Aetheria.State.Unity.Smoke.csproj b/Aetheria.State.Unity.Smoke/Aetheria.State.Unity.Smoke.csproj index 6516b3064..169606e45 100644 --- a/Aetheria.State.Unity.Smoke/Aetheria.State.Unity.Smoke.csproj +++ b/Aetheria.State.Unity.Smoke/Aetheria.State.Unity.Smoke.csproj @@ -9,5 +9,7 @@ + + diff --git a/Aetheria.State.Unity.Smoke/Program.cs b/Aetheria.State.Unity.Smoke/Program.cs index 847da0cdd..88af307ab 100644 --- a/Aetheria.State.Unity.Smoke/Program.cs +++ b/Aetheria.State.Unity.Smoke/Program.cs @@ -1,8 +1,8 @@ +extern alias PackageUnity; + using Aetheria.State; using Aetheria.State.Unity; -using GameCult.Aetheria.State.Unity; -using GameCult.Eve.Surface; -using MessagePack; +using PackageUnity::GameCult.Aetheria.State.Unity; var root = args.Length > 0 ? Path.GetFullPath(args[0]) : Directory.GetCurrentDirectory(); var statePath = args.Length > 1 @@ -164,836 +164,53 @@ if (packageSurface == null || packageSurface.ProviderId != "aetheria" || packageSurface.Surface.Root.Kind != "surface" || packageSurface.Surface.Root.Children.Count == 0 || - packageSurface.Commands.All(command => command.Command != "aetheria.catalog.refresh")) + packageSurface.Commands.All(command => command.Command != AetheriaRuntimeCatalogCommands.Refresh)) { throw new InvalidOperationException("Package runtime store did not read the typed Eve surface contract from CultCache."); } try { - var commit = AetheriaRuntimeStateCommitLog.QueuePlayerSettings( + var eveCommand = AetheriaRuntimeEveCommands.SubmitCatalogCommand( commitSmokeStatePath, - new AetheriaRuntimePlayerSettingsCommit + AetheriaRuntimeCatalogCommands.Refresh, + "aetheria-state-unity-smoke"); + await using var eveCommandNode = await AetheriaStateNode.OpenAsync( + commitSmokeStatePath, + "aetheria-unity-runtime-eve-command-smoke"); + await eveCommandNode.SubmitEveCommandAsync(new GameCult.Aetheria.State.Unity.AetheriaRuntimeEveCommandDocument + { + Schema = eveCommand.Schema, + CommandId = eveCommand.CommandId, + ProviderId = eveCommand.ProviderId, + SurfaceId = eveCommand.SurfaceId, + Command = eveCommand.Command, + IssuedAtUtc = eveCommand.IssuedAtUtc, + ClientId = eveCommand.ClientId, + PlayerSettings = new GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommandBody { - PlayerName = "Unity smoke", - TutorialPassed = true, - ActionBarInputs = new[] { "/1" } - }); - AetheriaRuntimeStateCommitLog.QueueLoadoutTemplate( - commitSmokeStatePath, - new AetheriaRuntimeLoadoutTemplateCommit + PlayerName = eveCommand.PlayerSettings.PlayerName + }, + InputSettings = new GameCult.Aetheria.State.Unity.AetheriaRuntimeInputSettingsCommandBody { - Name = "Unity Smoke Loadout", - OwnerPlayerKey = "global:aetheria.player_settings.v1", - RootEntity = new AetheriaRuntimeEntityLoadoutCommit - { - Name = "Unity Smoke Ship", - Kind = "ship", - FactionKey = "aetheria.corporation:legacy:smoke:faction", - Hull = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = "aetheria.item_definition:legacy:smoke:hull", - Quality = 0.7, - Durability = 0.6 - }, - Equipment = new[] - { - new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 1, - Y = 2, - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = "aetheria.item_definition:legacy:smoke:weapon", - Quality = 0.9, - Durability = 0.8, - Enabled = false, - OverrideShutdown = true - } - } - }, - CargoContents = new[] - { - new AetheriaRuntimeCargoBayLoadoutCommit - { - Items = new[] - { - new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 3, - Y = 4, - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = "aetheria.item_definition:legacy:smoke:ore", - Quantity = 5 - } - } - } - } - }, - DockingBayAssignments = new[] { -1 }, - WeaponGroups = new[] { new[] { 0 } } - } - }); - var runCommit = AetheriaRuntimeStateCommitLog.QueueRunCheckpoint( - commitSmokeStatePath, - new AetheriaRuntimeRunCheckpointCommit - { - RunId = "smoke-run", - GenerationSeed = 424242, - CurrentZoneIndex = 0, - CurrentEntityKey = "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1", - DiscoveredZoneIndices = new[] { 0 }, - ActionBarBindings = new[] - { - new AetheriaRuntimeActionBarBindingCommit - { - ControlPath = "/1", - Kind = "weapon_group", - WeaponGroup = 0 - }, - new AetheriaRuntimeActionBarBindingCommit - { - ControlPath = "/2", - Kind = "gear", - ItemKey = "aetheria.item_definition:legacy:smoke:weapon", - EquipmentIndex = 0, - BehaviorIndex = 1 - } - }, - FactionRelationships = new[] - { - new AetheriaRuntimeFactionRelationshipCommit - { - FactionKey = "aetheria.corporation:legacy:smoke:faction", - Relationship = "Friendly", - Standing = 3 - } - }, - Zones = new[] - { - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Name = "Unity Smoke Zone", - PositionX = 12.0, - PositionY = -4.0, - AdjacentZoneIndices = new[] { 1 }, - OwnerFactionIndex = 0, - Orbits = new[] - { - new AetheriaRuntimeOrbitSnapshotCommit - { - OrbitKey = "aetheria.orbit:legacy:smoke:orbit", - ParentOrbitKey = "aetheria.orbit:legacy:smoke:parent-orbit", - Distance = 100.0, - Phase = 0.25, - FixedPositionX = 5.0, - FixedPositionY = -6.0 - } - }, - Bodies = new[] - { - new AetheriaRuntimeBodySnapshotCommit - { - BodyKey = "aetheria.body:legacy:smoke:body", - Kind = "asteroid_belt", - Name = "Smoke Belt", - OrbitKey = "aetheria.orbit:legacy:smoke:orbit", - Mass = 42.0, - Resources = new[] - { - new AetheriaRuntimeBodyResourceCommit - { - ItemKey = "aetheria.item_definition:legacy:smoke:ore", - Amount = 3.5 - } - }, - BodyRadiusMultiplier = 1.25, - GravityRadiusMultiplier = 2.0, - GravityDepthMultiplier = 0.5, - GravityDepthExponent = 12.0, - Asteroids = new[] - { - new AetheriaRuntimeAsteroidCommit - { - Distance = 7.0, - Phase = 0.75, - Size = 2.0, - RotationSpeed = 0.5, - Damage = 1.25, - RespawnTimer = 6.5, - MiningAccumulators = new[] - { - new AetheriaRuntimeAsteroidMiningAccumulatorCommit - { - MinerEntityIndex = 0, - Amount = 0.75 - } - } - } - } - } - }, - DroppedPickups = new[] - { - new AetheriaRuntimeDroppedPickupCommit - { - PickupIndex = 2, - PositionX = 9.0, - PositionY = 1.5, - PositionZ = -3.0, - VelocityX = 0.25, - VelocityY = -0.5, - VelocityZ = 2.0, - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = "aetheria.item_definition:legacy:smoke:ore", - Quantity = 11 - } - } - }, - Entities = new[] - { - new AetheriaRuntimeEntitySnapshotCommit - { - EntityIndex = 0, - Name = "Unity Smoke Ship", - Kind = "ship", - PositionX = 1.0, - PositionY = 2.0, - PositionZ = 3.0, - DirectionX = 0.0, - DirectionY = 1.0, - FactionKey = "aetheria.corporation:legacy:smoke:faction", - VelocityX = 4.0, - VelocityY = -2.0, - TargetEntityIndex = 0, - IsActive = true, - HeatsinksEnabled = false, - OverrideShutdown = true, - TractorPower = 12.5, - Heatstroke = 0.25, - Hypothermia = 0.125, - HullItemKey = "aetheria.item_definition:legacy:smoke:hull", - Equipment = new[] - { - new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 0, - Y = 0, - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = "aetheria.item_definition:legacy:smoke:weapon", - Quality = 0.9, - Durability = 0.8, - Enabled = false, - OverrideShutdown = true - } - } - }, - CargoContents = new[] - { - new AetheriaRuntimeCargoBayLoadoutCommit - { - Items = new[] - { - new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 2, - Y = 3, - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = "aetheria.item_definition:legacy:smoke:ore", - Quantity = 7 - } - } - } - } - }, - DockingBayAssignments = new[] { -1 }, - Visibility = 12.75, - VisibilitySourceCount = 3, - Contacts = new[] - { - new AetheriaRuntimeEntityContactCommit - { - TargetEntityIndex = 0, - InfoGathered = 0.85, - Hostile = true, - Visible = true - } - }, - WeaponGroups = new[] { new[] { 0 } }, - ActiveConsumables = new[] - { - new AetheriaRuntimeActiveConsumableCommit - { - ItemKey = "aetheria.item_definition:legacy:smoke:consumable", - Quality = 0.75, - RemainingDuration = 3.0, - Duration = 5.0 - } - }, - BehaviorProgress = new[] - { - new AetheriaRuntimeBehaviorProgressCommit - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 1, - BehaviorKind = "Cooldown", - Progress = 0.5 - }, - new AetheriaRuntimeBehaviorProgressCommit - { - OwnerKind = "active_consumable", - OwnerIndex = 0, - BehaviorIndex = 0, - BehaviorKind = "Shield", - Progress = 0.75 - } - }, - WeaponStates = new[] - { - new AetheriaRuntimeWeaponStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 1, - BehaviorKind = "InstantWeapon", - Firing = true, - Ammo = 2, - BurstRemaining = 3, - BurstTimer = 0.25, - BurstInterval = 0.125, - CooldownProgress = 0.5, - CoolingDown = true - }, - new AetheriaRuntimeWeaponStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 1, - BehaviorIndex = 0, - BehaviorKind = "ChargedWeapon", - Ammo = 1, - Charging = true, - Charged = false, - Charge = 0.8 - }, - new AetheriaRuntimeWeaponStateCommit - { - OwnerKind = "active_consumable", - OwnerIndex = 0, - BehaviorIndex = 0, - BehaviorKind = "ConstantWeapon", - Firing = true, - Ammo = 1, - Reloading = true, - ReloadProgress = 0.4, - AmmoIntervalProgress = 0.6 - }, - new AetheriaRuntimeWeaponStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 2, - BehaviorIndex = 1, - BehaviorKind = "LockWeapon", - Ammo = 1, - LockProgress = 0.65, - LockTargetEntityIndex = 0 - } - }, - BehaviorStates = new[] - { - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 2, - BehaviorKind = "Sensor", - Pinging = true, - PingCooldown = 0.25, - PingLerp = 0.5, - PingRadius = 1200.0, - PingedEntityCount = 3 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 1, - BehaviorIndex = 0, - BehaviorKind = "Radiator", - RadiatorTemperature = 450.0, - Emissivity = 0.8, - PumpedHeat = 12.0, - WasteHeat = 1.5, - EnergyUsage = 2.25 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 2, - BehaviorIndex = 0, - BehaviorKind = "Reactor", - ReactorDraw = 4.5, - ReactorLoadRatio = 1.25 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 3, - BehaviorIndex = 0, - BehaviorKind = "Capacitor", - CapacitorCharge = 7.5, - CapacitorCapacity = 10.0, - CapacitorEfficiency = 0.95 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 4, - BehaviorIndex = 0, - BehaviorKind = "AetherDrive", - AetherDriveAxisX = 0.5, - AetherDriveAxisY = -0.25, - AetherDriveAxisZ = 0.75, - AetherDriveThrustX = 12.0, - AetherDriveThrustY = 6.0, - AetherDriveThrustZ = 3.0, - AetherDriveRpmX = 1200.0, - AetherDriveRpmY = 900.0, - AetherDriveRpmZ = 450.0, - AetherDriveMaximumRpm = 2400.0, - AetherDriveThrustDirectionX = 1.5, - AetherDriveThrustDirectionY = -0.5 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 5, - BehaviorIndex = 0, - BehaviorKind = "ResourceScanner", - ResourceScannerTargetBodyKey = "aetheria.body:legacy:smoke:body", - ResourceScannerAsteroidIndex = 2, - ResourceScannerScanTime = 1.25, - ResourceScannerRange = 500.0, - ResourceScannerMinimumDensity = 0.2, - ResourceScannerScanDuration = 3.5 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 6, - BehaviorIndex = 0, - BehaviorKind = "MiningTool", - MiningToolAsteroidBeltKey = "aetheria.body:legacy:smoke:body", - MiningToolAsteroidIndex = 3, - MiningToolRange = 275.0 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 7, - BehaviorIndex = 0, - BehaviorKind = "Thruster", - ThrusterAxis = 0.8, - ThrusterThrust = 125.0, - ThrusterTorque = -0.4 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 8, - BehaviorIndex = 0, - BehaviorKind = "Shield", - ShieldEfficiency = 0.7, - ShieldEnergyUsage = 1.4 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 9, - BehaviorIndex = 0, - BehaviorKind = "VelocityLimit", - VelocityLimit = 42.0 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 10, - BehaviorIndex = 0, - BehaviorKind = "Thermotoggle", - ThermotoggleTargetTemperature = 315.5 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 11, - BehaviorIndex = 0, - BehaviorKind = "Switch", - SwitchActivated = true - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 12, - BehaviorIndex = 0, - BehaviorKind = "Trigger", - TriggerPulled = true - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 13, - BehaviorIndex = 0, - BehaviorKind = "StatModifier", - StatModifierApplied = true, - StatModifierExecuted = true, - StatModifierTargetStatCount = 2 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 14, - BehaviorIndex = 0, - BehaviorKind = "TurretController", - TurretControllerWeaponCount = 2, - TurretControllerShotSpeed = 80.0, - TurretControllerPredictShots = true - } - }, - StatGrids = new[] - { - new AetheriaRuntimeEntityStatGridCommit - { - Name = "temperature", - Width = 2, - Height = 1, - Values = new[] { 280.0, 281.0 } - }, - new AetheriaRuntimeEntityStatGridCommit - { - Name = "hull_conductivity_x", - Width = 2, - Height = 1, - Values = new[] { 1.0, 0.0 } - } - } - } - } - } - } - }); - var pending = AetheriaRuntimeStateCommitLog.ReadPending(commitSmokeStatePath); - if (pending.Count != 3 || - pending[0].Kind != AetheriaRuntimeCommitKind.PlayerSettings || - pending[0].Schema != AetheriaRuntimeStateCommitLog.CommitSchema || - pending[0].Path != commit.Path) + ActionName = eveCommand.InputSettings.ActionName, + BindingIndex = eveCommand.InputSettings.BindingIndex, + InputSystemPath = eveCommand.InputSettings.InputSystemPath, + Enabled = eveCommand.InputSettings.Enabled + } + }); + var eveSubmitted = eveCommandNode.ReadObservedEveCommands(); + if (eveSubmitted.Count != 1 || + eveSubmitted[0].Schema != AetheriaRuntimeEveCommandClient.CommandSchema || + eveSubmitted[0].CommandId != eveCommand.CommandId || + eveSubmitted[0].ProviderId != "aetheria" || + eveSubmitted[0].SurfaceId != AetheriaRuntimeCatalogCommands.SurfaceId || + eveSubmitted[0].Command != AetheriaRuntimeCatalogCommands.Refresh || + eveSubmitted[0].PlayerSettings == null || + eveSubmitted[0].InputSettings == null) { - throw new InvalidOperationException("Runtime state commit log did not read the queued player settings command."); + throw new InvalidOperationException("Runtime Eve command state record did not preserve typed command envelopes separately from state commits."); } - AssertPendingCultCacheEnvelope(commit.Path, AetheriaRuntimeStateCommitDocument.SchemaId); - AssertRunCheckpointPendingPayloadUsesCurrentEntityKey(runCommit.Path); - - var typedNodeApplied = false; - await using (var commitNode = await AetheriaStateNode.OpenAsync(commitSmokeStatePath, "aetheria-unity-runtime-commit-smoke")) - { - var report = await AetheriaRuntimeCommitLogApplier.ApplyPendingAsync(commitNode); - var settings = await commitNode.GetPlayerSettingsAsync(); - var loadout = await commitNode.GetLoadoutTemplateAsync(new("global:aetheria.loadout_template.unity-smoke-loadout.v1")); - var run = await commitNode.GetRunStateAsync(new("global:aetheria.run_state.smoke-run.v1")); - var zone = await commitNode.GetZoneStateAsync(new("global:aetheria.run_state.smoke-run.zone.0.v1")); - var entity = await commitNode.GetEntitySnapshotAsync(new("global:aetheria.run_state.smoke-run.zone.0.entity.0.v1")); - typedNodeApplied = report.AppliedPlayerSettings == 1 && - report.AppliedLoadoutTemplates == 1 && - report.AppliedRunCheckpoints == 1 && - settings?.PlayerName == "Unity smoke" && - settings.Input.ActionBarInputs.Length == 1 && - loadout?.Name == "Unity Smoke Loadout" && - loadout.RootEntity.Hull.ItemKey == "aetheria.item_definition:legacy:smoke:hull" && - loadout.RootEntity.FactionKey == "aetheria.corporation:legacy:smoke:faction" && - loadout.RootEntity.Equipment.Length == 1 && - loadout.RootEntity.Equipment[0].Position.X == 1 && - !loadout.RootEntity.Equipment[0].Item.Enabled && - loadout.RootEntity.Equipment[0].Item.OverrideShutdown && - loadout.RootEntity.CargoContents.Length == 1 && - loadout.RootEntity.CargoContents[0].Items[0].Item.Quantity == 5 && - run?.ZoneKeys.Length == 1 && - run.CurrentEntityKey == "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" && - run.GenerationSeed == 424242 && - run.ActionBarBindings.Length == 2 && - run.ActionBarBindings[0].ControlPath == "/1" && - run.ActionBarBindings[0].Kind == "weapon_group" && - run.ActionBarBindings[0].WeaponGroup == 0 && - run.ActionBarBindings[1].ControlPath == "/2" && - run.ActionBarBindings[1].Kind == "gear" && - run.ActionBarBindings[1].TargetKey == "aetheria.item_definition:legacy:smoke:weapon" && - run.ActionBarBindings[1].EquipmentIndex == 0 && - run.ActionBarBindings[1].BehaviorIndex == 1 && - run.FactionRelationships.Length == 1 && - run.FactionRelationships[0].FactionKey == "aetheria.corporation:legacy:smoke:faction" && - run.FactionRelationships[0].Relationship == "Friendly" && - run.FactionRelationships[0].Standing == 3 && - zone?.EntityKeys.Length == 1 && - zone.Orbits.Length == 1 && - zone.Orbits[0].OrbitKey == "aetheria.orbit:legacy:smoke:orbit" && - zone.Orbits[0].ParentOrbitKey == "aetheria.orbit:legacy:smoke:parent-orbit" && - zone.Orbits[0].FixedPosition.X == 5.0 && - zone.Bodies.Length == 1 && - zone.Bodies[0].BodyKey == "aetheria.body:legacy:smoke:body" && - zone.Bodies[0].OrbitKey == "aetheria.orbit:legacy:smoke:orbit" && - zone.Bodies[0].Kind == "asteroid_belt" && - zone.Bodies[0].Resources.Length == 1 && - zone.Bodies[0].Resources[0].ItemKey == "aetheria.item_definition:legacy:smoke:ore" && - zone.Bodies[0].Asteroids.Length == 1 && - zone.Bodies[0].Asteroids[0].Damage == 1.25 && - zone.Bodies[0].Asteroids[0].RespawnTimer == 6.5 && - zone.Bodies[0].Asteroids[0].MiningAccumulators.Length == 1 && - zone.Bodies[0].Asteroids[0].MiningAccumulators[0].MinerEntityKey == "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" && - zone.Bodies[0].Asteroids[0].MiningAccumulators[0].Amount == 0.75 && - zone.DroppedPickups.Length == 1 && - zone.DroppedPickups[0].PickupIndex == 2 && - zone.DroppedPickups[0].Position.X == 9.0 && - zone.DroppedPickups[0].Velocity.Z == 2.0 && - zone.DroppedPickups[0].Item.ItemKey == "aetheria.item_definition:legacy:smoke:ore" && - zone.DroppedPickups[0].Item.Quantity == 11 && - entity?.Equipment.Length == 1 && - entity.FactionKey == "aetheria.corporation:legacy:smoke:faction" && - entity.Equipment[0].Quality == 0.9 && - entity.Equipment[0].Durability == 0.8 && - entity.Equipment[0].Quantity == 1 && - !entity.Equipment[0].Enabled && - entity.Equipment[0].OverrideShutdown && - entity.Velocity.X == 4.0 && - entity.Velocity.Y == -2.0 && - entity.TargetEntityKey == "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" && - entity.IsActive && - !entity.HeatsinksEnabled && - entity.OverrideShutdown && - entity.TractorPower == 12.5 && - entity.Heatstroke == 0.25 && - entity.Hypothermia == 0.125 && - entity.ActiveConsumables.Length == 1 && - entity.CargoContents.Length == 1 && - entity.CargoContents[0].Items[0].Item.Quantity == 7 && - entity.DockingBayAssignments.Length == 1 && - entity.DockingBayAssignments[0] == -1 && - entity.Visibility == 12.75 && - entity.VisibilitySourceCount == 3 && - entity.Contacts.Length == 1 && - entity.Contacts[0].TargetEntityKey == "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" && - entity.Contacts[0].InfoGathered == 0.85 && - entity.Contacts[0].Hostile && - entity.Contacts[0].Visible && - entity.ActiveConsumables[0].ItemKey == "aetheria.item_definition:legacy:smoke:consumable" && - entity.ActiveConsumables[0].RemainingDuration == 3.0 && - entity.BehaviorProgress.Length == 2 && - entity.BehaviorProgress[0].OwnerKind == "equipment" && - entity.BehaviorProgress[0].BehaviorKind == "Cooldown" && - entity.BehaviorProgress[0].Progress == 0.5 && - entity.BehaviorProgress[1].OwnerKind == "active_consumable" && - entity.BehaviorProgress[1].Progress == 0.75 && - entity.WeaponStates.Length == 4 && - entity.WeaponStates[0].BehaviorKind == "InstantWeapon" && - entity.WeaponStates[0].Firing && - entity.WeaponStates[0].Ammo == 2 && - entity.WeaponStates[0].BurstRemaining == 3 && - entity.WeaponStates[0].CooldownProgress == 0.5 && - entity.WeaponStates[0].CoolingDown && - entity.WeaponStates[1].BehaviorKind == "ChargedWeapon" && - entity.WeaponStates[1].Charging && - entity.WeaponStates[1].Charge == 0.8 && - entity.WeaponStates[2].OwnerKind == "active_consumable" && - entity.WeaponStates[2].BehaviorKind == "ConstantWeapon" && - entity.WeaponStates[2].Reloading && - entity.WeaponStates[2].AmmoIntervalProgress == 0.6 && - entity.WeaponStates[3].BehaviorKind == "LockWeapon" && - entity.WeaponStates[3].LockProgress == 0.65 && - entity.WeaponStates[3].LockTargetEntityKey == "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" && - entity.BehaviorStates.Length == 15 && - entity.BehaviorStates[0].BehaviorKind == "Sensor" && - entity.BehaviorStates[0].Pinging && - entity.BehaviorStates[0].PingRadius == 1200.0 && - entity.BehaviorStates[1].BehaviorKind == "Radiator" && - entity.BehaviorStates[1].RadiatorTemperature == 450.0 && - entity.BehaviorStates[1].PumpedHeat == 12.0 && - entity.BehaviorStates[2].BehaviorKind == "Reactor" && - entity.BehaviorStates[2].ReactorLoadRatio == 1.25 && - entity.BehaviorStates[3].BehaviorKind == "Capacitor" && - entity.BehaviorStates[3].CapacitorCharge == 7.5 && - entity.BehaviorStates[3].CapacitorEfficiency == 0.95 && - entity.BehaviorStates[4].BehaviorKind == "AetherDrive" && - entity.BehaviorStates[4].AetherDriveAxisX == 0.5 && - entity.BehaviorStates[4].AetherDriveRpmY == 900.0 && - entity.BehaviorStates[4].AetherDriveMaximumRpm == 2400.0 && - entity.BehaviorStates[4].AetherDriveThrustDirectionY == -0.5 && - entity.BehaviorStates[5].BehaviorKind == "ResourceScanner" && - entity.BehaviorStates[5].ResourceScannerTargetBodyKey == "aetheria.body:legacy:smoke:body" && - entity.BehaviorStates[5].ResourceScannerAsteroidIndex == 2 && - entity.BehaviorStates[5].ResourceScannerScanTime == 1.25 && - entity.BehaviorStates[5].ResourceScannerScanDuration == 3.5 && - entity.BehaviorStates[6].BehaviorKind == "MiningTool" && - entity.BehaviorStates[6].MiningToolAsteroidBeltKey == "aetheria.body:legacy:smoke:body" && - entity.BehaviorStates[6].MiningToolAsteroidIndex == 3 && - entity.BehaviorStates[6].MiningToolRange == 275.0 && - entity.BehaviorStates[7].BehaviorKind == "Thruster" && - entity.BehaviorStates[7].ThrusterAxis == 0.8 && - entity.BehaviorStates[7].ThrusterThrust == 125.0 && - entity.BehaviorStates[7].ThrusterTorque == -0.4 && - entity.BehaviorStates[8].BehaviorKind == "Shield" && - entity.BehaviorStates[8].ShieldEfficiency == 0.7 && - entity.BehaviorStates[8].ShieldEnergyUsage == 1.4 && - entity.BehaviorStates[9].BehaviorKind == "VelocityLimit" && - entity.BehaviorStates[9].VelocityLimit == 42.0 && - entity.BehaviorStates[10].BehaviorKind == "Thermotoggle" && - entity.BehaviorStates[10].ThermotoggleTargetTemperature == 315.5 && - entity.BehaviorStates[11].BehaviorKind == "Switch" && - entity.BehaviorStates[11].SwitchActivated && - entity.BehaviorStates[12].BehaviorKind == "Trigger" && - entity.BehaviorStates[12].TriggerPulled && - entity.BehaviorStates[13].BehaviorKind == "StatModifier" && - entity.BehaviorStates[13].StatModifierApplied && - entity.BehaviorStates[13].StatModifierExecuted && - entity.BehaviorStates[13].StatModifierTargetStatCount == 2 && - entity.BehaviorStates[14].BehaviorKind == "TurretController" && - entity.BehaviorStates[14].TurretControllerWeaponCount == 2 && - entity.BehaviorStates[14].TurretControllerShotSpeed == 80.0 && - entity.BehaviorStates[14].TurretControllerPredictShots && - entity.WeaponGroups.Length == 1 && - entity.StatGrids.Length == 2 && - entity.StatGrids.Any(grid => grid.Name == "temperature" && grid.Values.Length == 2 && grid.Values[1] == 281.0) && - entity.StatGrids.Any(grid => grid.Name == "hull_conductivity_x" && grid.Values.Length == 2 && grid.Values[0] == 1.0) && - AetheriaRuntimeStateCommitLog.ReadPending(commitSmokeStatePath).Count == 0; - } - - var packageSettings = AetheriaRuntimeCatalogStore.ReadPlayerSettings(commitSmokeStatePath); - var packageLoadouts = AetheriaRuntimeCatalogStore.ReadLoadoutTemplates(commitSmokeStatePath); - var packageRuns = AetheriaRuntimeCatalogStore.ReadRunStates(commitSmokeStatePath); - var packageZones = AetheriaRuntimeCatalogStore.ReadZoneStates(commitSmokeStatePath); - var packageEntities = AetheriaRuntimeCatalogStore.ReadEntitySnapshots(commitSmokeStatePath); - if (!typedNodeApplied || - packageSettings?.PlayerName != "Unity smoke" || - packageSettings.TutorialPassed != true || - packageSettings.ActionBarInputs.Count != 1 || - packageLoadouts.Count != 1 || - packageLoadouts[0].RootEntity.Hull.ItemKey != "aetheria.item_definition:legacy:smoke:hull" || - packageLoadouts[0].RootEntity.FactionKey != "aetheria.corporation:legacy:smoke:faction" || - packageLoadouts[0].RootEntity.Equipment[0].X != 1 || - packageLoadouts[0].RootEntity.Equipment[0].Item.Enabled || - !packageLoadouts[0].RootEntity.Equipment[0].Item.OverrideShutdown || - packageLoadouts[0].RootEntity.CargoContents[0].Items[0].Item.Quantity != 5 || - packageRuns.Count != 1 || - packageRuns[0].RunId != "smoke-run" || - packageRuns[0].GenerationSeed != 424242 || - packageRuns[0].CurrentEntityKey != "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" || - packageRuns[0].ZoneKeys.Count != 1 || - packageRuns[0].ActionBarBindings.Count != 2 || - packageRuns[0].ActionBarBindings[1].TargetKey != "aetheria.item_definition:legacy:smoke:weapon" || - packageRuns[0].FactionRelationships.Count != 1 || - packageRuns[0].FactionRelationships[0].FactionKey != "aetheria.corporation:legacy:smoke:faction" || - packageZones.Count != 1 || - packageZones[0].RecordKey != "global:aetheria.run_state.smoke-run.zone.0.v1" || - packageZones[0].Name != "Unity Smoke Zone" || - packageZones[0].EntityKeys.Count != 1 || - packageZones[0].Orbits.Count != 1 || - packageZones[0].Orbits[0].OrbitKey != "aetheria.orbit:legacy:smoke:orbit" || - packageZones[0].Orbits[0].ParentOrbitKey != "aetheria.orbit:legacy:smoke:parent-orbit" || - packageZones[0].Orbits[0].FixedPositionX != 5.0 || - packageZones[0].Bodies.Count != 1 || - packageZones[0].Bodies[0].BodyKey != "aetheria.body:legacy:smoke:body" || - packageZones[0].Bodies[0].OrbitKey != "aetheria.orbit:legacy:smoke:orbit" || - packageZones[0].Bodies[0].ResourceCount != 1 || - packageZones[0].Bodies[0].AsteroidCount != 1 || - packageZones[0].Bodies[0].DamagedAsteroidCount != 1 || - packageZones[0].Bodies[0].RespawningAsteroidCount != 1 || - packageZones[0].Bodies[0].AsteroidMiningAccumulatorCount != 1 || - packageZones[0].DroppedPickups.Count != 1 || - packageZones[0].DroppedPickups[0].PickupIndex != 2 || - packageZones[0].DroppedPickups[0].PositionX != 9.0 || - packageZones[0].DroppedPickups[0].VelocityZ != 2.0 || - packageZones[0].DroppedPickups[0].Item.ItemKey != "aetheria.item_definition:legacy:smoke:ore" || - packageZones[0].DroppedPickups[0].Item.Quantity != 11 || - packageEntities.Count != 1 || - packageEntities[0].RecordKey != "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" || - packageEntities[0].Name != "Unity Smoke Ship" || - packageEntities[0].FactionKey != "aetheria.corporation:legacy:smoke:faction" || - packageEntities[0].Equipment[0].Quality != 0.9 || - packageEntities[0].Equipment[0].Durability != 0.8 || - packageEntities[0].Equipment[0].Quantity != 1 || - packageEntities[0].Equipment[0].Enabled || - !packageEntities[0].Equipment[0].OverrideShutdown || - packageEntities[0].VelocityX != 4.0 || - packageEntities[0].VelocityY != -2.0 || - packageEntities[0].TargetEntityKey != "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" || - packageEntities[0].ActiveConsumables.Count != 1 || - packageEntities[0].CargoContents.Count != 1 || - packageEntities[0].CargoContents[0].Items[0].Item.Quantity != 7 || - packageEntities[0].DockingBayAssignments.Count != 1 || - packageEntities[0].DockingBayAssignments[0] != -1 || - packageEntities[0].Visibility != 12.75 || - packageEntities[0].VisibilitySourceCount != 3 || - packageEntities[0].Contacts.Count != 1 || - packageEntities[0].Contacts[0].TargetEntityKey != "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" || - packageEntities[0].Contacts[0].InfoGathered != 0.85 || - !packageEntities[0].Contacts[0].Hostile || - !packageEntities[0].Contacts[0].Visible || - packageEntities[0].ActiveConsumables[0].ItemKey != "aetheria.item_definition:legacy:smoke:consumable" || - packageEntities[0].BehaviorProgress.Count != 2 || - packageEntities[0].WeaponStates.Count != 4 || - packageEntities[0].WeaponStates[2].AmmoIntervalProgress != 0.6 || - packageEntities[0].WeaponStates[3].LockProgress != 0.65 || - packageEntities[0].WeaponStates[3].LockTargetEntityKey != "global:aetheria.run_state.smoke-run.zone.0.entity.0.v1" || - packageEntities[0].BehaviorStates.Count != 15 || - packageEntities[0].BehaviorStates[3].CapacitorCharge != 7.5 || - packageEntities[0].BehaviorStates[4].AetherDriveRpmY != 900.0 || - packageEntities[0].BehaviorStates[4].AetherDriveMaximumRpm != 2400.0 || - packageEntities[0].BehaviorStates[5].ResourceScannerTargetBodyKey != "aetheria.body:legacy:smoke:body" || - packageEntities[0].BehaviorStates[5].ResourceScannerScanDuration != 3.5 || - packageEntities[0].BehaviorStates[6].MiningToolAsteroidBeltKey != "aetheria.body:legacy:smoke:body" || - packageEntities[0].BehaviorStates[6].MiningToolRange != 275.0 || - packageEntities[0].BehaviorStates[7].ThrusterAxis != 0.8 || - packageEntities[0].BehaviorStates[7].ThrusterThrust != 125.0 || - packageEntities[0].BehaviorStates[8].ShieldEfficiency != 0.7 || - packageEntities[0].BehaviorStates[9].VelocityLimit != 42.0 || - packageEntities[0].BehaviorStates[10].ThermotoggleTargetTemperature != 315.5 || - !packageEntities[0].BehaviorStates[11].SwitchActivated || - !packageEntities[0].BehaviorStates[12].TriggerPulled || - !packageEntities[0].BehaviorStates[13].StatModifierApplied || - packageEntities[0].BehaviorStates[13].StatModifierTargetStatCount != 2 || - packageEntities[0].BehaviorStates[14].TurretControllerWeaponCount != 2 || - packageEntities[0].BehaviorStates[14].TurretControllerShotSpeed != 80.0 || - !packageEntities[0].BehaviorStates[14].TurretControllerPredictShots || - packageEntities[0].StatGrids.Count != 2 || - packageEntities[0].StatGrids.All(grid => grid.Name != "temperature" || grid.Values.Count != 2 || grid.Values[1] != 281.0)) - { - throw new InvalidOperationException( - "Runtime state commit log did not apply queued settings/loadout/run snapshots through the typed state node. " + - $"typedNodeApplied={typedNodeApplied}, packageSettings={(packageSettings == null ? "null" : $"{packageSettings.PlayerName}/{packageSettings.TutorialPassed}/{packageSettings.ActionBarInputs.Count}")}, packageLoadouts={packageLoadouts.Count}, packageRuns={packageRuns.Count}, packageZones={packageZones.Count}, packageEntities={packageEntities.Count}"); - } - - var eveCommand = AetheriaRuntimeEveCommandLog.QueueCommand( - commitSmokeStatePath, - new EveSurfaceCommandRequest( - "aetheria", - "aetheria.catalog.operator", - "aetheria.catalog.refresh", - new Dictionary { ["source"] = "unity-smoke" }, - DateTimeOffset.UtcNow, - "aetheria-state-unity-smoke")); - var evePending = AetheriaRuntimeEveCommandLog.ReadPending(commitSmokeStatePath); - if (evePending.Count != 1 || - evePending[0].Schema != AetheriaRuntimeEveCommandLog.CommandSchema || - evePending[0].CommandId != eveCommand.CommandId || - evePending[0].ProviderId != "aetheria" || - evePending[0].SurfaceId != "aetheria.catalog.operator" || - evePending[0].Command != "aetheria.catalog.refresh" || - evePending[0].Payload["source"] != "unity-smoke" || - AetheriaRuntimeStateCommitLog.ReadPending(commitSmokeStatePath).Count != 0) - { - throw new InvalidOperationException("Runtime Eve command log did not preserve typed command envelopes separately from state commits."); - } - AssertPendingCultCacheEnvelope(eveCommand.Path, AetheriaRuntimeEveCommandDocument.SchemaId); } finally { @@ -1011,131 +228,7 @@ Console.WriteLine($"Behavior payload sample: {behaviorHost.Name} {behaviorPayloa Console.WriteLine($"Behavior sample: {behaviorKind}"); Console.WriteLine($"Eve surface: {surface.Surface.Id}"); Console.WriteLine($"Package Eve surfaces: {packageSurfaces.Count}"); -Console.WriteLine("Runtime state commit log smoke: settings, loadouts, action-bar bindings, faction relationships, and run zone/entity/pickup snapshots queued, applied, and cleared"); -Console.WriteLine("Runtime Eve command log smoke: surface command queued separately from state commits"); - -static void AssertPendingCultCacheEnvelope(string path, string expectedSchemaId) -{ - var reader = new MessagePackReader(File.ReadAllBytes(path)); - var fieldCount = reader.ReadArrayHeader(); - if (fieldCount < 3) - { - throw new InvalidOperationException($"Pending file {path} is not a CultCache store snapshot."); - } - - var formatVersion = reader.ReadString(); - if (formatVersion != "cultcache.store.v1") - { - throw new InvalidOperationException($"Pending file {path} has format '{formatVersion}', not cultcache.store.v1."); - } - - var catalogCount = reader.ReadArrayHeader(); - var catalogHasSchema = false; - for (var index = 0; index < catalogCount; index++) - { - var catalogFieldCount = reader.ReadArrayHeader(); - var schemaId = catalogFieldCount > 0 ? reader.ReadString() : ""; - catalogHasSchema |= schemaId == expectedSchemaId; - for (var field = 1; field < catalogFieldCount; field++) - { - reader.Skip(); - } - } - - var recordCount = reader.ReadArrayHeader(); - if (recordCount != 1) - { - throw new InvalidOperationException($"Pending file {path} should contain one CultCache record, found {recordCount}."); - } - - var recordFieldCount = reader.ReadArrayHeader(); - if (recordFieldCount < 4) - { - throw new InvalidOperationException($"Pending file {path} has an incomplete CultCache record."); - } - - var key = reader.ReadString(); - var recordSchemaId = reader.ReadString(); - reader.Skip(); - var payload = reader.ReadBytes(); - var payloadLength = payload?.Length ?? 0; - if (!catalogHasSchema || recordSchemaId != expectedSchemaId || string.IsNullOrWhiteSpace(key) || payloadLength == 0) - { - throw new InvalidOperationException($"Pending file {path} does not publish a typed CultCache record for {expectedSchemaId}."); - } -} - -static void AssertRunCheckpointPendingPayloadUsesCurrentEntityKey(string path) -{ - var reader = new MessagePackReader(File.ReadAllBytes(path)); - var fieldCount = reader.ReadArrayHeader(); - if (fieldCount < 3) - { - throw new InvalidOperationException($"Pending file {path} is not a CultCache store snapshot."); - } - - reader.Skip(); // formatVersion - - var catalogCount = reader.ReadArrayHeader(); - for (var index = 0; index < catalogCount; index++) - { - reader.Skip(); - } - - var recordCount = reader.ReadArrayHeader(); - if (recordCount != 1) - { - throw new InvalidOperationException($"Pending file {path} should contain one CultCache record."); - } - - reader.ReadArrayHeader(); - reader.Skip(); // key - reader.Skip(); // schemaId - reader.Skip(); // storedAt - var payload = reader.ReadBytes() is { } payloadSequence - ? SequenceToArray(payloadSequence) - : Array.Empty(); - if (payload.Length == 0) - { - throw new InvalidOperationException($"Pending file {path} has an empty runtime commit payload."); - } - - var payloadReader = new MessagePackReader(payload); - var payloadFieldCount = payloadReader.ReadArrayHeader(); - if (payloadFieldCount < 7) - { - throw new InvalidOperationException($"Pending runtime commit payload {path} is incomplete."); - } - - payloadReader.Skip(); // schema - payloadReader.Skip(); // kind - payloadReader.Skip(); // commandId - payloadReader.Skip(); // createdAtUtc - payloadReader.Skip(); // playerSettings - payloadReader.Skip(); // loadoutTemplate - var runCheckpointPayload = SequenceToArray(payloadReader.ReadRaw()); - - var runReader = new MessagePackReader(runCheckpointPayload); - var runFieldCount = runReader.ReadArrayHeader(); - if (runFieldCount != 11) - { - throw new InvalidOperationException( - $"Pending run checkpoint payload should use the 11-field CurrentEntityKey layout, found {runFieldCount} fields."); - } -} - -static byte[] SequenceToArray(System.Buffers.ReadOnlySequence sequence) -{ - var payload = new byte[checked((int)sequence.Length)]; - var offset = 0; - foreach (var segment in sequence) - { - segment.Span.CopyTo(payload.AsSpan(offset)); - offset += segment.Length; - } - - return payload; -} +Console.WriteLine("Runtime Eve command smoke: surface command document is a typed state record"); static int CountRequiredBehaviorItemRefsMissingItemKeys(AetheriaRuntimeBehaviorPayload payload) { diff --git a/Aetheria.State.Unity/Aetheria.State.Unity.csproj b/Aetheria.State.Unity/Aetheria.State.Unity.csproj index e40e74ad4..7c0d12267 100644 --- a/Aetheria.State.Unity/Aetheria.State.Unity.csproj +++ b/Aetheria.State.Unity/Aetheria.State.Unity.csproj @@ -8,23 +8,7 @@ - - - - - - - - - - - - - - - - - + diff --git a/Aetheria.State.Unity/AetheriaRuntimeCatalogSnapshotMapper.cs b/Aetheria.State.Unity/AetheriaRuntimeCatalogSnapshotMapper.cs index 9494221df..6731be24e 100644 --- a/Aetheria.State.Unity/AetheriaRuntimeCatalogSnapshotMapper.cs +++ b/Aetheria.State.Unity/AetheriaRuntimeCatalogSnapshotMapper.cs @@ -78,7 +78,8 @@ internal static class AetheriaRuntimeCatalogSnapshotMapper stat.Max, stat.HeatExponentMultiplier, stat.DurabilityExponentMultiplier, - stat.QualityExponent); + stat.QualityExponent, + null); } private static AetheriaRuntimeShapeCell FromState(AetheriaShapeCell cell) diff --git a/Aetheria.State.Verify/Aetheria.State.Verify.csproj b/Aetheria.State.Verify/Aetheria.State.Verify.csproj index e9e2dc0f7..a9150ffa9 100644 --- a/Aetheria.State.Verify/Aetheria.State.Verify.csproj +++ b/Aetheria.State.Verify/Aetheria.State.Verify.csproj @@ -10,5 +10,6 @@ + diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 35618a89a..5dc6640cd 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -4755,6 +4755,7 @@ static void RequireClientTargetBootAuthority(string root) var boundaryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateBoundary.cs"); var bootPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateBoot.cs"); var clientTargetStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetStore.cs"); + var clientTargetCommandsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetCommands.cs"); var aetheriaStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaState.cs"); var verseDiscoveryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseDiscovery.cs"); var replicaBridgePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseReplicaBridge.cs"); @@ -4770,6 +4771,7 @@ static void RequireClientTargetBootAuthority(string root) boundaryPath, bootPath, clientTargetStorePath, + clientTargetCommandsPath, aetheriaStatePath, verseDiscoveryPath, replicaBridgePath, @@ -4795,6 +4797,7 @@ static void RequireClientTargetBootAuthority(string root) var boundary = File.ReadAllText(boundaryPath); var boot = File.ReadAllText(bootPath); var clientTargetStore = File.ReadAllText(clientTargetStorePath); + var clientTargetCommands = File.ReadAllText(clientTargetCommandsPath); var aetheriaState = File.ReadAllText(aetheriaStatePath); var verseDiscovery = File.ReadAllText(verseDiscoveryPath); var replicaBridge = File.ReadAllText(replicaBridgePath); @@ -4941,9 +4944,6 @@ static void RequireClientTargetBootAuthority(string root) "public AetheriaRuntimeClientTargetDocument RequestStateFilePath(string stateFilePath)", "public AetheriaRuntimeClientTargetDocument RequestDiscoveryEndpoints(IEnumerable? discoveryEndpoints)", "public AetheriaRuntimeClientTargetDocument SelectDiscoveredVerse(", - "public bool TryRequest(AetheriaClientTargetOperation operation, out AetheriaRuntimeClientTargetDocument? document)", - "public AetheriaRuntimeClientTargetDocument Request(AetheriaClientTargetOperation operation)", - "AetheriaClientTargetOperationKind.SelectDiscoveredVerse", "AetheriaRuntimeStateBoundary.GetClientTargetPath(_gameDataDirectory)", "AetheriaRuntimeStateBoundary.GetStateFilePath(_gameDataDirectory)", "AetheriaRuntimeVerseDiscovery.RefreshClientTarget(", @@ -4969,6 +4969,15 @@ static void RequireClientTargetBootAuthority(string root) "Aetheria state sugar still exposes Apply vocabulary for client-target edits; Unity should request typed target changes."); } + if (aetheriaState.Contains("public bool TryRequest(AetheriaClientTargetOperation", StringComparison.Ordinal) || + aetheriaState.Contains("public AetheriaRuntimeClientTargetDocument Request(AetheriaClientTargetOperation", StringComparison.Ordinal) || + clientTargetCommands.Contains("public readonly struct AetheriaClientTargetOperation", StringComparison.Ordinal) || + clientTargetCommands.Contains("public enum AetheriaClientTargetOperationKind", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "Aetheria client-target sugar still exposes generic operation-bag dispatch instead of native request methods plus a surface adapter."); + } + var forbiddenClientTargetSetters = new[] { "public AetheriaRuntimeClientTargetDocument SetTitle(", @@ -5081,8 +5090,7 @@ static void RequireClientTargetBootAuthority(string root) "AetheriaState.At(ActionGameManager.GameDataDirectory)", ".ClientTarget", "TryRequestClientTargetCommand(request)", - "AetheriaRuntimeClientTargetSurfaceCommands.TryRead(request, out var operation)", - ".ClientTarget.Request(operation)", + "AetheriaRuntimeClientTargetSurfaceCommands.TryRequest(", "ProjectMainMenuSurfaceState(", "AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot(", "AetheriaRuntimeMainMenuSurfaceBuilder.BuildSettings(", @@ -5424,9 +5432,6 @@ static void RequireVerseSettingsShellAndBridge(string root) "public AetheriaRuntimeClientTargetDocument RequestStateFilePath(string stateFilePath)", "public AetheriaRuntimeClientTargetDocument RequestDiscoveryEndpoints(IEnumerable? discoveryEndpoints)", "public AetheriaRuntimeClientTargetDocument SelectDiscoveredVerse(", - "public bool TryRequest(AetheriaClientTargetOperation operation, out AetheriaRuntimeClientTargetDocument? document)", - "public AetheriaRuntimeClientTargetDocument Request(AetheriaClientTargetOperation operation)", - "AetheriaClientTargetOperationKind.SelectDiscoveredVerse", "AetheriaRuntimeClientTargetStore.Update(", "AetheriaRuntimeVerseDiscovery.RefreshClientTarget(", "AetheriaRuntimeVerseReplicaBridge.Sync(", @@ -5449,6 +5454,15 @@ static void RequireVerseSettingsShellAndBridge(string root) "Aetheria state sugar still exposes Apply vocabulary for Verse settings edits; Unity should request typed target changes."); } + if (aetheriaState.Contains("public bool TryRequest(AetheriaClientTargetOperation", StringComparison.Ordinal) || + aetheriaState.Contains("public AetheriaRuntimeClientTargetDocument Request(AetheriaClientTargetOperation", StringComparison.Ordinal) || + clientTargetCommands.Contains("public readonly struct AetheriaClientTargetOperation", StringComparison.Ordinal) || + clientTargetCommands.Contains("public enum AetheriaClientTargetOperationKind", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "Verse settings sugar still exposes generic operation-bag dispatch instead of native request methods plus a surface adapter."); + } + var forbiddenClientTargetSetters = new[] { "public AetheriaRuntimeClientTargetDocument SetTitle(", @@ -5536,8 +5550,7 @@ static void RequireVerseSettingsShellAndBridge(string root) "AetheriaRuntimeEveCommands.TrySendVerseHostCommand(", "AetheriaState.At(ActionGameManager.GameDataDirectory)", ".ClientTarget", - "AetheriaRuntimeClientTargetSurfaceCommands.TryRead(request, out var operation)", - ".ClientTarget.Request(operation)", + "AetheriaRuntimeClientTargetSurfaceCommands.TryRequest(", "AetheriaRuntimeEveCommands.TrySendVerseHostCommand(" }; var missingMainMenuSymbols = requiredMainMenuSymbols @@ -5892,7 +5905,6 @@ static void RequireTypedDaemonCommandPayloads(string root) "public AetheriaRuntimeDaemonCommandEnvelope Send(", "public bool TrySend(", "ReadObservedDaemonCommands()", - "DeleteDaemonCommandAsync(", "_operationClient.Create(", "_operationClient.TrySend(command, out var envelope, out var error)", "command.ActionBarBinding.Kind", @@ -6275,12 +6287,10 @@ static void RequireDaemonVersePublication(string root) string.Join(", ", survivingDaemonQueueSymbols)); } - if (daemonDocuments.Contains("public int PendingBeforeApply", StringComparison.Ordinal) && - (!daemonDocuments.Contains("[IgnoreMember]", StringComparison.Ordinal) || - !daemonDocuments.Contains("public int ObservedCommandCount", StringComparison.Ordinal))) + if (daemonDocuments.Contains("PendingBeforeApply", StringComparison.Ordinal)) { throw new InvalidOperationException( - "Daemon health exposes PendingBeforeApply without the ObservedCommandCount compatibility wrapper."); + "Daemon health still exposes the legacy PendingBeforeApply queue vocabulary instead of observed Verse command documents."); } var requiredProviderAdvertisementSymbols = new[] @@ -6339,7 +6349,9 @@ static void RequireDaemonVersePublication(string root) "AetheriaRuntimeDaemonPublicationStore.PublishEditorSurface", "AetheriaRuntimeDaemonPublicationStore.PublishEditorTuiSurface", "ObservedCommands", - "ObservedCommandCount = commands.Length", + "AccountedCommandIds", + "frame.AccountedCommandIds = accountedBeforeTick", + "ObservedCommandCount = observedCommands.Length", "PublicationSource = \"daemon-published\"" }; var missingTickSymbols = requiredTickSymbols @@ -6365,10 +6377,8 @@ static void RequireDaemonVersePublication(string root) "PutDaemonGameSurfaceAsync(AetheriaRuntimeEveSurfaceStateProjector.ToState(gameSurface))", "PutDaemonEditorTuiSurfaceAsync(AetheriaRuntimeEveSurfaceStateProjector.ToState(editorTuiSurface))", "AetheriaEveCommandBridge.AcceptObservedAsync(", - "!options.KeepAccountedCommandRecords", "ReadObservedDaemonCommands()", - "DeleteAccountedDaemonCommandsAsync(node, result)", - "DeleteDaemonCommandAsync(commandId)", + "AccountedCommandIds = currentFrame?.AccountedCommandIds ?? Array.Empty()", "AetheriaRuntimeDaemonTickRunner.Tick(", "AetheriaProviderAdvertisementProjector.Build(verseHost, node.StatePath, updatedAtUtc)", "AetheriaOperationsSurfaceProjector.Build(eveStatus, verseHost, runtimeSession)", @@ -6397,6 +6407,21 @@ static void RequireDaemonVersePublication(string root) "MapGet(", "JsonSerializer.Serialize" }; + if (daemonHostSource.Contains("KeepAccountedCommandRecords", StringComparison.Ordinal) || + daemonHostSource.Contains("--keep-accounted-command-records", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "Aetheria.State.Daemon still exposes command-retention toggles after adopting receipt-ledger command accounting."); + } + + if (stateNode.Contains("DeleteDaemonCommandAsync(", StringComparison.Ordinal) || + daemonHostSource.Contains("DeleteAccountedDaemonCommandsAsync", StringComparison.Ordinal) || + daemonHostSource.Contains("DeleteDaemonCommandAsync(", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "Daemon command documents are still consumed by deletion instead of accounted by frame receipts."); + } + var survivingDaemonHostSymbols = forbiddenDaemonHostSymbols .Where(symbol => daemonHostSource.Contains(symbol, StringComparison.Ordinal)) .ToArray(); @@ -6643,10 +6668,9 @@ static void RequireTypedEveCommandBodies(string root) "ToDocument(", "AcceptObservedAsync(", "node.ReadObservedEveCommands()", - "node.DeleteEveCommandAsync(", + "AccountedCommandIds", "SubmitEveCommandAsync(", "ReadObservedEveCommands(", - "DeleteEveCommandAsync(", "EveCommandKey(", "CultNetDocumentBinding.ForDocument", "typeof(AetheriaRuntimeEveCommandDocument)", @@ -6661,6 +6685,19 @@ static void RequireTypedEveCommandBodies(string root) }; var mainMenu = File.ReadAllText(mainMenuPath); var typedCommandSources = eveCommandClient + "\n" + runtimeCommandPort + "\n" + eveCommandBridge + "\n" + stateNode + "\n" + documentRegistry + "\n" + actionGameManager + "\n" + mainMenu + "\n" + evePresenter; + if (typedCommandSources.Contains("DeleteEveCommandAsync(", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "Eve command documents are still consumed by deletion instead of accounted by acceptance receipts."); + } + + if (eveCommandBridge.Contains("AcceptedPaths", StringComparison.Ordinal) || + eveCommandBridge.Contains("RejectedPaths", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "Eve command acceptance report still exposes path vocabulary instead of command-id receipts."); + } + var missingTypedCommandSymbols = requiredTypedCommandSymbols .Where(symbol => !typedCommandSources.Contains(symbol, StringComparison.Ordinal)) .ToArray(); diff --git a/Aetheria.State/Aetheria.State.csproj b/Aetheria.State/Aetheria.State.csproj index ac1c3d428..00068e1e0 100644 --- a/Aetheria.State/Aetheria.State.csproj +++ b/Aetheria.State/Aetheria.State.csproj @@ -8,15 +8,49 @@ - + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + diff --git a/Aetheria.State/AetheriaCatalogSurfaceProjector.cs b/Aetheria.State/AetheriaCatalogSurfaceProjector.cs index e979c5c24..ddfd182b8 100644 --- a/Aetheria.State/AetheriaCatalogSurfaceProjector.cs +++ b/Aetheria.State/AetheriaCatalogSurfaceProjector.cs @@ -1,13 +1,14 @@ using System; using System.Linq; using Aetheria.State.Documents; +using GameCult.Aetheria.State.Unity; namespace Aetheria.State; public static class AetheriaCatalogSurfaceProjector { public const string SurfaceKey = "eve:surface:aetheria.catalog.operator"; - public const string SurfaceId = "aetheria.catalog.operator"; + public const string SurfaceId = AetheriaRuntimeCatalogCommands.SurfaceId; public static EveSurfaceState Build(AetheriaCatalogSnapshot catalog, string updatedAtUtc, long version = 1) { @@ -68,7 +69,7 @@ public static class AetheriaCatalogSurfaceProjector [ new EveCommandTemplate { - Command = "aetheria.catalog.refresh", + Command = AetheriaRuntimeCatalogCommands.Refresh, Label = "Refresh", Transport = "cultmesh" } diff --git a/Aetheria.State/AetheriaDocumentRegistry.cs b/Aetheria.State/AetheriaDocumentRegistry.cs index 3fcee08f1..104b8b1c9 100644 --- a/Aetheria.State/AetheriaDocumentRegistry.cs +++ b/Aetheria.State/AetheriaDocumentRegistry.cs @@ -1,5 +1,6 @@ using Aetheria.State.Documents; using GameCult.Caching; +using GameCult.Aetheria.State.Unity; using GameCult.Networking; namespace Aetheria.State; @@ -38,8 +39,14 @@ public static class AetheriaDocumentRegistry CultNetDocumentBinding.ForDocument(registry), CultNetDocumentBinding.ForDocument(registry), CultNetDocumentBinding.ForDocument(registry), - CultNetDocumentBinding.ForDocument(registry), - CultNetDocumentBinding.ForDocument(registry), + CultNetDocumentBinding.ForDocument(registry), + CultNetDocumentBinding.ForDocument(registry), + CultNetDocumentBinding.ForDocument(registry), + CultNetDocumentBinding.ForDocument(registry), + CultNetDocumentBinding.ForDocument(registry), + CultNetDocumentBinding.ForDocument(registry), + CultNetDocumentBinding.ForDocument(registry), + CultNetDocumentBinding.ForDocument(registry), CultNetDocumentBinding.ForDocument(registry), CultNetDocumentBinding.ForDocument(registry) }); @@ -61,8 +68,14 @@ public static class AetheriaDocumentRegistry typeof(AetheriaZoneState), typeof(AetheriaEntitySnapshot), typeof(AetheriaVerseHostSettings), - typeof(AetheriaRuntimeCommitDrainStatus), - typeof(AetheriaEveCommandDrainStatus), + typeof(AetheriaEveCommandAcceptanceStatus), + typeof(AetheriaRuntimeDaemonProviderAdvertisementDocument), + typeof(AetheriaRuntimeDaemonHealthDocument), + typeof(AetheriaRuntimeDaemonCommandBoundaryDocument), + typeof(AetheriaRuntimeDaemonFrameDocument), + typeof(AetheriaRuntimeDaemonSoaViewDocument), + typeof(AetheriaRuntimeDaemonCommandDocument), + typeof(AetheriaRuntimeEveCommandDocument), typeof(EveSurfaceState), typeof(EveProviderAdvertisementState) ]; diff --git a/Aetheria.State/AetheriaEveCommandBridge.cs b/Aetheria.State/AetheriaEveCommandBridge.cs index 1084dc748..82f8c5706 100644 --- a/Aetheria.State/AetheriaEveCommandBridge.cs +++ b/Aetheria.State/AetheriaEveCommandBridge.cs @@ -3,15 +3,18 @@ using GameCult.Aetheria.State.Unity; namespace Aetheria.State; -public sealed class AetheriaEveCommandApplyReport +public sealed class AetheriaEveCommandAcceptanceReport { - public int AppliedCatalogRefreshes { get; set; } - public int AppliedOperationsRefreshes { get; set; } - public int AppliedPlayerSettingsCommands { get; set; } - public int AppliedVerseHostCommands { get; set; } + public int AcceptedCatalogRefreshes { get; set; } + public int AcceptedOperationsRefreshes { get; set; } + public int AcceptedPlayerSettingsCommands { get; set; } + public int AcceptedInputSettingsCommands { get; set; } + public int AcceptedLoadoutTemplateCommands { get; set; } + public int AcceptedVerseHostCommands { get; set; } public int RejectedCommands { get; set; } - public string[] AcceptedPaths { get; set; } = []; - public string[] RejectedPaths { get; set; } = []; + public string[] AcceptedCommandIds { get; set; } = []; + public string[] RejectedCommandIds { get; set; } = []; + public string[] AccountedCommandIds { get; set; } = []; public string LastRejectedCommand { get; set; } = ""; public string LastRejectedReason { get; set; } = ""; } @@ -20,61 +23,45 @@ public static class AetheriaEveCommandBridge { public const string CommandSchema = "gamecult.eve.command.v1"; - public static string GetPendingDirectory(string stateFilePath) - { - if (string.IsNullOrWhiteSpace(stateFilePath)) - throw new ArgumentException("State file path must be non-empty.", nameof(stateFilePath)); - - return stateFilePath + ".eve.pending"; - } - - public static async Task ApplyPendingAsync( + public static async Task AcceptObservedAsync( AetheriaStateNode node, - bool deleteAccounted = true) + IEnumerable? accountedCommandIds = null) { if (node == null) throw new ArgumentNullException(nameof(node)); - var pendingDirectory = GetPendingDirectory(node.StatePath); - var report = new AetheriaEveCommandApplyReport(); + var report = new AetheriaEveCommandAcceptanceReport(); var accepted = new List(); var rejected = new List(); - - if (!Directory.Exists(pendingDirectory)) - return report; - - foreach (var path in Directory.EnumerateFiles(pendingDirectory, "*.cc").OrderBy(path => path, StringComparer.Ordinal)) + var accounted = new HashSet(accountedCommandIds ?? Array.Empty(), StringComparer.Ordinal); + foreach (var command in node.ReadObservedEveCommands() + .Where(command => !string.IsNullOrWhiteSpace(command.CommandId)) + .Where(command => !accounted.Contains(command.CommandId))) { - var command = ReadCommand(path); var rejection = Validate(command); if (!string.IsNullOrWhiteSpace(rejection)) { - RecordRejection(report, command, rejection, path, rejected, deleteAccounted); + RecordRejection(report, command, rejection, rejected); continue; } switch (command.Command) { - case "aetheria.catalog.refresh": + case AetheriaRuntimeCatalogCommands.Refresh: await node.PutCatalogSurfaceAsync( AetheriaCatalogSurfaceProjector.Build(node.ReadCatalogSnapshot(), command.IssuedAtUtc)).ConfigureAwait(false); - report.AppliedCatalogRefreshes++; + report.AcceptedCatalogRefreshes++; break; - case "aetheria.operations.refresh": - var commitStatus = await node.GetRuntimeCommitDrainStatusAsync().ConfigureAwait(false) ?? - EmptyCommitDrainStatus(node.StatePath, command.IssuedAtUtc); - var eveStatus = await node.GetEveCommandDrainStatusAsync().ConfigureAwait(false) ?? - EmptyEveCommandDrainStatus(node.StatePath, command.IssuedAtUtc); + case AetheriaRuntimeOperationsCommands.Refresh: + var eveStatus = await node.GetEveCommandAcceptanceStatusAsync().ConfigureAwait(false) ?? + EmptyEveCommandAcceptanceStatus(node.StatePath, command.IssuedAtUtc); var verseHostSettings = await node.GetVerseHostSettingsAsync().ConfigureAwait(false); - var runtimeSession = string.IsNullOrWhiteSpace(commitStatus.RuntimeId) - ? null - : await node.GetRuntimeSessionAsync(commitStatus.RuntimeId).ConfigureAwait(false); + var runtimeSession = await node.GetRuntimeSessionAsync(eveStatus.RuntimeId).ConfigureAwait(false); await node.PutOperationsSurfaceAsync( AetheriaOperationsSurfaceProjector.Build( - commitStatus, eveStatus, verseHostSettings, runtimeSession)).ConfigureAwait(false); - report.AppliedOperationsRefreshes++; + report.AcceptedOperationsRefreshes++; break; case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.Refresh: case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.SetPlayerName: @@ -83,32 +70,41 @@ public static class AetheriaEveCommandBridge case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.IncrementSignificantDigits: case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.CycleNebulaQuality: case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.ToggleShowAsteroidsInMinimap: - await ApplyPlayerSettingsCommandAsync(node, command).ConfigureAwait(false); - report.AppliedPlayerSettingsCommands++; + await ExecutePlayerSettingsCommandAsync(node, command).ConfigureAwait(false); + report.AcceptedPlayerSettingsCommands++; + break; + case GameCult.Aetheria.State.Unity.AetheriaRuntimeInputSettingsCommands.Refresh: + case GameCult.Aetheria.State.Unity.AetheriaRuntimeInputSettingsCommands.SetBindingOverride: + case GameCult.Aetheria.State.Unity.AetheriaRuntimeInputSettingsCommands.SetActionBarEnabled: + await ExecuteInputSettingsCommandAsync(node, command).ConfigureAwait(false); + report.AcceptedInputSettingsCommands++; + break; + case GameCult.Aetheria.State.Unity.AetheriaRuntimeLoadoutTemplateCommands.Save: + await ExecuteLoadoutTemplateCommandAsync(node, command).ConfigureAwait(false); + report.AcceptedLoadoutTemplateCommands++; break; case GameCult.Aetheria.State.Unity.AetheriaRuntimeVerseHostCommands.Refresh: case GameCult.Aetheria.State.Unity.AetheriaRuntimeVerseHostCommands.CycleVisibility: - await ApplyVerseHostCommandAsync(node, command).ConfigureAwait(false); - report.AppliedVerseHostCommands++; + await ExecuteVerseHostCommandAsync(node, command).ConfigureAwait(false); + report.AcceptedVerseHostCommands++; break; } - accepted.Add(path); - if (deleteAccounted) - File.Delete(path); + accepted.Add(command.CommandId ?? ""); } await node.FlushAsync().ConfigureAwait(false); - report.AcceptedPaths = accepted.ToArray(); - report.RejectedPaths = rejected.ToArray(); + report.AcceptedCommandIds = accepted.ToArray(); + report.RejectedCommandIds = rejected.ToArray(); + report.AccountedCommandIds = accounted + .Concat(report.AcceptedCommandIds) + .Concat(report.RejectedCommandIds) + .Where(commandId => !string.IsNullOrWhiteSpace(commandId)) + .Distinct(StringComparer.Ordinal) + .ToArray(); return report; } - private static AetheriaRuntimeEveCommandDocument ReadCommand(string path) - { - return AetheriaRuntimePendingCultCacheStore.ReadEveCommand(path); - } - private static string Validate(AetheriaRuntimeEveCommandDocument command) { if (!string.Equals(command.Schema, CommandSchema, StringComparison.Ordinal)) @@ -123,17 +119,21 @@ public static class AetheriaEveCommandBridge private static bool KnownCommand(string surfaceId, string command) { - return (string.Equals(surfaceId, AetheriaCatalogSurfaceProjector.SurfaceId, StringComparison.Ordinal) && - string.Equals(command, "aetheria.catalog.refresh", StringComparison.Ordinal)) || - (string.Equals(surfaceId, AetheriaOperationsSurfaceProjector.SurfaceId, StringComparison.Ordinal) && - string.Equals(command, "aetheria.operations.refresh", StringComparison.Ordinal)) || + return (string.Equals(surfaceId, AetheriaRuntimeCatalogCommands.SurfaceId, StringComparison.Ordinal) && + AetheriaRuntimeCatalogCommands.IsKnown(command)) || + (string.Equals(surfaceId, AetheriaRuntimeOperationsCommands.SurfaceId, StringComparison.Ordinal) && + AetheriaRuntimeOperationsCommands.IsKnown(command)) || (string.Equals(surfaceId, AetheriaPlayerSettingsSurfaceProjector.SurfaceId, StringComparison.Ordinal) && GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.IsKnown(command)) || + (string.Equals(surfaceId, GameCult.Aetheria.State.Unity.AetheriaRuntimeInputSettingsCommands.SurfaceId, StringComparison.Ordinal) && + GameCult.Aetheria.State.Unity.AetheriaRuntimeInputSettingsCommands.IsKnown(command)) || + (string.Equals(surfaceId, GameCult.Aetheria.State.Unity.AetheriaRuntimeLoadoutTemplateCommands.SurfaceId, StringComparison.Ordinal) && + GameCult.Aetheria.State.Unity.AetheriaRuntimeLoadoutTemplateCommands.IsKnown(command)) || (string.Equals(surfaceId, GameCult.Aetheria.State.Unity.AetheriaRuntimeVerseHostCommands.SurfaceId, StringComparison.Ordinal) && GameCult.Aetheria.State.Unity.AetheriaRuntimeVerseHostCommands.IsKnown(command)); } - private static async Task ApplyPlayerSettingsCommandAsync( + private static async Task ExecutePlayerSettingsCommandAsync( AetheriaStateNode node, AetheriaRuntimeEveCommandDocument command) { @@ -145,9 +145,7 @@ public static class AetheriaEveCommandBridge switch (command.Command) { case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.SetPlayerName: - settings.PlayerName = command.Payload.TryGetValue("value", out var playerName) - ? playerName ?? "" - : ""; + settings.PlayerName = command.PlayerSettings.PlayerName ?? ""; persistSettings = true; break; case GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.CycleTemperatureUnit: @@ -193,7 +191,66 @@ public static class AetheriaEveCommandBridge .ConfigureAwait(false); } - private static async Task ApplyVerseHostCommandAsync( + private static async Task ExecuteInputSettingsCommandAsync( + AetheriaStateNode node, + AetheriaRuntimeEveCommandDocument command) + { + var settings = await node.GetPlayerSettingsAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings(); + settings.Input ??= new AetheriaPlayerInputSettings(); + var persistSettings = false; + + switch (command.Command) + { + case GameCult.Aetheria.State.Unity.AetheriaRuntimeInputSettingsCommands.SetBindingOverride: + var actionName = command.InputSettings.ActionName ?? ""; + var inputSystemPath = command.InputSettings.InputSystemPath ?? ""; + var bindingIndex = command.InputSettings.BindingIndex; + if (!string.IsNullOrWhiteSpace(actionName) && bindingIndex >= 0) + { + settings.Input.BindingOverrides = settings.Input.BindingOverrides + .Where(binding => !string.Equals(binding.ActionName, actionName, StringComparison.Ordinal) || + binding.BindingIndex != bindingIndex) + .Concat(new[] + { + new AetheriaInputBindingOverride + { + ActionName = actionName, + BindingIndex = bindingIndex, + BindingPath = inputSystemPath + } + }) + .OrderBy(binding => binding.ActionName, StringComparer.Ordinal) + .ThenBy(binding => binding.BindingIndex) + .ToArray(); + persistSettings = true; + } + break; + case GameCult.Aetheria.State.Unity.AetheriaRuntimeInputSettingsCommands.SetActionBarEnabled: + var inputPath = command.InputSettings.InputSystemPath ?? ""; + var enabled = command.InputSettings.Enabled; + if (!string.IsNullOrWhiteSpace(inputPath)) + { + var inputs = settings.Input.ActionBarInputs + .Where(path => !string.Equals(path, inputPath, StringComparison.Ordinal)) + .ToList(); + if (enabled) + inputs.Add(inputPath); + settings.Input.ActionBarInputs = inputs + .OrderBy(path => path, StringComparer.Ordinal) + .ToArray(); + persistSettings = true; + } + break; + } + + if (persistSettings) + { + settings.LastUpdatedAtUtc = command.IssuedAtUtc; + await node.PutPlayerSettingsAsync(settings).ConfigureAwait(false); + } + } + + private static async Task ExecuteVerseHostCommandAsync( AetheriaStateNode node, AetheriaRuntimeEveCommandDocument command) { @@ -215,17 +272,12 @@ public static class AetheriaEveCommandBridge await node.PutVerseHostSettingsAsync(normalized).ConfigureAwait(false); } - var commitStatus = await node.GetRuntimeCommitDrainStatusAsync().ConfigureAwait(false) ?? - EmptyCommitDrainStatus(node.StatePath, command.IssuedAtUtc); - var eveStatus = await node.GetEveCommandDrainStatusAsync().ConfigureAwait(false) ?? - EmptyEveCommandDrainStatus(node.StatePath, command.IssuedAtUtc); - var runtimeSession = string.IsNullOrWhiteSpace(commitStatus.RuntimeId) - ? null - : await node.GetRuntimeSessionAsync(commitStatus.RuntimeId).ConfigureAwait(false); + var eveStatus = await node.GetEveCommandAcceptanceStatusAsync().ConfigureAwait(false) ?? + EmptyEveCommandAcceptanceStatus(node.StatePath, command.IssuedAtUtc); + var runtimeSession = await node.GetRuntimeSessionAsync(eveStatus.RuntimeId).ConfigureAwait(false); await node.PutOperationsSurfaceAsync( AetheriaOperationsSurfaceProjector.Build( - commitStatus, eveStatus, normalized, runtimeSession)).ConfigureAwait(false); @@ -233,6 +285,18 @@ public static class AetheriaEveCommandBridge AetheriaProviderAdvertisementProjector.Build(normalized, node.StatePath, command.IssuedAtUtc)).ConfigureAwait(false); } + private static async Task ExecuteLoadoutTemplateCommandAsync( + AetheriaStateNode node, + AetheriaRuntimeEveCommandDocument command) + { + var commit = command.LoadoutTemplate ?? throw new InvalidOperationException( + $"Loadout template command '{command.CommandId}' is missing its typed payload."); + var loadout = AetheriaRuntimeStateMapper.ToLoadoutTemplate(commit, command.IssuedAtUtc); + await node.PutLoadoutTemplateAsync( + AetheriaRuntimeStateMapper.LoadoutKey(loadout.Name), + loadout).ConfigureAwait(false); + } + private static string Cycle(string current, params string[] values) { if (values.Length == 0) @@ -246,37 +310,22 @@ public static class AetheriaEveCommandBridge } private static void RecordRejection( - AetheriaEveCommandApplyReport report, + AetheriaEveCommandAcceptanceReport report, AetheriaRuntimeEveCommandDocument command, string reason, - string path, - List rejected, - bool deleteAccounted) + List rejected) { report.RejectedCommands++; report.LastRejectedCommand = string.IsNullOrWhiteSpace(command.Command) ? command.CommandId : command.Command; report.LastRejectedReason = reason; - rejected.Add(path); - if (deleteAccounted) - File.Delete(path); + rejected.Add(command.CommandId ?? ""); } - private static AetheriaRuntimeCommitDrainStatus EmptyCommitDrainStatus(string statePath, string now) + private static AetheriaEveCommandAcceptanceStatus EmptyEveCommandAcceptanceStatus(string statePath, string now) { - return new AetheriaRuntimeCommitDrainStatus - { - RuntimeId = "aetheria-state", - StatePath = statePath, - LastPollAtUtc = now, - Status = "idle" - }; - } - - private static AetheriaEveCommandDrainStatus EmptyEveCommandDrainStatus(string statePath, string now) - { - return new AetheriaEveCommandDrainStatus + return new AetheriaEveCommandAcceptanceStatus { RuntimeId = "aetheria-state", StatePath = statePath, diff --git a/Aetheria.State/AetheriaOperationsSurfaceProjector.cs b/Aetheria.State/AetheriaOperationsSurfaceProjector.cs index 52ba5b738..79cc09485 100644 --- a/Aetheria.State/AetheriaOperationsSurfaceProjector.cs +++ b/Aetheria.State/AetheriaOperationsSurfaceProjector.cs @@ -1,22 +1,21 @@ using Aetheria.State.Documents; +using GameCult.Aetheria.State.Unity; namespace Aetheria.State; public static class AetheriaOperationsSurfaceProjector { public const string SurfaceKey = "eve:surface:aetheria.operations"; - public const string SurfaceId = "aetheria.operations"; + public const string SurfaceId = AetheriaRuntimeOperationsCommands.SurfaceId; public static EveSurfaceState Build( - AetheriaRuntimeCommitDrainStatus drainStatus, - AetheriaEveCommandDrainStatus? eveCommandStatus = null, + AetheriaEveCommandAcceptanceStatus? eveCommandStatus = null, AetheriaVerseHostSettings? verseHostSettings = null, AetheriaRuntimeSession? runtimeSession = null, long version = 1) { var normalizedVerseHost = AetheriaVerseHostSettingsNormalizer.Normalize(verseHostSettings); var updatedAtUtc = LatestTimestamp( - drainStatus.LastPollAtUtc, eveCommandStatus?.LastPollAtUtc, normalizedVerseHost.LastUpdatedAtUtc, runtimeSession?.LastSeenAtUtc); @@ -35,37 +34,22 @@ public static class AetheriaOperationsSurfaceProjector "surface", [], Node( - "aetheria.operations.commitDrain", + "aetheria.operations.eveCommandAcceptance", "card", - [("title", "Runtime Commit Drain")], - Metric("commitDrain.status", "Status", drainStatus.Status), - Metric("commitDrain.pending", "Pending Before Apply", drainStatus.PendingBeforeApply.ToString()), - Metric("commitDrain.applied", "Commands Applied", drainStatus.CommandsApplied.ToString()), - Metric("commitDrain.settings", "Settings", drainStatus.AppliedPlayerSettings.ToString()), - Metric("commitDrain.loadouts", "Loadouts", drainStatus.AppliedLoadoutTemplates.ToString()), - Metric("commitDrain.runs", "Run Checkpoints", drainStatus.AppliedRunCheckpoints.ToString()), - Metric("commitDrain.failures", "Consecutive Failures", drainStatus.ConsecutiveFailures.ToString()), + [("title", "Eve Request Acceptance")], + Metric("eveCommandAcceptance.status", "Status", eveCommandStatus?.Status ?? "missing"), + Metric("eveCommandAcceptance.observed", "Observed Before Accept", (eveCommandStatus?.ObservedBeforeAccept ?? 0).ToString()), + Metric("eveCommandAcceptance.accepted", "Commands Accepted", (eveCommandStatus?.CommandsAccepted ?? 0).ToString()), + Metric("eveCommandAcceptance.rejected", "Commands Rejected", (eveCommandStatus?.CommandsRejected ?? 0).ToString()), + Metric("eveCommandAcceptance.catalogRefreshes", "Catalog Refreshes", (eveCommandStatus?.AppliedCatalogRefreshes ?? 0).ToString()), + Metric("eveCommandAcceptance.operationsRefreshes", "Operations Refreshes", (eveCommandStatus?.AppliedOperationsRefreshes ?? 0).ToString()), + Metric("eveCommandAcceptance.playerSettings", "Player Settings Commands", (eveCommandStatus?.AppliedPlayerSettingsCommands ?? 0).ToString()), + Metric("eveCommandAcceptance.inputSettings", "Input Settings Commands", (eveCommandStatus?.AppliedInputSettingsCommands ?? 0).ToString()), + Metric("eveCommandAcceptance.loadoutTemplates", "Loadout Template Commands", (eveCommandStatus?.AppliedLoadoutTemplateCommands ?? 0).ToString()), + Metric("eveCommandAcceptance.verseHost", "Verse Host Commands", (eveCommandStatus?.AppliedVerseHostCommands ?? 0).ToString()), + Metric("eveCommandAcceptance.failures", "Consecutive Failures", (eveCommandStatus?.ConsecutiveFailures ?? 0).ToString()), Row( - "commitDrain.last", - ("runtime", drainStatus.RuntimeId), - ("lastPoll", drainStatus.LastPollAtUtc), - ("lastApplied", drainStatus.LastAppliedAtUtc), - ("error", drainStatus.LastError))), - Node( - "aetheria.operations.eveCommandDrain", - "card", - [("title", "Eve Command Drain")], - Metric("eveCommandDrain.status", "Status", eveCommandStatus?.Status ?? "missing"), - Metric("eveCommandDrain.pending", "Pending Before Apply", (eveCommandStatus?.PendingBeforeApply ?? 0).ToString()), - Metric("eveCommandDrain.accepted", "Commands Accepted", (eveCommandStatus?.CommandsAccepted ?? 0).ToString()), - Metric("eveCommandDrain.rejected", "Commands Rejected", (eveCommandStatus?.CommandsRejected ?? 0).ToString()), - Metric("eveCommandDrain.catalogRefreshes", "Catalog Refreshes", (eveCommandStatus?.AppliedCatalogRefreshes ?? 0).ToString()), - Metric("eveCommandDrain.operationsRefreshes", "Operations Refreshes", (eveCommandStatus?.AppliedOperationsRefreshes ?? 0).ToString()), - Metric("eveCommandDrain.playerSettings", "Player Settings Commands", (eveCommandStatus?.AppliedPlayerSettingsCommands ?? 0).ToString()), - Metric("eveCommandDrain.verseHost", "Verse Host Commands", (eveCommandStatus?.AppliedVerseHostCommands ?? 0).ToString()), - Metric("eveCommandDrain.failures", "Consecutive Failures", (eveCommandStatus?.ConsecutiveFailures ?? 0).ToString()), - Row( - "eveCommandDrain.last", + "eveCommandAcceptance.last", ("runtime", eveCommandStatus?.RuntimeId ?? ""), ("lastPoll", eveCommandStatus?.LastPollAtUtc ?? ""), ("lastAccepted", eveCommandStatus?.LastAcceptedAtUtc ?? ""), @@ -101,7 +85,7 @@ public static class AetheriaOperationsSurfaceProjector [ new EveCommandTemplate { - Command = "aetheria.operations.refresh", + Command = AetheriaRuntimeOperationsCommands.Refresh, Label = "Refresh", Transport = "cultmesh" } diff --git a/Aetheria.State/AetheriaProviderAdvertisementProjector.cs b/Aetheria.State/AetheriaProviderAdvertisementProjector.cs index 8f75a45cf..7165d35cf 100644 --- a/Aetheria.State/AetheriaProviderAdvertisementProjector.cs +++ b/Aetheria.State/AetheriaProviderAdvertisementProjector.cs @@ -1,11 +1,18 @@ using Aetheria.State.Documents; +using GameCult.Aetheria.State.Unity; namespace Aetheria.State; public static class AetheriaProviderAdvertisementProjector { public const string AdvertisementKey = "eve:provider:aetheria"; + public const string DaemonGameSurfaceKey = "eve:surface:aetheria.daemon.game"; + public const string DaemonGameTuiSurfaceKey = "eve:surface:aetheria.daemon.game.tui"; + public const string DaemonEditorSurfaceKey = "eve:surface:aetheria.daemon.editor"; + public const string DaemonEditorTuiSurfaceKey = "eve:surface:aetheria.daemon.editor.tui"; public const string ProviderId = "aetheria"; + private const string DaemonCommandBoundaryId = "aetheria.daemon.commands"; + private const string DaemonWitnessTransport = "cultcache-witness"; public static EveProviderAdvertisementState Build( AetheriaVerseHostSettings settings, @@ -44,10 +51,17 @@ public static class AetheriaProviderAdvertisementProjector "aetheria.entity_snapshot.v1", "aetheria.verse_host_settings.v1", "aetheria.runtime_session.v1", - "aetheria.runtime_commit_drain_status.v1", - "aetheria.eve_command_drain_status.v1", + "aetheria.eve_command_acceptance_status.v1", "gamecult.eve.surface.v1", - "gamecult.eve.command.v1" + "gamecult.eve.command.v1", + AetheriaRuntimeDaemonSchemas.ProviderAdvertisement, + AetheriaRuntimeDaemonSchemas.Frame, + AetheriaRuntimeDaemonSchemas.SoaView, + AetheriaRuntimeDaemonSchemas.Health, + AetheriaRuntimeDaemonSchemas.CommandBoundary, + AetheriaRuntimeDaemonSchemas.GameSurface, + AetheriaRuntimeDaemonSchemas.EditorSurface, + AetheriaRuntimeDaemonSchemas.Command ], Witnesses = [ @@ -56,6 +70,60 @@ public static class AetheriaProviderAdvertisementProjector Kind = "cultcache", Ref = statePath, Summary = "Aetheria typed CultCache state file" + }, + new EveProviderWitness + { + Kind = DaemonWitnessTransport, + Ref = AetheriaRuntimeStateBoundary.GetDaemonProviderPath(statePath), + Summary = "Aetheria daemon-owned provider advertisement" + }, + new EveProviderWitness + { + Kind = DaemonWitnessTransport, + Ref = AetheriaRuntimeStateBoundary.GetDaemonFramePath(statePath), + Summary = "Aetheria daemon simulation frame" + }, + new EveProviderWitness + { + Kind = DaemonWitnessTransport, + Ref = AetheriaRuntimeStateBoundary.GetDaemonSoaViewPath(statePath), + Summary = "Aetheria daemon SoA view for thin clients" + }, + new EveProviderWitness + { + Kind = DaemonWitnessTransport, + Ref = AetheriaRuntimeStateBoundary.GetDaemonHealthPath(statePath), + Summary = "Aetheria daemon health publication" + }, + new EveProviderWitness + { + Kind = DaemonWitnessTransport, + Ref = AetheriaRuntimeStateBoundary.GetDaemonCommandBoundaryPath(statePath), + Summary = "Aetheria daemon typed command boundary" + }, + new EveProviderWitness + { + Kind = DaemonWitnessTransport, + Ref = AetheriaRuntimeStateBoundary.GetDaemonGameSurfacePath(statePath), + Summary = "Aetheria daemon game Eve GUI surface" + }, + new EveProviderWitness + { + Kind = DaemonWitnessTransport, + Ref = AetheriaRuntimeStateBoundary.GetDaemonGameTuiSurfacePath(statePath), + Summary = "Aetheria daemon game Eve TUI surface" + }, + new EveProviderWitness + { + Kind = DaemonWitnessTransport, + Ref = AetheriaRuntimeStateBoundary.GetDaemonEditorSurfacePath(statePath), + Summary = "Aetheria daemon editor Eve GUI surface" + }, + new EveProviderWitness + { + Kind = DaemonWitnessTransport, + Ref = AetheriaRuntimeStateBoundary.GetDaemonEditorTuiSurfacePath(statePath), + Summary = "Aetheria daemon editor Eve TUI surface" } ], Surfaces = @@ -74,48 +142,74 @@ public static class AetheriaProviderAdvertisementProjector { SurfaceId = AetheriaPlayerSettingsSurfaceProjector.SurfaceId, Key = AetheriaPlayerSettingsSurfaceProjector.SurfaceKey + }, + new EveProviderSurfaceRef + { + SurfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId, + Key = DaemonGameSurfaceKey + }, + new EveProviderSurfaceRef + { + SurfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId, + Key = DaemonGameTuiSurfaceKey + }, + new EveProviderSurfaceRef + { + SurfaceId = AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId, + Key = DaemonEditorSurfaceKey + }, + new EveProviderSurfaceRef + { + SurfaceId = AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId, + Key = DaemonEditorTuiSurfaceKey } ], Commands = [ new EveProviderCommandRef { - Command = "aetheria.catalog.refresh", + Command = DaemonCommandBoundaryId, + Transport = DaemonWitnessTransport, + Summary = "Aetheria daemon typed command boundary" + }, + new EveProviderCommandRef + { + Command = AetheriaRuntimeCatalogCommands.Refresh, Summary = "Refresh catalog projection" }, new EveProviderCommandRef { - Command = "aetheria.operations.refresh", + Command = AetheriaRuntimeOperationsCommands.Refresh, Summary = "Refresh operations projection" }, new EveProviderCommandRef { - Command = GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.Refresh, + Command = AetheriaRuntimePlayerSettingsCommands.Refresh, Summary = "Refresh player settings projection" }, new EveProviderCommandRef { - Command = GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.CycleTemperatureUnit, + Command = AetheriaRuntimePlayerSettingsCommands.CycleTemperatureUnit, Summary = "Cycle the typed player temperature unit" }, new EveProviderCommandRef { - Command = GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.DecrementSignificantDigits, + Command = AetheriaRuntimePlayerSettingsCommands.DecrementSignificantDigits, Summary = "Decrease typed player significant digits" }, new EveProviderCommandRef { - Command = GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.IncrementSignificantDigits, + Command = AetheriaRuntimePlayerSettingsCommands.IncrementSignificantDigits, Summary = "Increase typed player significant digits" }, new EveProviderCommandRef { - Command = GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.CycleNebulaQuality, + Command = AetheriaRuntimePlayerSettingsCommands.CycleNebulaQuality, Summary = "Cycle typed player nebula quality" }, new EveProviderCommandRef { - Command = GameCult.Aetheria.State.Unity.AetheriaRuntimePlayerSettingsCommands.ToggleShowAsteroidsInMinimap, + Command = AetheriaRuntimePlayerSettingsCommands.ToggleShowAsteroidsInMinimap, Summary = "Toggle typed player minimap asteroid visibility" } ] diff --git a/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs b/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs deleted file mode 100644 index ddd9fd598..000000000 --- a/Aetheria.State/AetheriaRuntimeCommitLogApplier.cs +++ /dev/null @@ -1,756 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Unity; -using GameCult.Caching; - -namespace Aetheria.State; - -public sealed class AetheriaRuntimeCommitApplyReport -{ - public int AppliedPlayerSettings { get; set; } - public int AppliedLoadoutTemplates { get; set; } - public int AppliedRunCheckpoints { get; set; } - public string[] AppliedPaths { get; set; } = []; -} - -public static class AetheriaRuntimeCommitLogApplier -{ - public static async Task ApplyPendingAsync( - AetheriaStateNode node, - bool deleteApplied = true) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - - var pendingDirectory = node.StatePath + ".pending"; - var report = new AetheriaRuntimeCommitApplyReport(); - var applied = new List(); - - if (!Directory.Exists(pendingDirectory)) - return report; - - foreach (var path in Directory.EnumerateFiles(pendingDirectory, "*.cc").OrderBy(path => path, StringComparer.Ordinal)) - { - var command = ReadCommand(path); - switch (command.Kind) - { - case "player_settings": - var settings = ToPlayerSettings( - command.PlayerSettings ?? throw MissingPayload(command), - command.CreatedAtUtc); - await node.PutPlayerSettingsAsync(settings).ConfigureAwait(false); - await node.PutPlayerSettingsSurfaceAsync( - AetheriaPlayerSettingsSurfaceProjector.Build(settings, command.CreatedAtUtc)) - .ConfigureAwait(false); - report.AppliedPlayerSettings++; - break; - case "loadout_template": - var loadout = ToLoadoutTemplate( - command.LoadoutTemplate ?? throw MissingPayload(command), - command.CreatedAtUtc); - await node.PutLoadoutTemplateAsync(LoadoutKey(loadout.Name), loadout).ConfigureAwait(false); - report.AppliedLoadoutTemplates++; - break; - case "run_checkpoint": - await ApplyRunCheckpointAsync( - node, - command.RunCheckpoint ?? throw MissingPayload(command), - command.CreatedAtUtc) - .ConfigureAwait(false); - report.AppliedRunCheckpoints++; - break; - default: - throw new InvalidDataException($"Unknown Aetheria runtime commit kind '{command.Kind}' in {path}."); - } - - applied.Add(path); - if (deleteApplied) - File.Delete(path); - } - - await node.FlushAsync().ConfigureAwait(false); - report.AppliedPaths = applied.ToArray(); - return report; - } - - private static AetheriaRuntimeStateCommitDocument ReadCommand(string path) - { - var command = AetheriaRuntimePendingCultCacheStore.ReadStateCommit(path); - if (!string.Equals(command.Schema, AetheriaRuntimeStateCommitDocument.SchemaId, StringComparison.Ordinal)) - throw new InvalidDataException($"Unexpected Aetheria runtime commit schema '{command.Schema}' in {path}."); - return command; - } - - private static AetheriaPlayerSettings ToPlayerSettings( - AetheriaRuntimePlayerSettingsCommit settings, - string updatedAtUtc) - { - return new AetheriaPlayerSettings - { - PlayerName = settings.PlayerName ?? "", - TutorialPassed = settings.TutorialPassed, - StoryFileHashes = (settings.StoryFileHashes ?? Array.Empty()) - .Select(hash => new AetheriaStoryFileHash - { - StoryPath = hash.StoryPath ?? "", - Hash = hash.Hash ?? "" - }) - .ToArray(), - Gameplay = new AetheriaPlayerGameplaySettings - { - TemperatureUnit = settings.TemperatureUnit ?? "", - SignificantDigits = settings.SignificantDigits - }, - Graphics = new AetheriaPlayerGraphicsSettings - { - NebulaQuality = settings.NebulaQuality ?? "", - ShowAsteroidsInMinimap = settings.ShowAsteroidsInMinimap - }, - Input = new AetheriaPlayerInputSettings - { - BindingOverrides = (settings.BindingOverrides ?? Array.Empty()) - .Select(binding => new AetheriaInputBindingOverride - { - ActionName = binding.ActionName ?? "", - BindingIndex = binding.BindingIndex, - BindingPath = binding.BindingPath ?? "" - }) - .ToArray(), - ActionBarInputs = (settings.ActionBarInputs ?? Array.Empty()) - .Select(input => input ?? "") - .ToArray() - }, - LastUpdatedAtUtc = updatedAtUtc - }; - } - - private static AetheriaLoadoutTemplate ToLoadoutTemplate( - AetheriaRuntimeLoadoutTemplateCommit loadout, - string updatedAtUtc) - { - return new AetheriaLoadoutTemplate - { - Name = loadout.Name ?? "", - OwnerPlayerKey = loadout.OwnerPlayerKey ?? "", - RootEntity = ToEntityLoadout(loadout.RootEntity), - CreatedAtUtc = updatedAtUtc, - UpdatedAtUtc = updatedAtUtc - }; - } - - private static AetheriaEntityLoadout ToEntityLoadout(AetheriaRuntimeEntityLoadoutCommit? entity) - { - entity ??= new AetheriaRuntimeEntityLoadoutCommit(); - return new AetheriaEntityLoadout - { - Name = entity.Name ?? "", - Kind = entity.Kind ?? "", - FactionKey = entity.FactionKey ?? "", - Hull = ToLoadoutItem(entity.Hull), - Equipment = ToItemSlots(entity.Equipment), - CargoBays = ToItemSlots(entity.CargoBays), - DockingBays = ToItemSlots(entity.DockingBays), - CargoContents = ToCargoBays(entity.CargoContents), - DockingBayContents = ToCargoBays(entity.DockingBayContents), - DockingBayAssignments = ToIntArray(entity.DockingBayAssignments), - WeaponGroups = ToIntArrayArray(entity.WeaponGroups), - Children = (entity.Children ?? Array.Empty()) - .Select(ToEntityLoadout) - .ToArray() - }; - } - - private static AetheriaLoadoutItem ToLoadoutItem(AetheriaRuntimeLoadoutItemCommit? item) - { - item ??= new AetheriaRuntimeLoadoutItemCommit(); - return new AetheriaLoadoutItem - { - ItemKey = item.ItemKey ?? "", - Quality = item.Quality, - Durability = item.Durability, - Quantity = item.Quantity, - Enabled = item.Enabled, - OverrideShutdown = item.OverrideShutdown - }; - } - - private static AetheriaLoadoutItemSlot[] ToItemSlots( - IReadOnlyList? slots) - { - return (slots ?? Array.Empty()) - .Select(slot => new AetheriaLoadoutItemSlot - { - Position = new AetheriaGridCoord - { - X = slot.X, - Y = slot.Y - }, - Item = ToLoadoutItem(slot.Item) - }) - .ToArray(); - } - - private static AetheriaEntityItemSlot[] ToEntityItemSlots( - IReadOnlyList? slots) - { - return (slots ?? Array.Empty()) - .Select(slot => - { - var item = ToLoadoutItem(slot.Item); - return new AetheriaEntityItemSlot - { - Position = new AetheriaGridCoord - { - X = slot.X, - Y = slot.Y - }, - ItemKey = item.ItemKey, - Quality = item.Quality, - Durability = item.Durability, - Quantity = item.Quantity, - Enabled = item.Enabled, - OverrideShutdown = item.OverrideShutdown - }; - }) - .ToArray(); - } - - private static AetheriaCargoBayLoadout[] ToCargoBays( - IReadOnlyList? cargoBays) - { - return (cargoBays ?? Array.Empty()) - .Select(bay => new AetheriaCargoBayLoadout - { - Items = ToItemSlots(bay.Items) - }) - .ToArray(); - } - - private static async Task ApplyRunCheckpointAsync( - AetheriaStateNode node, - AetheriaRuntimeRunCheckpointCommit checkpoint, - string updatedAtUtc) - { - var run = new AetheriaRunState - { - RunId = checkpoint.RunId ?? "", - IsTutorial = checkpoint.IsTutorial, - EntranceZoneIndex = checkpoint.EntranceZoneIndex, - ExitZoneIndex = checkpoint.ExitZoneIndex, - CurrentZoneIndex = checkpoint.CurrentZoneIndex, - DiscoveredZoneIndices = ToIntArray(checkpoint.DiscoveredZoneIndices), - ActionBarBindings = ToActionBarBindings(checkpoint.ActionBarBindings), - FactionRelationships = ToFactionRelationships(checkpoint.FactionRelationships), - UpdatedAtUtc = updatedAtUtc, - GenerationSeed = checkpoint.GenerationSeed - }; - - var zoneKeys = new List(); - foreach (var zone in checkpoint.Zones ?? Array.Empty()) - { - var zoneKey = ZoneKey(run.RunId, zone.ZoneIndex); - var entityKeys = new List(); - foreach (var entity in zone.Entities ?? Array.Empty()) - { - var entityKey = EntityKey(run.RunId, zone.ZoneIndex, entity.EntityIndex); - var snapshot = ToEntitySnapshot(entity, run.RunId, zone.ZoneIndex); - await node.PutEntitySnapshotAsync(entityKey, snapshot).ConfigureAwait(false); - entityKeys.Add(entityKey.ToString()); - } - - await node.PutZoneStateAsync( - zoneKey, - new AetheriaZoneState - { - Name = zone.Name ?? "", - Position = new AetheriaVector2 - { - X = zone.PositionX, - Y = zone.PositionY - }, - AdjacentZoneIndices = ToIntArray(zone.AdjacentZoneIndices), - FactionIndices = ToIntArray(zone.FactionIndices), - OwnerFactionIndex = zone.OwnerFactionIndex, - EntityKeys = entityKeys.ToArray(), - Orbits = ToOrbitSnapshots(zone.Orbits), - Bodies = ToBodySnapshots(zone.Bodies, run.RunId, zone.ZoneIndex), - DroppedPickups = ToDroppedPickups(zone.DroppedPickups) - }) - .ConfigureAwait(false); - zoneKeys.Add(zoneKey.ToString()); - } - - if (zoneKeys.Count > 0) - run.ZoneKeys = zoneKeys.ToArray(); - run.CurrentEntityKey = checkpoint.CurrentEntityKey ?? ""; - await node.PutRunStateAsync(RunKey(run.RunId), run).ConfigureAwait(false); - } - - private static AetheriaDroppedPickupSnapshot[] ToDroppedPickups( - IReadOnlyList? pickups) - { - return (pickups ?? Array.Empty()) - .Select(pickup => new AetheriaDroppedPickupSnapshot - { - PickupIndex = pickup.PickupIndex, - Position = new AetheriaVector3 - { - X = pickup.PositionX, - Y = pickup.PositionY, - Z = pickup.PositionZ - }, - Velocity = new AetheriaVector3 - { - X = pickup.VelocityX, - Y = pickup.VelocityY, - Z = pickup.VelocityZ - }, - Item = ToLoadoutItem(pickup.Item) - }) - .ToArray(); - } - - private static AetheriaOrbitSnapshot[] ToOrbitSnapshots( - IReadOnlyList? orbits) - { - return (orbits ?? Array.Empty()) - .Select(orbit => new AetheriaOrbitSnapshot - { - OrbitKey = orbit.OrbitKey ?? "", - ParentOrbitKey = orbit.ParentOrbitKey ?? "", - Distance = orbit.Distance, - Phase = orbit.Phase, - FixedPosition = new AetheriaVector2 - { - X = orbit.FixedPositionX, - Y = orbit.FixedPositionY - } - }) - .ToArray(); - } - - private static AetheriaBodySnapshot[] ToBodySnapshots( - IReadOnlyList? bodies, - string runId, - int zoneIndex) - { - return (bodies ?? Array.Empty()) - .Select(body => new AetheriaBodySnapshot - { - BodyKey = body.BodyKey ?? "", - Kind = body.Kind ?? "", - Name = body.Name ?? "", - OrbitKey = body.OrbitKey ?? "", - Mass = body.Mass, - Resources = ToBodyResources(body.Resources), - BodyRadiusMultiplier = body.BodyRadiusMultiplier, - GravityRadiusMultiplier = body.GravityRadiusMultiplier, - GravityDepthMultiplier = body.GravityDepthMultiplier, - GravityDepthExponent = body.GravityDepthExponent, - Asteroids = ToAsteroidSnapshots(body.Asteroids, runId, zoneIndex), - GasGiantVisual = ToGasGiantVisual(body.GasGiantVisual), - SunVisual = ToSunVisual(body.SunVisual) - }) - .ToArray(); - } - - private static AetheriaBodyResource[] ToBodyResources( - IReadOnlyList? resources) - { - return (resources ?? Array.Empty()) - .Select(resource => new AetheriaBodyResource - { - ItemKey = resource.ItemKey ?? "", - Amount = resource.Amount - }) - .ToArray(); - } - - private static AetheriaAsteroidSnapshot[] ToAsteroidSnapshots( - IReadOnlyList? asteroids, - string runId, - int zoneIndex) - { - return (asteroids ?? Array.Empty()) - .Select(asteroid => new AetheriaAsteroidSnapshot - { - Distance = asteroid.Distance, - Phase = asteroid.Phase, - Size = asteroid.Size, - RotationSpeed = asteroid.RotationSpeed, - Damage = asteroid.Damage, - RespawnTimer = asteroid.RespawnTimer, - MiningAccumulators = ToAsteroidMiningAccumulators(asteroid.MiningAccumulators, runId, zoneIndex) - }) - .ToArray(); - } - - private static AetheriaAsteroidMiningAccumulatorSnapshot[] ToAsteroidMiningAccumulators( - IReadOnlyList? accumulators, - string runId, - int zoneIndex) - { - return (accumulators ?? Array.Empty()) - .Where(accumulator => accumulator.MinerEntityIndex >= 0) - .Select(accumulator => new AetheriaAsteroidMiningAccumulatorSnapshot - { - MinerEntityKey = EntityKey(runId, zoneIndex, accumulator.MinerEntityIndex).ToString(), - Amount = accumulator.Amount - }) - .ToArray(); - } - - private static AetheriaGasGiantVisualState ToGasGiantVisual(AetheriaRuntimeGasGiantVisualCommit? visual) - { - if (visual == null) - return new AetheriaGasGiantVisualState(); - - return new AetheriaGasGiantVisualState - { - FirstOffsetDomainRotationSpeed = visual.FirstOffsetDomainRotationSpeed, - FirstOffsetRotationSpeed = visual.FirstOffsetRotationSpeed, - SecondOffsetDomainRotationSpeed = visual.SecondOffsetDomainRotationSpeed, - SecondOffsetRotationSpeed = visual.SecondOffsetRotationSpeed, - AlbedoRotationSpeed = visual.AlbedoRotationSpeed, - WaveRadiusMultiplier = visual.WaveRadiusMultiplier, - WaveDepthMultiplier = visual.WaveDepthMultiplier, - WaveDepthExponent = visual.WaveDepthExponent, - WaveSpeedMultiplier = visual.WaveSpeedMultiplier, - MaterialOverrides = (visual.MaterialOverrides ?? Array.Empty()).ToArray(), - Colors = (visual.Colors ?? Array.Empty()) - .Select(ToColor) - .ToArray() - }; - } - - private static AetheriaSunVisualState ToSunVisual(AetheriaRuntimeSunVisualCommit? visual) - { - if (visual == null) - return new AetheriaSunVisualState(); - - return new AetheriaSunVisualState - { - LightColor = new AetheriaVector3 - { - X = visual.LightColorX, - Y = visual.LightColorY, - Z = visual.LightColorZ - }, - FogTintColor = new AetheriaVector3 - { - X = visual.FogTintColorX, - Y = visual.FogTintColorY, - Z = visual.FogTintColorZ - }, - LightRadiusMultiplier = visual.LightRadiusMultiplier - }; - } - - private static AetheriaColor ToColor(AetheriaRuntimeColorCommit color) - { - return new AetheriaColor - { - X = color.X, - Y = color.Y, - Z = color.Z, - W = color.W - }; - } - - private static AetheriaEntitySnapshot ToEntitySnapshot( - AetheriaRuntimeEntitySnapshotCommit entity, - string runId, - int zoneIndex) - { - return new AetheriaEntitySnapshot - { - Name = entity.Name ?? "", - Kind = entity.Kind ?? "", - Position = new AetheriaVector3 - { - X = entity.PositionX, - Y = entity.PositionY, - Z = entity.PositionZ - }, - Direction = new AetheriaVector2 - { - X = entity.DirectionX, - Y = entity.DirectionY - }, - Velocity = new AetheriaVector2 - { - X = entity.VelocityX, - Y = entity.VelocityY - }, - TargetEntityKey = entity.TargetEntityIndex >= 0 - ? EntityKey(runId, zoneIndex, entity.TargetEntityIndex).ToString() - : "", - IsActive = entity.IsActive, - HeatsinksEnabled = entity.HeatsinksEnabled, - OverrideShutdown = entity.OverrideShutdown, - TractorPower = entity.TractorPower, - Heatstroke = entity.Heatstroke, - Hypothermia = entity.Hypothermia, - FactionKey = entity.FactionKey ?? "", - HullItemKey = entity.HullItemKey ?? "", - Equipment = ToEntityItemSlots(entity.Equipment), - CargoBays = ToEntityItemSlots(entity.CargoBays), - DockingBays = ToEntityItemSlots(entity.DockingBays), - ChildEntityKeys = (entity.ChildEntityIndices ?? Array.Empty()) - .Select(childIndex => EntityKey(runId, zoneIndex, childIndex).ToString()) - .ToArray(), - WeaponGroups = ToIntArrayArray(entity.WeaponGroups) - .Select(group => new AetheriaWeaponGroupSnapshot { EquipmentIndices = group }) - .ToArray(), - StatGrids = ToEntityStatGrids(entity.StatGrids), - ActiveConsumables = ToActiveConsumables(entity.ActiveConsumables), - BehaviorProgress = ToBehaviorProgress(entity.BehaviorProgress), - WeaponStates = ToWeaponStates(entity.WeaponStates, runId, zoneIndex), - BehaviorStates = ToBehaviorStates(entity.BehaviorStates), - CargoContents = ToCargoBays(entity.CargoContents), - DockingBayContents = ToCargoBays(entity.DockingBayContents), - DockingBayAssignments = ToIntArray(entity.DockingBayAssignments), - Visibility = entity.Visibility, - VisibilitySourceCount = entity.VisibilitySourceCount, - Contacts = ToEntityContacts(entity.Contacts, runId, zoneIndex) - }; - } - - private static AetheriaEntityContactSnapshot[] ToEntityContacts( - IReadOnlyList? contacts, - string runId, - int zoneIndex) - { - return (contacts ?? Array.Empty()) - .Where(contact => contact.TargetEntityIndex >= 0) - .Select(contact => new AetheriaEntityContactSnapshot - { - TargetEntityKey = EntityKey(runId, zoneIndex, contact.TargetEntityIndex).ToString(), - InfoGathered = contact.InfoGathered, - Hostile = contact.Hostile, - Visible = contact.Visible - }) - .ToArray(); - } - - private static AetheriaBehaviorStateSnapshot[] ToBehaviorStates( - IReadOnlyList? states) - { - return (states ?? Array.Empty()) - .Select(state => new AetheriaBehaviorStateSnapshot - { - OwnerKind = state.OwnerKind ?? "", - OwnerIndex = state.OwnerIndex, - BehaviorIndex = state.BehaviorIndex, - BehaviorKind = state.BehaviorKind ?? "", - Pinging = state.Pinging, - PingCooldown = state.PingCooldown, - PingLerp = state.PingLerp, - PingRadius = state.PingRadius, - PingedEntityCount = state.PingedEntityCount, - RadiatorTemperature = state.RadiatorTemperature, - Emissivity = state.Emissivity, - PumpedHeat = state.PumpedHeat, - WasteHeat = state.WasteHeat, - EnergyUsage = state.EnergyUsage, - ReactorDraw = state.ReactorDraw, - ReactorLoadRatio = state.ReactorLoadRatio, - CapacitorCharge = state.CapacitorCharge, - CapacitorCapacity = state.CapacitorCapacity, - CapacitorEfficiency = state.CapacitorEfficiency, - AetherDriveAxisX = state.AetherDriveAxisX, - AetherDriveAxisY = state.AetherDriveAxisY, - AetherDriveAxisZ = state.AetherDriveAxisZ, - AetherDriveThrustX = state.AetherDriveThrustX, - AetherDriveThrustY = state.AetherDriveThrustY, - AetherDriveThrustZ = state.AetherDriveThrustZ, - AetherDriveRpmX = state.AetherDriveRpmX, - AetherDriveRpmY = state.AetherDriveRpmY, - AetherDriveRpmZ = state.AetherDriveRpmZ, - AetherDriveMaximumRpm = state.AetherDriveMaximumRpm, - AetherDriveThrustDirectionX = state.AetherDriveThrustDirectionX, - AetherDriveThrustDirectionY = state.AetherDriveThrustDirectionY, - ResourceScannerTargetBodyKey = state.ResourceScannerTargetBodyKey ?? "", - ResourceScannerAsteroidIndex = state.ResourceScannerAsteroidIndex, - ResourceScannerScanTime = state.ResourceScannerScanTime, - ResourceScannerRange = state.ResourceScannerRange, - ResourceScannerMinimumDensity = state.ResourceScannerMinimumDensity, - ResourceScannerScanDuration = state.ResourceScannerScanDuration, - MiningToolAsteroidBeltKey = state.MiningToolAsteroidBeltKey ?? "", - MiningToolAsteroidIndex = state.MiningToolAsteroidIndex, - MiningToolRange = state.MiningToolRange, - ThrusterAxis = state.ThrusterAxis, - ThrusterThrust = state.ThrusterThrust, - ThrusterTorque = state.ThrusterTorque, - ShieldEfficiency = state.ShieldEfficiency, - ShieldEnergyUsage = state.ShieldEnergyUsage, - VelocityLimit = state.VelocityLimit, - ThermotoggleTargetTemperature = state.ThermotoggleTargetTemperature, - SwitchActivated = state.SwitchActivated, - TriggerPulled = state.TriggerPulled, - StatModifierApplied = state.StatModifierApplied, - StatModifierExecuted = state.StatModifierExecuted, - StatModifierTargetStatCount = state.StatModifierTargetStatCount, - TurretControllerWeaponCount = state.TurretControllerWeaponCount, - TurretControllerShotSpeed = state.TurretControllerShotSpeed, - TurretControllerPredictShots = state.TurretControllerPredictShots - }) - .ToArray(); - } - - private static AetheriaWeaponStateSnapshot[] ToWeaponStates( - IReadOnlyList? states, - string runId, - int zoneIndex) - { - return (states ?? Array.Empty()) - .Select(state => new AetheriaWeaponStateSnapshot - { - OwnerKind = state.OwnerKind ?? "", - OwnerIndex = state.OwnerIndex, - BehaviorIndex = state.BehaviorIndex, - BehaviorKind = state.BehaviorKind ?? "", - Firing = state.Firing, - Ammo = state.Ammo, - BurstRemaining = state.BurstRemaining, - BurstTimer = state.BurstTimer, - BurstInterval = state.BurstInterval, - CooldownProgress = state.CooldownProgress, - CoolingDown = state.CoolingDown, - Charging = state.Charging, - Charged = state.Charged, - Charge = state.Charge, - Reloading = state.Reloading, - ReloadProgress = state.ReloadProgress, - AmmoIntervalProgress = state.AmmoIntervalProgress, - LockProgress = state.LockProgress, - LockTargetEntityKey = state.LockTargetEntityIndex >= 0 - ? EntityKey(runId, zoneIndex, state.LockTargetEntityIndex).ToString() - : "" - }) - .ToArray(); - } - - private static AetheriaBehaviorProgressSnapshot[] ToBehaviorProgress( - IReadOnlyList? progress) - { - return (progress ?? Array.Empty()) - .Select(entry => new AetheriaBehaviorProgressSnapshot - { - OwnerKind = entry.OwnerKind ?? "", - OwnerIndex = entry.OwnerIndex, - BehaviorIndex = entry.BehaviorIndex, - BehaviorKind = entry.BehaviorKind ?? "", - Progress = entry.Progress - }) - .ToArray(); - } - - private static AetheriaActiveConsumableSnapshot[] ToActiveConsumables( - IReadOnlyList? consumables) - { - return (consumables ?? Array.Empty()) - .Select(consumable => new AetheriaActiveConsumableSnapshot - { - ItemKey = consumable.ItemKey ?? "", - Quality = consumable.Quality, - RemainingDuration = consumable.RemainingDuration, - Duration = consumable.Duration - }) - .ToArray(); - } - - private static AetheriaEntityStatGrid[] ToEntityStatGrids( - IReadOnlyList? grids) - { - return (grids ?? Array.Empty()) - .Select(grid => new AetheriaEntityStatGrid - { - Name = grid.Name ?? "", - Width = grid.Width, - Height = grid.Height, - Values = (grid.Values ?? Array.Empty()).ToArray() - }) - .ToArray(); - } - - private static int[] ToIntArray(IReadOnlyList? values) - { - return (values ?? Array.Empty()).ToArray(); - } - - private static int[][] ToIntArrayArray(IReadOnlyList>? values) - { - return (values ?? Array.Empty>()) - .Select(ToIntArray) - .ToArray(); - } - - private static AetheriaActionBarBinding[] ToActionBarBindings( - IReadOnlyList? bindings) - { - return (bindings ?? Array.Empty()) - .Select(binding => new AetheriaActionBarBinding - { - ControlPath = binding.ControlPath ?? "", - Kind = binding.Kind ?? "", - TargetKey = binding.ItemKey ?? "", - EquipmentIndex = binding.EquipmentIndex, - BehaviorIndex = binding.BehaviorIndex, - WeaponGroup = binding.WeaponGroup - }) - .ToArray(); - } - - private static AetheriaFactionRelationshipState[] ToFactionRelationships( - IReadOnlyList? relationships) - { - return (relationships ?? Array.Empty()) - .Select(relationship => new AetheriaFactionRelationshipState - { - FactionKey = relationship.FactionKey ?? "", - Relationship = relationship.Relationship ?? "", - Standing = relationship.Standing - }) - .ToArray(); - } - - private static CultRecordKey LoadoutKey(string name) - { - return new CultRecordKey($"global:aetheria.loadout_template.{StableToken(name)}.v1"); - } - - private static CultRecordKey RunKey(string runId) - { - return new CultRecordKey($"global:aetheria.run_state.{StableToken(runId)}.v1"); - } - - private static CultRecordKey ZoneKey(string runId, int zoneIndex) - { - return new CultRecordKey($"global:aetheria.run_state.{StableToken(runId)}.zone.{zoneIndex}.v1"); - } - - private static CultRecordKey EntityKey(string runId, int zoneIndex, int entityIndex) - { - return new CultRecordKey($"global:aetheria.run_state.{StableToken(runId)}.zone.{zoneIndex}.entity.{entityIndex}.v1"); - } - - private static string StableToken(string value) - { - var chars = (string.IsNullOrWhiteSpace(value) ? "unnamed" : value.Trim().ToLowerInvariant()) - .Select(ch => char.IsLetterOrDigit(ch) ? ch : '-') - .ToArray(); - var token = new string(chars).Trim('-'); - while (token.Contains("--", StringComparison.Ordinal)) - token = token.Replace("--", "-", StringComparison.Ordinal); - return string.IsNullOrWhiteSpace(token) ? "unnamed" : token; - } - - private static InvalidDataException MissingPayload(AetheriaRuntimeStateCommitDocument command) - { - return new InvalidDataException($"Aetheria runtime commit '{command.CommandId}' has no {command.Kind} payload."); - } -} diff --git a/Aetheria.State/AetheriaRuntimeEveSurfaceStateProjector.cs b/Aetheria.State/AetheriaRuntimeEveSurfaceStateProjector.cs new file mode 100644 index 000000000..1a1251bc1 --- /dev/null +++ b/Aetheria.State/AetheriaRuntimeEveSurfaceStateProjector.cs @@ -0,0 +1,55 @@ +using Aetheria.State.Documents; +using GameCult.Aetheria.State.Unity; + +namespace Aetheria.State; + +public static class AetheriaRuntimeEveSurfaceStateProjector +{ + public static EveSurfaceState ToState(AetheriaRuntimeSurfaceDocument document) + { + return new EveSurfaceState + { + ProviderId = document.ProviderId, + ProviderKind = document.ProviderKind, + Title = document.Title, + Version = document.Version, + UpdatedAtUtc = document.UpdatedAtUtc, + Surface = ToSurface(document.Surface), + Commands = document.Commands + .Select(command => new EveCommandTemplate + { + Command = command.Command, + Label = command.Label, + Transport = command.Transport + }) + .ToArray() + }; + } + + private static EveSurface ToSurface(AetheriaRuntimeSurfaceTree surface) + { + return new EveSurface + { + Id = surface.Id, + Root = ToComponent(surface.Root), + Styles = surface.Styles + .Select(style => new EveStyleToken + { + Name = style.Name, + Value = style.Value + }) + .ToArray() + }; + } + + private static EveSurfaceComponent ToComponent(AetheriaRuntimeSurfaceComponent component) + { + return new EveSurfaceComponent + { + Id = component.Id, + Kind = component.Kind, + Props = new Dictionary(component.Props, StringComparer.Ordinal), + Children = component.Children.Select(ToComponent).ToArray() + }; + } +} diff --git a/Aetheria.State/AetheriaRuntimeStateMapper.cs b/Aetheria.State/AetheriaRuntimeStateMapper.cs new file mode 100644 index 000000000..f0af1ac9d --- /dev/null +++ b/Aetheria.State/AetheriaRuntimeStateMapper.cs @@ -0,0 +1,113 @@ +using Aetheria.State.Documents; +using GameCult.Aetheria.State.Unity; +using GameCult.Caching; + +namespace Aetheria.State; + +public static class AetheriaRuntimeStateMapper +{ + public static AetheriaLoadoutTemplate ToLoadoutTemplate( + AetheriaRuntimeLoadoutTemplateCommit loadout, + string updatedAtUtc) + { + return new AetheriaLoadoutTemplate + { + Name = loadout.Name ?? "", + OwnerPlayerKey = loadout.OwnerPlayerKey ?? "", + RootEntity = ToEntityLoadout(loadout.RootEntity), + CreatedAtUtc = updatedAtUtc, + UpdatedAtUtc = updatedAtUtc + }; + } + + public static CultRecordKey LoadoutKey(string name) + { + return new CultRecordKey($"global:aetheria.loadout_template.{StableToken(name)}.v1"); + } + + private static AetheriaEntityLoadout ToEntityLoadout(AetheriaRuntimeEntityLoadoutCommit? entity) + { + entity ??= new AetheriaRuntimeEntityLoadoutCommit(); + return new AetheriaEntityLoadout + { + Name = entity.Name ?? "", + Kind = entity.Kind ?? "", + FactionKey = entity.FactionKey ?? "", + Hull = ToLoadoutItem(entity.Hull), + Equipment = ToItemSlots(entity.Equipment), + CargoBays = ToItemSlots(entity.CargoBays), + DockingBays = ToItemSlots(entity.DockingBays), + CargoContents = ToCargoBays(entity.CargoContents), + DockingBayContents = ToCargoBays(entity.DockingBayContents), + DockingBayAssignments = ToIntArray(entity.DockingBayAssignments), + WeaponGroups = ToIntArrayArray(entity.WeaponGroups), + Children = (entity.Children ?? Array.Empty()) + .Select(ToEntityLoadout) + .ToArray() + }; + } + + private static AetheriaLoadoutItem ToLoadoutItem(AetheriaRuntimeLoadoutItemCommit? item) + { + item ??= new AetheriaRuntimeLoadoutItemCommit(); + return new AetheriaLoadoutItem + { + ItemKey = item.ItemKey ?? "", + Quality = item.Quality, + Durability = item.Durability, + Quantity = item.Quantity, + Enabled = item.Enabled, + OverrideShutdown = item.OverrideShutdown + }; + } + + private static AetheriaLoadoutItemSlot[] ToItemSlots( + IReadOnlyList? slots) + { + return (slots ?? Array.Empty()) + .Select(slot => new AetheriaLoadoutItemSlot + { + Position = new AetheriaGridCoord + { + X = slot.X, + Y = slot.Y + }, + Item = ToLoadoutItem(slot.Item) + }) + .ToArray(); + } + + private static AetheriaCargoBayLoadout[] ToCargoBays( + IReadOnlyList? cargoBays) + { + return (cargoBays ?? Array.Empty()) + .Select(bay => new AetheriaCargoBayLoadout + { + Items = ToItemSlots(bay.Items) + }) + .ToArray(); + } + + private static int[] ToIntArray(IReadOnlyList? values) + { + return (values ?? Array.Empty()).ToArray(); + } + + private static int[][] ToIntArrayArray(IReadOnlyList>? values) + { + return (values ?? Array.Empty>()) + .Select(ToIntArray) + .ToArray(); + } + + private static string StableToken(string value) + { + var chars = (string.IsNullOrWhiteSpace(value) ? "unnamed" : value.Trim().ToLowerInvariant()) + .Select(ch => char.IsLetterOrDigit(ch) ? ch : '-') + .ToArray(); + var token = new string(chars).Trim('-'); + while (token.Contains("--", StringComparison.Ordinal)) + token = token.Replace("--", "-", StringComparison.Ordinal); + return string.IsNullOrWhiteSpace(token) ? "unnamed" : token; + } +} diff --git a/Aetheria.State/AetheriaStateNode.cs b/Aetheria.State/AetheriaStateNode.cs index 1e6f7ea33..87ee8f2bd 100644 --- a/Aetheria.State/AetheriaStateNode.cs +++ b/Aetheria.State/AetheriaStateNode.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Threading.Tasks; using Aetheria.State.Documents; +using GameCult.Aetheria.State.Unity; using GameCult.Caching; using GameCult.Caching.MessagePack; using GameCult.Mesh; @@ -207,6 +208,164 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable new CultRecordKey(AetheriaProviderAdvertisementProjector.AdvertisementKey)); } + public Task> PutDaemonProviderAdvertisementAsync( + AetheriaRuntimeDaemonProviderAdvertisementDocument advertisement) + { + return Database.PutAsync(new CultRecordKey("daemon:aetheria.provider_advertisement.v1"), advertisement); + } + + public Task GetDaemonProviderAdvertisementAsync() + { + return Database.GetAsync( + new CultRecordKey("daemon:aetheria.provider_advertisement.v1")); + } + + public Task> PutDaemonHealthAsync( + AetheriaRuntimeDaemonHealthDocument health) + { + return Database.PutAsync(new CultRecordKey("daemon:aetheria.health.v1"), health); + } + + public Task GetDaemonHealthAsync() + { + return Database.GetAsync( + new CultRecordKey("daemon:aetheria.health.v1")); + } + + public Task> PutDaemonCommandBoundaryAsync( + AetheriaRuntimeDaemonCommandBoundaryDocument boundary) + { + return Database.PutAsync(new CultRecordKey("daemon:aetheria.command_boundary.v1"), boundary); + } + + public Task GetDaemonCommandBoundaryAsync() + { + return Database.GetAsync( + new CultRecordKey("daemon:aetheria.command_boundary.v1")); + } + + public Task> SubmitDaemonCommandAsync( + AetheriaRuntimeDaemonCommandDocument command) + { + if (command == null) throw new ArgumentNullException(nameof(command)); + command.Schema = AetheriaRuntimeDaemonSchemas.Command; + if (string.IsNullOrWhiteSpace(command.CommandId)) + command.CommandId = Guid.NewGuid().ToString("N"); + if (string.IsNullOrWhiteSpace(command.IssuedAtUtc)) + command.IssuedAtUtc = DateTime.UtcNow.ToString("O"); + + return Database.PutAsync(DaemonCommandKey(command.CommandId), command); + } + + public IReadOnlyList ReadObservedDaemonCommands() + { + return Cache + .GetAll() + .OrderBy(command => command.IssuedAtUtc ?? "", StringComparer.Ordinal) + .ThenBy(command => command.CommandId ?? "", StringComparer.Ordinal) + .ToArray(); + } + + private static CultRecordKey DaemonCommandKey(string commandId) + { + return new CultRecordKey($"daemon:commands:{StableToken(commandId)}:gamecult.aetheria.daemon_command.v1"); + } + + public Task> SubmitEveCommandAsync( + AetheriaRuntimeEveCommandDocument command) + { + if (command == null) throw new ArgumentNullException(nameof(command)); + command.Schema = AetheriaRuntimeEveCommandDocument.SchemaId; + if (string.IsNullOrWhiteSpace(command.CommandId)) + command.CommandId = Guid.NewGuid().ToString("N"); + if (string.IsNullOrWhiteSpace(command.IssuedAtUtc)) + command.IssuedAtUtc = DateTime.UtcNow.ToString("O"); + + return Database.PutAsync(EveCommandKey(command.CommandId), command); + } + + public IReadOnlyList ReadObservedEveCommands() + { + return Cache + .GetAll() + .OrderBy(command => command.IssuedAtUtc ?? "", StringComparer.Ordinal) + .ThenBy(command => command.CommandId ?? "", StringComparer.Ordinal) + .ToArray(); + } + + private static CultRecordKey EveCommandKey(string commandId) + { + return new CultRecordKey($"eve:commands:{StableToken(commandId)}:gamecult.eve.command.v1"); + } + + public Task> PutDaemonFrameAsync( + AetheriaRuntimeDaemonFrameDocument frame) + { + return Database.PutAsync(new CultRecordKey("daemon:aetheria.frame.latest.v1"), frame); + } + + public Task GetDaemonFrameAsync() + { + return Database.GetAsync( + new CultRecordKey("daemon:aetheria.frame.latest.v1")); + } + + public Task> PutDaemonSoaViewAsync( + AetheriaRuntimeDaemonSoaViewDocument view) + { + return Database.PutAsync(new CultRecordKey("daemon:aetheria.soa_view.latest.v1"), view); + } + + public Task GetDaemonSoaViewAsync() + { + return Database.GetAsync( + new CultRecordKey("daemon:aetheria.soa_view.latest.v1")); + } + + public Task> PutDaemonGameSurfaceAsync(EveSurfaceState surface) + { + return Database.PutAsync(new CultRecordKey(AetheriaProviderAdvertisementProjector.DaemonGameSurfaceKey), surface); + } + + public Task GetDaemonGameSurfaceAsync() + { + return Database.GetAsync( + new CultRecordKey(AetheriaProviderAdvertisementProjector.DaemonGameSurfaceKey)); + } + + public Task> PutDaemonGameTuiSurfaceAsync(EveSurfaceState surface) + { + return Database.PutAsync(new CultRecordKey(AetheriaProviderAdvertisementProjector.DaemonGameTuiSurfaceKey), surface); + } + + public Task GetDaemonGameTuiSurfaceAsync() + { + return Database.GetAsync( + new CultRecordKey(AetheriaProviderAdvertisementProjector.DaemonGameTuiSurfaceKey)); + } + + public Task> PutDaemonEditorSurfaceAsync(EveSurfaceState surface) + { + return Database.PutAsync(new CultRecordKey(AetheriaProviderAdvertisementProjector.DaemonEditorSurfaceKey), surface); + } + + public Task GetDaemonEditorSurfaceAsync() + { + return Database.GetAsync( + new CultRecordKey(AetheriaProviderAdvertisementProjector.DaemonEditorSurfaceKey)); + } + + public Task> PutDaemonEditorTuiSurfaceAsync(EveSurfaceState surface) + { + return Database.PutAsync(new CultRecordKey(AetheriaProviderAdvertisementProjector.DaemonEditorTuiSurfaceKey), surface); + } + + public Task GetDaemonEditorTuiSurfaceAsync() + { + return Database.GetAsync( + new CultRecordKey(AetheriaProviderAdvertisementProjector.DaemonEditorTuiSurfaceKey)); + } + public Task> PutRuntimeSessionAsync(AetheriaRuntimeSession session) { return Database.PutAsync(new CultRecordKey($"runtime:{session.RuntimeId}:aetheria.runtime_session.v1"), session); @@ -284,28 +443,16 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable new CultRecordKey("global:aetheria.verse_host_settings.v1")); } - public Task> PutRuntimeCommitDrainStatusAsync( - AetheriaRuntimeCommitDrainStatus status) + public Task> PutEveCommandAcceptanceStatusAsync( + AetheriaEveCommandAcceptanceStatus status) { - return Database.PutAsync(new CultRecordKey("global:aetheria.runtime_commit_drain_status.v1"), status); + return Database.PutAsync(new CultRecordKey("global:aetheria.eve_command_acceptance_status.v1"), status); } - public Task GetRuntimeCommitDrainStatusAsync() + public Task GetEveCommandAcceptanceStatusAsync() { - return Database.GetAsync( - new CultRecordKey("global:aetheria.runtime_commit_drain_status.v1")); - } - - public Task> PutEveCommandDrainStatusAsync( - AetheriaEveCommandDrainStatus status) - { - return Database.PutAsync(new CultRecordKey("global:aetheria.eve_command_drain_status.v1"), status); - } - - public Task GetEveCommandDrainStatusAsync() - { - return Database.GetAsync( - new CultRecordKey("global:aetheria.eve_command_drain_status.v1")); + return Database.GetAsync( + new CultRecordKey("global:aetheria.eve_command_acceptance_status.v1")); } public Task FlushAsync(bool soft = false) @@ -313,6 +460,20 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable return _node.FlushAsync(soft); } + private static string StableToken(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return "empty"; + + var chars = value + .Select(character => char.IsLetterOrDigit(character) ? character : '-') + .ToArray(); + var token = new string(chars).Trim('-').ToLowerInvariant(); + while (token.Contains("--", StringComparison.Ordinal)) + token = token.Replace("--", "-", StringComparison.Ordinal); + return string.IsNullOrWhiteSpace(token) ? "empty" : token; + } + public void Dispose() { _node.Dispose(); diff --git a/Aetheria.State/Documents/AetheriaEveCommandDrainStatus.cs b/Aetheria.State/Documents/AetheriaEveCommandAcceptanceStatus.cs similarity index 70% rename from Aetheria.State/Documents/AetheriaEveCommandDrainStatus.cs rename to Aetheria.State/Documents/AetheriaEveCommandAcceptanceStatus.cs index d8ce33e67..c6e4eac2e 100644 --- a/Aetheria.State/Documents/AetheriaEveCommandDrainStatus.cs +++ b/Aetheria.State/Documents/AetheriaEveCommandAcceptanceStatus.cs @@ -3,14 +3,14 @@ using MessagePack; namespace Aetheria.State.Documents; -[CultDocument("aetheria.eve_command_drain_status", "aetheria.eve_command_drain_status.v1")] +[CultDocument("aetheria.eve_command_acceptance_status", "aetheria.eve_command_acceptance_status.v1")] [CultGlobal] [MessagePackObject] -public sealed class AetheriaEveCommandDrainStatus +public sealed class AetheriaEveCommandAcceptanceStatus { [Key(0)] [CultName] - public string Name { get; set; } = "Aetheria Eve command drain"; + public string Name { get; set; } = "Aetheria Eve command acceptance"; [Key(1)] [CultIndex("runtimeId")] @@ -26,7 +26,7 @@ public sealed class AetheriaEveCommandDrainStatus public string LastAcceptedAtUtc { get; set; } = ""; [Key(5)] - public int PendingBeforeApply { get; set; } + public int ObservedBeforeAccept { get; set; } [Key(6)] public int CommandsAccepted { get; set; } @@ -60,4 +60,13 @@ public sealed class AetheriaEveCommandDrainStatus [Key(16)] public int AppliedVerseHostCommands { get; set; } + + [Key(17)] + public int AppliedInputSettingsCommands { get; set; } + + [Key(18)] + public int AppliedLoadoutTemplateCommands { get; set; } + + [Key(19)] + public string[] AccountedCommandIds { get; set; } = []; } diff --git a/Aetheria.State/Documents/AetheriaRuntimeCommitDrainStatus.cs b/Aetheria.State/Documents/AetheriaRuntimeCommitDrainStatus.cs deleted file mode 100644 index 9cb42d16e..000000000 --- a/Aetheria.State/Documents/AetheriaRuntimeCommitDrainStatus.cs +++ /dev/null @@ -1,51 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.runtime_commit_drain_status", "aetheria.runtime_commit_drain_status.v1")] -[CultGlobal] -[MessagePackObject] -public sealed class AetheriaRuntimeCommitDrainStatus -{ - [Key(0)] - [CultName] - public string Name { get; set; } = "Aetheria runtime commit drain"; - - [Key(1)] - [CultIndex("runtimeId")] - public string RuntimeId { get; set; } = ""; - - [Key(2)] - public string StatePath { get; set; } = ""; - - [Key(3)] - public string LastPollAtUtc { get; set; } = ""; - - [Key(4)] - public string LastAppliedAtUtc { get; set; } = ""; - - [Key(5)] - public int PendingBeforeApply { get; set; } - - [Key(6)] - public int CommandsApplied { get; set; } - - [Key(7)] - public int AppliedPlayerSettings { get; set; } - - [Key(8)] - public int AppliedLoadoutTemplates { get; set; } - - [Key(9)] - public int AppliedRunCheckpoints { get; set; } - - [Key(10)] - public int ConsecutiveFailures { get; set; } - - [Key(11)] - public string LastError { get; set; } = ""; - - [Key(12)] - public string Status { get; set; } = "ok"; -} diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md index 3b29ab614..8bb3aaf29 100644 --- a/Aetheria.State/README.md +++ b/Aetheria.State/README.md @@ -38,27 +38,28 @@ The Unity client no longer writes `PlayerSettings.msgpack`, `.loadout`, or replacement for bespoke loadout files, with structured hull/equipment/cargo/ docking item slots, nested child-entity loadouts, and weapon groups. `RuntimePlayerSettings` is the Unity session projection for menus, formatting, -and input binding edits; menu and input-screen changes queue typed -`.cc.pending` player-settings commits and are not portable state authority. -Unity settings, loadout templates, and run checkpoints now queue typed -`.cc.pending` runtime commit documents beside -`GameData/aetheria-world.cc`. `Aetheria.State` owns applying those documents -into canonical typed state through `AetheriaRuntimeCommitLogApplier`; the -Unity-side command log is command-only and cannot become durable truth by -itself. -`AetheriaRuntimeSession` is the typed Verse signal for the running state host -or one-shot pending drain. `Economy.Server` owns the long-running -`cultmesh-state-host` heartbeat and publishes `starting`, `running`, and -`stopping` states through `Aetheria.State`; drain status remains operation -telemetry, not daemon-session ownership. -Unity Eve surfaces also emit typed `gamecult.eve.command.v1` documents under -`GameData/aetheria-world.cc.eve.pending`. `AetheriaEveCommandBridge` is the -provider-owned acceptance organ for those commands: it validates the provider, -surface, and command template before running the narrow refresh handlers for -the catalog and operations surfaces plus the typed player-settings mutation -handlers for gameplay/graphics Eve controls and typed Verse-host visibility -commands. Renderer callbacks do not accept commands or mutate game state -locally. +and input binding display. Menu and input-screen changes send typed Eve request +documents to the provider-owned Aetheria Verse service; they are not portable +state authority and must not mutate daemon-owned state inside Unity. +The old `.cc.pending` runtime commit document lane is deleted. Gameplay state, +loadout templates, and run checkpoints are accepted by the daemon/Verse owner +through typed state operations and daemon command documents, not by +renderer-side commit authority. +`AetheriaRuntimeSession` is the typed Verse signal for the running daemon. +`Aetheria.State.Daemon` owns the long-running `aetheria-daemon` heartbeat and +publishes `starting`, `running`, and `stopping` states through `Aetheria.State`; +legacy outbox status remains operation telemetry, not daemon-session ownership. +Unity Eve surfaces construct typed `gamecult.eve.command.v1` command documents. +The neutral `AetheriaCommandPort` owns command submission for headless and +non-Unity clients; Unity input providers reach the same records through the +`AetheriaRuntimeCommandPort` facade. The Verse daemon uses `AetheriaStateNode` +to observe and accept those CultNet records in the Aetheria state graph. +`AetheriaEveCommandBridge` is the provider-owned command acceptor for those +records: it validates the provider, surface, and command template before +running the narrow refresh handlers for the catalog and operations surfaces +plus the typed player-settings mutation handlers for gameplay/graphics Eve +controls and typed Verse-host visibility commands. Renderer callbacks do not +accept commands or mutate game state locally. `AetheriaEveRuntimeBootstrap` mounts the `aetheria.operations` surface through a runtime-created `UIDocument` after scene load, with environment and command-line switches for diagnostics. UI Toolkit is now a live runtime lowering path, not @@ -66,6 +67,15 @@ only a package sample. The old `SavedGame`/`SavedZone` DTOs and `Galaxy` loader constructor have also been deleted; the new document family is live Verse run/zone state, not a bespoke save-file format. +Daemon run frames project into Unity through the explicit +`EntityConstructionBlueprintProjector.ProjectObservedFromBlueprint` lowering +path; authoritative blueprint construction remains named separately as a +migration target for code that still has to move into the daemon. +Main-menu gameplay boot also projects the daemon run through +`Galaxy.ProjectObservedDaemonRun` rather than constructing or generating a +Unity-owned run. The Unity handle for this shell is +`ActionGameManager.ObservedGalaxy` to keep the client-side object named as an +observation. The dead `SavedStory` JSON DTO is gone. `ZoneConstructionBlueprint` and `RuntimeEntityBlueprint` remain in-memory construction/loadout projections for Unity runtime code, with `RuntimeEntityBlueprintProjector` capturing and @@ -104,13 +114,13 @@ lookup overloads and the derived behavior `AmmoType`/`Item` GUID properties have been deleted. Action-bar consumable bindings now read the target `ItemKey` directly from typed runtime catalog rows instead of reconstructing it from legacy item IDs. -Unity runtime commit DTOs carry typed `ItemKey`/`HullItemKey` fields only for +Daemon snapshot DTOs carry typed `ItemKey`/`HullItemKey` fields only for loadout items, action-bar targets, body resources, entity hulls, and active -consumables; the commit applier no longer reconstructs item keys from legacy -item GUID fields. -Unity runtime commit DTOs now carry typed `FactionKey` only for loadout -entities, run entity snapshots, and faction relationship state. The old -pending-command `CorporationLegacyId` fallback fields are deleted. +consumables; daemon command application no longer reconstructs item keys from +legacy item GUID fields. +Daemon snapshot DTOs now carry typed `FactionKey` only for loadout entities, +run entity snapshots, and faction relationship state. The old command-document +`CorporationLegacyId` fallback fields are deleted. `EntityConstructionBlueprint` also carries `FactionKey` only. Loadout restore, entity construction, run entity snapshots, and faction relationship commits no longer rebuild corporation references from legacy `Faction.ID` GUIDs. @@ -124,10 +134,10 @@ GUID link fields. sector-map link/influence rendering, zone security ownership checks, and runtime faction-relationship ordering now use `FactionKey`. The legacy `Faction.ID` projection field is deleted from the temporary simulation shell. -Run checkpoint commit DTOs carry typed `OrbitKey`, `ParentOrbitKey`, and -`BodyKey` only for zone orbit/body snapshots. The old pending-command +Run checkpoint snapshot DTOs carry typed `OrbitKey`, `ParentOrbitKey`, and +`BodyKey` only for zone orbit/body snapshots. The old command-document `OrbitLegacyId`, `ParentLegacyId`, and `BodyLegacyId` fallback fields are -deleted. Behavior-state runtime commits and readback snapshots carry typed +deleted. Behavior-state daemon snapshots and readback snapshots carry typed body-key fields for resource scanner targets and mining tool asteroid belts; the old raw body ID commit/readback field names are deleted. `ResourceScanner` and `MiningTool` now retain typed body-key runtime state directly, while @@ -319,8 +329,8 @@ does not write daemon-owned Verse state. Unity can use this as the first package boundary once CultLib/Eve runtime packaging is available. Unity `MainMenu` now lowers a typed Verse settings shell over that split: client-target edits persist locally in `aetheria-client.cc`, while Verse-host -visibility changes queue provider-owned Eve commands against the selected local -Verse state file. +visibility changes append provider-owned Eve request documents against the +selected local Verse state file. `Galaxy` now consumes this package-owned runtime catalog for faction selection and name generation, so generated sectors use typed corporation v2 and `aetheria.name_file.v2` records instead of legacy `Faction`/`NameFile` @@ -346,13 +356,13 @@ reviving `PlayerSettings.msgpack`, `.loadout`, or `.zone` serialization. Unity `MainMenu` now lowers that same player-settings Eve document contract locally through UI Toolkit instead of hand-authoring separate gameplay/graphics widgets; the menu shell keeps name entry and back-navigation while gameplay -still owns command semantics through `ActionGameManager.CommitRuntimePlayerSettingsCommand`. +still owns command semantics through `ActionGameManager.RequestRuntimePlayerSettingsCommand`. The run document also carries `CurrentEntityKey`, so Continue restore binds the saved player entity by exact typed record identity instead of reconstructing it from a transient entity-slot index. Canonical typed run state no longer stores `CurrentZoneEntityIndex`; older stored runs are translated through the package -reader into `CurrentEntityKey`, and older queued runtime commits are translated -through the pending commit reader when those compatibility seams are needed. +reader into `CurrentEntityKey`. The older runtime commit importer has +been removed from the first-party state host and Unity package. `Aetheria.State.Verify` opens a materialized state file and checks that the typed migration ledger matches the actual item, corporation, and name-file @@ -371,38 +381,38 @@ symbols such as MessagePack, Newtonsoft, JsonKnownTypes, RethinkDB, or old `DatabaseEntry`/`DatabaseLink` references; the remaining MessagePack usage is a named package/import CultCache transport boundary, not gameplay state authority. The package boundary is also fenced: MessagePack symbols may only appear in the -runtime catalog reader, pending CultCache envelope store, and typed runtime/Eve +runtime catalog reader, CultCache document store, and typed runtime/Eve command document files until generated CultCache serializers replace that temporary Unity-side codec. The verifier also checks that the Eve runtime bootstrap exists, mounts the operations surface through the UI Toolkit presenter, -and keeps renderer commands routed through the typed Eve command log. +and keeps renderer commands routed through the typed Eve command inbox. Use it after import when `GameData/aetheria-world.cc` changes. `Aetheria.State.Unity.Smoke` opens the materialized state through the runtime facade and proves read-only Unity-facing catalog access without the legacy -catalog reader. It also proves runtime commit envelopes for settings and run -zone/entity snapshots can be queued, applied through `Aetheria.State`, and -cleared. Pending runtime state and Eve command queue files are CultCache-shaped -`.cc` records with embedded schema catalogs; their current record payload codec -is still temporary package code until Unity consumes generated CultCache +catalog reader. Live Unity code uses typed Eve or daemon request documents +instead of runtime commit outboxes. Runtime and Eve requests are typed `.cc` +records with embedded schema catalogs; their current record payload codec is +still temporary package code until Unity consumes generated CultCache serializers directly. -`Economy.Server` opens the CultMesh state host, applies pending runtime commits -and pending Eve surface commands on startup, and polls for new pending work -while running. For a bounded one-shot drain without leaving the host running: +`Aetheria.State.Daemon` opens the CultMesh Verse daemon, publishes daemon-owned +Verse identity/health/game/editor surfaces, accepts observed Eve command +documents on startup, and executes observed daemon command records each tick. +For a bounded one-shot daemon tick: ```powershell -dotnet run --project .\Economy.Server\Economy.Server.csproj -- --apply-pending-once +dotnet run --project .\Aetheria.State.Daemon\Aetheria.State.Daemon.csproj -- --once ``` -The server publishes `AetheriaRuntimeCommitDrainStatus`, -`AetheriaEveCommandDrainStatus`, the `aetheria.operations` Eve surface, and the -typed `aetheria.player_settings` Eve surface after drain attempts, so pending -depth, accepted/applied/rejected counts, failures, timestamps, and player -settings controls are typed state rather than console-only status. It also +The daemon publishes `AetheriaEveCommandAcceptanceStatus`, the +`aetheria.operations` Eve surface, and the +typed `aetheria.player_settings` Eve surface after request acceptance attempts, +so observed depth, accepted/applied/rejected counts, failures, timestamps, and +player settings controls are typed state rather than console-only status. It also persists daemon-owned `aetheria.verse_host_settings.v1`, with boot-time defaults or overrides for service id, Verse id, visibility, and CultMesh -address. When visibility is `public`, the long-running daemon also serves a +address. When visibility is `public`, the daemon also serves a derived CultMesh Verse catalog from that same typed host document, using the located service name as the discoverable `cultnet://...:3075` endpoint. The operations surface and provider advertisement project from that typed host @@ -427,19 +437,10 @@ The Unity Verse-settings shell can also trigger the same one-shot sync through the `Aetheria.State.Replica` bridge when the repo-local tool is available. That keeps target selection local while the replica organ owns daemon transport. -`Aetheria.State.ApplyPending` is the smaller local operator applicator for -queued Unity runtime commits and Eve commands when the server host is not being -used. It also republishes the typed drain status, operations surface, -player-settings surface, typed `aetheria.verse_host_settings.v1`, and provider -advertisement after applying pending files: - -```powershell -dotnet run --project .\Aetheria.State.ApplyPending\Aetheria.State.ApplyPending.csproj -- . -``` - -Pass an explicit state path as the second argument when applying a non-default -`.cc` file. Use `--keep` only for diagnostics; normal application deletes -successfully applied command files. +The old standalone `Aetheria.State.DrainCommands` applicator is deleted. Command +acceptance is daemon behavior now; bounded operation uses `Aetheria.State.Daemon +--once` so the same Verse member publishes the same provider, health, command +boundary, and Eve surfaces. Current rebuild notes: diff --git a/Aetheria.State/docs/verse-daemon-shape.md b/Aetheria.State/docs/verse-daemon-shape.md new file mode 100644 index 000000000..dea8af1ef --- /dev/null +++ b/Aetheria.State/docs/verse-daemon-shape.md @@ -0,0 +1,52 @@ +# Aetheria Verse Daemon Shape + +Aetheria should follow the Odin/VoidBot daemon shape instead of growing a private Unity-shaped command system. + +## Authority + +Odin is the all-seer, not the owner. It discovers Aetheria's provider advertisement and interface bindings, indexes the schema/catalog surface, and gives agents a registry overview. Idunn keeps the daemon alive from daemon-published health and command-boundary records. Bifrost hosts the MCP crossing for Codex and other xeno agents. Eve/CultUI defines presentation shape. The Aetheria daemon remains the side-effect owner. + +The daemon owns Aetheria simulation state, typed command application, health publication, command boundaries, provider advertisement, and provider-owned Eve GUI/TUI surfaces. Unity is a renderer and input lowerer. A browser, a terminal, Blender, or another runtime should see the same daemon-owned Verse through the same typed publications. + +Unity-side entity graph creation must name the boundary it is crossing. `EntityConstructionBlueprintProjector.InstantiateAuthoritativeFromBlueprint` is for local authoritative construction paths that still need to be migrated into the daemon; daemon frames use `EntityConstructionBlueprintProjector.ProjectObservedFromBlueprint` to lower observed state into Unity objects. A Unity frame consumer must not call the authoritative construction entry point. + +The same rule applies at the galaxy shell. `Galaxy.ProjectObservedDaemonRun` may build a Unity navigation/rendering projection from an authoritative daemon run frame. Main menu boot must not call a `Galaxy` constructor directly or run procedural generation; the daemon owns the run and Unity lowers it. The Unity-side public handle is `ActionGameManager.ObservedGalaxy`, not `CurrentGalaxy`, because it is a projection of daemon state. + +VoidBot's Odin MCP shape is the useful boundary model for agents: list providers through Odin, list Verses through Odin, load a provider-owned interface context through Odin, and invoke only commands that the provider interface advertises. The MCP is not a Brokkr special case and not a raw daemon socket wrapper. + +Brokkr is just one daemon that happens to publish Unity editor surfaces. Aetheria should behave the same way: publish provider advertisements and Eve GUI/TUI surfaces into the local Verse, let Odin index them, and let Bifrost expose ergonomic MCP tools that lower or invoke those surfaces without taking ownership. + +## Publications + +Required Aetheria daemon publications are the live typed schemas in `AetheriaRuntimeDaemonSchemas`: + +- `gamecult.aetheria.daemon_provider_advertisement.v1`: Verse id, provider ids, daemon id, schema catalog, state witnesses, Eve surface keys, command boundaries, and transport profile metadata. +- `gamecult.aetheria.daemon_health.v1`: daemon id, Verse id, state path, frame id, command acceptance status, simulation status, publication source, transport, and command-boundary path. +- `gamecult.aetheria.daemon_command_boundary.v1`: typed command schemas accepted by the daemon, authority requirements, receipt schema, and delivery transport. +- `gamecult.aetheria.daemon_frame.v1` plus `gamecult.aetheria.daemon_soa_view.v1`: authoritative simulation state for renderer clients. +- `gamecult.aetheria.daemon_game_surface.v1` and `gamecult.aetheria.daemon_editor_surface.v1`: GUI/TUI views of the same provider-owned state. + +The provider advertisement is not a dashboard index. It is the daemon's promise about what it owns, where its witnesses live, what command boundary it accepts, and what transports can reach it. + +Aetheria currently has two advertisement layers during the bridge: + +- `gamecult.eve.provider_advertisement.v1` is the Odin-visible Eve provider card for the Aetheria Verse daemon and compatibility surfaces such as catalog, operations, and player settings. +- `gamecult.aetheria.daemon_provider_advertisement.v1` is the daemon-owned Aetheria runtime contract for authoritative frames, SoA views, game/editor GUI surfaces, game/editor TUI surfaces, health, and command boundaries. + +The bridge layer must point at daemon-owned witnesses instead of becoming a second source of truth. Long term, Odin should discover the daemon-owned provider advertisement and interface bindings directly; the older state-host card should become a compatibility adapter or disappear. + +## Queues + +Queues are an implementation detail. Eve commands and daemon commands are typed `gamecult.eve.command.v1` and `gamecult.aetheria.daemon_command.v1` records in the Aetheria state graph; the old `.pending` runtime commit outbox is gone. None of those folders are the public API, daemon identity, or a substitute for CultNet/CultMesh command ports. Anything still named `Queue*`, `CommandLog`, `Inbox`, `mailbox`, `.eve.commands`, `.daemon.commands`, `.cc.pending`, or `PendingCultCacheStore` in Unity-facing daemon/Eve command code should lower to typed command documents and be demoted behind typed operation methods as the CultNet command boundaries come online. + +`AetheriaCommandPort` is the neutral command submission implementation for typed command records. Unity-side command lowerers may use `AetheriaRuntimeCommandPort` as a facade over that same port. They must not drain those records or mutate state in response; ownership stays with the daemon Verse member. + +Migration rule: do not expose string command names and payload maps as public Aetheria APIs. Compatibility adapters may decode external Eve requests at the edge, but persisted Aetheria command documents and Unity gameplay calls should use typed command bodies and typed operation methods. + +Daemon-published game surface buttons select typed daemon command documents by command id (`aetheria.daemon.commands.*`). They must not smuggle daemon command fields through generic Eve payload maps such as `commandKind`, `weaponGroup`, or `scalarValue`; richer commands need explicit typed body builders. + +Unity menu handlers should not construct Aetheria command bodies by spelunking `EveSurfaceCommandRequest.Payload`. Pass the request to the shared typed command client at the Eve edge, then submit the resulting `gamecult.eve.command.v1` or `gamecult.aetheria.daemon_command.v1` document. + +## Cut Line + +If a status/control surface matters to operators or agents, publish it as typed CultCache state or a `.cc` witness plus an Eve/CultUI CultMesh surface. Do not add a private HTTP dashboard, Unity-only inspector, JSON status blob, or agent-specific daemon wrapper as canonical truth. diff --git a/Aetheria.sln b/Aetheria.sln new file mode 100644 index 000000000..1e188399f --- /dev/null +++ b/Aetheria.sln @@ -0,0 +1,85 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-firstpass", "Assembly-CSharp-firstpass.csproj", "{7c9c511c-1c6f-9c1f-c455-88359f498803}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp.csproj", "{47728ed0-fa53-fa99-9dca-8eb78b1dbfe4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Aetheria.State.Unity", "GameCult.Aetheria.State.Unity.csproj", "{d6094cfd-26d1-83d1-2366-5762cca2bc33}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniRx", "UniRx.csproj", "{99486114-c78b-e9b0-654f-710398497645}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Aetheria.EveRuntime", "GameCult.Aetheria.EveRuntime.csproj", "{a1a8b65b-ca79-2fa2-3803-5f4833778bdb}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aetheria.Shared.Unity", "Aetheria.Shared.Unity.csproj", "{31542707-f1f7-33c1-eef6-37982be14f82}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-Editor-firstpass", "Assembly-CSharp-Editor-firstpass.csproj", "{b690125b-09ac-e423-4873-1648ff205166}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-Editor", "Assembly-CSharp-Editor.csproj", "{6529161a-3c3e-1394-61a8-ab66047d3732}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ink", "Ink.csproj", "{6bae5155-742f-1170-f8b5-ff696c59aad8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Brokkr.Editor", "GameCult.Brokkr.Editor.csproj", "{f041e9dc-dc28-1d6c-75a3-824162b4a1ae}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CultMath", "CultMath.csproj", "{6f7a47c4-21dc-64ef-0969-96b956a000f4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Brokkr", "GameCult.Brokkr.csproj", "{fc28bcbe-9ef4-444f-c1c9-dde8a3a864ad}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Substance.Platform", "Substance.Platform.csproj", "{d6a66898-22ba-8995-ccfd-eb708fdff52a}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests.csproj", "{28568750-512b-4d45-69bf-887882a23da7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Eve.UnityUIToolkit", "GameCult.Eve.UnityUIToolkit.csproj", "{5cdb0413-71d7-c6b3-4ad9-d8e730b55ffb}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Eve.Surface", "GameCult.Eve.Surface.csproj", "{037dacf0-10f4-20ed-7963-3a1e1ac15b64}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Substance.EditorHelper", "Substance.EditorHelper.csproj", "{f12f044e-28bc-36bc-242c-ecd2736d345d}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessagePack.SourceDisabled", "MessagePack.SourceDisabled.csproj", "{b916e744-81e2-a0ba-3884-e845f1c5ef88}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7c9c511c-1c6f-9c1f-c455-88359f498803}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7c9c511c-1c6f-9c1f-c455-88359f498803}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47728ed0-fa53-fa99-9dca-8eb78b1dbfe4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47728ed0-fa53-fa99-9dca-8eb78b1dbfe4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {d6094cfd-26d1-83d1-2366-5762cca2bc33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {d6094cfd-26d1-83d1-2366-5762cca2bc33}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99486114-c78b-e9b0-654f-710398497645}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99486114-c78b-e9b0-654f-710398497645}.Debug|Any CPU.Build.0 = Debug|Any CPU + {a1a8b65b-ca79-2fa2-3803-5f4833778bdb}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {a1a8b65b-ca79-2fa2-3803-5f4833778bdb}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31542707-f1f7-33c1-eef6-37982be14f82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31542707-f1f7-33c1-eef6-37982be14f82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {b690125b-09ac-e423-4873-1648ff205166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {b690125b-09ac-e423-4873-1648ff205166}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6529161a-3c3e-1394-61a8-ab66047d3732}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6529161a-3c3e-1394-61a8-ab66047d3732}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6bae5155-742f-1170-f8b5-ff696c59aad8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6bae5155-742f-1170-f8b5-ff696c59aad8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {f041e9dc-dc28-1d6c-75a3-824162b4a1ae}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {f041e9dc-dc28-1d6c-75a3-824162b4a1ae}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6f7a47c4-21dc-64ef-0969-96b956a000f4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6f7a47c4-21dc-64ef-0969-96b956a000f4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {fc28bcbe-9ef4-444f-c1c9-dde8a3a864ad}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {fc28bcbe-9ef4-444f-c1c9-dde8a3a864ad}.Debug|Any CPU.Build.0 = Debug|Any CPU + {d6a66898-22ba-8995-ccfd-eb708fdff52a}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {d6a66898-22ba-8995-ccfd-eb708fdff52a}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28568750-512b-4d45-69bf-887882a23da7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28568750-512b-4d45-69bf-887882a23da7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5cdb0413-71d7-c6b3-4ad9-d8e730b55ffb}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5cdb0413-71d7-c6b3-4ad9-d8e730b55ffb}.Debug|Any CPU.Build.0 = Debug|Any CPU + {037dacf0-10f4-20ed-7963-3a1e1ac15b64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {037dacf0-10f4-20ed-7963-3a1e1ac15b64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {f12f044e-28bc-36bc-242c-ecd2736d345d}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {f12f044e-28bc-36bc-242c-ecd2736d345d}.Debug|Any CPU.Build.0 = Debug|Any CPU + {b916e744-81e2-a0ba-3884-e845f1c5ef88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {b916e744-81e2-a0ba-3884-e845f1c5ef88}.Debug|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Assets/DefaultVolumeProfile.asset b/Assets/DefaultVolumeProfile.asset new file mode 100644 index 000000000..c4d0d4b63 --- /dev/null +++ b/Assets/DefaultVolumeProfile.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70e9ed4656132bf185009cb47db521fdeece0b1f4d464a73cb9d0bd04fe3bda5 +size 497 diff --git a/Assets/DefaultVolumeProfile.asset.meta b/Assets/DefaultVolumeProfile.asset.meta new file mode 100644 index 000000000..2fdac1435 --- /dev/null +++ b/Assets/DefaultVolumeProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7830976a04fc12843bb7f63ba390f142 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AetheriaInput.cs b/Assets/Scripts/AetheriaInput.cs index 9978716ce..717b5cbcb 100644 --- a/Assets/Scripts/AetheriaInput.cs +++ b/Assets/Scripts/AetheriaInput.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was auto-generated by com.unity.inputsystem:InputActionCodeGenerator -// version 1.14.0 +// version 1.19.0 // from Assets/Resources/Aetheria.inputactions // // Changes to this file may cause incorrect behavior and will be lost if @@ -85,6 +85,7 @@ public partial class @AetheriaInput: IInputActionCollection2, IDisposable public @AetheriaInput() { asset = InputActionAsset.FromJson(@"{ + ""version"": 1, ""name"": ""Aetheria"", ""maps"": [ { diff --git a/Assets/Scripts/Editor/NameTools.cs b/Assets/Scripts/Editor/NameTools.cs index 909b2f631..742a5aa4c 100644 --- a/Assets/Scripts/Editor/NameTools.cs +++ b/Assets/Scripts/Editor/NameTools.cs @@ -6,7 +6,7 @@ using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; -using Random = Unity.Mathematics.Random; +using Random = CultMath.Random; public class NameTools : EditorWindow { diff --git a/Assets/Scripts/Gameplay/ActionBarSlot.cs b/Assets/Scripts/Gameplay/ActionBarSlot.cs index 8e791ebc0..79aabbcbf 100644 --- a/Assets/Scripts/Gameplay/ActionBarSlot.cs +++ b/Assets/Scripts/Gameplay/ActionBarSlot.cs @@ -73,7 +73,7 @@ public class ActionBarConsumableBinding : ActionBarBinding public override void Activate() { - Entity.TryActivateConsumable(Target); + ActionGameManager.Instance.TryRequestDaemonActionBarConsumable(TargetItemKey); } public override void Deactivate() @@ -162,13 +162,13 @@ public class ActionBarGearBinding : ActionBarBinding public override void Activate() { Active = true; - Behavior.Activate(); + ActionGameManager.Instance.TryRequestDaemonActionBarBehavior(EquipmentIndex, BehaviorIndex, true); } public override void Deactivate() { Active = false; - Behavior.Deactivate(); + ActionGameManager.Instance.TryRequestDaemonActionBarBehavior(EquipmentIndex, BehaviorIndex, false); } public override void Update() @@ -191,18 +191,12 @@ public class ActionBarWeaponGroupBinding : ActionBarBinding public override void Activate() { - foreach (var weapon in Entity.WeaponGroups[Group].weapons) - { - weapon.Activate(); - } + ActionGameManager.Instance.TryRequestDaemonActionBarWeaponGroup(Group, true); } public override void Deactivate() { - foreach (var weapon in Entity.WeaponGroups[Group].weapons) - { - weapon.Deactivate(); - } + ActionGameManager.Instance.TryRequestDaemonActionBarWeaponGroup(Group, false); } public override void Update() diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonIndirectRenderer.cs b/Assets/Scripts/Gameplay/AetheriaDaemonIndirectRenderer.cs new file mode 100644 index 000000000..12d2e318d --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonIndirectRenderer.cs @@ -0,0 +1,184 @@ +using System.Collections.Generic; +using GameCult.Aetheria.State.Unity; +using UnityEngine; +using UnityEngine.Rendering; + +public sealed class AetheriaDaemonIndirectRenderer : MonoBehaviour +{ + [SerializeField] + private AetheriaDaemonObserver observer; + + [SerializeField] + private AetheriaDaemonRenderAssetCatalog assetCatalog; + + [SerializeField] + private bool logMissingAssets; + + [SerializeField] + private Camera targetCamera; + + private MaterialPropertyBlock _properties; + private readonly Dictionary _batchesByGroupId = + new Dictionary(); + private readonly HashSet _seenGroupIds = new HashSet(); + private readonly List _staleGroupIds = new List(); + private static readonly int MatrixBufferPropertyId = + Shader.PropertyToID(AetheriaDaemonRenderBuffer.MatrixBufferPropertyName); + private static readonly int InstanceCountPropertyId = + Shader.PropertyToID(AetheriaDaemonRenderBuffer.InstanceCountPropertyName); + + public int LastDrawnCount { get; private set; } + public bool HasDrawTarget => + observer != null && + assetCatalog != null; + + private void Reset() + { + observer = GetComponent(); + } + + private void LateUpdate() + { + DrawLatest(); + } + + public bool DrawLatest() + { + LastDrawnCount = 0; + if (!HasDrawTarget) + { + return false; + } + + if (!observer.HasRenderNativeView) + { + return false; + } + + _properties ??= new MaterialPropertyBlock(); + var drewAny = false; + var index = observer.LastSoaIndex; + var view = observer.LastRenderNativeView; + _seenGroupIds.Clear(); + foreach (var renderGroup in index.RenderGroups) + { + _seenGroupIds.Add(renderGroup.GroupId); + if (!assetCatalog.TryResolveMesh(renderGroup.MeshKey, out var mesh) || + !assetCatalog.TryResolveMaterial(renderGroup.MaterialKey, out var material)) + { + if (logMissingAssets) + { + Debug.LogWarning( + $"Could not resolve daemon render group {renderGroup.GroupId} " + + $"mesh='{renderGroup.MeshKey}' material='{renderGroup.MaterialKey}'."); + } + + continue; + } + + if (!_batchesByGroupId.TryGetValue(renderGroup.GroupId, out var batch)) + { + batch = new AetheriaDaemonRenderGroupBatch(); + _batchesByGroupId.Add(renderGroup.GroupId, batch); + } + + if (batch.LastUploadedGeneration != view.Generation) + { + batch.UploadLatest(view, renderGroup, mesh, renderGroup.DefaultScale); + } + + if (!batch.HasGpuBuffers) + { + continue; + } + + _properties.Clear(); + _properties.SetInt(InstanceCountPropertyId, batch.InstanceCount); + _properties.SetBuffer(MatrixBufferPropertyId, batch.MatrixBuffer); + + Graphics.DrawMeshInstancedIndirect( + mesh, + renderGroup.SubMeshIndex, + material, + GetDrawBounds(renderGroup), + batch.IndirectArgsBuffer, + 0, + _properties, + GetShadowCastingMode(renderGroup), + renderGroup.ReceiveShadows, + renderGroup.Layer, + targetCamera); + + LastDrawnCount += batch.InstanceCount; + drewAny = true; + } + + ReleaseStaleBatches(); + return drewAny; + } + + private Bounds GetDrawBounds(AetheriaRuntimeDaemonRenderGroupDocument renderGroup) + { + return new Bounds( + new Vector3( + renderGroup.BoundsCenterX, + renderGroup.BoundsCenterY, + renderGroup.BoundsCenterZ), + new Vector3( + renderGroup.BoundsSizeX, + renderGroup.BoundsSizeY, + renderGroup.BoundsSizeZ)); + } + + private static ShadowCastingMode GetShadowCastingMode(AetheriaRuntimeDaemonRenderGroupDocument renderGroup) + { + switch (renderGroup.ShadowMode) + { + case AetheriaRuntimeDaemonRenderShadowModes.Off: + return ShadowCastingMode.Off; + case AetheriaRuntimeDaemonRenderShadowModes.TwoSided: + return ShadowCastingMode.TwoSided; + case AetheriaRuntimeDaemonRenderShadowModes.ShadowsOnly: + return ShadowCastingMode.ShadowsOnly; + default: + return ShadowCastingMode.On; + } + } + + private void OnDisable() + { + foreach (var batch in _batchesByGroupId.Values) + { + batch.Dispose(); + } + + _batchesByGroupId.Clear(); + _seenGroupIds.Clear(); + } + + private void ReleaseStaleBatches() + { + if (_batchesByGroupId.Count == _seenGroupIds.Count) + { + return; + } + + _staleGroupIds.Clear(); + foreach (var groupId in _batchesByGroupId.Keys) + { + if (!_seenGroupIds.Contains(groupId)) + { + _staleGroupIds.Add(groupId); + } + } + + for (var i = 0; i < _staleGroupIds.Count; i++) + { + var groupId = _staleGroupIds[i]; + _batchesByGroupId[groupId].Dispose(); + _batchesByGroupId.Remove(groupId); + } + + _staleGroupIds.Clear(); + } +} diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonIndirectRenderer.cs.meta b/Assets/Scripts/Gameplay/AetheriaDaemonIndirectRenderer.cs.meta new file mode 100644 index 000000000..325f42abb --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonIndirectRenderer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 66b38a2f45120a041941eae4412f7da1 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs b/Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs new file mode 100644 index 000000000..8f1135717 --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs @@ -0,0 +1,158 @@ +using System; +using GameCult.Aetheria.State.Unity; +using UnityEngine; + +public sealed class AetheriaDaemonObserver : MonoBehaviour +{ + [SerializeField] + private bool pollInUpdate = true; + + [SerializeField] + private float pollIntervalSeconds = 0.05f; + + [SerializeField] + private bool logChanges; + + [SerializeField] + private string clientId = "unity-observer"; + + private readonly AetheriaRuntimeDaemonObservationCursor _cursor = new AetheriaRuntimeDaemonObservationCursor(); + private float _nextPollTime; + private AetheriaRuntimeDaemonOperationClient _operationClient; + private AetheriaDaemonOperations _operations; + private AetheriaDaemonSoaMemoryMap _soaMemoryMap; + private AetheriaDaemonRenderNativeView _renderNativeView; + + public AetheriaRuntimeObservedDaemonState LastObservedState { get; private set; } + public AetheriaRuntimeDaemonObservationResult LastObservation { get; private set; } + public long LastFrameId => _cursor.LastFrameId; + public long LastSoaGeneration => _cursor.LastSoaGeneration; + public bool HasAuthoritativeState => LastObservedState != null && LastObservedState.IsAuthoritative; + public bool HasSoaView => LastObservedState != null && LastObservedState.HasSoaView; + public AetheriaRuntimeDaemonSoaViewIndex LastSoaIndex => + LastObservedState?.SoaIndex ?? AetheriaRuntimeDaemonSoaViewIndex.Empty; + public AetheriaDaemonSoaMemoryMap LastSoaMemoryMap => _soaMemoryMap; + public AetheriaDaemonRenderNativeView LastRenderNativeView => _renderNativeView; + public bool HasRenderNativeView => _renderNativeView.IsCreated; + public AetheriaDaemonOperations Operations => _operations ??= new AetheriaDaemonOperations(this); + + public event Action ObservedDaemonStateChanged; + + internal AetheriaRuntimeDaemonCommandEnvelope SendOperation( + AetheriaRuntimeDaemonCommandKinds kind, + Action configure = null) + { + _operationClient ??= new AetheriaRuntimeDaemonOperationClient( + ActionGameManager.RuntimeStateFilePath, + clientId, + LastObservedState?.Frame.SessionId ?? "local"); + + var command = _operationClient.Create(kind, LastObservedState); + configure?.Invoke(command); + if (!_operationClient.TrySend(command, out var envelope, out var error)) + { + throw new InvalidOperationException( + $"Failed to submit Aetheria daemon operation {kind}: {error}"); + } + + if (logChanges) + { + Debug.Log($"Submitted Aetheria daemon operation {envelope!.Kind}: {envelope.CommandId}"); + } + + return envelope!; + } + + private void Update() + { + if (!pollInUpdate || Time.unscaledTime < _nextPollTime) + { + return; + } + + _nextPollTime = Time.unscaledTime + Mathf.Max(0.001f, pollIntervalSeconds); + Poll(); + } + + public bool Poll() + { + if (!AetheriaRuntimeStateReader.TryReadObservedDaemonState( + ActionGameManager.RuntimeStateFilePath, + out var observed)) + { + LastObservation = _cursor.Observe(null); + return false; + } + + var result = _cursor.Observe(observed); + LastObservedState = observed; + LastObservation = result; + + if (result.SoaViewChanged) + { + RemapSoaView(observed); + } + + if (result.Changed) + { + ObservedDaemonStateChanged?.Invoke(observed, result); + if (logChanges) + { + Debug.Log( + $"Observed Aetheria daemon frame {result.FrameId} " + + $"soa generation {result.SoaGeneration} authoritative={result.IsAuthoritative}"); + } + } + + return true; + } + + public void ResetObservation() + { + _cursor.Reset(); + DisposeSoaMemoryMap(); + LastObservedState = null; + LastObservation = null; + } + + private void OnDisable() + { + DisposeSoaMemoryMap(); + } + + private void RemapSoaView(AetheriaRuntimeObservedDaemonState observed) + { + DisposeSoaMemoryMap(); + if (observed == null || !observed.HasSoaView) + { + return; + } + + if (AetheriaDaemonSoaMemoryMap.TryOpen(observed.SoaIndex, out var map, out var error)) + { + _soaMemoryMap = map; + if (!AetheriaDaemonRenderNativeView.TryCreate(observed.SoaIndex, map, out _renderNativeView)) + { + _renderNativeView = default; + if (logChanges) + { + Debug.LogWarning("Mapped Aetheria daemon SoA memory, but no complete render native view was published."); + } + } + + return; + } + + if (logChanges) + { + Debug.LogWarning($"Could not map Aetheria daemon SoA view read-only: {error}"); + } + } + + private void DisposeSoaMemoryMap() + { + _renderNativeView = default; + _soaMemoryMap?.Dispose(); + _soaMemoryMap = null; + } +} diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs.meta b/Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs.meta new file mode 100644 index 000000000..f73ced863 --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4f5008a2876ee014fb9e442f1f6103e0 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonRenderAssetCatalog.cs b/Assets/Scripts/Gameplay/AetheriaDaemonRenderAssetCatalog.cs new file mode 100644 index 000000000..0fb885cfb --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonRenderAssetCatalog.cs @@ -0,0 +1,81 @@ +using System; +using UnityEngine; + +[CreateAssetMenu( + fileName = "AetheriaDaemonRenderAssetCatalog", + menuName = "Aetheria/Daemon Render Asset Catalog")] +public sealed class AetheriaDaemonRenderAssetCatalog : ScriptableObject +{ + [SerializeField] + private MeshEntry[] meshes = Array.Empty(); + + [SerializeField] + private MaterialEntry[] materials = Array.Empty(); + + public bool TryResolveMesh(string key, out Mesh mesh) + { + mesh = null; + if (string.IsNullOrWhiteSpace(key)) + { + return false; + } + + for (var i = 0; i < meshes.Length; i++) + { + if (string.Equals(meshes[i].Key, key, StringComparison.Ordinal)) + { + mesh = meshes[i].Mesh; + return mesh != null; + } + } + + mesh = Resources.Load(NormalizeResourceKey(key)); + return mesh != null; + } + + public bool TryResolveMaterial(string key, out Material material) + { + material = null; + if (string.IsNullOrWhiteSpace(key)) + { + return false; + } + + for (var i = 0; i < materials.Length; i++) + { + if (string.Equals(materials[i].Key, key, StringComparison.Ordinal)) + { + material = materials[i].Material; + return material != null; + } + } + + material = Resources.Load(NormalizeResourceKey(key)); + return material != null; + } + + private static string NormalizeResourceKey(string key) + { + const string resourcesPrefix = "resources://"; + if (key.StartsWith(resourcesPrefix, StringComparison.OrdinalIgnoreCase)) + { + return key.Substring(resourcesPrefix.Length); + } + + return key; + } + + [Serializable] + private struct MeshEntry + { + public string Key; + public Mesh Mesh; + } + + [Serializable] + private struct MaterialEntry + { + public string Key; + public Material Material; + } +} diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonRenderAssetCatalog.cs.meta b/Assets/Scripts/Gameplay/AetheriaDaemonRenderAssetCatalog.cs.meta new file mode 100644 index 000000000..f5da0d4cf --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonRenderAssetCatalog.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8fa370b1ea2904d40ae295007ac76b48 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs b/Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs new file mode 100644 index 000000000..b8a5a1150 --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs @@ -0,0 +1,294 @@ +using GameCult.Aetheria.State.Unity; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +public sealed class AetheriaDaemonRenderBuffer : MonoBehaviour +{ + public const string MatrixBufferPropertyName = "_AetheriaObjectToWorld"; + public const string InstanceCountPropertyName = "_AetheriaInstanceCount"; + + private const int MatrixStrideBytes = 64; + private const int DrawMeshInstancedIndirectArgsCount = 5; + + [SerializeField] + private AetheriaDaemonObserver observer; + + [SerializeField] + private Mesh indirectArgsMesh; + + [SerializeField] + private int innerLoopBatchCount = 64; + + [SerializeField] + private bool updateInLateUpdate; + + private readonly uint[] _indirectArgs = new uint[DrawMeshInstancedIndirectArgsCount]; + private NativeArray _nativeMatrices; + private GraphicsBuffer _matrixBuffer; + private GraphicsBuffer _indirectArgsBuffer; + private long _allocatedGeneration = long.MinValue; + private int _allocatedCount; + + public GraphicsBuffer MatrixBuffer => _matrixBuffer; + public GraphicsBuffer IndirectArgsBuffer => _indirectArgsBuffer; + public int InstanceCount { get; private set; } + public long LastUploadedFrameId { get; private set; } + public long LastUploadedGeneration { get; private set; } + public bool HasRenderableView => observer != null && observer.HasRenderNativeView; + public bool HasGpuBuffers => + _matrixBuffer != null && + _matrixBuffer.IsValid() && + InstanceCount > 0; + + private static readonly int MatrixBufferPropertyId = Shader.PropertyToID(MatrixBufferPropertyName); + private static readonly int InstanceCountPropertyId = Shader.PropertyToID(InstanceCountPropertyName); + + private void Reset() + { + observer = GetComponent(); + } + + private void LateUpdate() + { + if (updateInLateUpdate) + { + UploadLatest(); + } + } + + public bool UploadLatest() + { + return UploadLatest(indirectArgsMesh); + } + + public bool UploadLatest(Mesh argsMesh) + { + return UploadLatest(argsMesh, -1); + } + + public bool UploadLatest(Mesh argsMesh, int renderGroupId) + { + return UploadLatest(argsMesh, renderGroupId, 0); + } + + public bool UploadLatest(Mesh argsMesh, int renderGroupId, int subMeshIndex) + { + if (!HasRenderableView) + { + InstanceCount = 0; + UpdateIndirectArgs(null, 0); + return false; + } + + var view = observer.LastRenderNativeView; + EnsureCapacity(view); + if (!_nativeMatrices.IsCreated || _nativeMatrices.Length == 0) + { + InstanceCount = 0; + UpdateIndirectArgs(argsMesh, 0); + return false; + } + + var index = observer.LastSoaIndex; + var defaultScale = ResolveDefaultScale(index, renderGroupId); + var renderLod = ResolveRenderLod(index, renderGroupId); + var count = renderGroupId >= 0 && view.HasRenderGroupId + ? BuildRenderGroupMatrices(view, renderGroupId, renderLod, defaultScale) + : BuildAllMatrices(view, renderLod, defaultScale); + + _matrixBuffer.SetData(_nativeMatrices, 0, 0, count); + InstanceCount = count; + LastUploadedFrameId = view.FrameId; + LastUploadedGeneration = view.Generation; + UpdateIndirectArgs(argsMesh, count, subMeshIndex); + return true; + } + + private int BuildAllMatrices(AetheriaDaemonRenderNativeView view, int renderLod, float defaultScale) + { + if (view.HasRenderVisibility || renderLod >= 0) + { + return BuildCompactedMatrices(view, -1, renderLod, defaultScale); + } + + var count = math.min(view.Count, _nativeMatrices.Length); + var handle = view.ScheduleBuildRenderMatrices( + _nativeMatrices, + defaultScale, + innerLoopBatchCount); + handle.Complete(); + return count; + } + + private int BuildRenderGroupMatrices( + AetheriaDaemonRenderNativeView view, + int renderGroupId, + int renderLod, + float defaultScale) + { + return BuildCompactedMatrices(view, renderGroupId, renderLod, defaultScale); + } + + private int BuildCompactedMatrices( + AetheriaDaemonRenderNativeView view, + int renderGroupId, + int renderLod, + float defaultScale) + { + using var writtenCount = new NativeArray(1, Allocator.TempJob); + var job = new AetheriaDaemonRenderGroupMatrixJob + { + PositionX = view.PositionX, + PositionY = view.PositionY, + PositionZ = view.PositionZ, + RotationRadians = view.RotationRadians, + PhysicsBodyRadius = view.PhysicsBodyRadius, + RenderScale = view.RenderScale, + RenderVisibility = view.RenderVisibility, + RenderLod = view.RenderLod, + RenderGroupId = view.RenderGroupId, + Matrices = _nativeMatrices, + WrittenCount = writtenCount, + TargetRenderGroupId = renderGroupId >= 0 ? (uint)renderGroupId : 0, + TargetRenderLod = renderLod, + HasRotation = view.HasRotation, + HasPhysicsRadius = view.HasPhysicsRadius, + HasRenderScale = view.HasRenderScale, + HasRenderVisibility = view.HasRenderVisibility, + HasRenderLod = view.HasRenderLod, + HasRenderGroupFilter = renderGroupId >= 0, + HasRenderLodFilter = renderLod >= 0, + UniformScale = defaultScale + }; + + job.Schedule().Complete(); + return math.clamp(writtenCount[0], 0, _nativeMatrices.Length); + } + + private static float ResolveDefaultScale(AetheriaRuntimeDaemonSoaViewIndex index, int renderGroupId) + { + if (index != null && + renderGroupId >= 0 && + index.TryGetRenderGroup(renderGroupId, out var renderGroup)) + { + return math.max(renderGroup.DefaultScale, 0.0001f); + } + + var renderGroups = index?.RenderGroups; + if (renderGroups != null && renderGroups.Count == 1) + { + return math.max(renderGroups[0].DefaultScale, 0.0001f); + } + + return 1.0f; + } + + private static int ResolveRenderLod(AetheriaRuntimeDaemonSoaViewIndex index, int renderGroupId) + { + if (index != null && + renderGroupId >= 0 && + index.TryGetRenderGroup(renderGroupId, out var renderGroup)) + { + return renderGroup.Lod; + } + + var renderGroups = index?.RenderGroups; + if (renderGroups != null && renderGroups.Count == 1) + { + return renderGroups[0].Lod; + } + + return -1; + } + + public void ApplyTo(MaterialPropertyBlock properties) + { + if (properties == null) + { + return; + } + + properties.SetInt(InstanceCountPropertyId, InstanceCount); + if (HasGpuBuffers) + { + properties.SetBuffer(MatrixBufferPropertyId, _matrixBuffer); + } + } + + private void OnDisable() + { + ReleaseBuffers(); + InstanceCount = 0; + LastUploadedFrameId = 0; + LastUploadedGeneration = 0; + } + + private void EnsureCapacity(AetheriaDaemonRenderNativeView view) + { + var count = math.max(0, view.Count); + if (_nativeMatrices.IsCreated && + _allocatedGeneration == view.Generation && + _allocatedCount == count) + { + return; + } + + ReleaseBuffers(); + _allocatedGeneration = view.Generation; + _allocatedCount = count; + if (count <= 0) + { + return; + } + + _nativeMatrices = new NativeArray( + count, + Allocator.Persistent, + NativeArrayOptions.UninitializedMemory); + _matrixBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, count, MatrixStrideBytes); + _indirectArgsBuffer = new GraphicsBuffer( + GraphicsBuffer.Target.IndirectArguments, + DrawMeshInstancedIndirectArgsCount, + sizeof(uint)); + } + + private void UpdateIndirectArgs(Mesh mesh, int instanceCount) + { + UpdateIndirectArgs(mesh, instanceCount, 0); + } + + private void UpdateIndirectArgs(Mesh mesh, int instanceCount, int subMeshIndex) + { + if (_indirectArgsBuffer == null || !_indirectArgsBuffer.IsValid()) + { + return; + } + + var safeSubMeshIndex = mesh != null + ? math.clamp(subMeshIndex, 0, math.max(0, mesh.subMeshCount - 1)) + : 0; + _indirectArgs[0] = mesh != null ? mesh.GetIndexCount(safeSubMeshIndex) : 0; + _indirectArgs[1] = (uint)math.max(0, instanceCount); + _indirectArgs[2] = mesh != null ? mesh.GetIndexStart(safeSubMeshIndex) : 0; + _indirectArgs[3] = mesh != null ? mesh.GetBaseVertex(safeSubMeshIndex) : 0; + _indirectArgs[4] = 0; + _indirectArgsBuffer.SetData(_indirectArgs); + } + + private void ReleaseBuffers() + { + if (_nativeMatrices.IsCreated) + { + _nativeMatrices.Dispose(); + } + + _matrixBuffer?.Release(); + _matrixBuffer = null; + _indirectArgsBuffer?.Release(); + _indirectArgsBuffer = null; + _allocatedGeneration = long.MinValue; + _allocatedCount = 0; + } +} diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs.meta b/Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs.meta new file mode 100644 index 000000000..5fe14c6d3 --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9cba277ef259bee438796ab4c0f3df64 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupBatch.cs b/Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupBatch.cs new file mode 100644 index 000000000..37709c7da --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupBatch.cs @@ -0,0 +1,177 @@ +using System; +using GameCult.Aetheria.State.Unity; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +public sealed class AetheriaDaemonRenderGroupBatch : IDisposable +{ + private const int MatrixStrideBytes = 64; + private const int DrawMeshInstancedIndirectArgsCount = 5; + + private readonly uint[] _indirectArgs = new uint[DrawMeshInstancedIndirectArgsCount]; + private NativeArray _nativeMatrices; + private GraphicsBuffer _matrixBuffer; + private GraphicsBuffer _indirectArgsBuffer; + private int _capacity; + + public int GroupId { get; private set; } + public int InstanceCount { get; private set; } + public long LastUploadedFrameId { get; private set; } + public long LastUploadedGeneration { get; private set; } + public GraphicsBuffer MatrixBuffer => _matrixBuffer; + public GraphicsBuffer IndirectArgsBuffer => _indirectArgsBuffer; + public bool HasGpuBuffers => + _matrixBuffer != null && + _matrixBuffer.IsValid() && + _indirectArgsBuffer != null && + _indirectArgsBuffer.IsValid() && + InstanceCount > 0; + + public bool UploadLatest( + AetheriaDaemonRenderNativeView view, + AetheriaRuntimeDaemonRenderGroupDocument renderGroup, + Mesh mesh, + float uniformScale) + { + if (!view.IsCreated || mesh == null) + { + InstanceCount = 0; + UpdateIndirectArgs(mesh, 0, renderGroup?.SubMeshIndex ?? 0); + return false; + } + + var targetCapacity = renderGroup.InstanceCount >= 0 + ? math.min(renderGroup.InstanceCount, view.Count) + : view.Count; + EnsureCapacity(targetCapacity); + if (!_nativeMatrices.IsCreated || _nativeMatrices.Length == 0) + { + InstanceCount = 0; + UpdateIndirectArgs(mesh, 0, renderGroup.SubMeshIndex); + return false; + } + + var count = view.HasRenderGroupId + ? BuildRenderGroupMatrices(view, renderGroup.GroupId, renderGroup.Lod, uniformScale) + : BuildAllMatrices(view, renderGroup.Lod, uniformScale); + + _matrixBuffer.SetData(_nativeMatrices, 0, 0, count); + GroupId = renderGroup.GroupId; + InstanceCount = count; + LastUploadedFrameId = view.FrameId; + LastUploadedGeneration = view.Generation; + UpdateIndirectArgs(mesh, count, renderGroup.SubMeshIndex); + return count > 0; + } + + public void Dispose() + { + if (_nativeMatrices.IsCreated) + { + _nativeMatrices.Dispose(); + } + + _matrixBuffer?.Release(); + _matrixBuffer = null; + _indirectArgsBuffer?.Release(); + _indirectArgsBuffer = null; + _capacity = 0; + InstanceCount = 0; + } + + private int BuildAllMatrices(AetheriaDaemonRenderNativeView view, int renderLod, float uniformScale) + { + return BuildCompactedMatrices(view, -1, renderLod, uniformScale); + } + + private int BuildRenderGroupMatrices( + AetheriaDaemonRenderNativeView view, + int renderGroupId, + int renderLod, + float uniformScale) + { + return BuildCompactedMatrices(view, renderGroupId, renderLod, uniformScale); + } + + private int BuildCompactedMatrices( + AetheriaDaemonRenderNativeView view, + int renderGroupId, + int renderLod, + float uniformScale) + { + using var writtenCount = new NativeArray(1, Allocator.TempJob); + var job = new AetheriaDaemonRenderGroupMatrixJob + { + PositionX = view.PositionX, + PositionY = view.PositionY, + PositionZ = view.PositionZ, + RotationRadians = view.RotationRadians, + PhysicsBodyRadius = view.PhysicsBodyRadius, + RenderScale = view.RenderScale, + RenderVisibility = view.RenderVisibility, + RenderLod = view.RenderLod, + RenderGroupId = view.RenderGroupId, + Matrices = _nativeMatrices, + WrittenCount = writtenCount, + TargetRenderGroupId = renderGroupId >= 0 ? (uint)renderGroupId : 0, + TargetRenderLod = renderLod, + HasRotation = view.HasRotation, + HasPhysicsRadius = view.HasPhysicsRadius, + HasRenderScale = view.HasRenderScale, + HasRenderVisibility = view.HasRenderVisibility, + HasRenderLod = view.HasRenderLod, + HasRenderGroupFilter = renderGroupId >= 0, + HasRenderLodFilter = renderLod >= 0, + UniformScale = uniformScale + }; + + job.Schedule().Complete(); + return math.clamp(writtenCount[0], 0, _nativeMatrices.Length); + } + + private void EnsureCapacity(int capacity) + { + capacity = math.max(0, capacity); + if (_nativeMatrices.IsCreated && _capacity == capacity) + { + return; + } + + Dispose(); + _capacity = capacity; + if (capacity <= 0) + { + return; + } + + _nativeMatrices = new NativeArray( + capacity, + Allocator.Persistent, + NativeArrayOptions.UninitializedMemory); + _matrixBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, MatrixStrideBytes); + _indirectArgsBuffer = new GraphicsBuffer( + GraphicsBuffer.Target.IndirectArguments, + DrawMeshInstancedIndirectArgsCount, + sizeof(uint)); + } + + private void UpdateIndirectArgs(Mesh mesh, int instanceCount, int subMeshIndex) + { + if (_indirectArgsBuffer == null || !_indirectArgsBuffer.IsValid()) + { + return; + } + + var safeSubMeshIndex = mesh != null + ? math.clamp(subMeshIndex, 0, math.max(0, mesh.subMeshCount - 1)) + : 0; + _indirectArgs[0] = mesh != null ? mesh.GetIndexCount(safeSubMeshIndex) : 0; + _indirectArgs[1] = (uint)math.max(0, instanceCount); + _indirectArgs[2] = mesh != null ? mesh.GetIndexStart(safeSubMeshIndex) : 0; + _indirectArgs[3] = mesh != null ? mesh.GetBaseVertex(safeSubMeshIndex) : 0; + _indirectArgs[4] = 0; + _indirectArgsBuffer.SetData(_indirectArgs); + } +} diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupBatch.cs.meta b/Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupBatch.cs.meta new file mode 100644 index 000000000..315a65f8d --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupBatch.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0038a222765ff82468c22a516cfcdbc1 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupMatrixJob.cs b/Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupMatrixJob.cs new file mode 100644 index 000000000..7f93630f1 --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupMatrixJob.cs @@ -0,0 +1,92 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; + +[BurstCompile] +public struct AetheriaDaemonRenderGroupMatrixJob : IJob +{ + [ReadOnly] + public NativeArray PositionX; + + [ReadOnly] + public NativeArray PositionY; + + [ReadOnly] + public NativeArray PositionZ; + + [ReadOnly] + public NativeArray RotationRadians; + + [ReadOnly] + public NativeArray PhysicsBodyRadius; + + [ReadOnly] + public NativeArray RenderScale; + + [ReadOnly] + public NativeArray RenderVisibility; + + [ReadOnly] + public NativeArray RenderLod; + + [ReadOnly] + public NativeArray RenderGroupId; + + [WriteOnly] + public NativeArray Matrices; + + public NativeArray WrittenCount; + public uint TargetRenderGroupId; + public int TargetRenderLod; + public bool HasRotation; + public bool HasPhysicsRadius; + public bool HasRenderScale; + public bool HasRenderVisibility; + public bool HasRenderLod; + public bool HasRenderGroupFilter; + public bool HasRenderLodFilter; + public float UniformScale; + + public void Execute() + { + var written = 0; + for (var index = 0; index < PositionX.Length; index++) + { + if (HasRenderGroupFilter && RenderGroupId[index] != TargetRenderGroupId) + { + continue; + } + + if (HasRenderVisibility && RenderVisibility[index] == 0) + { + continue; + } + + if (HasRenderLodFilter && (!HasRenderLod || RenderLod[index] != TargetRenderLod)) + { + continue; + } + + if (written >= Matrices.Length) + { + break; + } + + var position = new float3(PositionX[index], PositionY[index], PositionZ[index]); + var rotation = HasRotation + ? quaternion.RotateY(RotationRadians[index]) + : quaternion.identity; + var scaleValue = HasRenderScale + ? math.max(RenderScale[index], 0.0001f) + : HasPhysicsRadius + ? math.max(PhysicsBodyRadius[index] * 2.0f, 0.0001f) + : math.max(UniformScale, 0.0001f); + + Matrices[written] = float4x4.TRS(position, rotation, new float3(scaleValue)); + written++; + } + + WrittenCount[0] = written; + } +} diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupMatrixJob.cs.meta b/Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupMatrixJob.cs.meta new file mode 100644 index 000000000..eb679bf07 --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupMatrixJob.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 146aa8d6d00cc1a44b44a5f83d69edea \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonRenderMatrixJob.cs b/Assets/Scripts/Gameplay/AetheriaDaemonRenderMatrixJob.cs new file mode 100644 index 000000000..4f55a4652 --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonRenderMatrixJob.cs @@ -0,0 +1,49 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; + +[BurstCompile] +public struct AetheriaDaemonRenderMatrixJob : IJobParallelFor +{ + [ReadOnly] + public NativeArray PositionX; + + [ReadOnly] + public NativeArray PositionY; + + [ReadOnly] + public NativeArray PositionZ; + + [ReadOnly] + public NativeArray RotationRadians; + + [ReadOnly] + public NativeArray PhysicsBodyRadius; + + [ReadOnly] + public NativeArray RenderScale; + + [WriteOnly] + public NativeArray Matrices; + + public bool HasRotation; + public bool HasPhysicsRadius; + public bool HasRenderScale; + public float UniformScale; + + public void Execute(int index) + { + var position = new float3(PositionX[index], PositionY[index], PositionZ[index]); + var rotation = HasRotation + ? quaternion.RotateY(RotationRadians[index]) + : quaternion.identity; + var scaleValue = HasRenderScale + ? math.max(RenderScale[index], 0.0001f) + : HasPhysicsRadius + ? math.max(PhysicsBodyRadius[index] * 2.0f, 0.0001f) + : math.max(UniformScale, 0.0001f); + + Matrices[index] = float4x4.TRS(position, rotation, new float3(scaleValue)); + } +} diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonRenderMatrixJob.cs.meta b/Assets/Scripts/Gameplay/AetheriaDaemonRenderMatrixJob.cs.meta new file mode 100644 index 000000000..d1f3e8d30 --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonRenderMatrixJob.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f6b3b3dc2e2a29245a24b772e2cb87b4 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonRenderNativeView.cs b/Assets/Scripts/Gameplay/AetheriaDaemonRenderNativeView.cs new file mode 100644 index 000000000..64ce7fad2 --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonRenderNativeView.cs @@ -0,0 +1,248 @@ +using GameCult.Aetheria.State.Unity; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; + +public struct AetheriaDaemonRenderNativeView +{ + public long FrameId; + public long Generation; + public int Count; + public NativeArray PositionX; + public NativeArray PositionY; + public NativeArray PositionZ; + public NativeArray RotationRadians; + public NativeArray VelocityX; + public NativeArray VelocityY; + public NativeArray VelocityZ; + public NativeArray PhysicsBodyRadius; + public NativeArray PhysicsBodyMass; + public NativeArray PhysicsBodyInverseMass; + public NativeArray RenderScale; + public NativeArray RenderVisibility; + public NativeArray RenderLod; + public NativeArray RenderGroupId; + + public bool IsCreated => + Count > 0 && + PositionX.IsCreated && + PositionY.IsCreated && + PositionZ.IsCreated; + + public bool HasRotation => RotationRadians.IsCreated; + public bool HasVelocity => VelocityX.IsCreated && VelocityY.IsCreated && VelocityZ.IsCreated; + public bool HasPhysicsRadius => PhysicsBodyRadius.IsCreated; + public bool HasPhysicsMass => PhysicsBodyMass.IsCreated; + public bool HasPhysicsInverseMass => PhysicsBodyInverseMass.IsCreated; + public bool HasRenderScale => RenderScale.IsCreated; + public bool HasRenderVisibility => RenderVisibility.IsCreated; + public bool HasRenderLod => RenderLod.IsCreated; + public bool HasRenderGroupId => RenderGroupId.IsCreated; + + public JobHandle ScheduleBuildRenderMatrices( + NativeArray matrices, + float uniformScale = 1.0f, + int innerLoopBatchCount = 64, + JobHandle dependsOn = default) + { + if (!IsCreated) + { + return dependsOn; + } + + var count = math.min(Count, matrices.IsCreated ? matrices.Length : 0); + if (count <= 0) + { + return dependsOn; + } + + var job = new AetheriaDaemonRenderMatrixJob + { + PositionX = PositionX, + PositionY = PositionY, + PositionZ = PositionZ, + RotationRadians = RotationRadians, + PhysicsBodyRadius = PhysicsBodyRadius, + RenderScale = RenderScale, + Matrices = matrices, + HasRotation = HasRotation, + HasPhysicsRadius = HasPhysicsRadius, + HasRenderScale = HasRenderScale, + UniformScale = uniformScale + }; + + return job.Schedule(count, math.max(1, innerLoopBatchCount), dependsOn); + } + + public static bool TryCreate( + AetheriaRuntimeDaemonSoaViewIndex index, + AetheriaDaemonSoaMemoryMap map, + out AetheriaDaemonRenderNativeView view) + { + view = default; + if (index == null || map == null) + { + return false; + } + + if (!map.TryCreateFirstNativeArrayOfKind( + index, + AetheriaRuntimeDaemonSoaColumnKinds.PositionX, + out view.PositionX) || + !map.TryCreateFirstNativeArrayOfKind( + index, + AetheriaRuntimeDaemonSoaColumnKinds.PositionY, + out view.PositionY) || + !map.TryCreateFirstNativeArrayOfKind( + index, + AetheriaRuntimeDaemonSoaColumnKinds.PositionZ, + out view.PositionZ)) + { + view = default; + return false; + } + + view.Count = view.PositionX.Length; + if (view.PositionY.Length != view.Count || view.PositionZ.Length != view.Count) + { + view = default; + return false; + } + + TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RotationRadians, view.Count, out view.RotationRadians); + TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.VelocityX, view.Count, out view.VelocityX); + TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.VelocityY, view.Count, out view.VelocityY); + TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.VelocityZ, view.Count, out view.VelocityZ); + TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyRadius, view.Count, out view.PhysicsBodyRadius); + TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyMass, view.Count, out view.PhysicsBodyMass); + TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyInverseMass, view.Count, out view.PhysicsBodyInverseMass); + TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RenderScale, view.Count, out view.RenderScale); + TryAssignOptionalByteColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RenderVisibility, view.Count, out view.RenderVisibility); + TryAssignOptionalIntColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RenderLod, view.Count, out view.RenderLod); + TryAssignOptionalUIntColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RenderGroupId, view.Count, out view.RenderGroupId); + + if ((view.VelocityX.IsCreated || view.VelocityY.IsCreated || view.VelocityZ.IsCreated) && + !view.HasVelocity) + { + view.VelocityX = default; + view.VelocityY = default; + view.VelocityZ = default; + } + + view.FrameId = index.View.FrameId; + view.Generation = index.View.Generation; + return true; + } + + private static bool TryAssignOptionalColumn( + AetheriaRuntimeDaemonSoaViewIndex index, + AetheriaDaemonSoaMemoryMap map, + string kind, + int expectedCount, + out NativeArray array) + { + array = default; + if (!index.TryGetFirstColumnOfKind(kind, out _)) + { + return false; + } + + if (!map.TryCreateFirstNativeArrayOfKind(index, kind, out array)) + { + array = default; + return false; + } + + if (array.Length == expectedCount) + { + return true; + } + + array = default; + return false; + } + + private static bool TryAssignOptionalUIntColumn( + AetheriaRuntimeDaemonSoaViewIndex index, + AetheriaDaemonSoaMemoryMap map, + string kind, + int expectedCount, + out NativeArray array) + { + array = default; + if (!index.TryGetFirstColumnOfKind(kind, out _)) + { + return false; + } + + if (!map.TryCreateFirstNativeArrayOfKind(index, kind, out array)) + { + array = default; + return false; + } + + if (array.Length == expectedCount) + { + return true; + } + + array = default; + return false; + } + + private static bool TryAssignOptionalIntColumn( + AetheriaRuntimeDaemonSoaViewIndex index, + AetheriaDaemonSoaMemoryMap map, + string kind, + int expectedCount, + out NativeArray array) + { + array = default; + if (!index.TryGetFirstColumnOfKind(kind, out _)) + { + return false; + } + + if (!map.TryCreateFirstNativeArrayOfKind(index, kind, out array)) + { + array = default; + return false; + } + + if (array.Length == expectedCount) + { + return true; + } + + array = default; + return false; + } + + private static bool TryAssignOptionalByteColumn( + AetheriaRuntimeDaemonSoaViewIndex index, + AetheriaDaemonSoaMemoryMap map, + string kind, + int expectedCount, + out NativeArray array) + { + array = default; + if (!index.TryGetFirstColumnOfKind(kind, out _)) + { + return false; + } + + if (!map.TryCreateFirstNativeArrayOfKind(index, kind, out array)) + { + array = default; + return false; + } + + if (array.Length == expectedCount) + { + return true; + } + + array = default; + return false; + } +} diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonRenderNativeView.cs.meta b/Assets/Scripts/Gameplay/AetheriaDaemonRenderNativeView.cs.meta new file mode 100644 index 000000000..70217db9e --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonRenderNativeView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 48740dbd37e79954ca00a3f6bd7ccef1 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonSoaMemoryMap.cs b/Assets/Scripts/Gameplay/AetheriaDaemonSoaMemoryMap.cs new file mode 100644 index 000000000..e84b14bdf --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonSoaMemoryMap.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.IO.MemoryMappedFiles; +using GameCult.Aetheria.State.Unity; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +public unsafe sealed class AetheriaDaemonSoaMemoryMap : IDisposable +{ + private readonly Dictionary _buffersById = + new Dictionary(StringComparer.Ordinal); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private readonly List _safetyHandles = new List(); +#endif + + private bool _disposed; + + private AetheriaDaemonSoaMemoryMap() + { + } + + public static bool TryOpen( + AetheriaRuntimeDaemonSoaViewIndex index, + out AetheriaDaemonSoaMemoryMap map, + out string error) + { + map = null; + error = ""; + + if (index == null) + { + error = "No daemon SoA view index was provided."; + return false; + } + + if (!index.IsValid) + { + error = index.ValidationErrors.Count > 0 + ? index.ValidationErrors[0] + : "Daemon SoA view index is invalid."; + return false; + } + + var opened = new AetheriaDaemonSoaMemoryMap(); + try + { + foreach (var buffer in index.View.Buffers) + { + if (!IsMappableReadOnlyBuffer(buffer)) + { + continue; + } + + opened.OpenBuffer(buffer); + } + + map = opened; + return true; + } + catch (Exception exception) + { + opened.Dispose(); + error = exception.Message; + return false; + } + } + + public bool TryCreateNativeArray( + AetheriaRuntimeDaemonSoaColumnBinding binding, + out NativeArray array) + where T : struct + { + ThrowIfDisposed(); + array = default; + + if (binding == null || + !binding.DirectMemoryCompatible || + !_buffersById.TryGetValue(binding.Buffer.BufferId, out var buffer)) + { + return false; + } + + var byteOffset = checked((ulong)binding.AbsoluteByteOffset); + var byteLength = checked((ulong)Math.Max(0, binding.ByteLength)); + if (byteOffset + byteLength > buffer.ByteLength) + { + return false; + } + + var pointer = buffer.Pointer + byteOffset; + array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray( + pointer, + binding.Column.ElementCount, + Allocator.None); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + var safetyHandle = AtomicSafetyHandle.Create(); + _safetyHandles.Add(safetyHandle); + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, safetyHandle); +#endif + + return true; + } + + public bool TryCreateFirstNativeArrayOfKind( + AetheriaRuntimeDaemonSoaViewIndex index, + string kind, + out NativeArray array) + where T : struct + { + ThrowIfDisposed(); + array = default; + + if (index == null || !index.TryGetFirstColumnOfKind(kind, out var binding)) + { + return false; + } + + return TryCreateNativeArray(binding, out array); + } + + public void Dispose() + { + if (_disposed) + { + return; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + for (var i = 0; i < _safetyHandles.Count; i++) + { + AtomicSafetyHandle.Release(_safetyHandles[i]); + } + + _safetyHandles.Clear(); +#endif + + foreach (var buffer in _buffersById.Values) + { + buffer.Dispose(); + } + + _buffersById.Clear(); + _disposed = true; + } + + private void OpenBuffer(AetheriaRuntimeDaemonSoaBufferDocument buffer) + { + if (string.IsNullOrWhiteSpace(buffer.BufferId) || + _buffersById.ContainsKey(buffer.BufferId)) + { + return; + } + + var byteLength = checked((ulong)(buffer.ByteOffset + buffer.ByteLength)); + var memoryMappedFile = MemoryMappedFile.OpenExisting( + buffer.Location, + MemoryMappedFileRights.Read); + var accessor = memoryMappedFile.CreateViewAccessor(0, (long)byteLength, MemoryMappedFileAccess.Read); + + byte* pointer = null; + accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer); + + _buffersById.Add( + buffer.BufferId, + new MappedBuffer(memoryMappedFile, accessor, pointer, byteLength)); + } + + private static bool IsMappableReadOnlyBuffer(AetheriaRuntimeDaemonSoaBufferDocument buffer) + { + return buffer != null && + !buffer.ObserverWritable && + !string.IsNullOrWhiteSpace(buffer.Location) && + string.Equals(buffer.Backend, AetheriaRuntimeDaemonSoaBackends.MemoryMappedFile, StringComparison.Ordinal); + } + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(AetheriaDaemonSoaMemoryMap)); + } + } + + private sealed class MappedBuffer : IDisposable + { + private readonly MemoryMappedFile _memoryMappedFile; + private readonly MemoryMappedViewAccessor _accessor; + private bool _disposed; + + public MappedBuffer( + MemoryMappedFile memoryMappedFile, + MemoryMappedViewAccessor accessor, + byte* pointer, + ulong byteLength) + { + _memoryMappedFile = memoryMappedFile; + _accessor = accessor; + Pointer = pointer; + ByteLength = byteLength; + } + + public byte* Pointer { get; } + public ulong ByteLength { get; } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _accessor.SafeMemoryMappedViewHandle.ReleasePointer(); + _accessor.Dispose(); + _memoryMappedFile.Dispose(); + _disposed = true; + } + } +} diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonSoaMemoryMap.cs.meta b/Assets/Scripts/Gameplay/AetheriaDaemonSoaMemoryMap.cs.meta new file mode 100644 index 000000000..83080b339 --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaDaemonSoaMemoryMap.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d8988c7d796df504dab64fd2c64eb868 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/ArticulationPoint.cs b/Assets/Scripts/Gameplay/ArticulationPoint.cs index ba9b43c07..f922ef760 100644 --- a/Assets/Scripts/Gameplay/ArticulationPoint.cs +++ b/Assets/Scripts/Gameplay/ArticulationPoint.cs @@ -1,15 +1,11 @@ -using System.Collections; -using System.Collections.Generic; using UnityEngine; -using Unity.Mathematics; -using static Unity.Mathematics.math; public class ArticulationPoint : MonoBehaviour { public Transform Target; public int Group; - + public float YawMin; public float YawMax; @@ -26,26 +22,26 @@ public class ArticulationPoint : MonoBehaviour if (Target) { var targetLocal = transform.InverseTransformPoint(Target.position); - + var yaw = Vector2.SignedAngle(new Vector2(0, 1), new Vector2(targetLocal.x, targetLocal.z)); var pitch = Vector2.SignedAngle(new Vector2(1, 0), new Vector2(targetLocal.z, targetLocal.y)); - var targetYaw = clamp(_yaw - yaw, YawMin, YawMax); - var targetPitch = clamp(_pitch - pitch, PitchMin, PitchMax); + var targetYaw = Mathf.Clamp(_yaw - yaw, YawMin, YawMax); + var targetPitch = Mathf.Clamp(_pitch - pitch, PitchMin, PitchMax); - if (abs(targetYaw - _yaw) < Speed * Time.deltaTime) + if (Mathf.Abs(targetYaw - _yaw) < Speed * Time.deltaTime) _yaw = targetYaw; - else _yaw = _yaw + sign(targetYaw - _yaw) * Speed * Time.deltaTime; + else _yaw = _yaw + Mathf.Sign(targetYaw - _yaw) * Speed * Time.deltaTime; - if(_yaw < -360) + if (_yaw < -360) _yaw += 360; - if(_yaw > 360) + if (_yaw > 360) _yaw -= 360; - if (abs(targetPitch - _pitch) < Speed * Time.deltaTime) + if (Mathf.Abs(targetPitch - _pitch) < Speed * Time.deltaTime) _pitch = targetPitch; - else _pitch = _pitch + sign(targetPitch - _pitch) * Speed * Time.deltaTime; - + else _pitch = _pitch + Mathf.Sign(targetPitch - _pitch) * Speed * Time.deltaTime; + transform.localRotation = Quaternion.Euler(_pitch, _yaw, 0); } } diff --git a/Assets/Scripts/Gameplay/EntityInstance.cs b/Assets/Scripts/Gameplay/EntityInstance.cs index 950dae92e..23c0b346b 100644 --- a/Assets/Scripts/Gameplay/EntityInstance.cs +++ b/Assets/Scripts/Gameplay/EntityInstance.cs @@ -5,17 +5,13 @@ using System.Linq; using GameCult.Aetheria.State.Unity; using UniRx; using UnityEngine; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using float2 = Unity.Mathematics.float2; +using cfloat3 = CultMath.float3; public class EntityInstance : MonoBehaviour { public MeshRenderer MapIcon; public Transform InfluencePrefab; - public Transform PingPrefab; public Material InvisibleMaterial; - public static Transform EffectManagerParent; public ShieldManager Shield; public HullCollider[] HullColliders; @@ -25,7 +21,6 @@ public class EntityInstance : MonoBehaviour public WeaponHardpoint[] WeaponHardpoints; public ArticulationPoint[] ArticulationPoints; - public GameObject DestroyEffect; public event Action OnFadedOut; public event Action OnFadedIn; @@ -40,41 +35,21 @@ public class EntityInstance : MonoBehaviour private bool _fadedElementsVisible = false; private bool _unfadedElementsVisible = false; private float _fadeTime; - private bool _destroyed; - private (Transform transform, MeshRenderer meshRenderer) _currentPing; - private float _pingBrightness; - private Sensor _sensor; private Transform _influenceInstance; //private List<(GameObject source, EquippedItem item)> _audioSources = new List<(GameObject source, EquippedItem item)>(); // private (Reactor reactor, GameObject sfxSource) _reactor; // private Dictionary _radiatorSfx = new Dictionary(); - private static Dictionary _instantWeaponManagers = new Dictionary(); - private static Dictionary _constantWeaponManagers = new Dictionary(); - - public static void ClearWeaponManagers() - { - _instantWeaponManagers.Clear(); - _constantWeaponManagers.Clear(); - } - private static AetheriaRuntimeCatalogItem FindTypedHull(ItemInstance hull) { return ActionGameManager.RuntimeCatalog?.FindItem(hull?.ItemKey ?? ""); } - private static Shape ToShape(int width, int height, IReadOnlyList cells) - { - var shape = new Shape(width, height); - foreach (var cell in cells) - { - if (cell.X >= 0 && cell.Y >= 0 && cell.X < width && cell.Y < height) - shape[int2(cell.X, cell.Y)] = true; - } + private static Vector2 ToVector2(CultMath.float2 value) => new Vector2(value.x, value.y); + private static Vector2 ToVector2(Unity.Mathematics.float2 value) => new Vector2(value.x, value.y); - return shape; - } + private static cfloat3 ToCult(Vector3 value) => new cfloat3(value.x, value.y, value.z); public CompassIcon CompassIcon { get; set; } public Dictionary Barrels { get; private set; } @@ -186,81 +161,10 @@ public class EntityInstance : MonoBehaviour return; } - var hullShape = ToShape(typedHull.ShapeWidth, typedHull.ShapeHeight, typedHull.ShapeCells); - var typedHardpoints = typedHull.Hardpoints.ToArray(); - if(Shield) Shield.Entity = entity; foreach (var hullCollider in HullColliders) hullCollider.Entity = entity; - foreach (var item in entity.Equipment) - { - foreach (var behavior in item.Behaviors) - { - if (behavior is Sensor sensor) - { - _sensor = sensor; - sensor.OnPingStart += () => - { - var pingInstance = Instantiate(PingPrefab); - var pingMesh = pingInstance.GetComponent(); - pingInstance.position = entity.Position; - _pingBrightness = pingMesh.material.GetFloat("_Depth"); - _currentPing = (pingInstance, pingMesh); - }; - sensor.OnPingEnd += OnSensorPingEnd; - } - - if (behavior is InstantWeapon instantWeapon) - { - var effectPrefab = instantWeapon.EffectPrefab; - if (!_instantWeaponManagers.ContainsKey(effectPrefab)) - { - var managerPrefab = UnityHelpers.LoadAsset(effectPrefab); - if(managerPrefab) - { - _instantWeaponManagers.Add(effectPrefab, Instantiate(managerPrefab, EffectManagerParent)); - } - else Debug.LogError($"No InstantWeaponEffectManager prefab found at path {effectPrefab}"); - } - - instantWeapon.OnFire += () => - _instantWeaponManagers[effectPrefab].Fire(instantWeapon, item, this, entity.Target.Value != null && ZoneRenderer.EntityInstances.ContainsKey(entity.Target.Value) ? ZoneRenderer.EntityInstances[entity.Target.Value] : null); - - if (behavior is ChargedWeapon chargedWeapon) - { - var chargeManager = _instantWeaponManagers[effectPrefab].GetComponent(); - if (chargeManager) - { - chargedWeapon.OnStartCharging += () => chargeManager.StartCharging(chargedWeapon, item, this); - chargedWeapon.OnStopCharging += () => chargeManager.StopCharging(chargedWeapon); - chargedWeapon.OnCharged += () => chargeManager.Charged(chargedWeapon); - chargedWeapon.OnFailed += () => chargeManager.Failed(chargedWeapon); - } - } - } - - if (behavior is ConstantWeapon constantWeapon) - { - var effectPrefab = constantWeapon.EffectPrefab; - if (!_constantWeaponManagers.ContainsKey(effectPrefab)) - { - var managerPrefab = UnityHelpers.LoadAsset(effectPrefab); - if(managerPrefab) - { - _constantWeaponManagers.Add(effectPrefab, Instantiate(managerPrefab, EffectManagerParent)); - } - else Debug.LogError($"No ConstantWeaponEffectManager prefab found at path {effectPrefab}"); - } - - constantWeapon.OnStartFiring += () => - _constantWeaponManagers[effectPrefab].StartFiring(constantWeapon, item, this, entity.Target.Value != null ? ZoneRenderer.EntityInstances[entity.Target.Value] : null); - constantWeapon.OnStopFiring += () => - _constantWeaponManagers[effectPrefab].StopFiring(item); - } - } - - } RadiatorMeshes = new Dictionary(); Barrels = new Dictionary(); BarrelIndices = new Dictionary(); @@ -289,128 +193,6 @@ public class EntityInstance : MonoBehaviour } } - void DamageSchematic(float damage, Shape hitShape) - { - foreach (var v in hitShape.Coordinates) - hitShape[v] = hitShape[v] && hullShape[v]; - - float hullDamage = 0; - var damagePerCell = damage / hitShape.Coordinates.Length; - foreach (var v in hitShape.Coordinates) - { - var d = damagePerCell; - - // Subtract surface damage from armor, passing on the remainder to the item and then to the hull - var prev = entity.Armor[v.x, v.y]; - entity.Armor[v.x, v.y] = max(prev - d, 0); - entity.ArmorDamage.OnNext((v, d)); - d = max(d - prev, 0); - - if (d > 0.1f) - { - var item = entity.GearOccupancy[v.x, v.y]; - if (item != null) - { - prev = item.EquippableItem.Durability; - item.EquippableItem.Durability = max(prev - d, 0); - entity.ItemDamage.OnNext((item, d)); - d = max(d - prev, 0); - } - } - - hullDamage += d; - } - - if(hullDamage > .1f) - { - entity.Hull.Durability -= hullDamage; - entity.HullDamage.OnNext(hullDamage); - } - } - - foreach (var collider in HullColliders) - { - collider.Splash.Subscribe(splash => - { - var hitShape = new Shape(hullShape.Width, hullShape.Height); - foreach (var v in hullShape.Coordinates) - { - var localHitDirection = transform.InverseTransformDirection(splash.Direction); - var direction = normalize(float2(localHitDirection.x, localHitDirection.z)); - var cellDot = dot(normalize(v - hullShape.CenterOfMass), direction); - if (cellDot < 0) hitShape[v] = true; - } - DamageSchematic(splash.Damage, hitShape); - }); - - collider.Hit.Subscribe(hit => - { - Entity.IncomingHit.OnNext(hit.Source); - var hardpointIndex = (int) hit.TexCoord.x - 1; - - var hitShape = new Shape(hullShape.Width, hullShape.Height); - - // U coordinate between 0-1 indicates a hit that didn't land directly on a hardpoint - // Find the 2D position of the hit scaled to the schematic - float2 hitPos = float2.zero; - if (hardpointIndex < 0 || hardpointIndex >= typedHardpoints.Length) - { - hitPos = float2(hit.TexCoord.x * hullShape.Width, hit.TexCoord.y * hullShape.Height); - // Search all schematic border cells for the cell which is closest to the hit position - var hitCell = int2(-1); - var distance = float.MaxValue; - foreach (var v in hullShape.Coordinates) - { - var cellDist = lengthsq(hitPos - v); - if (cellDist < distance) - { - distance = cellDist; - hitCell = v; - hitPos = v + float2(.5f); - } - } - - hitShape[hitCell] = true; - } - else - { - // Collider UV coordinates starting with 1 correspond to hardpoint index - var hardpoint = typedHardpoints[hardpointIndex]; - - // Obtain the hull coordinates of all cells occupied by the hardpoint - var hardpointShape = ToShape(hardpoint.ShapeWidth, hardpoint.ShapeHeight, hardpoint.ShapeCells); - var hardpointCells = hullShape.Inset(hardpointShape, int2(hardpoint.PositionX, hardpoint.PositionY)); - hitPos = hardpointCells.CenterOfMass; - foreach (var v in hardpointCells.Coordinates) - hitShape[v] = true; - } - - for (int i = 0; i < Mathf.RoundToInt(hit.Spread); i++) - { - hitShape = hitShape.Expand(); - } - - if (hit.Penetration > .5f) - { - // Find the local 2D vector corresponding to the direction of the incoming hit - var localHitDirection = transform.InverseTransformDirection(hit.Direction); - var penetrationVector = normalize(float2(localHitDirection.x, localHitDirection.z)); - // TODO: Bresenham's line algorithm - // March a ray through the ship from the hit position - var penetrationPoint = hitPos; - var penetrationDistance = 0f; - while (penetrationDistance < hit.Penetration && hullShape[int2(penetrationPoint)]) - { - penetrationDistance += .5f; - hitShape[int2(penetrationPoint)] = true; - penetrationPoint += penetrationVector * .5f; - } - } - - DamageSchematic(hit.Damage, hitShape); - }); - } - LookAtPoint = new GameObject($"{entity.Name} Look Point").transform; foreach (var articulationPoint in ArticulationPoints) @@ -418,23 +200,6 @@ public class EntityInstance : MonoBehaviour articulationPoint.Target = LookAtPoint; } - _subscriptions.Add(Entity.HullDamage.Subscribe(_ => - { - if (!_destroyed && Entity.Hull.Durability < .01f) - { - if (!this) return; - if (ActionGameManager.Instance?.CommitEntityDestroyed(entity, ZoneRenderer) != true) - return; - - _destroyed = true; - if (DestroyEffect != null) - { - var t = Instantiate(DestroyEffect).transform; - t.position = transform.position; - } - } - })); - if (entity is OrbitalEntity orbital && orbital.IsSecureArea) { _influenceInstance = Instantiate(InfluencePrefab); @@ -442,11 +207,6 @@ public class EntityInstance : MonoBehaviour } } - private void OnSensorPingEnd() - { - Destroy(_currentPing.transform.gameObject); - } - public Transform GetBarrel(HardpointData hardpoint) { if (Barrels.ContainsKey(hardpoint)) @@ -461,11 +221,6 @@ public class EntityInstance : MonoBehaviour public virtual void Update() { - if (_currentPing.transform) - { - _currentPing.transform.localScale = _sensor.PingRadius * Vector3.one; - _currentPing.meshRenderer.material.SetFloat("_Depth", _pingBrightness * _sensor.PingBrightness); - } if (_fading) { if (_fadingIn) @@ -510,20 +265,23 @@ public class EntityInstance : MonoBehaviour foreach (var x in Barrels) { - Entity.HardpointTransforms[x.Key] = (x.Value[0].position, x.Value[0].forward); + Entity.HardpointTransforms[x.Key] = ( + ToCult(x.Value[0].position), + ToCult(x.Value[0].forward)); } - LookAtPoint.position = transform.position + (Vector3) Entity.LookDirection * - (Entity.Target.Value != null ? max(Entity.TargetRange,Entity.ItemManager.GameplaySettings.ConvergenceMinimumDistance) : 10000); - LocalSpace.localPosition = transform.position = Entity.Position; + var entityPosition = (Vector3)AetheriaMath.ToUnity(Entity.CultPosition); + var entityLookDirection = (Vector3)AetheriaMath.ToUnity(Entity.CultLookDirection); + + LookAtPoint.position = transform.position + entityLookDirection * + (Entity.Target.Value != null ? Mathf.Max(Entity.TargetRange, Entity.ItemManager.GameplaySettings.ConvergenceMinimumDistance) : 10000); + LocalSpace.localPosition = transform.position = entityPosition; if (_influenceInstance) - _influenceInstance.position = new Vector3(Entity.Position.x, 0, Entity.Position.z); + _influenceInstance.position = new Vector3(entityPosition.x, 0, entityPosition.z); } public virtual void OnDestroy() { - if(_sensor!=null) - _sensor.OnPingEnd -= OnSensorPingEnd; if (_influenceInstance) Destroy(_influenceInstance.gameObject); Destroy(LocalSpace.gameObject); foreach(var x in _subscriptions) diff --git a/Assets/Scripts/Gameplay/FieldDriver.cs b/Assets/Scripts/Gameplay/FieldDriver.cs index 7bbf78ee7..eba82d862 100644 --- a/Assets/Scripts/Gameplay/FieldDriver.cs +++ b/Assets/Scripts/Gameplay/FieldDriver.cs @@ -1,15 +1,11 @@ using System; -using System.Collections; using System.Collections.Generic; using UnityEngine; -using UnityEngine.InputSystem; -using Unity.Mathematics; -using static Unity.Mathematics.math; public class FieldDriver : MonoBehaviour { public Camera Camera; - public float2 Push; + public Vector2 Push; public float FrontTwist; public float RearTwist; public float TestMagnitude; @@ -28,19 +24,6 @@ public class FieldDriver : MonoBehaviour public float MeleeAngle; public float MeleeAngleExponent; - public float TendrilBaseRadius = 3; - public float TendrilTipRadius = .1f; - public float TendrilExtensionExponent = .75f; - public float TendrilExtendBaseAnimationExponent = .5f; - public float TendrilBaseDamping = 2; - public float TendrilTipRadiusAnimationExponent = 2f; - public float TendrilFadeExponent = 2; - // public float GrabScaleAnimationExponent = .5f; - - public float GrabExtendTime; - public float GrabEnvelopTime; - public float GrabPullTime; - public RectTransform.Axis RefractionAxis = RectTransform.Axis.Horizontal; private Material _field; @@ -49,57 +32,36 @@ public class FieldDriver : MonoBehaviour private float _waveOffset; private float _meleeTime; private bool _meleeActive; - private MeshCollider _collider; - private Transform _grabObject; - private Vector3 _grabObjectStartPos; - private Vector3 _grabObjectStartTendrilBasePos; - private Vector3 _grabObjectEndPos; - private Vector3 _grabObjectVelocity; - private float _grabObjectScale; - private float _grabTime; - private GrabPhase _grabPhase; - private Vector3 _tendrilBasePos; - private Vector3 _tendrilBendTarget; - private Vector3 _tendrilTargetPos; - private float _fadePoint; - - private enum GrabPhase - { - Extend, - Envelop, - Pull - } private struct FieldHit { - public float3 Position; - public float3 Direction; + public Vector3 Position; + public Vector3 Direction; public float Magnitude; public float Time; } void Start() { - _collider = GetComponent(); _field = GetComponent().material; var clickableCollider = GetComponent(); if(clickableCollider!=null) { clickableCollider.OnClick += (_, _, ray, hit) => { - AddHit(hit.point, ray.direction, TestMagnitude); + AddHit(hit.Point, ray.direction, TestMagnitude); }; } _hitBuffer = new ComputeBuffer(MaxHits, 32); } - public void AddHit(float3 position, float3 direction, float magnitude) + public void AddHit(Vector3 position, Vector3 direction, float magnitude) { if (_hits.Count >= MaxHits) return; var hit = new FieldHit { - Position = Vector3.Scale(normalize(transform.InverseTransformPoint(position)), transform.localScale), - Direction = normalize(transform.rotation * direction), + Position = Vector3.Scale(transform.InverseTransformPoint(position).normalized, transform.localScale), + Direction = (transform.rotation * direction).normalized, Magnitude = magnitude, Time = 0 }; @@ -115,31 +77,21 @@ public class FieldDriver : MonoBehaviour _meleeTime = 0; } - public bool CanGrab => _grabObject == null; - - public void GrabObject(Transform t, Vector3 v) - { - _grabPhase = GrabPhase.Extend; - _grabObject = t; - _grabObjectStartPos = t.position; - _grabObjectVelocity = v; - _grabObjectScale = t.localScale.x; - _tendrilBasePos = _grabObjectStartTendrilBasePos = _collider.ClosestPoint(_grabObjectStartPos); - _fadePoint = .999f; - } - void Update() { - var pushMag = min(length(Push), 1); + var pushMag = Mathf.Min(Push.magnitude, 1); - _waveOffset = frac(_waveOffset + Time.deltaTime * FlowSpeed * pow(max(pushMag, max(abs(FrontTwist), abs(RearTwist))), FlowSpeedThrottleExponent)); + _waveOffset = Mathf.Repeat( + _waveOffset + Time.deltaTime * FlowSpeed * + Mathf.Pow(Mathf.Max(pushMag, Mathf.Max(Mathf.Abs(FrontTwist), Mathf.Abs(RearTwist))), FlowSpeedThrottleExponent), + 1); var refractionRotation = Matrix4x4.Rotate(Camera.transform.rotation).inverse; if (RefractionAxis == RectTransform.Axis.Vertical) refractionRotation = Matrix4x4.Rotate(Quaternion.Euler(90, 0, 0)) * refractionRotation; - _field.SetFloat("_WaveOffset", _waveOffset * PI * 2); + _field.SetFloat("_WaveOffset", _waveOffset * Mathf.PI * 2); _field.SetFloat("_Push", pushMag); _field.SetVector("_PushDirection", new Vector4(-Push.x, 0, -Push.y)); _field.SetFloat("_TwistFront", FrontTwist); @@ -168,8 +120,8 @@ public class FieldDriver : MonoBehaviour _meleeTime += Time.deltaTime / MeleeDuration; if (_meleeTime < 1) { - _field.SetVector("_MeleeDirection", Quaternion.AngleAxis(sign(_meleeTime*2-1) * MeleeAngle * pow(abs(_meleeTime * 2 - 1), MeleeAngleExponent), Vector3.up) * -Vector3.forward); - _field.SetFloat("_MeleeDisplacement", pow(1 - 2 * abs(_meleeTime - .5f), MeleeRangeExponent) * MeleeRange); + _field.SetVector("_MeleeDirection", Quaternion.AngleAxis(Mathf.Sign(_meleeTime * 2 - 1) * MeleeAngle * Mathf.Pow(Mathf.Abs(_meleeTime * 2 - 1), MeleeAngleExponent), Vector3.up) * -Vector3.forward); + _field.SetFloat("_MeleeDisplacement", Mathf.Pow(1 - 2 * Mathf.Abs(_meleeTime - .5f), MeleeRangeExponent) * MeleeRange); _field.SetFloat("_MeleeShape", MeleeShaping); _field.SetFloat("_MeleeFlattening", MeleeFlatness); } @@ -179,73 +131,6 @@ public class FieldDriver : MonoBehaviour _field.SetFloat("_MeleeDisplacement", 0); } } - - - if(_grabObject != null) - { - _grabTime += Time.deltaTime / _grabPhase switch - { - GrabPhase.Extend => GrabExtendTime, - GrabPhase.Envelop => GrabEnvelopTime, - GrabPhase.Pull => GrabPullTime, - _ => throw new ArgumentOutOfRangeException() - }; - if (_grabTime > 1) - { - _grabTime = 0; - if (_grabPhase == GrabPhase.Envelop) - { - _grabObjectEndPos = _grabObject.position; - } - if (_grabPhase == GrabPhase.Pull) - { - Destroy(_grabObject.gameObject); - _grabObject = null; - _field.SetFloat("_TendrilInfluence", 0); - } - else _grabPhase++; - } - - if (_grabObject != null) - { - _tendrilBasePos = AetheriaMath.Damp(_tendrilBasePos, _grabObject.position, TendrilBaseDamping, Time.deltaTime); - switch (_grabPhase) - { - case GrabPhase.Extend: - _tendrilBendTarget = _grabObjectStartPos; - _grabObject.position += _grabObjectVelocity * Time.deltaTime; - _tendrilTargetPos = lerp( - lerp(_grabObjectStartTendrilBasePos, _grabObjectStartPos, _grabTime), - lerp(_grabObjectStartPos, _grabObject.position, _grabTime), - pow(_grabTime, TendrilExtensionExponent)); - _field.SetFloat("_TendrilInfluence", pow(_grabTime, TendrilExtendBaseAnimationExponent)); - _field.SetFloat("_TendrilSize", lerp(TendrilBaseRadius/2,TendrilBaseRadius,pow(_grabTime, TendrilExtendBaseAnimationExponent))); - _field.SetFloat("_TendrilRadius", _grabObjectScale * pow(_grabTime, TendrilTipRadiusAnimationExponent) * TendrilTipRadius); - break; - case GrabPhase.Envelop: - _tendrilBendTarget = _grabObjectStartPos; - _tendrilTargetPos = _grabObject.position += _grabObjectVelocity * Time.deltaTime * (1-_grabTime); - _field.SetFloat("_TendrilInfluence", 1); - _field.SetFloat("_TendrilSize", TendrilBaseRadius); - _field.SetFloat("_TendrilRadius", _grabObjectScale * TendrilTipRadius); - break; - case GrabPhase.Pull: - _tendrilTargetPos = _grabObject.position = lerp(_grabObjectEndPos, transform.position, _grabTime*_grabTime); - _tendrilBendTarget = lerp(_grabObjectStartPos, _grabObjectEndPos, _grabTime*_grabTime); - if (_fadePoint > .99 && transform.InverseTransformPoint(_grabObject.position).sqrMagnitude < 1) - _fadePoint = _grabTime; - _field.SetFloat("_TendrilInfluence", pow(smoothstep(1, _fadePoint, _grabTime), TendrilFadeExponent)); - _field.SetFloat("_TendrilSize", TendrilBaseRadius); - _field.SetFloat("_TendrilRadius", _grabObjectScale * TendrilTipRadius); - break; - default: - throw new ArgumentOutOfRangeException(); - } - _field.SetVector("_TendrilBase", Vector3.Scale(transform.InverseTransformPoint(_tendrilBasePos).normalized, transform.localScale)); - _field.SetVector("_TendrilBend", Vector3.Scale(transform.InverseTransformPoint(_tendrilBendTarget), transform.localScale)); - _field.SetVector("_TendrilTarget", Vector3.Scale(transform.InverseTransformPoint(_tendrilTargetPos), transform.localScale)); - } - } } private float almostIdentity( float x ) @@ -258,20 +143,10 @@ public class FieldDriver : MonoBehaviour return t * t * (3.0f - 2.0f * t); } - private const int GIZMO_STEPS = 16; - private void OnDrawGizmosSelected() + private float SmoothStep(float edge0, float edge1, float x) { - if (_grabObject != null) - { - Vector3 previous = _tendrilBasePos; - for (int i = 1; i <= GIZMO_STEPS; i++) - { - var l = (float)i / GIZMO_STEPS; - var next = AetheriaMath.GetQuadraticSplinePosition(_tendrilBasePos, _tendrilBendTarget, _tendrilTargetPos, l); - Gizmos.DrawLine(previous, next); - previous = next; - } - } + var t = Mathf.Clamp01((x - edge0) / (edge1 - edge0)); + return t * t * (3.0f - 2.0f * t); } private void OnDestroy() diff --git a/Assets/Scripts/Gameplay/GameSettings.cs b/Assets/Scripts/Gameplay/GameSettings.cs index 112c1bf07..2bfb31458 100644 --- a/Assets/Scripts/Gameplay/GameSettings.cs +++ b/Assets/Scripts/Gameplay/GameSettings.cs @@ -15,7 +15,6 @@ public class GameSettings : ScriptableObject public string DefaultBossSoundbank = "Boss_Metal"; public string AmbienceSoundBank = "Ambience"; public string StartingHullName = "Longinus"; - public float PickupLifetime = 30; public float LootDropProbability = .25f; public float LootDropVelocity = 25; public float HeatstrokePhasingFloor = 0; @@ -86,4 +85,4 @@ public class BodySettingsCollection { public float MinimumMass; public CelestialBodySettings[] BodySettings; -} \ No newline at end of file +} diff --git a/Assets/Scripts/Gameplay/GridObject.cs b/Assets/Scripts/Gameplay/GridObject.cs index 772ed781c..180a2dd7c 100644 --- a/Assets/Scripts/Gameplay/GridObject.cs +++ b/Assets/Scripts/Gameplay/GridObject.cs @@ -1,7 +1,5 @@ using System; -using Unity.Mathematics; using UnityEngine; -using static Unity.Mathematics.math; using Random = UnityEngine.Random; public class GridObject : MonoBehaviour @@ -13,7 +11,6 @@ public class GridObject : MonoBehaviour public float Drag = .1f; public float LaunchDrag; - private float2 _selfVelocity; private float _timeOffset; public Zone Zone { get; set; } @@ -27,24 +24,6 @@ public class GridObject : MonoBehaviour void Update() { var t = transform; - t.localRotation = Quaternion.Euler(sin(Time.time - _timeOffset * RotationSpeed) * 90, 0, cos(Time.time - _timeOffset * RotationSpeed) * 90); - - if (Zone == null) return; - - var position = t.position; - var gridHeight = Zone.GetHeight(position.Flatland()) + GridOffset; - Velocity += Vector3.up * (sign(gridHeight - position.y) * GridAttraction * Time.deltaTime); - Velocity *= max(0, 1 - LaunchDrag * Time.deltaTime); - var normal = Zone.GetNormal(position.Flatland()); - var force = new float2(normal.x, normal.z); - var forceMagnitude = lengthsq(force); - if (forceMagnitude > .001f) - { - var fa = 1 / (1 - forceMagnitude) - 1; - _selfVelocity += normalize(force) * Zone.Settings.GravityStrength * fa * Gravity; - } - - _selfVelocity *= max(0, 1 - Drag * Time.deltaTime); - t.position = position + (Velocity + new Vector3(_selfVelocity.x, 0, _selfVelocity.y)) * Time.deltaTime; + t.localRotation = Quaternion.Euler(Mathf.Sin(Time.time - _timeOffset * RotationSpeed) * 90, 0, Mathf.Cos(Time.time - _timeOffset * RotationSpeed) * 90); } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Gameplay/HullCollider.cs b/Assets/Scripts/Gameplay/HullCollider.cs index cf5ee155e..4553d280c 100644 --- a/Assets/Scripts/Gameplay/HullCollider.cs +++ b/Assets/Scripts/Gameplay/HullCollider.cs @@ -1,65 +1,65 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using UniRx; -using Unity.Mathematics; -using static Unity.Mathematics.math; using UnityEngine; -using float2 = Unity.Mathematics.float2; public class HullCollider : MonoBehaviour { - public Subject Hit = new Subject(); - public Subject Splash = new Subject(); - + private Renderer[] _renderers; + public Entity Entity { get; set; } - - public void SendHit(float damage, float penetration, float spread, DamageType damageType, Entity source, Vector2 texCoord, Vector3 direction) + + public Bounds YmirBounds => CalculateBounds(transform.position); + + public float YmirPlanarRadius => Mathf.Max(YmirBounds.extents.x, YmirBounds.extents.z, 0.001f); + + public void RefreshYmirBoundsSource() { - Hit.OnNext(new HullHitEventArgs - { - Damage = damage, - Penetration = penetration, - Spread = spread, - DamageType = damageType, - Source = source, - TexCoord = texCoord, - Direction = direction - }); - } - - public void SendSplash(float damage, DamageType damageType, Entity source, Vector3 direction) - { - Splash.OnNext(new HullSplashEventArgs - { - Damage = damage, - DamageType = damageType, - Source = source, - Direction = direction - }); + _renderers = GetComponentsInChildren(); } - private void OnCollisionEnter(Collision other) + private void Awake() { - Entity.Velocity += ((float3) other.impulse).xz / Time.deltaTime; + RefreshYmirBoundsSource(); } - public class HullHitEventArgs + private void OnEnable() { - public float Damage; - public float Penetration; - public float Spread; - public DamageType DamageType; - public Entity Source; - public float2 TexCoord; - public float3 Direction; + if (_renderers == null || _renderers.Length == 0) + RefreshYmirBoundsSource(); } - - public class HullSplashEventArgs + + private Bounds CalculateBounds(Vector3 fallbackCenter) { - public float Damage; - public DamageType DamageType; - public Entity Source; - public float3 Direction; + if (_renderers == null) + RefreshYmirBoundsSource(); + + if (_renderers == null || _renderers.Length == 0) + return new Bounds(fallbackCenter, Vector3.one); + + var firstRenderer = FirstLiveRenderer(); + if (firstRenderer == null) + return new Bounds(fallbackCenter, Vector3.one); + + var bounds = firstRenderer.bounds; + for (var i = 0; i < _renderers.Length; i++) + { + var renderer = _renderers[i]; + if (renderer == null || renderer == firstRenderer) + continue; + + bounds.Encapsulate(renderer.bounds); + } + + return bounds; } -} \ No newline at end of file + + private Renderer FirstLiveRenderer() + { + for (var i = 0; i < _renderers.Length; i++) + { + if (_renderers[i] != null) + return _renderers[i]; + } + + return null; + } + +} diff --git a/Assets/Scripts/Gameplay/ItemPickup.cs b/Assets/Scripts/Gameplay/ItemPickup.cs index 81436f564..a55674dda 100644 --- a/Assets/Scripts/Gameplay/ItemPickup.cs +++ b/Assets/Scripts/Gameplay/ItemPickup.cs @@ -1,8 +1,8 @@ using System; using TMPro; -using Unity.Mathematics; using UnityEngine; -using static Unity.Mathematics.math; +using static CultMath.math; +using cfloat3 = CultMath.float3; public class ItemPickup : MonoBehaviour { @@ -15,27 +15,28 @@ public class ItemPickup : MonoBehaviour public ItemInstance Item { get; set; } public ZoneRenderer ZoneRenderer { get; set; } - public float3 ViewOrigin { get; set; } - public float3 ViewDirection { get; set; } + public cfloat3 ViewOrigin { get; set; } + public cfloat3 ViewDirection { get; set; } private float _displayTime; private void Update() { - var diff = (float3) transform.position - ViewOrigin; + var position = transform.position; + var diff = new cfloat3(position.x, position.y, position.z) - ViewOrigin; var toThis = normalize(diff); - var viewAngle = acos(Vector3.Dot(toThis, ViewDirection)) * Mathf.Rad2Deg; + var viewAngle = acos(dot(toThis, ViewDirection)) * AetheriaMath.Rad2Deg; if (length(diff) < LabelDisplayMaxDistance && viewAngle < LabelDisplayAngle) _displayTime = Time.time; var targetAlpha = Time.time - _displayTime < LabelPersistDuration ? 1 : 0; var c = ScanLabel.color; c.a = c.a + sign(targetAlpha - c.a) * (Time.deltaTime / LabelFadeDuration); ScanLabel.color = c; - ScanLabelContainer.rotation = Quaternion.LookRotation(-toThis); + ScanLabelContainer.rotation = Quaternion.LookRotation((Vector3)AetheriaMath.ToUnity(-toThis)); } private void OnDestroy() { ZoneRenderer?.DestroyLoot(this); } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Gameplay/Physics.meta b/Assets/Scripts/Gameplay/Physics.meta new file mode 100644 index 000000000..26dafc8a0 --- /dev/null +++ b/Assets/Scripts/Gameplay/Physics.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: baa9b1a7f453a7a4aafe9c44c9fba7c3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs b/Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs new file mode 100644 index 000000000..419ccb168 --- /dev/null +++ b/Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs @@ -0,0 +1,865 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using UnityEngine; + +public sealed class AetheriaYmirPhysicsBridge : MonoBehaviour +{ + private const string ProjectileBodyId = "aetheria.projectile"; + private const string TargetBodyPrefix = "aetheria.target."; + + private static AetheriaYmirPhysicsBridge _instance; + + public string ServiceUrl = "http://127.0.0.1:8877/simulate/step"; + public string OverlapSphereUrl = "http://127.0.0.1:8877/query/overlap-sphere"; + public string OverlapCircleUrl = "http://127.0.0.1:8877/query/overlap-circle"; + public string CastCircleUrl = "http://127.0.0.1:8877/query/cast-circle"; + public string CastSphereUrl = "http://127.0.0.1:8877/query/cast-sphere"; + public bool EnableProjectileCutover = true; + public float ProjectileRadius = 0.1f; + public float ProjectileMass = 1.0f; + public float TargetRadiusPadding = 0.1f; + public int TimeoutMilliseconds = 20; + public AetheriaDaemonObserver DaemonObserver; + + private readonly List _projectileBodies = new List(); + private readonly List _targetBodies = new List(); + private readonly List _zoneBodies = new List(); + private readonly List _daemonBodies = new List(); + private readonly List _clickableBodies = new List(); + private readonly Dictionary _hullBodyMap = new Dictionary(StringComparer.Ordinal); + private readonly Dictionary _clickableBodyMap = new Dictionary(StringComparer.Ordinal); + + public static AetheriaYmirPhysicsBridge Instance + { + get + { + if (_instance != null) return _instance; + + _instance = FindAnyObjectByType(); + if (_instance != null) return _instance; + + var bridge = new GameObject("Aetheria Ymir Physics Bridge"); + DontDestroyOnLoad(bridge); + _instance = bridge.AddComponent(); + return _instance; + } + } + + public bool TryStepProjectile(Projectile projectile, float deltaTime, out AetheriaYmirProjectileStep step) + { + step = default; + if (!EnableProjectileCutover || projectile == null || deltaTime <= 0) + return false; + + var request = BuildProjectileRequest(projectile, deltaTime); + if (request.world.bodies.Length == 0) + return false; + + YmirStepResult result; + try + { + result = PostJson(ServiceUrl, request); + } + catch (Exception error) + { + Debug.LogWarning($"Ymir projectile step failed: {error.Message}"); + return false; + } + + if (result == null || result.world == null) + return false; + + var projectileBody = FindBody(result.world, ProjectileBodyId); + if (projectileBody == null) + return false; + + step.Position = new Vector3(projectileBody.position.x, projectile.transform.position.y, projectileBody.position.y); + step.Velocity = new Vector3(projectileBody.velocity.x, projectile.Velocity.y, projectileBody.velocity.y); + + foreach (var contact in result.contacts ?? Array.Empty()) + { + var otherBody = string.Equals(contact.bodyA, ProjectileBodyId, StringComparison.Ordinal) + ? contact.bodyB + : string.Equals(contact.bodyB, ProjectileBodyId, StringComparison.Ordinal) + ? contact.bodyA + : ""; + if (string.IsNullOrWhiteSpace(otherBody)) + continue; + + var hull = ResolveTargetHull(projectile.TargetInstance, otherBody); + if (hull == null) + continue; + + step.Hit = new AetheriaYmirProjectileHit( + hull, + new Vector3(contact.point.x, projectile.transform.position.y, contact.point.y), + new Vector3(contact.normal.x, 0, contact.normal.y).normalized); + break; + } + + return true; + } + + public bool TryOverlapDaemonBodies( + Vector3 center, + float radius, + out IReadOnlyList hits) + { + hits = Array.Empty(); + if (!TryBuildDaemonWorld(out var world) || radius <= 0) + return false; + + var request = new YmirCircleOverlapRequest + { + world = world, + center = ToVec2(center), + radius = radius + }; + + return TryOverlapCircle(request, out hits); + } + + public bool TryCastDaemonBodies( + Vector3 origin, + Vector3 direction, + float distance, + float radius, + out IReadOnlyList hits) + { + hits = Array.Empty(); + if (!TryBuildDaemonWorld(out var world) || distance <= 0 || radius < 0) + return false; + + var planarDirection = new Vector3(direction.x, 0, direction.z); + if (planarDirection.sqrMagnitude <= float.Epsilon) + return false; + + var request = new YmirCircleCastRequest + { + world = world, + origin = ToVec2(origin), + direction = ToVec2(planarDirection.normalized), + distance = distance, + radius = radius + }; + + return TryCastCircle(request, "Ymir daemon body circle cast query", out hits); + } + + public bool TryOverlapTargetHulls( + EntityInstance target, + Vector3 center, + float radius, + out IReadOnlyList hits) + { + hits = Array.Empty(); + if (!EnableProjectileCutover || target == null || radius <= 0) + return false; + + var world = BuildTargetWorld(target); + if (world.bodies.Length == 0) + return false; + + var request = new YmirCircleOverlapRequest + { + center = ToVec2(center), + radius = radius, + world = world + }; + + if (!TryOverlapCircle(request, out var ymirHits)) + return false; + + var resolved = new List(); + foreach (var hit in ymirHits) + { + var hull = ResolveTargetHull(target, hit.bodyId); + if (hull == null) + continue; + + resolved.Add(new AetheriaYmirOverlapHit( + hull, + new Vector3(hit.point.x, center.y, hit.point.y), + new Vector3(hit.normal.x, 0, hit.normal.y).normalized, + hit.penetration, + hit.distance)); + } + + hits = resolved; + return true; + } + + public bool TryOverlapSphere( + IReadOnlyList bodies, + Vector3 center, + float radius, + out IReadOnlyList hits) + { + hits = Array.Empty(); + if (bodies == null || bodies.Count == 0) + return false; + + var bodyArray = bodies as YmirSphereQueryBody[] ?? CopySphereQueryBodies(bodies); + return TryOverlapSphere(bodyArray, center, radius, "Ymir sphere overlap query", out hits); + } + + public bool TryCastTargetHulls( + EntityInstance target, + Vector3 origin, + Vector3 direction, + float distance, + float radius, + out IReadOnlyList hits) + { + hits = Array.Empty(); + if (!EnableProjectileCutover || target == null || distance <= 0 || direction.sqrMagnitude <= 0.000001f) + return false; + + var world = BuildTargetWorld(target); + if (world.bodies.Length == 0) + return false; + + var request = new YmirCircleCastRequest + { + origin = ToVec2(origin), + direction = ToVec2(direction.normalized), + distance = distance, + radius = Mathf.Max(0, radius), + world = world + }; + + if (!TryCastCircle(request, "Ymir target cast query", out var ymirHits)) + return false; + + var resolved = new List(); + foreach (var hit in ymirHits) + { + var hull = ResolveTargetHull(target, hit.bodyId); + if (hull == null) + continue; + + resolved.Add(new AetheriaYmirCastHit( + hull, + ResolveShield(hull), + new Vector3(hit.point.x, origin.y, hit.point.y), + new Vector3(hit.normal.x, 0, hit.normal.y).normalized, + hit.distance)); + } + + hits = resolved; + return true; + } + + public bool TryCastClickables( + IReadOnlyList clickables, + Ray ray, + float distance, + out AetheriaYmirClickableHit hit) + { + hit = default; + if (!EnableProjectileCutover || clickables == null || clickables.Count == 0 || distance <= 0) + return false; + + _clickableBodyMap.Clear(); + _clickableBodies.Clear(); + for (var i = 0; i < clickables.Count; i++) + { + var clickable = clickables[i]; + if (clickable == null || !clickable.isActiveAndEnabled) + continue; + + var bounds = clickable.ClickBounds; + var bodyId = $"aetheria.clickable.{i}"; + _clickableBodyMap[bodyId] = clickable; + _clickableBodies.Add(new YmirSphereQueryBody + { + id = bodyId, + position = ToVec3(bounds.center), + radius = Mathf.Max(0.001f, bounds.extents.magnitude) + }); + } + + if (_clickableBodies.Count == 0) + return false; + + var bodyArray = _clickableBodies.ToArray(); + var request = new YmirSphereCastRequest + { + origin = ToVec3(ray.origin), + direction = ToVec3(ray.direction.normalized), + distance = distance, + radius = 0, + bodies = bodyArray + }; + + if (!TryCastSphere(request, "Ymir clickable cast query", out var ymirHits)) + return false; + + foreach (var resultHit in ymirHits) + { + if (!_clickableBodyMap.TryGetValue(resultHit.bodyId, out var clickable) || clickable == null) + continue; + + hit = new AetheriaYmirClickableHit( + clickable, + ToVector3(resultHit.point), + ToVector3(resultHit.normal).normalized, + resultHit.distance); + return true; + } + + return false; + } + + public bool TryOverlapZoneHulls( + ZoneRenderer zoneRenderer, + Entity sourceEntity, + Vector3 center, + float radius, + out IReadOnlyList hits) + { + hits = Array.Empty(); + if (!EnableProjectileCutover || zoneRenderer == null || radius <= 0) + return false; + + _hullBodyMap.Clear(); + var world = BuildZoneWorld(zoneRenderer, sourceEntity, _hullBodyMap); + if (world.bodies.Length == 0) + return false; + + var request = new YmirCircleOverlapRequest + { + center = ToVec2(center), + radius = radius, + world = world + }; + + if (!TryOverlapCircle(request, out var ymirHits)) + return false; + + var resolved = new List(); + foreach (var hit in ymirHits) + { + if (!_hullBodyMap.TryGetValue(hit.bodyId ?? "", out var hull)) + continue; + + resolved.Add(new AetheriaYmirOverlapHit( + hull, + new Vector3(hit.point.x, center.y, hit.point.y), + new Vector3(hit.normal.x, 0, hit.normal.y).normalized, + hit.penetration, + hit.distance)); + } + + hits = resolved; + return true; + } + + public bool TryCastZoneHulls( + ZoneRenderer zoneRenderer, + Entity sourceEntity, + Vector3 origin, + Vector3 direction, + float distance, + float radius, + out IReadOnlyList hits) + { + hits = Array.Empty(); + if (!EnableProjectileCutover || zoneRenderer == null || distance <= 0 || direction.sqrMagnitude <= 0.000001f) + return false; + + _hullBodyMap.Clear(); + var world = BuildZoneWorld(zoneRenderer, sourceEntity, _hullBodyMap); + if (world.bodies.Length == 0) + return false; + + var request = new YmirCircleCastRequest + { + origin = ToVec2(origin), + direction = ToVec2(direction.normalized), + distance = distance, + radius = Mathf.Max(0, radius), + world = world + }; + + if (!TryCastCircle(request, "Ymir zone cast query", out var ymirHits)) + return false; + + var resolved = new List(); + foreach (var hit in ymirHits) + { + if (!_hullBodyMap.TryGetValue(hit.bodyId ?? "", out var hull)) + continue; + + resolved.Add(new AetheriaYmirCastHit( + hull, + ResolveShield(hull), + new Vector3(hit.point.x, origin.y, hit.point.y), + new Vector3(hit.normal.x, 0, hit.normal.y).normalized, + hit.distance)); + } + + hits = resolved; + return true; + } + + private YmirStepRequest BuildProjectileRequest(Projectile projectile, float deltaTime) + { + var transform = projectile.transform; + _projectileBodies.Clear(); + _projectileBodies.Add(new YmirPhysicsBody + { + id = ProjectileBodyId, + position = ToVec2(transform.position), + velocity = ToVec2(projectile.Velocity), + direction = ToVec2(transform.forward), + angularVelocity = 0, + torque = 0, + momentOfInertia = 1, + radius = Mathf.Max(ProjectileRadius, 0.001f), + mass = Mathf.Max(ProjectileMass, 0.001f), + isStatic = false, + restitution = 0 + }); + + var target = projectile.TargetInstance; + if (target == null) + { + return new YmirStepRequest + { + deltaTime = deltaTime, + world = new YmirWorld + { + time = projectile.YmirWorldTime, + bodies = _projectileBodies.ToArray(), + fields = Array.Empty() + } + }; + } + + _projectileBodies.AddRange(BuildTargetWorld(target).bodies); + return new YmirStepRequest + { + deltaTime = deltaTime, + world = new YmirWorld + { + time = projectile.YmirWorldTime, + bodies = _projectileBodies.ToArray(), + fields = Array.Empty() + } + }; + } + + private YmirWorld BuildTargetWorld(EntityInstance target) + { + _targetBodies.Clear(); + for (var i = 0; i < target.HullColliders.Length; i++) + { + var hull = target.HullColliders[i]; + if (hull == null) + continue; + + var bounds = hull.YmirBounds; + _targetBodies.Add(new YmirPhysicsBody + { + id = TargetBodyPrefix + i, + position = ToVec2(bounds.center), + velocity = default, + direction = ToVec2(hull.transform.forward), + angularVelocity = 0, + torque = 0, + momentOfInertia = 1, + radius = hull.YmirPlanarRadius + TargetRadiusPadding, + mass = 1, + isStatic = true, + restitution = 0 + }); + } + + return new YmirWorld + { + time = 0, + bodies = _targetBodies.ToArray(), + fields = Array.Empty() + }; + } + + private YmirWorld BuildZoneWorld( + ZoneRenderer zoneRenderer, + Entity sourceEntity, + Dictionary bodyMap) + { + _zoneBodies.Clear(); + var entityIndex = 0; + foreach (var entityInstance in zoneRenderer.EntityInstances.Values) + { + if (entityInstance == null || entityInstance.Entity == sourceEntity) + continue; + + for (var hullIndex = 0; hullIndex < entityInstance.HullColliders.Length; hullIndex++) + { + var hull = entityInstance.HullColliders[hullIndex]; + if (hull == null) + continue; + + var bounds = hull.YmirBounds; + var bodyId = $"aetheria.entity.{entityIndex}.hull.{hullIndex}"; + bodyMap[bodyId] = hull; + _zoneBodies.Add(new YmirPhysicsBody + { + id = bodyId, + position = ToVec2(bounds.center), + velocity = default, + direction = ToVec2(hull.transform.forward), + angularVelocity = 0, + torque = 0, + momentOfInertia = 1, + radius = hull.YmirPlanarRadius + TargetRadiusPadding, + mass = 1, + isStatic = true, + restitution = 0 + }); + } + + entityIndex++; + } + + return new YmirWorld + { + time = 0, + bodies = _zoneBodies.ToArray(), + fields = Array.Empty() + }; + } + + private bool TryBuildDaemonWorld(out YmirWorld world) + { + world = null; + var observer = ResolveDaemonObserver(); + if (observer == null || !observer.HasRenderNativeView) + return false; + + var view = observer.LastRenderNativeView; + if (!view.IsCreated || !view.HasPhysicsRadius) + return false; + + _daemonBodies.Clear(); + for (var i = 0; i < view.Count; i++) + { + var radius = view.PhysicsBodyRadius[i]; + if (radius <= 0) + continue; + + var inverseMass = view.HasPhysicsInverseMass ? view.PhysicsBodyInverseMass[i] : 1.0f; + var mass = view.HasPhysicsMass && view.PhysicsBodyMass[i] > 0 + ? view.PhysicsBodyMass[i] + : inverseMass > 0 + ? 1.0f / inverseMass + : 1.0f; + var rotation = view.HasRotation ? view.RotationRadians[i] : 0; + + _daemonBodies.Add(new YmirPhysicsBody + { + id = $"aetheria.daemon.body.{i}", + position = new YmirVec2 { x = view.PositionX[i], y = view.PositionZ[i] }, + velocity = view.HasVelocity + ? new YmirVec2 { x = view.VelocityX[i], y = view.VelocityZ[i] } + : default, + direction = new YmirVec2 { x = Mathf.Sin(rotation), y = Mathf.Cos(rotation) }, + angularVelocity = 0, + torque = 0, + momentOfInertia = 1, + radius = radius, + mass = Mathf.Max(mass, 0.001f), + isStatic = view.HasPhysicsInverseMass && inverseMass <= 0, + restitution = 0 + }); + } + + if (_daemonBodies.Count == 0) + return false; + + world = new YmirWorld + { + time = Time.time, + bodies = _daemonBodies.ToArray(), + fields = Array.Empty() + }; + return true; + } + + private AetheriaDaemonObserver ResolveDaemonObserver() + { + if (DaemonObserver != null) + return DaemonObserver; + + DaemonObserver = FindAnyObjectByType(); + return DaemonObserver; + } + + private bool TryOverlapSphere( + YmirSphereQueryBody[] bodies, + Vector3 center, + float radius, + string failureContext, + out IReadOnlyList hits) + { + hits = Array.Empty(); + if (!EnableProjectileCutover || bodies == null || bodies.Length == 0 || radius <= 0) + return false; + + try + { + var result = PostJson(OverlapSphereUrl, new YmirSphereOverlapRequest + { + center = ToVec3(center), + radius = radius, + bodies = bodies + }); + hits = result.hits ?? Array.Empty(); + return true; + } + catch (Exception error) + { + Debug.LogWarning($"{failureContext} failed: {error.Message}"); + return false; + } + } + + private bool TryCastSphere( + YmirSphereCastRequest request, + string failureContext, + out IReadOnlyList hits) + { + hits = Array.Empty(); + if (!EnableProjectileCutover || request == null || request.bodies == null || request.bodies.Length == 0 || request.distance <= 0 || request.radius < 0) + return false; + + try + { + var result = PostJson(CastSphereUrl, request); + hits = result.hits ?? Array.Empty(); + return true; + } + catch (Exception error) + { + Debug.LogWarning($"{failureContext} failed: {error.Message}"); + return false; + } + } + + private bool TryOverlapCircle( + YmirCircleOverlapRequest request, + out IReadOnlyList hits) + { + hits = Array.Empty(); + if (!EnableProjectileCutover || request == null || request.world == null || request.world.bodies == null || request.world.bodies.Length == 0 || request.radius <= 0) + return false; + + if (string.IsNullOrWhiteSpace(OverlapCircleUrl)) + { + Debug.LogWarning("Ymir circle overlap query failed: daemon URL is not configured."); + return false; + } + + try + { + var result = PostJson(OverlapCircleUrl, request); + hits = result.hits ?? Array.Empty(); + return true; + } + catch (Exception error) + { + Debug.LogWarning($"Ymir circle overlap query failed: {error.Message}"); + return false; + } + } + + private bool TryCastCircle( + YmirCircleCastRequest request, + string failureContext, + out IReadOnlyList hits) + { + hits = Array.Empty(); + if (!EnableProjectileCutover || request == null || request.world == null || request.world.bodies == null || request.world.bodies.Length == 0 || request.distance <= 0 || request.radius < 0) + return false; + + try + { + var result = PostJson(CastCircleUrl, request); + hits = result.hits ?? Array.Empty(); + return true; + } + catch (Exception error) + { + Debug.LogWarning($"{failureContext} failed: {error.Message}"); + return false; + } + } + + private static YmirSphereQueryBody[] CopySphereQueryBodies(IReadOnlyList bodies) + { + var copy = new YmirSphereQueryBody[bodies.Count]; + for (var i = 0; i < copy.Length; i++) + copy[i] = bodies[i]; + return copy; + } + + private TResult PostJson(string url, TRequest request) + where TResult : class + { + if (string.IsNullOrWhiteSpace(url)) + throw new InvalidOperationException("Ymir daemon URL is not configured."); + + var json = JsonUtility.ToJson(request); + var bytes = Encoding.UTF8.GetBytes(json); + var http = (HttpWebRequest)WebRequest.Create(url); + http.Method = "POST"; + http.ContentType = "application/json; charset=utf-8"; + http.ContentLength = bytes.Length; + http.Timeout = TimeoutMilliseconds; + http.ReadWriteTimeout = TimeoutMilliseconds; + + using (var stream = http.GetRequestStream()) + stream.Write(bytes, 0, bytes.Length); + + using var response = (HttpWebResponse)http.GetResponse(); + using var reader = new StreamReader(response.GetResponseStream()); + var payload = reader.ReadToEnd(); + if (string.IsNullOrWhiteSpace(payload)) + throw new InvalidDataException("Ymir daemon returned an empty response."); + + var result = JsonUtility.FromJson(payload); + if (result == null) + throw new InvalidDataException("Ymir daemon returned a response that could not be parsed."); + + return result; + } + + private static YmirPhysicsBody FindBody(YmirWorld world, string bodyId) + { + foreach (var body in world.bodies ?? Array.Empty()) + { + if (string.Equals(body.id, bodyId, StringComparison.Ordinal)) + return body; + } + + return null; + } + + private static HullCollider ResolveTargetHull(EntityInstance target, string bodyId) + { + if (!bodyId.StartsWith(TargetBodyPrefix, StringComparison.Ordinal)) + return null; + + return int.TryParse(bodyId.Substring(TargetBodyPrefix.Length), out var index) && + index >= 0 && + index < target.HullColliders.Length + ? target.HullColliders[index] + : null; + } + + private static ShieldManager ResolveShield(HullCollider hull) + { + var instance = hull != null ? hull.GetComponentInParent() : null; + return instance != null ? instance.Shield : null; + } + + private static YmirVec2 ToVec2(Vector3 value) => new YmirVec2 { x = value.x, y = value.z }; + + private static YmirVec2 ToVec2(CultMath.float2 value) => new YmirVec2 { x = value.x, y = value.y }; + + private static YmirVec3 ToVec3(Vector3 value) => new YmirVec3 { x = value.x, y = value.y, z = value.z }; + + private static Vector3 ToVector3(YmirVec3 value) => new Vector3(value.x, value.y, value.z); + + private static Bounds ObjectBounds(GameObject gameObject, Vector3 fallbackCenter) + { + var renderers = gameObject.GetComponentsInChildren(); + if (renderers.Length == 0) + return new Bounds(fallbackCenter, Vector3.one); + + var bounds = renderers[0].bounds; + for (var i = 1; i < renderers.Length; i++) + bounds.Encapsulate(renderers[i].bounds); + + return bounds; + } +} + +public struct AetheriaYmirProjectileStep +{ + public Vector3 Position; + public Vector3 Velocity; + public AetheriaYmirProjectileHit Hit; + public bool HasHit => Hit.Hull != null; +} + +public readonly struct AetheriaYmirProjectileHit +{ + public AetheriaYmirProjectileHit(HullCollider hull, Vector3 point, Vector3 normal) + { + Hull = hull; + Point = point; + Normal = normal; + } + + public HullCollider Hull { get; } + public Vector3 Point { get; } + public Vector3 Normal { get; } +} + +public readonly struct AetheriaYmirOverlapHit +{ + public AetheriaYmirOverlapHit(HullCollider hull, Vector3 point, Vector3 normal, float penetration, float distance) + { + Hull = hull; + Point = point; + Normal = normal; + Penetration = penetration; + Distance = distance; + } + + public HullCollider Hull { get; } + public Vector3 Point { get; } + public Vector3 Normal { get; } + public float Penetration { get; } + public float Distance { get; } +} + +public readonly struct AetheriaYmirCastHit +{ + public AetheriaYmirCastHit(HullCollider hull, ShieldManager shield, Vector3 point, Vector3 normal, float distance) + { + Hull = hull; + Shield = shield; + Point = point; + Normal = normal; + Distance = distance; + } + + public HullCollider Hull { get; } + public ShieldManager Shield { get; } + public Vector3 Point { get; } + public Vector3 Normal { get; } + public float Distance { get; } +} + +public readonly struct AetheriaYmirClickableHit +{ + public AetheriaYmirClickableHit(ClickableCollider clickable, Vector3 point, Vector3 normal, float distance) + { + Clickable = clickable; + Point = point; + Normal = normal; + Distance = distance; + } + + public ClickableCollider Clickable { get; } + public Vector3 Point { get; } + public Vector3 Normal { get; } + public float Distance { get; } +} diff --git a/Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs.meta b/Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs.meta new file mode 100644 index 000000000..5713e06fb --- /dev/null +++ b/Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8d2521ff2dd1835498a2c00656d4bff6 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/ShieldManager.cs b/Assets/Scripts/Gameplay/ShieldManager.cs index 447b4b5b5..5e1f066a1 100644 --- a/Assets/Scripts/Gameplay/ShieldManager.cs +++ b/Assets/Scripts/Gameplay/ShieldManager.cs @@ -1,79 +1,42 @@ -using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; -using Unity.Mathematics; -using static Unity.Mathematics.math; using UnityEngine; public class ShieldManager : MonoBehaviour { public Prototype ShieldPrototype; public float CollisionHitDuration = 3; + public float LootInteractionCooldown = 0.25f; + public float ShieldCollisionCooldown = 0.1f; public Entity Entity { get; set; } - private void OnCollisionEnter(Collision other) + private bool IsShieldActive() { - var otherShield = other.collider.GetComponent(); - if (!otherShield) - { - var gridObject = other.collider.GetComponent(); - if (gridObject) - { - var itemPickup = other.collider.GetComponent(); - var mine = other.collider.GetComponent(); - if (itemPickup) - { - if (ActionGameManager.Instance != null && - ActionGameManager.Instance.CommitLootPickup(Entity, itemPickup.Item)) - { - // TODO: Pickup notification! - Destroy(itemPickup.gameObject); - } - else - { - // TODO: Pickup failed notification! - var cp = other.GetContact(0); - gridObject.Velocity += cp.normal * 25; - Debug.Log("Attempted item pickup, but no space in cargo bay!"); - } - } - else if (mine) - { - mine.Explode(); - } - else - { - Debug.Log("Shield collision occurred with grid object, but other collider isn't a mine or an item pickup!"); - } - } - else - Debug.Log("Shield collision occurred, but other collider isn't a shield or a grid object!"); - return; - } - var contact = other.GetContact(0); - var normal = normalize(float2(contact.normal.x, contact.normal.z)); - var tangent = normal.Rotate(ItemRotation.CounterClockwise); - var v1n = dot(normal, Entity.Velocity); - var v1t = dot(tangent, Entity.Velocity); - var v2n = dot(normal, otherShield.Entity.Velocity); - //var v2t = dot(tangent, otherShield.Entity.Velocity); - - var v1np = PostCollisionVelocity(v1n, Entity.Mass, v2n, otherShield.Entity.Mass); - Entity.Velocity = tangent * v1t + normal * v1np; - if(Entity.Shield != null && Entity.Shield.Item.Active.Value) ShowHit(contact.point, CollisionHitDuration); + return Entity?.Shield != null && Entity.Shield.Item.Active.Value; } - private float PostCollisionVelocity(float v1, float m1, float v2, float m2) + private Bounds ShieldBounds() { - return (v1 * (m1 - m2) + m2 * v2) / (m1 + m2); + var renderers = GetComponentsInChildren(); + if (renderers.Length == 0) + return new Bounds(transform.position, Vector3.one); + + var bounds = renderers[0].bounds; + for (var i = 1; i < renderers.Length; i++) + bounds.Encapsulate(renderers[i].bounds); + + return bounds; + } + + private static float ShieldPlanarRadius(Bounds bounds) + { + return Mathf.Max(bounds.extents.x, bounds.extents.z, 0.001f); } public void ShowHit(Vector3 point, float duration) { var shield = ShieldPrototype.Instantiate(); - shield.Direction = normalize(shield.transform.InverseTransformPoint(point)); + shield.Direction = shield.transform.InverseTransformPoint(point).normalized; shield.Duration = duration; } } diff --git a/Assets/Scripts/Gameplay/ShieldTest.cs b/Assets/Scripts/Gameplay/ShieldTest.cs index aee32990e..ecc70b764 100644 --- a/Assets/Scripts/Gameplay/ShieldTest.cs +++ b/Assets/Scripts/Gameplay/ShieldTest.cs @@ -1,8 +1,4 @@ -using System.Collections; -using System.Collections.Generic; using UnityEngine; -using Unity.Mathematics; -using static Unity.Mathematics.math; public class ShieldTest : MonoBehaviour { @@ -12,12 +8,13 @@ public class ShieldTest : MonoBehaviour // Update is called once per frame void Update() { - _velocity += -sign(transform.position.x) * Acceleration * Time.deltaTime; + var previousX = transform.position.x; + _velocity += -Mathf.Sign(transform.position.x) * Acceleration * Time.deltaTime; transform.position += Vector3.right * (_velocity * Time.deltaTime); - } - private void OnCollisionEnter(Collision other) - { - _velocity = -_velocity; + if (Mathf.Sign(transform.position.x) != Mathf.Sign(previousX)) + { + _velocity = -_velocity; + } } } diff --git a/Assets/Scripts/Gameplay/ShipInstance.cs b/Assets/Scripts/Gameplay/ShipInstance.cs index 7e960ccf9..6a9766496 100644 --- a/Assets/Scripts/Gameplay/ShipInstance.cs +++ b/Assets/Scripts/Gameplay/ShipInstance.cs @@ -1,10 +1,7 @@ using System; -using System.Collections; using System.Linq; using GameCult.Aetheria.State.Unity; using UnityEngine; -using Unity.Mathematics; -using static Unity.Mathematics.math; public class ShipInstance : EntityInstance { @@ -108,11 +105,11 @@ public class ShipInstance : EntityInstance base.Update(); TractorBeam.Power = Entity.TractorPower; - TractorBeam.Direction = Entity.LookDirection; + TractorBeam.Direction = (Vector3)AetheriaMath.ToUnity(Entity.CultLookDirection); if (_aetherDrive != null) { - var thrust = length(_aetherDrive.Drive.ThrustDirection); + var thrust = CultMath.math.length(_aetherDrive.Drive.ThrustDirection); var forceOverLifetime = _aetherDrive.Particles.forceOverLifetime; forceOverLifetime.xMultiplier = _aetherDrive.Drive.ThrustDirection.x * _aetherDrive.BaseForce; forceOverLifetime.zMultiplier = _aetherDrive.Drive.ThrustDirection.y * _aetherDrive.BaseForce; @@ -128,7 +125,8 @@ public class ShipInstance : EntityInstance emissionModule.rateOverTimeMultiplier = thrusterInstance.BaseEmission * thrusterInstance.Thruster.Axis * (item.Durability / GetMaxDurability(item)); } - transform.rotation = Ship.Rotation; + var rotation = Ship.Rotation; + transform.rotation = new Quaternion(rotation.x, rotation.y, rotation.z, rotation.w); } private float GetMaxDurability(ItemInstance item) diff --git a/Assets/Scripts/Gameplay/TimedDestroy.cs b/Assets/Scripts/Gameplay/TimedDestroy.cs deleted file mode 100644 index fbbd86f8c..000000000 --- a/Assets/Scripts/Gameplay/TimedDestroy.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using UnityEngine; - -public class TimedDestroy : MonoBehaviour -{ - public float Duration; - - private float _startTime; - - private void Start() - { - _startTime = Time.time; - } - - public void Update() - { - if(Time.time - _startTime > Duration) Destroy(gameObject); - } -} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/TimedDestroy.cs.meta b/Assets/Scripts/Gameplay/TimedDestroy.cs.meta deleted file mode 100644 index 2f6bac661..000000000 --- a/Assets/Scripts/Gameplay/TimedDestroy.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 5d5539a23b814254b038a16f32f1d8a9 -timeCreated: 1618453298 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/TractorBeam.cs b/Assets/Scripts/Gameplay/TractorBeam.cs index 579cc813f..f7710ed20 100644 --- a/Assets/Scripts/Gameplay/TractorBeam.cs +++ b/Assets/Scripts/Gameplay/TractorBeam.cs @@ -15,19 +15,6 @@ public class TractorBeam : MonoBehaviour { transform.rotation = Quaternion.LookRotation(Direction); var emission = ParticleSystem.emission; - if (Power > .01f) - { - var objectFound = false; - foreach (var o in Physics.SphereCastAll(transform.position, Radius, Direction, Distance, 1 << 21)) - { - var gridObject = o.collider.GetComponent(); - if (gridObject == null) continue; - objectFound = true; - gridObject.Velocity += Vector3.Normalize(transform.position - gridObject.transform.position) * (Traction * Time.deltaTime); - } - - emission.rateOverTimeMultiplier = objectFound ? Power : 0; - } - else emission.rateOverTimeMultiplier = 0; + emission.rateOverTimeMultiplier = Power > .01f ? Power : 0; } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Gameplay/Weapons/ConstantLaser.cs b/Assets/Scripts/Gameplay/Weapons/ConstantLaser.cs index eb7f14d52..679c720d0 100644 --- a/Assets/Scripts/Gameplay/Weapons/ConstantLaser.cs +++ b/Assets/Scripts/Gameplay/Weapons/ConstantLaser.cs @@ -1,7 +1,4 @@ -using System.Collections; -using System.Collections.Generic; using UnityEngine; -using static Unity.Mathematics.math; public class ConstantLaser : MonoBehaviour { @@ -70,34 +67,34 @@ public class ConstantLaser : MonoBehaviour } LineRenderer.SetPosition(0, transform.position); - bool hitFound = false; - foreach (var hit in Physics.RaycastAll(new Ray(transform.position, transform.forward), Range, 1 | (1 << 17))) + var hitFound = false; + if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls( + ActionGameManager.Instance?.ZoneRenderer, + SourceEntity, + transform.position, + transform.forward, + Range, + 0, + out var hits)) { - var shield = hit.collider.GetComponent(); - if (shield) + foreach (var hit in hits) { - if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue; - if (shield.Entity != SourceEntity) + var hull = hit.Hull; + var entity = hull.Entity; + if (entity.Shield != null && entity.Shield.Item.Active.Value) { - shield.Entity.Shield.TakeHit(DamageType, Damage); - shield.ShowHit(hit.point, sqrt(Damage)); - LineRenderer.SetPosition(1, hit.point); - hitFound = true; - break; - } - } - var hull = hit.collider.GetComponent(); - if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage))) - { - if (hull.Entity != SourceEntity) - { - hull.SendHit(Damage * Time.deltaTime, Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, transform.forward); - LineRenderer.SetPosition(1, hit.point); + hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage)); + LineRenderer.SetPosition(1, hit.Point); hitFound = true; break; } + + LineRenderer.SetPosition(1, hit.Point); + hitFound = true; + break; } } + if(!hitFound) LineRenderer.SetPosition(1, transform.position + transform.forward * Range); } diff --git a/Assets/Scripts/Gameplay/Weapons/ConstantLightning.cs b/Assets/Scripts/Gameplay/Weapons/ConstantLightning.cs index d50717a22..a39f0b7fd 100644 --- a/Assets/Scripts/Gameplay/Weapons/ConstantLightning.cs +++ b/Assets/Scripts/Gameplay/Weapons/ConstantLightning.cs @@ -1,7 +1,4 @@ -using System.Collections; -using System.Collections.Generic; using UnityEngine; -using static Unity.Mathematics.math; public class ConstantLightning : MonoBehaviour { @@ -51,28 +48,34 @@ public class ConstantLightning : MonoBehaviour } Lightning.FixedEndpoint = false; - var hits = Physics.SphereCastAll(Barrel.position, HitRadius, Barrel.forward, Range, 1 | (1 << 17)); - foreach (var hit in hits) + if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls( + Source?.ZoneRenderer, + Source?.Entity, + Barrel.position, + Barrel.forward, + Range, + HitRadius, + out var hits)) { - var shield = hit.collider.GetComponent(); - if (shield && (shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) + foreach (var hit in hits) { - if (shield.Entity == Source.Entity) continue; - shield.Entity.Shield.TakeHit(DamageType, Damage * Time.deltaTime); - shield.ShowHit(hit.point, sqrt(Damage)); - } - var hull = hit.collider.GetComponent(); - if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage))) - { - if (hull.Entity == Source.Entity) continue; - hull.SendHit(Damage * Time.deltaTime, Penetration, Spread, DamageType, Source.Entity, hit.textureCoord, Barrel.forward); - } + var hull = hit.Hull; + var entity = hull.Entity; + if (entity.Shield != null && entity.Shield.Item.Active.Value) + { + hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage)); + } + else + { + } - Lightning.FixedEndpoint = true; - Lightning.EndPosition = hit.point; + Lightning.FixedEndpoint = true; + Lightning.EndPosition = hit.Point; - break; + break; + } } + if(!Lightning.FixedEndpoint) Lightning.EndPosition = Barrel.position + Barrel.forward * Range; diff --git a/Assets/Scripts/Gameplay/Weapons/ConstantParticleWeapon.cs b/Assets/Scripts/Gameplay/Weapons/ConstantParticleWeapon.cs index f09d10ed4..5b24b29d2 100644 --- a/Assets/Scripts/Gameplay/Weapons/ConstantParticleWeapon.cs +++ b/Assets/Scripts/Gameplay/Weapons/ConstantParticleWeapon.cs @@ -1,32 +1,26 @@ -using System; -using System.Collections; -using System.Collections.Generic; using System.Linq; using UnityEngine; public class ConstantParticleWeapon : MonoBehaviour { public ParticleSystem[] Particles; + public float CastRadius = 0.25f; public float Damage { get; set; } + public float Range { get; set; } public DamageType DamageType { get; set; } public EntityInstance Source { get; set; } public EntityInstance Target { get; set; } private bool _stopping; - private float _emission; - private List _collisionParticles = new List(); - private HullCollider _hull; - - private void Start() - { - _emission = Particles[0].emission.rateOverTime.constant; - } private void OnEnable() { foreach(var p in Particles) - p.enableEmission = true; + { + var emission = p.emission; + emission.enabled = true; + } _stopping = false; } @@ -38,18 +32,6 @@ public class ConstantParticleWeapon : MonoBehaviour main.simulationSpace = ParticleSystemSimulationSpace.Custom; main.customSimulationSpace = Source.LocalSpace; } - - var trigger = Particles[0].trigger; - while(trigger.colliderCount > 0) - trigger.RemoveCollider(0); - - if (Target == null) return; - - foreach (var collider in Target.HullColliders) - { - _hull = collider; - trigger.AddCollider(collider.GetComponent()); - } } private void Update() @@ -60,27 +42,32 @@ public class ConstantParticleWeapon : MonoBehaviour GetComponent().ReturnToPool(); return; } - } - private void OnParticleTrigger() - { - if (Target == null || !_hull) return; - _collisionParticles.Clear(); - Particles[0].GetTriggerParticles(ParticleSystemTriggerEventType.Enter, _collisionParticles); - if(_collisionParticles.Count > 0) + if (_stopping || Target == null || Range <= 0) + return; + + if (AetheriaYmirPhysicsBridge.Instance.TryCastTargetHulls( + Target, + transform.position, + transform.forward, + Range, + CastRadius, + out var hits)) { - _hull.SendSplash( - Damage * Time.deltaTime, - DamageType, - Source.Entity, - (transform.position - Target.transform.position).normalized); + foreach (var hit in hits) + { + break; + } } } public void Stop() { foreach(var p in Particles) - p.enableEmission = false; + { + var emission = p.emission; + emission.enabled = false; + } _stopping = true; } } diff --git a/Assets/Scripts/Gameplay/Weapons/ConstantParticleWeaponManager.cs b/Assets/Scripts/Gameplay/Weapons/ConstantParticleWeaponManager.cs index 64b8c95b2..c953f7d9a 100644 --- a/Assets/Scripts/Gameplay/Weapons/ConstantParticleWeaponManager.cs +++ b/Assets/Scripts/Gameplay/Weapons/ConstantParticleWeaponManager.cs @@ -21,6 +21,7 @@ public class ConstantParticleWeaponManager : ConstantWeaponEffectManager t.position = barrel.position; p.Damage = weapon.Damage; p.DamageType = weapon.DamageType; + p.Range = weapon.Range; _weapons.Add(item, p); p.Initialize(); } diff --git a/Assets/Scripts/Gameplay/Weapons/GuidedProjectile.cs b/Assets/Scripts/Gameplay/Weapons/GuidedProjectile.cs index e5a90a61a..143d9e6b5 100644 --- a/Assets/Scripts/Gameplay/Weapons/GuidedProjectile.cs +++ b/Assets/Scripts/Gameplay/Weapons/GuidedProjectile.cs @@ -1,13 +1,8 @@ using System; using System.Collections; -using System.Collections.Generic; using UnityEngine; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using static Unity.Mathematics.noise; using Random = UnityEngine.Random; using static Noise1D; -using float3 = Unity.Mathematics.float3; public class GuidedProjectile : MonoBehaviour { @@ -36,7 +31,7 @@ public class GuidedProjectile : MonoBehaviour private Vector3 _previousTargetPosition; public Transform Target { get; set; } - public float3 StartPosition { get; set; } + public Vector3 StartPosition { get; set; } public float Range { get; set; } public Vector3 Velocity { get; set; } public float Damage { get; set; } @@ -52,9 +47,7 @@ public class GuidedProjectile : MonoBehaviour _active = _alive = true; _phase = Random.value * 100; _prevDist = Single.MaxValue; - Particles.startColor = Color.white; - //var main = Particles.main; - //main.startColor = new ParticleSystem.MinMaxGradient { mode = ParticleSystemGradientMode.Color, color = Color.white }; + SetParticleColor(Color.white); Particles.Clear(true); Particles.Play(true); } @@ -72,18 +65,18 @@ public class GuidedProjectile : MonoBehaviour StartCoroutine(FadeOut()); return; } - var position = (float3) t.position; + var position = t.position; var targetPosition = TargetPosition?.Invoke() ?? Target.position; - _targetVelocity = lerp(_targetVelocity, targetPosition - _previousTargetPosition, saturate(Time.deltaTime * 5)); + _targetVelocity = Vector3.Lerp(_targetVelocity, targetPosition - _previousTargetPosition, Mathf.Clamp01(Time.deltaTime * 5)); _previousTargetPosition = targetPosition; - targetPosition = AetheriaMath.FirstOrderIntercept(position,float3.zero, TopSpeed, targetPosition, _targetVelocity); + targetPosition = AetheriaMath.FirstOrderIntercept(position, Vector3.zero, TopSpeed, targetPosition, _targetVelocity); var diff = targetPosition - transform.position; var targetDist = diff.magnitude; - var sourceDist = length(StartPosition.xz - position.xz); + var sourceDist = Vector2.Distance(new Vector2(StartPosition.x, StartPosition.z), new Vector2(position.x, position.z)); - if (sourceDist > Range || dot(diff,Velocity) < 0) + if (sourceDist > Range || Vector3.Dot(diff, Velocity) < 0) { StartCoroutine(FadeOut()); if (HitEffect != null) @@ -98,8 +91,8 @@ public class GuidedProjectile : MonoBehaviour var targetDistFlat = diff.Flatland().magnitude; var curveLerp = 1 - targetDistFlat / (sourceDist + targetDistFlat); var dir = diff.normalized; - var right = cross(dir, float3(0, 1, 0)); - var up = cross(dir, right); + var right = Vector3.Cross(dir, Vector3.up); + var up = Vector3.Cross(dir, right); if (Children > 0 && SplitTime < curveLerp) { @@ -108,9 +101,9 @@ public class GuidedProjectile : MonoBehaviour var child = ChildProjectile.Instantiate(); child.transform.position = t.position; child.StartPosition = StartPosition; - var randomDirection = normalize(Random.insideUnitCircle); + var randomDirection = Random.insideUnitCircle.normalized; var perpendicularRandom = randomDirection.x * right + randomDirection.y * up; - child.Velocity = normalize(lerp(perpendicularRandom, dir, SplitSeparationForwardness)) * length(Velocity) * SplitSeparationVelocity; + child.Velocity = Vector3.Lerp(perpendicularRandom, dir, SplitSeparationForwardness).normalized * Velocity.magnitude * SplitSeparationVelocity; child.Range = Range; child.Damage = Damage / Children; child.Penetration = Penetration; @@ -135,56 +128,53 @@ public class GuidedProjectile : MonoBehaviour } } - var dodge = normalize(lerp( - normalize(right * noise(Time.time * Frequency + _phase) + up * noise(Time.time * Frequency + (100 + _phase))), - Vector3.up, LiftCurve.Evaluate(curveLerp))); + var dodge = Vector3.Lerp( + (right * noise(Time.time * Frequency + _phase) + up * noise(Time.time * Frequency + (100 + _phase))).normalized, + Vector3.up, + LiftCurve.Evaluate(curveLerp)).normalized; var desired = Vector3.Slerp(dodge, dir, GuidanceCurve.Evaluate(curveLerp)).normalized * TopSpeed; var thrustCurve = ThrustCurve.Evaluate(curveLerp); var thrust = Thrust * thrustCurve; var c = Color.white * thrustCurve; c.a = 1; - Particles.startColor = c; + SetParticleColor(c); Velocity += (desired-Velocity).normalized * (thrust * Time.deltaTime); } if(_alive) { - var ray = new Ray(t.position, Velocity); - foreach (var hit in Physics.RaycastAll(ray, Velocity.magnitude * Time.deltaTime, 1 | (1 << 17))) + if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls( + ActionGameManager.Instance?.ZoneRenderer, + SourceEntity, + t.position, + Velocity, + Velocity.magnitude * Time.deltaTime, + 0, + out var hits) && + hits.Count > 0) { - var shield = hit.collider.GetComponent(); - if (shield) + var hit = hits[0]; + var hull = hit.Hull; + var entity = hull.Entity; + if (entity.Shield != null && entity.Shield.Item.Active.Value) { - if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue; - if (shield.Entity != SourceEntity) - { - shield.Entity.Shield.TakeHit(DamageType, Damage); - shield.ShowHit(hit.point, sqrt(Damage)); - } + hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage)); } - var hull = hit.collider.GetComponent(); - if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage))) + else { - if (hull.Entity != SourceEntity) - { - hull.SendHit(Damage, Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, transform.forward); - transform.position = hit.point; - StartCoroutine(Kill()); - } - } - else// if (hit.transform.gameObject.layer == 1) - { - StartCoroutine(Kill()); - return; } + + transform.position = hit.Point; + StartCoroutine(Kill()); if (HitEffect != null) { var ht = HitEffect.Instantiate(); - ht.SetParent(hit.collider.transform); - ht.position = hit.point; - return; + ht.SetParent(hull.transform); + ht.position = hit.Point; } + + return; } t.position += Velocity * Time.deltaTime; @@ -200,15 +190,19 @@ public class GuidedProjectile : MonoBehaviour var lerp = 1 - (Time.time - startTime) / FadeOutTime; var c = Color.white * lerp; c.a = 1; - Particles.startColor = c; - //var main = Particles.main; - //main.startColor = new ParticleSystem.MinMaxGradient { mode = ParticleSystemGradientMode.Color, color = Color.white * lerp }; + SetParticleColor(c); yield return null; } StartCoroutine(Kill()); } + private void SetParticleColor(Color color) + { + var main = Particles.main; + main.startColor = color; + } + IEnumerator Kill() { _active = false; @@ -223,4 +217,4 @@ public class GuidedProjectile : MonoBehaviour OnKill?.Invoke(); GetComponent().ReturnToPool(); } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Gameplay/Weapons/GuidedProjectileManager.cs b/Assets/Scripts/Gameplay/Weapons/GuidedProjectileManager.cs index fb3723254..332d69ae1 100644 --- a/Assets/Scripts/Gameplay/Weapons/GuidedProjectileManager.cs +++ b/Assets/Scripts/Gameplay/Weapons/GuidedProjectileManager.cs @@ -2,9 +2,8 @@ using System.Collections; using System.Collections.Generic; using UniRx; -using Unity.Mathematics; using UnityEngine; -using static Unity.Mathematics.math; +using cfloat3 = CultMath.float3; public class GuidedProjectileManager : InstantWeaponEffectManager { @@ -64,7 +63,13 @@ public class GuidedProjectileManager : InstantWeaponEffectManager p.Velocity = barrel.forward * weapon.Velocity; p.Thrust = weapon.EvaluateGuidedProjectileThrust(); p.TopSpeed = weapon.EvaluateGuidedProjectileVelocity(); - p.TargetPosition = () => source.Entity.Position + length( (float3)source.LookAtPoint.position - source.Entity.Position) * source.Entity.LookDirection; + p.TargetPosition = () => + { + var lookPosition = source.LookAtPoint.position; + var lookPoint = new cfloat3(lookPosition.x, lookPosition.y, lookPosition.z); + var lookDistance = CultMath.math.length(lookPoint - source.Entity.CultPosition); + return (Vector3)AetheriaMath.ToUnity(source.Entity.CultPosition + lookDistance * source.Entity.CultLookDirection); + }; } } } diff --git a/Assets/Scripts/Gameplay/Weapons/HitscanEffect.cs b/Assets/Scripts/Gameplay/Weapons/HitscanEffect.cs index df94961bb..f478bbf5d 100644 --- a/Assets/Scripts/Gameplay/Weapons/HitscanEffect.cs +++ b/Assets/Scripts/Gameplay/Weapons/HitscanEffect.cs @@ -1,7 +1,4 @@ -using System.Collections; -using System.Collections.Generic; using UnityEngine; -using static Unity.Mathematics.math; public class HitscanEffect : MonoBehaviour { @@ -27,45 +24,47 @@ public class HitscanEffect : MonoBehaviour { _startTime = Time.time; var hitFound = false; - var ray = new Ray(transform.position, transform.forward); - foreach (var hit in Physics.RaycastAll(ray, Range, 1 | (1 << 17))) + if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls( + ActionGameManager.Instance?.ZoneRenderer, + SourceEntity, + transform.position, + transform.forward, + Range, + 0, + out var hits)) { - var shield = hit.collider.GetComponent(); - if (shield) + foreach (var hit in hits) { - if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue; - if (shield.Entity != SourceEntity) + var hull = hit.Hull; + var entity = hull.Entity; + if (entity.Shield != null && entity.Shield.Item.Active.Value) { - shield.Entity.Shield.TakeHit(DamageType, Damage); - shield.ShowHit(hit.point, sqrt(Damage)); + hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage)); hitFound = true; } - } - var hull = hit.collider.GetComponent(); - if (hull) - { - if (hull.Entity != SourceEntity) + else { - hull.SendHit(Damage, Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, transform.forward); hitFound = true; } - } - if (hitFound && HitEffect != null) - { - var ht = HitEffect.Instantiate(); - ht.SetParent(hit.collider.transform); - ht.position = hit.point; + if (hitFound && HitEffect != null) + { + var ht = HitEffect.Instantiate(); + ht.SetParent(hull.transform); + ht.position = hit.Point; + } + + var length = (hit.Point - transform.position).magnitude; + Line.SetPosition(1, Vector3.forward * length); + var emission = LineEffect.emission; + emission.rateOverTimeMultiplier = length; + var shape = LineEffect.shape; + shape.position = Vector3.forward * (length / 2); + shape.scale = Vector3.one * (length / 2); + break; } - - var length = (hit.point - transform.position).magnitude; - Line.SetPosition(1, Vector3.forward * length); - var emission = LineEffect.emission; - emission.rateOverTimeMultiplier = length; - var shape = LineEffect.shape; - shape.position = Vector3.forward * (length / 2); - shape.scale = Vector3.one * (length / 2); } + if(!hitFound) { Line.SetPosition(1, Vector3.forward * Range); diff --git a/Assets/Scripts/Gameplay/Weapons/Laser.cs b/Assets/Scripts/Gameplay/Weapons/Laser.cs index 2074e3a75..4a7512d7b 100644 --- a/Assets/Scripts/Gameplay/Weapons/Laser.cs +++ b/Assets/Scripts/Gameplay/Weapons/Laser.cs @@ -1,8 +1,6 @@ using System; -using System.Collections; using System.Collections.Generic; using UnityEngine; -using static Unity.Mathematics.math; public class Laser : MonoBehaviour { @@ -36,34 +34,34 @@ public class Laser : MonoBehaviour } LineRenderer.SetPosition(0, transform.position); - bool hitFound = false; - foreach (var hit in Physics.RaycastAll(new Ray(transform.position, transform.forward), Range, 1 | (1 << 17))) + var hitFound = false; + if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls( + ActionGameManager.Instance?.ZoneRenderer, + SourceEntity, + transform.position, + transform.forward, + Range, + 0, + out var hits)) { - var shield = hit.collider.GetComponent(); - if (shield) + foreach (var hit in hits) { - if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue; - if (shield.Entity != SourceEntity) + var hull = hit.Hull; + var entity = hull.Entity; + if (entity.Shield != null && entity.Shield.Item.Active.Value) { - shield.Entity.Shield.TakeHit(DamageType, Damage); - shield.ShowHit(hit.point, sqrt(Damage)); - LineRenderer.SetPosition(1, hit.point); - hitFound = true; - break; - } - } - var hull = hit.collider.GetComponent(); - if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage))) - { - if (hull.Entity != SourceEntity) - { - hull.SendHit(Damage * (Time.deltaTime / Duration), Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, transform.forward); - LineRenderer.SetPosition(1, hit.point); + hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage)); + LineRenderer.SetPosition(1, hit.Point); hitFound = true; break; } + + LineRenderer.SetPosition(1, hit.Point); + hitFound = true; + break; } } + if(!hitFound) LineRenderer.SetPosition(1, transform.position + transform.forward * Range); diff --git a/Assets/Scripts/Gameplay/Weapons/Lightning.cs b/Assets/Scripts/Gameplay/Weapons/Lightning.cs index 5432761f3..9b4662563 100644 --- a/Assets/Scripts/Gameplay/Weapons/Lightning.cs +++ b/Assets/Scripts/Gameplay/Weapons/Lightning.cs @@ -1,12 +1,5 @@ using System; -using System.Collections; -using System.Collections.Generic; using UnityEngine; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using static Unity.Mathematics.noise; -using Random = UnityEngine.Random; -using static Unity.Mathematics.math; public class Lightning : MonoBehaviour { @@ -32,37 +25,44 @@ public class Lightning : MonoBehaviour _colliderHit = false; LightningCompute.OnLeaderComplete = null; LightningCompute.FixedEndpoint = false; - var hits = Physics.SphereCastAll(Barrel.position, HitRadius, Barrel.forward, Range, 1 | (1 << 17)); - foreach (var hit in hits) + _endpoint = Barrel.position + Barrel.forward * Range; + if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls( + Source?.ZoneRenderer, + Source?.Entity, + Barrel.position, + Barrel.forward, + Range, + HitRadius, + out var hits)) { - var shield = hit.collider.GetComponent(); - if (shield && (shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) + foreach (var hit in hits) { - if (shield.Entity == Source.Entity) continue; - LightningCompute.OnLeaderComplete = () => + var hull = hit.Hull; + var entity = hull.Entity; + if (entity.Shield != null && entity.Shield.Item.Active.Value) { - shield.Entity.Shield.TakeHit(DamageType, Damage); - shield.ShowHit(hit.point, sqrt(Damage)); - }; - } - var hull = hit.collider.GetComponent(); - if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage))) - { - if (hull.Entity == Source.Entity) continue; - LightningCompute.OnLeaderComplete = () => - hull.SendHit(Damage, Penetration, Spread, DamageType, Source.Entity, hit.textureCoord, Barrel.forward); - } + LightningCompute.OnLeaderComplete = () => + { + hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage)); + }; + } + else + { + LightningCompute.OnLeaderComplete = null; + } - _colliderHit = true; - _colliderLocalPosition = hit.collider.transform.InverseTransformPoint(hit.point); - _colliderTransform = hit.collider.transform; - LightningCompute.FixedEndpoint = true; - break; + _colliderHit = true; + _colliderLocalPosition = hull.transform.InverseTransformPoint(hit.Point); + _colliderTransform = hull.transform; + LightningCompute.FixedEndpoint = true; + _endpoint = hit.Point; + break; + } } + LightningCompute.OnPulseComplete = () => GetComponent().ReturnToPool(); LightningCompute.StartAnimation(); - _endpoint = Barrel.position + Barrel.forward * Range; } private void Update() diff --git a/Assets/Scripts/Gameplay/Weapons/Mine.cs b/Assets/Scripts/Gameplay/Weapons/Mine.cs index 0d9c2a48c..8a114c042 100644 --- a/Assets/Scripts/Gameplay/Weapons/Mine.cs +++ b/Assets/Scripts/Gameplay/Weapons/Mine.cs @@ -1,8 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; using UnityEngine; using Random = UnityEngine.Random; @@ -51,8 +49,6 @@ public class Mine : MonoBehaviour // Update is called once per frame void Update() { - var position = transform.position; - if (Time.time - _startTime > ActivationDelay && !_active) { _active = true; @@ -60,25 +56,11 @@ public class Mine : MonoBehaviour if(_active) { - _emission = lerp(_emission, _blastCountdown ? ArmedEmission : ActiveEmission, Time.deltaTime * 10); + _emission = Mathf.Lerp(_emission, _blastCountdown ? ArmedEmission : ActiveEmission, Time.deltaTime * 10); _pulseLerp += Time.deltaTime / (_blastCountdown ? ArmedCycleDuration : ActiveCycleDuration); _pulseLerp %= 1; - if(!_blastCountdown) - { - foreach (var collider in Physics.OverlapSphere(position, BlastRange, 1)) - { - var hull = collider.GetComponent(); - if (hull) - { - _blastCountdown = true; - _blastTime = Time.time + BlastDelay; - } - } - } - if ((position - Source.transform.position).magnitude > Range || - Time.time - _startTime > Lifetime || - _blastCountdown && Time.time > _blastTime) + if (Time.time - _startTime > Lifetime) { Explode(); } @@ -89,21 +71,6 @@ public class Mine : MonoBehaviour public void Explode() { var position = transform.position; - foreach (var collider in Physics.OverlapSphere(position, BlastRange, 1 | (1 << 17))) - { - var shield = collider.GetComponent(); - if (shield && (shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) - { - shield.Entity.Shield.TakeHit(DamageType, Damage); - shield.ShowHit(position, sqrt(Damage)); - } - var hull = collider.GetComponent(); - if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage))) - { - hull.SendSplash(Damage, DamageType, Source.Entity, (collider.transform.position - position).normalized); - } - } - var ht = HitEffect.Instantiate(); ht.position = position; GetComponent().ReturnToPool(); diff --git a/Assets/Scripts/Gameplay/Weapons/MineManager.cs b/Assets/Scripts/Gameplay/Weapons/MineManager.cs index 8bc69a585..eadcf53b2 100644 --- a/Assets/Scripts/Gameplay/Weapons/MineManager.cs +++ b/Assets/Scripts/Gameplay/Weapons/MineManager.cs @@ -1,14 +1,11 @@ using System.Collections; using System.Collections.Generic; using UnityEngine; -using Unity.Mathematics; -using static Unity.Mathematics.math; using Random = UnityEngine.Random; public class MineManager : InstantWeaponEffectManager { public Prototype ProjectilePrototype; - public bool InheritVelocity; public override void Fire(InstantWeapon weapon, EquippedItem item, EntityInstance source, EntityInstance target) { @@ -21,11 +18,9 @@ public class MineManager : InstantWeaponEffectManager p.GridObject.Velocity = Quaternion.Euler( Random.Range(-angle, angle), Random.Range(-angle, angle), - Random.Range(-angle, angle)) * + Random.Range(-angle, angle)) * barrel.forward * weapon.Velocity; - if(InheritVelocity) - p.GridObject.Velocity += new Vector3(source.Entity.Velocity.x, 0, source.Entity.Velocity.y); p.Damage = weapon.Damage; p.Range = weapon.Range; p.DamageType = weapon.DamageType; diff --git a/Assets/Scripts/Gameplay/Weapons/ParticleChargeEffect.cs b/Assets/Scripts/Gameplay/Weapons/ParticleChargeEffect.cs index cc684a4e4..82cdfd94b 100644 --- a/Assets/Scripts/Gameplay/Weapons/ParticleChargeEffect.cs +++ b/Assets/Scripts/Gameplay/Weapons/ParticleChargeEffect.cs @@ -1,6 +1,4 @@ -using System; using System.Collections; -using System.Collections.Generic; using UnityEngine; public class ParticleChargeEffect : WeaponChargeEffect @@ -21,7 +19,7 @@ public class ParticleChargeEffect : WeaponChargeEffect OverchargeEffect.Clear(true); FailureEffect.Clear(true); ChargeEffect.Play(true); - ChargeEffect.enableEmission = true; + SetEmission(ChargeEffect, true); _overloaded = false; _charge = 0; @@ -30,23 +28,23 @@ public class ParticleChargeEffect : WeaponChargeEffect public override void StopCharging() { if (_overloaded) - OverchargeEffect.enableEmission = false; + SetEmission(OverchargeEffect, false); else - ChargeEffect.enableEmission = false; + SetEmission(ChargeEffect, false); StartCoroutine(Kill()); } public override void Charged() { - ChargeEffect.enableEmission = false; + SetEmission(ChargeEffect, false); OverchargeEffect.Play(true); - OverchargeEffect.enableEmission = true; + SetEmission(OverchargeEffect, true); _overloaded = true; } public override void Failed() { - OverchargeEffect.enableEmission = false; + SetEmission(OverchargeEffect, false); FailureEffect.Play(true); } @@ -57,11 +55,18 @@ public class ParticleChargeEffect : WeaponChargeEffect if (!_overloaded) { - ChargeEffect.playbackSpeed = _charge; + var main = ChargeEffect.main; + main.simulationSpeed = _charge; } } - IEnumerator Kill() + private static void SetEmission(ParticleSystem particleSystem, bool enabled) + { + var emission = particleSystem.emission; + emission.enabled = enabled; + } + + private IEnumerator Kill() { while (ChargeEffect.particleCount > 0 || OverchargeEffect.particleCount > 0 || FailureEffect.particleCount > 0) { diff --git a/Assets/Scripts/Gameplay/Weapons/Projectile.cs b/Assets/Scripts/Gameplay/Weapons/Projectile.cs index 31d6d61c1..2f83d36ef 100644 --- a/Assets/Scripts/Gameplay/Weapons/Projectile.cs +++ b/Assets/Scripts/Gameplay/Weapons/Projectile.cs @@ -1,8 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; using UnityEngine; using Random = UnityEngine.Random; @@ -28,7 +26,9 @@ public class Projectile : MonoBehaviour public float Spread { get; set; } public DamageType DamageType { get; set; } public Entity SourceEntity { get; set; } + public EntityInstance TargetInstance { get; set; } public float Range { get; set; } + public float YmirWorldTime { get; private set; } // Start is called before the first frame update void Start() @@ -51,68 +51,73 @@ public class Projectile : MonoBehaviour var t = transform; var position = t.position; Velocity -= Vector3.up * (Gravity * Time.deltaTime); - Velocity *= max(0, 1 - Drag * Time.deltaTime); + Velocity *= Mathf.Max(0, 1 - Drag * Time.deltaTime); var forward = Velocity.normalized; t.forward = forward; - var ray = new Ray(position, Velocity); - foreach (var hit in Physics.RaycastAll(ray, Velocity.magnitude * Time.deltaTime, 1 | (1 << 17))) + if (!AetheriaYmirPhysicsBridge.Instance.TryStepProjectile(this, Time.deltaTime, out var ymirStep)) { - var shield = hit.collider.GetComponent(); - var hull = hit.collider.GetComponent(); - if (shield) - { - if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue; - if (shield.Entity != SourceEntity) - { - shield.Entity.Shield.TakeHit(DamageType, Damage*DirectHitDamageMultiplier); - shield.ShowHit(hit.point, sqrt(Damage * DirectHitDamageMultiplier)); - } - } - else if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage))) - { - if (hull.Entity != SourceEntity) - { - hull.SendHit(Damage*DirectHitDamageMultiplier, Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, forward); - transform.position = hit.point; - StartCoroutine(Kill()); - } - } - else - { - StartCoroutine(Kill()); - return; - } - - if (HitEffect != null) - { - var ht = HitEffect.Instantiate(); - ht.SetParent(hit.collider.transform); - ht.position = hit.point; - return; - } + Debug.LogWarning("Ymir projectile step unavailable; projectile killed instead of falling back to Unity physics."); + StartCoroutine(Kill()); + return; } - - transform.position += Velocity * Time.deltaTime; + + YmirWorldTime += Time.deltaTime; + Velocity = ymirStep.Velocity; + t.position = ymirStep.Position; + if (ymirStep.HasHit) + { + ApplyYmirHit(ymirStep, forward); + return; + } + var distanceTraveled = (transform.position - StartPosition).magnitude; if(distanceTraveled > Range) StartCoroutine(Kill()); if (AirburstRange > 1 && distanceTraveled > AirburstDistance) { StartCoroutine(Kill()); - var ht = HitEffect.Instantiate(); - ht.position = t.position; - foreach (var collider in Physics.OverlapSphere(t.position, AirburstRange, 1)) + if (HitEffect != null) { - var hull = collider.GetComponent(); - if (hull) + var ht = HitEffect.Instantiate(); + ht.position = t.position; + } + + if (AetheriaYmirPhysicsBridge.Instance.TryOverlapTargetHulls(TargetInstance, t.position, AirburstRange, out var hits)) + { + foreach (var hit in hits) { - hull.SendSplash(Damage, DamageType, SourceEntity, (collider.transform.position - t.position).normalized); + var direction = (hit.Hull.transform.position - t.position).normalized; } } } } } + private void ApplyYmirHit(AetheriaYmirProjectileStep step, Vector3 forward) + { + var hull = step.Hit.Hull; + if (hull == null || hull.Entity == SourceEntity) + return; + + if (hull.Entity.Shield != null && + hull.Entity.Shield.Item.Active.Value) + { + } + else + { + } + + transform.position = step.Hit.Point; + if (HitEffect != null) + { + var ht = HitEffect.Instantiate(); + ht.SetParent(hull.transform); + ht.position = step.Hit.Point; + } + + StartCoroutine(Kill()); + } + IEnumerator Kill() { _alive = false; diff --git a/Assets/Scripts/Gameplay/Weapons/ProjectileManager.cs b/Assets/Scripts/Gameplay/Weapons/ProjectileManager.cs index 31d31b67f..9e18df8e9 100644 --- a/Assets/Scripts/Gameplay/Weapons/ProjectileManager.cs +++ b/Assets/Scripts/Gameplay/Weapons/ProjectileManager.cs @@ -1,8 +1,6 @@ using System.Collections; using System.Collections.Generic; using UnityEngine; -using Unity.Mathematics; -using static Unity.Mathematics.math; using Random = UnityEngine.Random; public class ProjectileManager : InstantWeaponEffectManager @@ -17,6 +15,7 @@ public class ProjectileManager : InstantWeaponEffectManager var barrel = source.GetBarrel(hp); var angle = weapon.Spread / 2; p.SourceEntity = source.Entity; + p.TargetInstance = target; p.Velocity = Quaternion.Euler( Random.Range(-angle, angle), Random.Range(-angle, angle), @@ -25,14 +24,16 @@ public class ProjectileManager : InstantWeaponEffectManager weapon.Velocity; p.StartPosition = p.transform.position = barrel.position + p.Velocity * (Random.value * Time.deltaTime); if(InheritVelocity) - p.Velocity += new Vector3(source.Entity.Velocity.x, 0, source.Entity.Velocity.y); + p.Velocity += (Vector3)AetheriaMath.ToUnityXZ(source.Entity.CultVelocity); p.Damage = weapon.Damage; p.Range = weapon.Range; p.Penetration = weapon.Penetration; p.Spread = weapon.DamageSpread; p.DamageType = weapon.DamageType; p.Zone = source.Entity.Zone; - p.AirburstDistance = target != null ? length(source.Entity.Position - target.Entity.Position) : (weapon.Range * .75f); + p.AirburstDistance = target != null + ? CultMath.math.length(source.Entity.CultPosition - target.Entity.CultPosition) + : (weapon.Range * .75f); p.Trail.Clear(); } } diff --git a/Assets/Scripts/Gravity.cs b/Assets/Scripts/Gravity.cs index b5f3460ab..94b5be3b9 100644 --- a/Assets/Scripts/Gravity.cs +++ b/Assets/Scripts/Gravity.cs @@ -24,7 +24,7 @@ public class Gravity : MonoBehaviour if (_instance != null) return _instance; - _instance = (Gravity)FindObjectOfType(typeof(Gravity)); + _instance = FindAnyObjectByType(); return _instance; } } @@ -47,7 +47,6 @@ public class Gravity : MonoBehaviour if (Instance.ForceMode == GravityForceMode.Sign) { var hits = Instance.GravityObjects.Where(go => (go.transform.position.Flatland() - position).sqrMagnitude < go.transform.lossyScale.x) -// var hits = Physics.RaycastAll(new Ray(new Vector3(position.x, 10, position.y), Vector3.down), 20, LayerMask.GetMask("Displacement")) .Where(h => !h.CompareTag("IgnoreGravity")); Vector2 force = Vector2.zero; @@ -130,4 +129,4 @@ public enum GravityForceMode { Sign, Gradient -} \ No newline at end of file +} diff --git a/Assets/Scripts/ServerShared/Aetheria.Shared.Unity.asmdef b/Assets/Scripts/ServerShared/Aetheria.Shared.Unity.asmdef index 418cb55d2..8303c0740 100644 --- a/Assets/Scripts/ServerShared/Aetheria.Shared.Unity.asmdef +++ b/Assets/Scripts/ServerShared/Aetheria.Shared.Unity.asmdef @@ -2,6 +2,7 @@ "name": "Aetheria.Shared.Unity", "rootNamespace": "", "references": [ + "CultMath", "Unity.Mathematics", "UniRx", "Ink", diff --git a/Assets/Scripts/ServerShared/AetheriaMath.cs b/Assets/Scripts/ServerShared/AetheriaMath.cs index 13a19a90d..ff3358183 100644 --- a/Assets/Scripts/ServerShared/AetheriaMath.cs +++ b/Assets/Scripts/ServerShared/AetheriaMath.cs @@ -1,13 +1,78 @@ using System.Collections; using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using CultMath; +using static CultMath.math; +using double2 = CultMath.double2; +using double3 = CultMath.double3; +using float2 = CultMath.float2; +using float3 = CultMath.float3; +using unityfloat2 = Unity.Mathematics.float2; +using unityfloat3 = Unity.Mathematics.float3; public static class AetheriaMath { public const float Deg2Rad = 0.01745329f; public const float Rad2Deg = 57.29578f; + public static float2 ToCult(unityfloat2 value) => float2(value.x, value.y); + public static float3 ToCult(unityfloat3 value) => float3(value.x, value.y, value.z); + public static float2 ToCultXZ(unityfloat3 value) => float2(value.x, value.z); + public static unityfloat2 ToUnity(float2 value) => new(value.x, value.y); + public static unityfloat3 ToUnity(float3 value) => new(value.x, value.y, value.z); + public static unityfloat3 ToUnityXZ(float2 value) => new(value.x, 0, value.y); + + public static float AngleDegrees(float2 from, float2 to) + { + return AngleRadians(from, to) * Rad2Deg; + } + + public static float AngleDegrees(float3 from, float3 to) + { + return AngleRadians(from, to) * Rad2Deg; + } + + public static float AngleDegrees(unityfloat2 from, unityfloat2 to) + { + return AngleDegrees(ToCult(from), ToCult(to)); + } + + public static float AngleDegrees(unityfloat3 from, unityfloat3 to) + { + return AngleDegrees(ToCult(from), ToCult(to)); + } + + public static float AngleRadians(float2 from, float2 to) + { + var denom = sqrt(lengthsq(from) * lengthsq(to)); + if (denom <= 0.000001f) + { + return 0; + } + + return acos(clamp(dot(from, to) / denom, -1.0f, 1.0f)); + } + + public static float AngleRadians(float3 from, float3 to) + { + var denom = sqrt(lengthsq(from) * lengthsq(to)); + if (denom <= 0.000001f) + { + return 0; + } + + return acos(clamp(dot(from, to) / denom, -1.0f, 1.0f)); + } + + public static float AngleRadians(unityfloat2 from, unityfloat2 to) + { + return AngleRadians(ToCult(from), ToCult(to)); + } + + public static float AngleRadians(unityfloat3 from, unityfloat3 to) + { + return AngleRadians(ToCult(from), ToCult(to)); + } + public static float Decay(float source, float lambda, float dt) { return source * exp(-lambda * dt); @@ -18,10 +83,20 @@ public static class AetheriaMath return source * exp(-lambda * dt); } + public static unityfloat3 Decay(unityfloat3 source, float lambda, float dt) + { + return ToUnity(Decay(ToCult(source), lambda, dt)); + } + public static float3 Decay(float3 source, float3 lambda, float dt) { return source * exp(-lambda * dt); } + + public static unityfloat3 Decay(unityfloat3 source, unityfloat3 lambda, float dt) + { + return ToUnity(Decay(ToCult(source), ToCult(lambda), dt)); + } public static float Damp(float a, float b, float lambda, float dt) { @@ -32,11 +107,21 @@ public static class AetheriaMath { return lerp(a, b, 1 - exp(-lambda * dt)); } + + public static unityfloat2 Damp(unityfloat2 a, unityfloat2 b, float lambda, float dt) + { + return ToUnity(Damp(ToCult(a), ToCult(b), lambda, dt)); + } public static float3 Damp(float3 a, float3 b, float lambda, float dt) { return lerp(a, b, 1 - exp(-lambda * dt)); } + + public static unityfloat3 Damp(unityfloat3 a, unityfloat3 b, float lambda, float dt) + { + return ToUnity(Damp(ToCult(a), ToCult(b), lambda, dt)); + } public static double Damp(double a, double b, double lambda, double dt) { @@ -72,6 +157,22 @@ public static class AetheriaMath ); return targetPosition + t*targetRelativeVelocity; } + + public static unityfloat3 FirstOrderIntercept + ( + unityfloat3 shooterPosition, + unityfloat3 shooterVelocity, + float shotSpeed, + unityfloat3 targetPosition, + unityfloat3 targetVelocity + ) { + return ToUnity(FirstOrderIntercept( + ToCult(shooterPosition), + ToCult(shooterVelocity), + shotSpeed, + ToCult(targetPosition), + ToCult(targetVelocity))); + } //first-order intercept using relative target position public static float FirstOrderInterceptTime @@ -112,6 +213,15 @@ public static class AetheriaMath else //determinant = 0; one intercept path, pretty much never happens return max(-b/(2f*a), 0f); //don't shoot back in time } + + public static float FirstOrderInterceptTime + ( + float shotSpeed, + unityfloat3 targetRelativePosition, + unityfloat3 targetRelativeVelocity + ) { + return FirstOrderInterceptTime(shotSpeed, ToCult(targetRelativePosition), ToCult(targetRelativeVelocity)); + } // http://csharphelper.com/blog/2016/09/find-the-shortest-distance-between-a-point-and-a-line-segment-in-c/ // Calculate the distance between @@ -240,4 +350,38 @@ public static class AetheriaMath x = saturate(x); return x * x * x * (x * (x * 6 - 15) + 10); } + + public static float2 Rotate(float2 value, float radians) + { + var s = (float)System.Math.Sin(radians); + var c = (float)System.Math.Cos(radians); + return float2(value.x * c - value.y * s, value.x * s + value.y * c); + } + + public static float2 Rotate(float2 value, ItemRotation rotation) + { + switch (rotation) + { + case ItemRotation.None: + return value; + case ItemRotation.CounterClockwise: + return float2(-value.y, value.x); + case ItemRotation.Reversed: + return float2(-value.x, -value.y); + case ItemRotation.Clockwise: + return float2(value.y, -value.x); + default: + throw new System.ArgumentOutOfRangeException(nameof(rotation), rotation, null); + } + } + + public static unityfloat2 Rotate(unityfloat2 value, float radians) + { + return ToUnity(Rotate(ToCult(value), radians)); + } + + public static unityfloat2 Rotate(unityfloat2 value, ItemRotation rotation) + { + return ToUnity(Rotate(ToCult(value), rotation)); + } } diff --git a/Assets/Scripts/ServerShared/AetheriaRuntimeItemReference.cs b/Assets/Scripts/ServerShared/AetheriaRuntimeItemReference.cs index 7a3f994d6..d9b37b8cb 100644 --- a/Assets/Scripts/ServerShared/AetheriaRuntimeItemReference.cs +++ b/Assets/Scripts/ServerShared/AetheriaRuntimeItemReference.cs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Unity.Mathematics; +using cfloat3 = CultMath.float3; public interface INamedEntry { @@ -26,5 +26,5 @@ public class AetheriaRuntimeItemReference public interface ITintInspector { - float3 TintColor { get; } + cfloat3 TintColor { get; } } diff --git a/Assets/Scripts/ServerShared/Agents/Agent.cs b/Assets/Scripts/ServerShared/Agents/Agent.cs index 051ff28e7..88fb0ef2d 100644 --- a/Assets/Scripts/ServerShared/Agents/Agent.cs +++ b/Assets/Scripts/ServerShared/Agents/Agent.cs @@ -7,9 +7,8 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using UniRx; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using float2 = Unity.Mathematics.float2; +using static CultMath.math; +using cfloat2 = CultMath.float2; public class Agent { @@ -55,26 +54,27 @@ public class Agent } } - public void Accelerate(float2 targetVelocity, bool noTurn = false) + public void Accelerate(cfloat2 targetVelocity, bool noTurn = false) { - var deltaV = targetVelocity - Ship.Velocity; + var deltaV = targetVelocity - Ship.CultVelocity; var deltaVMag = length(deltaV); var deltaVDirection = normalize(deltaV); // If Delta V is above the threshold, direct the ship towards the delta and use only main thrusters if (!noTurn && deltaVMag > FORWARD_DELTA_THRESHOLD) { - Ship.LookDirection = float3(deltaVDirection.x, 0, deltaVDirection.y); - Ship.MovementDirection = float2(0, pow(dot(Ship.Direction, deltaVDirection), 2)); + Ship.CultLookDirectionXZ = deltaVDirection; + Ship.MovementDirection = new cfloat2(0, pow(dot(Ship.CultDirection, deltaVDirection), 2)); } // If Delta V is low, direct the ship towards the target and use all thrusters else if(deltaVMag > THRUST_DELTA_THRESHOLD) { - var right = Ship.Direction.Rotate(ItemRotation.Clockwise); - Ship.MovementDirection = float2(dot(right, deltaVDirection), dot(Ship.Direction, deltaVDirection)); + var shipDirection = Ship.CultDirection; + var right = AetheriaMath.Rotate(shipDirection, ItemRotation.Clockwise); + Ship.MovementDirection = new cfloat2(dot(right, deltaVDirection), dot(shipDirection, deltaVDirection)); } else { - Ship.MovementDirection = float2.zero; + Ship.MovementDirection = cfloat2.zero; } } } diff --git a/Assets/Scripts/ServerShared/Agents/States/Combat.cs b/Assets/Scripts/ServerShared/Agents/States/Combat.cs index cf0aa4f46..532506dd1 100644 --- a/Assets/Scripts/ServerShared/Agents/States/Combat.cs +++ b/Assets/Scripts/ServerShared/Agents/States/Combat.cs @@ -2,10 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; using UniRx; -using static Unity.Mathematics.math; -using float2 = Unity.Mathematics.float2; -using float3 = Unity.Mathematics.float3; -using float2x2 = Unity.Mathematics.float2x2; +using static CultMath.math; +using cfloat3 = CultMath.float3; public class CombatState : BaseState { @@ -13,7 +11,7 @@ public class CombatState : BaseState private float _optimumRange; private readonly List<(int index, float dps)> _availableGroups = new List<(int index, float dps)>(); private readonly List _availableLockingWeapons = new List(); - + public CombatState(Agent agent) : base(agent) { SampleDps(); @@ -31,7 +29,9 @@ public class CombatState : BaseState _availableGroups.Clear(); _availableLockingWeapons.Clear(); - var toTarget = target.Position - _agent.Ship.Position; + var shipPosition = _agent.Ship.CultPosition; + var targetPosition = target.CultPosition; + var toTarget = targetPosition - shipPosition; var targetDistance = length(toTarget); for (var i = 0; i < _agent.Ship.WeaponGroups.Length; i++) { @@ -66,16 +66,17 @@ public class CombatState : BaseState } } - var targetRight = target.Direction.Rotate(ItemRotation.Clockwise); + var targetDirection = target.CultDirection; + var targetRight = AetheriaMath.Rotate(targetDirection, ItemRotation.Clockwise); var optimumRangeDelta = abs(_optimumRange - targetDistance); var directionToTarget = normalize(toTarget.xz); var targetPortAlignment = dot(targetRight, directionToTarget); - var targetForeAlignment = dot(-target.Direction, directionToTarget); + var targetForeAlignment = dot(-targetDirection, directionToTarget); var forwardness = saturate(optimumRangeDelta / _agent.Settings.AgentMaxForwardDistance) * _agent.Settings.AgentForwardLerp; if (targetForeAlignment > 0) forwardness = lerp(forwardness, 1, pow(targetPortAlignment, 2)); var movementDirection = normalize(lerp( - directionToTarget.Rotate(targetPortAlignment > 0 ? ItemRotation.Clockwise : ItemRotation.CounterClockwise), + AetheriaMath.Rotate(directionToTarget, targetPortAlignment > 0 ? ItemRotation.Clockwise : ItemRotation.CounterClockwise), targetDistance > _optimumRange ? directionToTarget : -directionToTarget, forwardness)); if (selectedGroup >= 0) @@ -84,17 +85,16 @@ public class CombatState : BaseState if(testWeapon.Velocity > 1) { var targetHull = _agent.ItemManager.GetRuntimeItem(target.Hull); - var targetVelocity = float3(target.Velocity.x, 0, target.Velocity.y); - var shipVelocity = float3(_agent.Ship.Velocity.x, 0, _agent.Ship.Velocity.y); + var targetVelocity = new cfloat3(target.CultVelocity.x, 0, target.CultVelocity.y); var predictedPosition = AetheriaMath.FirstOrderIntercept( - _agent.Ship.Position, - float3.zero, + shipPosition, + cfloat3.zero, testWeapon.Velocity, - target.Position, + targetPosition, targetVelocity ); predictedPosition.y = _agent.Ship.Zone.GetHeight(predictedPosition.xz) + (float)(targetHull?.HullGridOffset ?? 0); - toTarget = normalize(predictedPosition - _agent.Ship.Position); + toTarget = normalize(predictedPosition - shipPosition); } var shouldFire = dot( @@ -108,7 +108,7 @@ public class CombatState : BaseState weapon.Deactivate(); } } - _agent.Ship.LookDirection = toTarget; + _agent.Ship.CultLookDirection = toTarget; _agent.Accelerate(movementDirection * _agent.TopSpeed, true);//selectedGroup >= 0); // Fire charged guns! diff --git a/Assets/Scripts/ServerShared/Agents/States/MatchVelocity.cs b/Assets/Scripts/ServerShared/Agents/States/MatchVelocity.cs index 706989d53..c7d8c3b74 100644 --- a/Assets/Scripts/ServerShared/Agents/States/MatchVelocity.cs +++ b/Assets/Scripts/ServerShared/Agents/States/MatchVelocity.cs @@ -1,10 +1,9 @@ -using Unity.Mathematics; -using static Unity.Mathematics.math; -using float2 = Unity.Mathematics.float2; +using static CultMath.math; +using cfloat2 = CultMath.float2; public abstract class MatchVelocityState : BaseState { - protected abstract float2 TargetVelocity { get; } + protected abstract cfloat2 TargetVelocity { get; } protected MatchVelocityState(Agent agent) : base(agent) { } @@ -13,12 +12,12 @@ public abstract class MatchVelocityState : BaseState _agent.Accelerate(TargetVelocity); } - public float2 MatchDistanceTime + public cfloat2 MatchDistanceTime { get { - var velocity = length(_agent.Ship.Velocity); - var deltaV = TargetVelocity - _agent.Ship.Velocity; + var velocity = length(_agent.Ship.CultVelocity); + var deltaV = TargetVelocity - _agent.Ship.CultVelocity; var stoppingTime = length(deltaV) / (_agent.Ship.ForwardThrust / _agent.Ship.Mass); var stoppingDistance = stoppingTime * (velocity / 2); @@ -26,7 +25,7 @@ public abstract class MatchVelocityState : BaseState var turnaroundTime = _agent.Ship.TurnTime(deltaV); var turnaroundDistance = turnaroundTime * velocity; - return float2(stoppingDistance + turnaroundDistance, stoppingTime + turnaroundTime); + return new cfloat2(stoppingDistance + turnaroundDistance, stoppingTime + turnaroundTime); } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/ServerShared/Agents/States/MoveTo.cs b/Assets/Scripts/ServerShared/Agents/States/MoveTo.cs index 00008b365..dfb84581c 100644 --- a/Assets/Scripts/ServerShared/Agents/States/MoveTo.cs +++ b/Assets/Scripts/ServerShared/Agents/States/MoveTo.cs @@ -1,13 +1,12 @@ using System; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using float2 = Unity.Mathematics.float2; +using static CultMath.math; +using cfloat2 = CultMath.float2; public abstract class MoveToState : BaseState { private VelocityLimit _velocityLimit; - protected abstract float2 TargetPosition { get; } + protected abstract cfloat2 TargetPosition { get; } public float Distance { get; private set; } protected MoveToState(Agent agent) : base(agent) @@ -16,13 +15,13 @@ public abstract class MoveToState : BaseState public override void Update(float delta) { - var diff = TargetPosition - _agent.Ship.Position.xz; + var diff = TargetPosition - _agent.Ship.CultPositionXZ; var dir = normalize(diff); Distance = length(diff); // We want to go top speed in the direction of our target var desiredVelocity = dir * _agent.TopSpeed; - _agent.Ship.LookDirection = float3(dir.x, 0, dir.y); + _agent.Ship.CultLookDirectionXZ = dir; _agent.Accelerate(desiredVelocity); } } @@ -30,7 +29,7 @@ public abstract class MoveToState : BaseState public class MoveToEntityState : MoveToState { public Entity TargetEntity { get; set; } - protected override float2 TargetPosition => TargetEntity?.Position.xz ?? float2.zero; + protected override cfloat2 TargetPosition => TargetEntity != null ? TargetEntity.CultPositionXZ : cfloat2.zero; public MoveToEntityState(Agent agent) : base(agent) { } } @@ -40,5 +39,5 @@ public class MoveToOrbitState : MoveToState public string OrbitKey { get; set; } = ""; public MoveToOrbitState(Agent agent) : base(agent) { } - protected override float2 TargetPosition => _agent.Ship.Zone.GetOrbitPosition(OrbitKey); + protected override cfloat2 TargetPosition => AetheriaMath.ToCult(_agent.Ship.Zone.GetOrbitPosition(OrbitKey)); } diff --git a/Assets/Scripts/ServerShared/Behaviors/AetherDrive.cs b/Assets/Scripts/ServerShared/Behaviors/AetherDrive.cs index 1b160e30a..e55f40a4f 100644 --- a/Assets/Scripts/ServerShared/Behaviors/AetherDrive.cs +++ b/Assets/Scripts/ServerShared/Behaviors/AetherDrive.cs @@ -2,15 +2,16 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; +using cfloat2 = CultMath.float2; +using cfloat3 = CultMath.float3; public class AetherDrive : Behavior { - private readonly float3 _rotorDiameter; - private readonly float3 _rotorMass; + private readonly cfloat3 _rotorDiameter; + private readonly cfloat3 _rotorMass; private readonly PerformanceStat _maximumRpm; - private readonly float3 _couplingLambda; + private readonly cfloat3 _couplingLambda; private readonly PerformanceStat _lambdaMultiplier; private readonly PerformanceStat _couplingEfficiency; private readonly PerformanceStat _torque; @@ -19,15 +20,15 @@ public class AetherDrive : Behavior private readonly PerformanceStat _passiveCoupling; private readonly uint _rpmAudioParameter; private readonly uint _torqueRatioAudioParameter; - private float3 _axis; + private cfloat3 _axis; - public float3 Thrust { get; private set; } - public float3 Rpm { get; private set; } + public cfloat3 Thrust { get; private set; } + public cfloat3 Rpm { get; private set; } public float MaximumRpm { get; private set; } - public float2 ThrustDirection { get; private set; } + public cfloat2 ThrustDirection { get; private set; } public string Particles { get; } - public float3 Axis + public cfloat3 Axis { get => _axis; set => _axis = clamp(value, -1, 1); @@ -83,16 +84,17 @@ public class AetherDrive : Behavior { var rotorSpeed = Rpm * _rotorDiameter / 100; - var forward = normalize(Entity.Direction); - var right = forward.Rotate(ItemRotation.Clockwise); + var forward = normalize(Entity.CultDirection); + var right = AetheriaMath.Rotate(Entity.CultDirection, ItemRotation.Clockwise); + var velocity = Entity.CultVelocity; - var speed = float2(dot(Entity.Velocity, forward), dot(Entity.Velocity, right)); + var speed = float2(dot(velocity, forward), dot(velocity, right)); var couplingEfficiency = Evaluate(_couplingEfficiency); var efficiency = float3(saturate(1 - speed / max(rotorSpeed.xy, 1) * sign(_axis.xy)) * couplingEfficiency, 1); Thrust = (Rpm - AetheriaMath.Decay(Rpm, _couplingLambda, dt)) * _rotorMass * efficiency; - var couplingLambda = _couplingLambda * Item.Evaluate(_lambdaMultiplier) * max(abs(_axis), Evaluate(_passiveCoupling)); + var couplingLambda = _couplingLambda * Evaluate(_lambdaMultiplier) * max(abs(_axis), Evaluate(_passiveCoupling)); var previousRpm = Rpm; Rpm = AetheriaMath.Decay(Rpm, couplingLambda, dt); var rpmLoss = previousRpm - Rpm; @@ -102,13 +104,14 @@ public class AetherDrive : Behavior AddHeat((heat.x + heat.y + heat.z)*ItemManager.GameplaySettings.AetherHeatMultiplier); ThrustDirection = forward * (_axis.x * force.x / Entity.Mass) + right * (_axis.y * force.y / Entity.Mass); - Entity.Velocity += ThrustDirection; + Entity.CultVelocity += ThrustDirection; - Entity.Direction = mul(Entity.Direction, - Unity.Mathematics.float2x2.Rotate(force.z * _axis.z * ItemManager.GameplaySettings.AetherTorqueMultiplier / Entity.Mass)); + Entity.CultDirection = AetheriaMath.Rotate( + Entity.CultDirection, + force.z * _axis.z * ItemManager.GameplaySettings.AetherTorqueMultiplier / Entity.Mass); - if(float.IsNaN(Entity.Velocity.x)) - ItemManager.Log("FUCK FUCK FUCK FUCK"); + if(float.IsNaN(Entity.CultVelocity.x)) + ItemManager.Log("AetherDrive produced NaN velocity."); MaximumRpm = Evaluate(_maximumRpm); var torqueProfile = float3( @@ -136,11 +139,11 @@ public class AetherDrive : Behavior } public void RestoreRuntimeState( - float3 axis, - float3 thrust, - float3 rpm, + cfloat3 axis, + cfloat3 thrust, + cfloat3 rpm, float maximumRpm, - float2 thrustDirection) + cfloat2 thrustDirection) { Axis = axis; Thrust = thrust; diff --git a/Assets/Scripts/ServerShared/Behaviors/AutoWeapon.cs b/Assets/Scripts/ServerShared/Behaviors/AutoWeapon.cs index fec71d657..3836b2c16 100644 --- a/Assets/Scripts/ServerShared/Behaviors/AutoWeapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/AutoWeapon.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; - public class AutoWeapon : InstantWeapon { diff --git a/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs b/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs index d180a2e1d..b8f4b14ac 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs @@ -7,9 +7,9 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using GameCult.Aetheria.State.Unity; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using static Unity.Mathematics.noise; +using static CultMath.math; +using cfloat3 = CultMath.float3; +using cfloat4 = CultMath.float4; public abstract class Behavior { @@ -23,7 +23,7 @@ public abstract class Behavior protected Entity Entity => Item?.Entity ?? Consumable.Entity; public float Temperature => Item?.Temperature ?? Consumable.Entity.MaxTemp; - public float3 Direction + public cfloat3 Direction { get { @@ -36,12 +36,13 @@ public abstract class Behavior } else { - var itemDirection = Entity.Direction.Rotate(Item.EquippableItem.Rotation); - return float3(itemDirection.x, 0, itemDirection.y); + var itemDirection = AetheriaMath.Rotate(Entity.CultDirection, Item.EquippableItem.Rotation); + return new cfloat3(itemDirection.x, 0, itemDirection.y); } } - return float3(Entity.Direction.x, 0, Entity.Direction.y); + var entityDirection = Entity.CultDirection; + return new cfloat3(entityDirection.x, 0, entityDirection.y); } } @@ -65,6 +66,18 @@ public abstract class Behavior public float Evaluate(PerformanceStat stat) => Item?.Evaluate(stat) ?? Consumable.Evaluate(stat); + public float Evaluate(PerformanceStat stat, StatConditionMask condition, float value) => + Item?.Evaluate(stat, condition, value) ?? Consumable.Evaluate(stat, condition, value); + + public float Evaluate( + PerformanceStat stat, + StatConditionMask firstCondition, + float firstValue, + StatConditionMask secondCondition, + float secondValue) => + Item?.Evaluate(stat, firstCondition, firstValue, secondCondition, secondValue) ?? + Consumable.Evaluate(stat, firstCondition, firstValue, secondCondition, secondValue); + public bool TryGetPerformanceStat(string statName, out PerformanceStat stat) { if (!string.IsNullOrWhiteSpace(statName) && @@ -82,6 +95,7 @@ public abstract class Behavior if (!string.IsNullOrWhiteSpace(statName) && stat != null) { _performanceStats[statName] = stat; + Item?.RegisterPerformanceStat(Kind, statName, stat); } } @@ -208,7 +222,7 @@ public sealed class RuntimeBehaviorDefinition return _fields.TryGetValue(key, out var value) ? checked((uint)value.NumberValue) : fallback; } - public float3 Float3(int key, float3 fallback = default) + public cfloat3 Float3(int key, cfloat3 fallback = default) { return _fields.TryGetValue(key, out var value) ? ToFloat3(value) : fallback; } @@ -228,7 +242,9 @@ public sealed class RuntimeBehaviorDefinition public PerformanceStat PerformanceStat(int key, PerformanceStat fallback) { - return _fields.TryGetValue(key, out var value) ? ToPerformanceStat(value) : fallback; + return _fields.TryGetValue(key, out var value) + ? AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(value) + : fallback; } public BezierCurve BezierCurve(int key, BezierCurve fallback) @@ -238,15 +254,12 @@ public sealed class RuntimeBehaviorDefinition return fallback; } - return new BezierCurve - { - Keys = value.Children.Count > 0 - ? value.Children[0].Children.Select(ToFloat4).ToArray() - : Array.Empty() - }; + return global::BezierCurve.FromKeys(value.Children.Count > 0 + ? value.Children[0].Children.Select(ToBezierKey) + : Enumerable.Empty<(float, float, float, float)>()); } - public float4[] Float4Array(int key, float4[] fallback) + public cfloat4[] Float4Array(int key, cfloat4[] fallback) { return _fields.TryGetValue(key, out var value) ? value.Children.Select(ToFloat4).ToArray() @@ -260,49 +273,29 @@ public sealed class RuntimeBehaviorDefinition return fallback; } - return new StatReference - { - Target = ChildString(value, 1), - Stat = ChildString(value, 2) - }; + return AetheriaRuntimeBehaviorValueReader.ReadStatReference(value); } public T Enum(int key, T fallback) where T : struct { - if (!_fields.TryGetValue(key, out var value)) - { - return fallback; - } - - if (!string.IsNullOrWhiteSpace(value.StringValue) && - System.Enum.TryParse(value.StringValue, true, out T parsed)) - { - return parsed; - } - - return (T)System.Enum.ToObject(typeof(T), checked((int)value.NumberValue)); + return _fields.TryGetValue(key, out var value) + ? AetheriaRuntimeBehaviorValueReader.ReadEnum(value, fallback) + : fallback; } - private static PerformanceStat ToPerformanceStat(AetheriaRuntimeBehaviorValue value) + private static cfloat4 ToFloat4(AetheriaRuntimeBehaviorValue value) { - return new PerformanceStat - { - Min = ChildFloat(value, 0), - Max = ChildFloat(value, 1), - HeatExponentMultiplier = ChildFloat(value, 2), - DurabilityExponentMultiplier = ChildFloat(value, 3), - QualityExponent = ChildFloat(value, 4) - }; + return new cfloat4(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2), ChildFloat(value, 3)); } - private static float4 ToFloat4(AetheriaRuntimeBehaviorValue value) + private static (float, float, float, float) ToBezierKey(AetheriaRuntimeBehaviorValue value) { - return new float4(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2), ChildFloat(value, 3)); + return (ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2), ChildFloat(value, 3)); } - private static float3 ToFloat3(AetheriaRuntimeBehaviorValue value) + private static cfloat3 ToFloat3(AetheriaRuntimeBehaviorValue value) { - return new float3(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2)); + return new cfloat3(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2)); } private static float ChildFloat(AetheriaRuntimeBehaviorValue value, int index) @@ -314,4 +307,9 @@ public sealed class RuntimeBehaviorDefinition { return 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; + } } diff --git a/Assets/Scripts/ServerShared/Behaviors/Capacitor.cs b/Assets/Scripts/ServerShared/Behaviors/Capacitor.cs index f4bda3fb7..f79d4fd33 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Capacitor.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Capacitor.cs @@ -2,9 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; public class Capacitor : Behavior { diff --git a/Assets/Scripts/ServerShared/Behaviors/ChargedWeapon.cs b/Assets/Scripts/ServerShared/Behaviors/ChargedWeapon.cs index 5104e6a50..33d74f674 100644 --- a/Assets/Scripts/ServerShared/Behaviors/ChargedWeapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/ChargedWeapon.cs @@ -1,8 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; public class ChargedWeapon : InstantWeapon { @@ -30,9 +29,9 @@ public class ChargedWeapon : InstantWeapon public override float DamagePerSecond => Damage * _chargeFiringDamageMultiplier / (Cooldown + ChargeTime); public override float RangeDamagePerSecond(float range) { - return Damage * - _chargeFiringDamageMultiplier * - DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range))) / + return EvaluateRangeDamage(range, StatConditionMask.Charge, saturate(_charge)) * + lerp(1, _chargeFiringDamageMultiplier, saturate(_charge)) * + _chargeFiringDamageMultiplier / (Cooldown + ChargeTime); } @@ -106,7 +105,8 @@ public class ChargedWeapon : InstantWeapon ChargeTime = Evaluate(_chargeTime); ChargeEnergy = Evaluate(_chargeEnergy); ChargeHeat = Evaluate(_chargeHeat); - Damage *= lerp(1, _chargeFiringDamageMultiplier, saturate(_charge)); + Damage = EvaluateDamage(StatConditionMask.Charge, saturate(_charge)) * + lerp(1, _chargeFiringDamageMultiplier, saturate(_charge)); Heat *= lerp(1, _chargeFiringHeatMultiplier, saturate(_charge)); Spread *= lerp(1, _chargeFiringSpreadMultiplier, saturate(_charge)); BurstCount *= lerp(1, _chargeFiringBurstCountMultiplier, saturate(_charge)); diff --git a/Assets/Scripts/ServerShared/Behaviors/Cockpit.cs b/Assets/Scripts/ServerShared/Behaviors/Cockpit.cs index 89bec6210..a7f3ff81c 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Cockpit.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Cockpit.cs @@ -2,10 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; - public class Cockpit : Behavior { public Cockpit(RuntimeBehaviorDefinition definition, EquippedItem item) : base(definition, item) diff --git a/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs b/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs index 135449c0a..0e2129823 100644 --- a/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs @@ -3,8 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior { @@ -30,7 +29,7 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior public override float DamagePerSecond => Damage; public override float RangeDamagePerSecond(float range) { - return Damage * DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range))); + return EvaluateRangeDamage(range); } public event Action OnReloadBegin; diff --git a/Assets/Scripts/ServerShared/Behaviors/Cooldown.cs b/Assets/Scripts/ServerShared/Behaviors/Cooldown.cs index d4e307878..58890b753 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Cooldown.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Cooldown.cs @@ -2,10 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System; -using System.Linq; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; public class Cooldown : Behavior, IAlwaysUpdatedBehavior, IProgressBehavior { diff --git a/Assets/Scripts/ServerShared/Behaviors/HeatStorage.cs b/Assets/Scripts/ServerShared/Behaviors/HeatStorage.cs index aeeea7538..340ed0ed4 100644 --- a/Assets/Scripts/ServerShared/Behaviors/HeatStorage.cs +++ b/Assets/Scripts/ServerShared/Behaviors/HeatStorage.cs @@ -2,10 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; - public class HeatStorage : Behavior { public HeatStorage(RuntimeBehaviorDefinition definition, EquippedItem item) : base(definition, item) diff --git a/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs b/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs index 68a24bd22..6ec6588b1 100644 --- a/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs @@ -1,8 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior { @@ -29,7 +28,7 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior public override float DamagePerSecond => Damage / Cooldown; public override float RangeDamagePerSecond(float range) { - return Damage * DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range))) / Cooldown; + return EvaluateRangeDamage(range) / Cooldown; } public override int Ammo diff --git a/Assets/Scripts/ServerShared/Behaviors/LockWeapon.cs b/Assets/Scripts/ServerShared/Behaviors/LockWeapon.cs index d5d564f8e..7efa59739 100644 --- a/Assets/Scripts/ServerShared/Behaviors/LockWeapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/LockWeapon.cs @@ -1,8 +1,5 @@ using System; -using System.Collections; -using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; public class LockWeapon : InstantWeapon { @@ -82,10 +79,11 @@ public class LockWeapon : InstantWeapon DirectionImpact = Evaluate(_directionImpact); Decay = Evaluate(_decay); - var degrees = acos(dot(normalize(Entity.Target.Value.Position - Entity.Position), normalize(Entity.LookDirection))) * 57.2958f; - if (degrees < LockAngle) + var targetDirection = Entity.Target.Value.CultPosition - Entity.CultPosition; + var angleDegrees = AetheriaMath.AngleDegrees(targetDirection, Entity.CultLookDirection); + if (angleDegrees < LockAngle) { - var lerp = 1 - unlerp(0, 90, degrees); + var lerp = 1 - unlerp(0, 90, angleDegrees); _lock = saturate(_lock + pow(lerp, DirectionImpact) * dt * LockSpeed * pow(Entity.EntityInfoGathered[Entity.Target.Value], SensorImpact)); } else _lock = saturate(_lock - dt * Decay); diff --git a/Assets/Scripts/ServerShared/Behaviors/MiningTool.cs b/Assets/Scripts/ServerShared/Behaviors/MiningTool.cs index 7c3217d0e..d6787c187 100644 --- a/Assets/Scripts/ServerShared/Behaviors/MiningTool.cs +++ b/Assets/Scripts/ServerShared/Behaviors/MiningTool.cs @@ -2,10 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System; -using System.Linq; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; public class MiningTool : Behavior { @@ -45,10 +42,11 @@ public class MiningTool : Behavior public override bool Execute(float dt) { Range = Evaluate(_range); - if (Entity.Zone.TryGetAsteroidBelt(AsteroidBeltBodyKey, out var belt) && - !string.IsNullOrWhiteSpace(AsteroidBeltBodyKey) && + if (!string.IsNullOrWhiteSpace(AsteroidBeltBodyKey) && + Entity.Zone.TryGetAsteroidBelt(AsteroidBeltBodyKey, out var belt) && Entity.Zone.AsteroidExists(AsteroidBeltBodyKey, Asteroid) && - length(Entity.Position.xz - belt.Transforms[Asteroid].xy) - belt.Transforms[Asteroid].w < Range) + Entity.Zone.TryGetCultAsteroidTransform(AsteroidBeltBodyKey, Asteroid, out var asteroidPosition, out var asteroidRadius) && + length(Entity.CultPositionXZ - asteroidPosition) - asteroidRadius < Range) { Entity.Zone.MineAsteroid( Entity, diff --git a/Assets/Scripts/ServerShared/Behaviors/Radiator.cs b/Assets/Scripts/ServerShared/Behaviors/Radiator.cs index 8fff88746..edf5891f1 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Radiator.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Radiator.cs @@ -2,8 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; public class Radiator : Behavior, IAlwaysUpdatedBehavior, IInitializableBehavior { diff --git a/Assets/Scripts/ServerShared/Behaviors/Reactor.cs b/Assets/Scripts/ServerShared/Behaviors/Reactor.cs index c9dbc6649..2a862c5ea 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Reactor.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Reactor.cs @@ -6,8 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; using UniRx; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; public class Reactor : Behavior, IOrderedBehavior, IDisposable { diff --git a/Assets/Scripts/ServerShared/Behaviors/Reflector.cs b/Assets/Scripts/ServerShared/Behaviors/Reflector.cs index bb29a93e3..c342f71a3 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Reflector.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Reflector.cs @@ -2,9 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Unity.Mathematics; -using static Unity.Mathematics.math; - public class Reflector : Behavior { private readonly PerformanceStat _crossSection; @@ -23,7 +20,7 @@ public class Reflector : Behavior public override bool Execute(float dt) { - Entity.VisibilitySources[this] = Evaluate(_crossSection) * Entity.Zone.GetLight(Entity.Position.xz); + Entity.VisibilitySources[this] = Evaluate(_crossSection) * Entity.Zone.GetLight(Entity.CultPositionXZ); return true; } diff --git a/Assets/Scripts/ServerShared/Behaviors/ResourceScanner.cs b/Assets/Scripts/ServerShared/Behaviors/ResourceScanner.cs index a57272da1..200c02adc 100644 --- a/Assets/Scripts/ServerShared/Behaviors/ResourceScanner.cs +++ b/Assets/Scripts/ServerShared/Behaviors/ResourceScanner.cs @@ -3,9 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using System.Linq; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior { @@ -60,9 +58,8 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior { if (Entity.Zone.TryGetAsteroidBelt(ScanTargetBodyKey, out var belt)) { - if (Asteroid > -1 && - belt.ContainsAsteroid(Asteroid) && - length(Entity.Position.xz - belt.Transforms[Asteroid].xy) < Range) + if (Entity.Zone.TryGetCultAsteroidTransform(ScanTargetBodyKey, Asteroid, out var asteroidPosition, out _) && + length(Entity.CultPositionXZ - asteroidPosition) < Range) { _scanTime += dt; if (_scanTime > ScanDuration) @@ -75,7 +72,7 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior } else if (Entity.Zone.TryGetPlanet(ScanTargetBodyKey, out var planet)) { - if(length(Entity.Position.xz - Entity.Zone.GetOrbitPosition(planet.OrbitKey)) < Range) + if(length(Entity.CultPositionXZ - AetheriaMath.ToCult(Entity.Zone.GetOrbitPosition(planet.OrbitKey))) < Range) { _scanTime += dt; if (_scanTime > ScanDuration) diff --git a/Assets/Scripts/ServerShared/Behaviors/Sensor.cs b/Assets/Scripts/ServerShared/Behaviors/Sensor.cs index 1ff077bb5..f29fafe95 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Sensor.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Sensor.cs @@ -4,8 +4,7 @@ using System; using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; public class Sensor : Behavior, IEventBehavior { @@ -117,12 +116,13 @@ public class Sensor : Behavior, IEventBehavior // TODO: Handle Active Detection / Visibility From Reflected Radiance var forward = Direction.xz; + var entityPosition = Entity.CultPositionXZ; foreach (var entity in Entity.Zone.Entities) { if (entity == Entity) continue; - var diff = entity.Position.xz - Entity.Position.xz; - var angle = acos(dot(forward, normalize(diff))); + var diff = entity.CultPositionXZ - entityPosition; + var angle = AetheriaMath.AngleRadians(forward, diff); var dist = length(diff); float previous, next; Entity.EntityInfoGathered.TryGetValue(entity, out previous); diff --git a/Assets/Scripts/ServerShared/Behaviors/Shield.cs b/Assets/Scripts/ServerShared/Behaviors/Shield.cs index 6bf7e2cac..18fb2f656 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Shield.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Shield.cs @@ -2,9 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Unity.Mathematics; -using static Unity.Mathematics.math; - public class Shield : Behavior, IProgressBehavior { public float Efficiency { get; private set; } diff --git a/Assets/Scripts/ServerShared/Behaviors/StatModifier.cs b/Assets/Scripts/ServerShared/Behaviors/StatModifier.cs index 7443c343f..77aec72d4 100644 --- a/Assets/Scripts/ServerShared/Behaviors/StatModifier.cs +++ b/Assets/Scripts/ServerShared/Behaviors/StatModifier.cs @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; +using System.Collections.Generic; using System.Linq; using GameCult.Aetheria.State.Unity; @@ -14,14 +15,14 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa private readonly StatModifierType _type; private readonly string _requiredBehaviorKind; - private PerformanceStat[] _stats; + private StatModifierTarget[] _targets; private bool _applied; private bool _executed; public bool Applied => _applied; public bool Executed => _executed; - public int TargetStatCount => _stats?.Length ?? 0; + public int TargetStatCount => _targets?.Length ?? 0; public StatModifier(RuntimeBehaviorDefinition definition, EquippedItem item) : base(definition, item) { @@ -47,13 +48,19 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa public void Initialize() { - _stats = Entity.Equipment - .Where(HasRequiredBehavior) - .SelectMany(gear => gear.Behaviors ?? Array.Empty()) - .Where(behavior => BehaviorKindMatches(behavior.Kind, _targetBehaviorKind)) - .Select(FindTargetStat) - .Where(stat => stat != null) - .ToArray(); + var targets = new List(); + foreach (var gear in Entity.Equipment.Where(HasRequiredBehavior)) + foreach (var behavior in gear.Behaviors ?? Array.Empty()) + { + if (!BehaviorKindMatches(behavior.Kind, _targetBehaviorKind)) + continue; + + var stat = FindTargetStat(behavior); + if (stat != null) + targets.Add(new StatModifierTarget(gear, stat)); + } + + _targets = targets.ToArray(); } private PerformanceStat FindTargetStat(Behavior behavior) @@ -91,17 +98,21 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa private void ApplyModifier() { _applied = true; - foreach (var stat in _stats) - (_type == StatModifierType.Constant - ? stat.GetConstantModifiers(Entity) - : stat.GetScaleModifiers(Entity))[this] = Evaluate(_modifier); + RefreshModifier(); } private void RemoveModifier() { _applied = false; - foreach (var stat in _stats) - (_type == StatModifierType.Constant ? stat.GetConstantModifiers(Entity) : stat.GetScaleModifiers(Entity)).Remove(this); + foreach (var target in _targets ?? Array.Empty()) + target.Item.RemoveStatModifier(target.Stat, this, _type); + } + + private void RefreshModifier() + { + var value = Evaluate(_modifier); + foreach (var target in _targets ?? Array.Empty()) + target.Item.SetStatModifier(target.Stat, this, _type, value); } public override bool Execute(float dt) @@ -120,6 +131,8 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa { if(_executed && !_applied) ApplyModifier(); + else if(_executed && _applied) + RefreshModifier(); if(!_executed && _applied) RemoveModifier(); _executed = false; @@ -127,14 +140,28 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa public void RestoreRuntimeState(bool applied, bool executed) { - if (_stats == null) + if (_targets == null) Initialize(); if (applied && !_applied) ApplyModifier(); + if (applied && _applied) + RefreshModifier(); if (!applied && _applied) RemoveModifier(); _executed = executed; } + + private readonly struct StatModifierTarget + { + public StatModifierTarget(EquippedItem item, PerformanceStat stat) + { + Item = item; + Stat = stat; + } + + public EquippedItem Item { get; } + public PerformanceStat Stat { get; } + } } public enum StatModifierType diff --git a/Assets/Scripts/ServerShared/Behaviors/Thruster.cs b/Assets/Scripts/ServerShared/Behaviors/Thruster.cs index 12ddf2414..5ea0a7f1d 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Thruster.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Thruster.cs @@ -2,8 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; +using cfloat2 = CultMath.float2; public class Thruster : Behavior, IAnalogBehavior { @@ -40,7 +40,7 @@ public class Thruster : Behavior, IAnalogBehavior var hullCenter = hullShape.CenterOfMass; var itemCenter = hullShape.Inset(itemShape, item.Position, item.EquippableItem.Rotation).CenterOfMass; var toCenter = hullCenter - itemCenter; - Torque = -dot(normalize(toCenter), float2(1, 0).Rotate(item.EquippableItem.Rotation)); + Torque = -dot(normalize(toCenter), AetheriaMath.Rotate(new cfloat2(1, 0), item.EquippableItem.Rotation)); Thrust = Evaluate(_thrust); } @@ -65,9 +65,10 @@ public class Thruster : Behavior, IAnalogBehavior if(_input > .01f && Entity.TryConsumeEnergy(_input * Evaluate(_energyUsage))) { Thrust = Evaluate(_thrust); - Entity.Velocity -= Direction.xz * _input * Thrust / Entity.Mass * dt; - Entity.Direction = mul(Entity.Direction, - Unity.Mathematics.float2x2.Rotate(_input * Torque * Thrust * ItemManager.GameplaySettings.TorqueMultiplier / Entity.Mass * dt)); + Entity.CultVelocity -= Direction.xz * _input * Thrust / Entity.Mass * dt; + Entity.CultDirection = AetheriaMath.Rotate( + Entity.CultDirection, + _input * Torque * Thrust * ItemManager.GameplaySettings.TorqueMultiplier / Entity.Mass * dt); AddHeat(_input * Evaluate(_heat) * dt); var vis = _input * Evaluate(_visibility); if (!Entity.VisibilitySources.TryGetValue(this, out var existingVisibility) || vis > existingVisibility) diff --git a/Assets/Scripts/ServerShared/Behaviors/TurretController.cs b/Assets/Scripts/ServerShared/Behaviors/TurretController.cs index b42655d2c..adcadc2cf 100644 --- a/Assets/Scripts/ServerShared/Behaviors/TurretController.cs +++ b/Assets/Scripts/ServerShared/Behaviors/TurretController.cs @@ -5,9 +5,9 @@ using System.Collections.Generic; using System.Linq; using UniRx; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using float3 = Unity.Mathematics.float3; +using static CultMath.math; +using cfloat3 = CultMath.float3; + public class TurretController : Behavior, IInitializableBehavior { private List _weapons = new List(); @@ -44,27 +44,29 @@ public class TurretController : Behavior, IInitializableBehavior { if (Entity.Target.Value != null) { - var diff = Entity.Target.Value.Position - Entity.Position; + var entityPosition = Entity.CultPosition; + var targetPosition = Entity.Target.Value.CultPosition; + var diff = targetPosition - entityPosition; if (_predictShots) { var targetHull = Entity.ItemManager.GetRuntimeItem(Entity.Target.Value.Hull); - var targetVelocity = float3(Entity.Target.Value.Velocity.x, 0, Entity.Target.Value.Velocity.y); + var targetVelocity = new cfloat3(Entity.Target.Value.CultVelocity.x, 0, Entity.Target.Value.CultVelocity.y); var predictedPosition = AetheriaMath.FirstOrderIntercept( - Entity.Position, float3.zero, _shotSpeed, - Entity.Target.Value.Position, targetVelocity + entityPosition, cfloat3.zero, _shotSpeed, + targetPosition, targetVelocity ); predictedPosition.y = Entity.Zone.GetHeight(predictedPosition.xz) + (float)(targetHull?.HullGridOffset ?? 0); - Entity.LookDirection = normalize(predictedPosition - Entity.Position); + Entity.CultLookDirection = normalize(predictedPosition - entityPosition); } else - Entity.LookDirection = normalize(diff); + Entity.CultLookDirection = normalize(diff); var dist = length(diff); foreach (var x in _weapons) { var fire = dot( x.Direction, - Entity.LookDirection) > .99f; + Entity.CultLookDirection) > .99f; if (x.EvaluateRange() > dist && fire) { x.Activate(); diff --git a/Assets/Scripts/ServerShared/Behaviors/VelocityConversion.cs b/Assets/Scripts/ServerShared/Behaviors/VelocityConversion.cs index 54b3c7ea6..d341d6ac4 100644 --- a/Assets/Scripts/ServerShared/Behaviors/VelocityConversion.cs +++ b/Assets/Scripts/ServerShared/Behaviors/VelocityConversion.cs @@ -2,8 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; + public class VelocityConversion : Behavior { private readonly PerformanceStat _lambda; @@ -22,7 +22,8 @@ public class VelocityConversion : Behavior public override bool Execute(float dt) { - Entity.Velocity = AetheriaMath.Damp(Entity.Velocity, Entity.Direction * length(Entity.Velocity), Evaluate(_lambda), dt); + var velocity = Entity.CultVelocity; + Entity.CultVelocity = AetheriaMath.Damp(velocity, Entity.CultDirection * length(velocity), Evaluate(_lambda), dt); return true; } } diff --git a/Assets/Scripts/ServerShared/Behaviors/VelocityLimit.cs b/Assets/Scripts/ServerShared/Behaviors/VelocityLimit.cs index 58ef9b3d3..41a5df2d8 100644 --- a/Assets/Scripts/ServerShared/Behaviors/VelocityLimit.cs +++ b/Assets/Scripts/ServerShared/Behaviors/VelocityLimit.cs @@ -2,8 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; public class VelocityLimit : Behavior { @@ -28,8 +27,9 @@ public class VelocityLimit : Behavior public override bool Execute(float dt) { Limit = Evaluate(_topSpeed); - if (length(Entity.Velocity) > Limit) - Entity.Velocity = normalize(Entity.Velocity) * Limit; + var velocity = Entity.CultVelocity; + if (length(velocity) > Limit) + Entity.CultVelocity = normalize(velocity) * Limit; return true; } diff --git a/Assets/Scripts/ServerShared/Behaviors/Weapon.cs b/Assets/Scripts/ServerShared/Behaviors/Weapon.cs index 10a220029..4aa148d73 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Weapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Weapon.cs @@ -4,8 +4,8 @@ using System.Collections.Generic; using System.Linq; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; +using float4 = CultMath.float4; public abstract class Weapon : Behavior, IActivatedBehavior { @@ -156,6 +156,25 @@ public abstract class Weapon : Behavior, IActivatedBehavior Velocity = Evaluate(_velocity); } + protected float EvaluateRangeDamage(float range) + { + var normalizedRange = saturate(unlerp(MinRange, Range, range)); + return Evaluate(_damage, StatConditionMask.Range, normalizedRange) * + DamageCurve.Evaluate(normalizedRange); + } + + protected float EvaluateRangeDamage(float range, StatConditionMask condition, float value) + { + var normalizedRange = saturate(unlerp(MinRange, Range, range)); + return Evaluate(_damage, StatConditionMask.Range, normalizedRange, condition, value) * + DamageCurve.Evaluate(normalizedRange); + } + + protected float EvaluateDamage(StatConditionMask condition, float value) + { + return Evaluate(_damage, condition, value); + } + public override bool Execute(float dt) { UpdateStats(); diff --git a/Assets/Scripts/ServerShared/Behaviors/Wear.cs b/Assets/Scripts/ServerShared/Behaviors/Wear.cs index 728a9f4f4..0ff1c14e2 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Wear.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Wear.cs @@ -2,10 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; - public class Wear : Behavior { private readonly bool _perSecond; diff --git a/Assets/Scripts/ServerShared/BezierCurve.cs b/Assets/Scripts/ServerShared/BezierCurve.cs index f05224ea8..b08f07638 100644 --- a/Assets/Scripts/ServerShared/BezierCurve.cs +++ b/Assets/Scripts/ServerShared/BezierCurve.cs @@ -1,7 +1,8 @@ using System; +using System.Collections.Generic; using System.Linq; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using CultMath; +using static CultMath.math; public class BezierCurve { @@ -13,6 +14,16 @@ public class BezierCurve private float? _maximum; + public static BezierCurve FromKeys(IEnumerable<(float time, float value, float inTangent, float outTangent)> keys) + { + return new BezierCurve + { + Keys = keys + .Select(key => float4(key.time, key.value, key.inTangent, key.outTangent)) + .ToArray() + }; + } + public float Maximum { get diff --git a/Assets/Scripts/ServerShared/ColorMath.cs b/Assets/Scripts/ServerShared/ColorMath.cs index d1616d5be..0d3e48f74 100644 --- a/Assets/Scripts/ServerShared/ColorMath.cs +++ b/Assets/Scripts/ServerShared/ColorMath.cs @@ -2,15 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System.Collections; -using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; +using cfloat3 = CultMath.float3; +using cfloat4 = CultMath.float4; // Thanks to Ian Taylor at https://www.chilliant.com/rgb2hsv.html public static class ColorMath { - public static float3 HueToRgb(in float h) + public static cfloat3 HueToRgb(in float h) { float r = abs(h * 6 - 3) - 1; float g = 2 - abs(h * 6 - 2); @@ -18,40 +17,40 @@ public static class ColorMath return saturate(float3(r,g,b)); } - public static float3 HsvToRgb(in float3 hsv) + public static cfloat3 HsvToRgb(in cfloat3 hsv) { - float3 rgb = HueToRgb(hsv.x); + cfloat3 rgb = HueToRgb(hsv.x); return ((rgb - 1) * hsv.y + 1) * hsv.z; } - - public static float3 HslToRgb(in float3 hsl) + + public static cfloat3 HslToRgb(in cfloat3 hsl) { - float3 rgb = HueToRgb(hsl.x); + cfloat3 rgb = HueToRgb(hsl.x); float c = (1 - abs(2 * hsl.z - 1)) * hsl.y; return (rgb - 0.5f) * c + hsl.z; } - + const float Epsilon = 1e-10f; - public static float3 RgbToHcv(in float3 rgb) + public static cfloat3 RgbToHcv(in cfloat3 rgb) { // Based on work by Sam Hocevar and Emil Persson - float4 p = (rgb.y < rgb.z) ? float4(rgb.zy, -1.0f, 2.0f/3.0f) : float4(rgb.zy, 0.0f, -1.0f/3.0f); - float4 q = (rgb.x < p.x) ? float4(p.xyw, rgb.x) : float4(rgb.x, p.yzx); + cfloat4 p = (rgb.y < rgb.z) ? float4(rgb.zy, -1.0f, 2.0f/3.0f) : float4(rgb.zy, 0.0f, -1.0f/3.0f); + cfloat4 q = (rgb.x < p.x) ? float4(p.xyw, rgb.x) : float4(rgb.x, p.yzx); float c = q.x - min(q.w, q.y); float h = abs((q.w - q.y) / (6 * c + Epsilon) + q.z); return float3(h, c, q.x); } - - public static float3 RgbToHsv(in float3 rgb) + + public static cfloat3 RgbToHsv(in cfloat3 rgb) { - float3 hcv = RgbToHcv(rgb); + cfloat3 hcv = RgbToHcv(rgb); float s = hcv.y / (hcv.z + Epsilon); return float3(hcv.x, s, hcv.z); } - - public static float3 RgbToHsl(in float3 rgb) + + public static cfloat3 RgbToHsl(in cfloat3 rgb) { - float3 hcv = RgbToHcv(rgb); + cfloat3 hcv = RgbToHcv(rgb); float l = hcv.z - hcv.y * 0.5f; float s = hcv.y / (1 - abs(l * 2 - 1) + Epsilon); return float3(hcv.x, s, l); diff --git a/Assets/Scripts/ServerShared/Corporations.cs b/Assets/Scripts/ServerShared/Corporations.cs index 43ed5d80d..76251d89d 100644 --- a/Assets/Scripts/ServerShared/Corporations.cs +++ b/Assets/Scripts/ServerShared/Corporations.cs @@ -5,7 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; -using Unity.Mathematics; +using float3 = CultMath.float3; public class Faction : INamedEntry { diff --git a/Assets/Scripts/ServerShared/Entity.cs b/Assets/Scripts/ServerShared/Entity.cs index 819e88c4f..a862d807e 100644 --- a/Assets/Scripts/ServerShared/Entity.cs +++ b/Assets/Scripts/ServerShared/Entity.cs @@ -7,10 +7,36 @@ using System.Collections.Generic; using System.Linq; using GameCult.Aetheria.State.Unity; using UniRx; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using bool2 = CultMath.bool2; +using cfloat2 = CultMath.float2; +using cfloat3 = CultMath.float3; +using float2 = Unity.Mathematics.float2; +using float3 = Unity.Mathematics.float3; using int2 = Unity.Mathematics.int2; +internal static class AetheriaScalar +{ + public static float Clamp(float value, float min, float max) + { + return value < min ? min : value > max ? max : value; + } + + public static float Lerp(float from, float to, float t) + { + return from + (to - from) * t; + } + + public static float Unlerp(float from, float to, float value) + { + return Math.Abs(to - from) <= 0.000001f ? 0 : (value - from) / (to - from); + } + + public static float Saturate(float value) + { + return Clamp(value, 0, 1); + } +} + public abstract class Entity { public Zone Zone; @@ -19,8 +45,38 @@ public abstract class Entity public EquippedItem EquippedHull; public float3 Position; - public float2 Direction = float2(0,1); + public float2 Direction = new float2(0,1); public float2 Velocity; + + public cfloat3 CultPosition + { + get => AetheriaMath.ToCult(Position); + set => Position = AetheriaMath.ToUnity(value); + } + + public cfloat2 CultPositionXZ + { + get => AetheriaMath.ToCultXZ(Position); + set => Position.xz = AetheriaMath.ToUnity(value); + } + + public float CultPositionY + { + get => Position.y; + set => Position.y = value; + } + + public cfloat2 CultDirection + { + get => AetheriaMath.ToCult(Direction); + set => Direction = AetheriaMath.ToUnity(value); + } + + public cfloat2 CultVelocity + { + get => AetheriaMath.ToCult(Velocity); + set => Velocity = AetheriaMath.ToUnity(value); + } public float[,] Temperature; public float[,] NewTemperature; @@ -40,6 +96,18 @@ public abstract class Entity public ReactiveProperty Target = new ReactiveProperty((Entity)null); public float3 LookDirection; + + public cfloat3 CultLookDirection + { + get => AetheriaMath.ToCult(LookDirection); + set => LookDirection = AetheriaMath.ToUnity(value); + } + + public cfloat2 CultLookDirectionXZ + { + get => AetheriaMath.ToCultXZ(LookDirection); + set => LookDirection = AetheriaMath.ToUnityXZ(value); + } public string Name; @@ -47,9 +115,9 @@ public abstract class Entity public readonly Dictionary Messages = new Dictionary(); public readonly Dictionary VisibilitySources = new Dictionary(); - public readonly ReactiveDictionary EntityInfoGathered = new ReactiveDictionary(); - public readonly Dictionary HardpointTransforms = - new Dictionary(); + public readonly ReactiveDictionary EntityInfoGathered = new ReactiveDictionary(); + public readonly Dictionary HardpointTransforms = + new Dictionary(); public (List weapons, List items)[] WeaponGroups; @@ -95,6 +163,11 @@ public abstract class Entity { get => _active; } + + public void RestoreActiveState(bool active) + { + _active = active; + } public IEnumerable Weapons { @@ -317,8 +390,8 @@ public abstract class Entity public void RestoreThermalExposure(float heatstroke, float hypothermia) { - Heatstroke = saturate(heatstroke); - Hypothermia = saturate(hypothermia); + Heatstroke = AetheriaScalar.Saturate(heatstroke); + Hypothermia = AetheriaScalar.Saturate(hypothermia); } public void RestoreActiveConsumable(ConsumableItem item, float remainingDuration, float duration) @@ -436,7 +509,7 @@ public abstract class Entity throw new InvalidOperationException($"Unable to map entity {Name}: missing typed hull row for {Hull?.ItemKey}"); var hullShape = GetHullShape(typedHull); - EquippedHull = new EquippedItem(ItemManager, Hull, int2.zero, this); + EquippedHull = new EquippedItem(ItemManager, Hull, new int2(0, 0), this); Equipment.Add(EquippedHull); Mass = ItemManager.GetMass(Hull); Temperature = new float[hullShape.Width, hullShape.Height]; @@ -546,7 +619,7 @@ public abstract class Entity if (itemInstance is SimpleCommodity simpleCommodity) { - var targetQuantity = min(simpleCommodity.Quantity, quantity - quantityTransferred); + var targetQuantity = Math.Min(simpleCommodity.Quantity, quantity - quantityTransferred); if (!target.CargoBays.Any(c => originInventory.TryTransferItem(c, simpleCommodity, targetQuantity))) { quantityTransferred += targetQuantity - simpleCommodity.Quantity; @@ -714,7 +787,7 @@ public abstract class Entity if (cells == null) return shape; foreach (var cell in cells) - shape[new int2(cell.X, cell.Y)] = true; + shape.SetCell(cell.X, cell.Y, true); return shape; } @@ -801,14 +874,14 @@ public abstract class Entity // Don't allow equipping while deployed if (_active) { - hullCoord = int2.zero; + hullCoord = new int2(0, 0); return false; } var typedItem = ItemManager.GetRuntimeItem(item); var typedHull = ItemManager.GetRuntimeItem(Hull); if (typedItem == null || typedHull == null) { - hullCoord = int2.zero; + hullCoord = new int2(0, 0); return false; } @@ -850,7 +923,7 @@ public abstract class Entity } } - hullCoord = int2.zero; + hullCoord = new int2(0, 0); return false; } @@ -989,7 +1062,7 @@ public abstract class Entity { if(cap.Charge > 0.01f) { - var chargeRemoved = min(chargeToRemove / chargedCapacitors, cap.Charge); + var chargeRemoved = Math.Min(chargeToRemove / chargedCapacitors, cap.Charge); cap.AddCharge(-chargeRemoved); energy -= chargeRemoved; } @@ -1058,9 +1131,9 @@ public abstract class Entity public virtual void Update(float delta) { - TargetRange = Target.Value == null ? -1 : length(Position - Target.Value.Position); + TargetRange = Target.Value == null ? -1 : CultMath.math.length(CultPosition - Target.Value.CultPosition); - var localSecurityLevel = Zone.GetSecurityLevel(Position.xz); + var localSecurityLevel = Zone.GetSecurityLevel(CultPositionXZ); if (CurrentSecurityLevel.Value != localSecurityLevel) CurrentSecurityLevel.Value = localSecurityLevel; foreach (var v in VisibilitySources.Keys.ToArray()) @@ -1090,9 +1163,9 @@ public abstract class Entity if (cockpitTemp > ItemManager.GameplaySettings.HeatstrokeTemperature) { var previous = Heatstroke; - Heatstroke = saturate( + Heatstroke = AetheriaScalar.Saturate( Heatstroke + - pow(cockpitTemp - ItemManager.GameplaySettings.HeatstrokeTemperature, ItemManager.GameplaySettings.HeatstrokeExponent) * + MathF.Pow(cockpitTemp - ItemManager.GameplaySettings.HeatstrokeTemperature, ItemManager.GameplaySettings.HeatstrokeExponent) * ItemManager.GameplaySettings.HeatstrokeMultiplier * delta); if(previous < ItemManager.GameplaySettings.SevereHeatstrokeRiskThreshold && Heatstroke > ItemManager.GameplaySettings.SevereHeatstrokeRiskThreshold) HeatstrokeRisk.OnNext(Unit.Default); @@ -1104,15 +1177,15 @@ public abstract class Entity } else { - Heatstroke = saturate(Heatstroke - ItemManager.GameplaySettings.HeatstrokeRecoverySpeed * delta); + Heatstroke = AetheriaScalar.Saturate(Heatstroke - ItemManager.GameplaySettings.HeatstrokeRecoverySpeed * delta); } if (cockpitTemp < ItemManager.GameplaySettings.HypothermiaTemperature) { var previous = Hypothermia; - Hypothermia = saturate( + Hypothermia = AetheriaScalar.Saturate( Hypothermia + - pow(ItemManager.GameplaySettings.HypothermiaTemperature - cockpitTemp, ItemManager.GameplaySettings.HypothermiaExponent) * + MathF.Pow(ItemManager.GameplaySettings.HypothermiaTemperature - cockpitTemp, ItemManager.GameplaySettings.HypothermiaExponent) * ItemManager.GameplaySettings.HypothermiaMultiplier * delta); if(previous < ItemManager.GameplaySettings.SevereHeatstrokeRiskThreshold && Heatstroke > ItemManager.GameplaySettings.SevereHeatstrokeRiskThreshold) HypothermiaRisk.OnNext(Unit.Default); @@ -1124,7 +1197,7 @@ public abstract class Entity } else { - Hypothermia = saturate(Hypothermia - ItemManager.GameplaySettings.HypothermiaRecoverySpeed * delta); + Hypothermia = AetheriaScalar.Saturate(Hypothermia - ItemManager.GameplaySettings.HypothermiaRecoverySpeed * delta); } } @@ -1151,10 +1224,10 @@ public abstract class Entity if (Parent != null) { - Position = Parent.Position; - Velocity = Parent.Velocity; + CultPosition = Parent.CultPosition; + CultVelocity = Parent.CultVelocity; } - else Position.y = Zone.GetHeight(Position.xz) + (float)(ItemManager.GetRuntimeItem(Hull)?.HullGridOffset ?? 0); + else CultPositionY = Zone.GetHeight(CultPositionXZ) + (float)(ItemManager.GetRuntimeItem(Hull)?.HullGridOffset ?? 0); } private void UpdateTemperature(float delta) @@ -1175,7 +1248,7 @@ public abstract class Entity var totalTemp = temp / ItemManager.GameplaySettings.HeatConductionMultiplier; var totalConductivity = 1f / ItemManager.GameplaySettings.HeatConductionMultiplier; - if (hullShape[int2(v.x - 1, v.y)]) + if (hullShape.GetCell(v.x - 1, v.y)) { var conductivity = (GearOccupancy[v.x, v.y]?.Conductivity ?? 1) * (GearOccupancy[v.x - 1, v.y]?.Conductivity ?? 1) * @@ -1185,7 +1258,7 @@ public abstract class Entity totalTemp += Temperature[v.x - 1, v.y] * conductivity; } - if (hullShape[int2(v.x + 1, v.y)]) + if (hullShape.GetCell(v.x + 1, v.y)) { var conductivity = (GearOccupancy[v.x, v.y]?.Conductivity ?? 1) * (GearOccupancy[v.x + 1, v.y]?.Conductivity ?? 1) * @@ -1196,7 +1269,7 @@ public abstract class Entity } - if (hullShape[int2(v.x, v.y - 1)]) + if (hullShape.GetCell(v.x, v.y - 1)) { var conductivity = (GearOccupancy[v.x, v.y]?.Conductivity ?? 1) * (GearOccupancy[v.x, v.y - 1]?.Conductivity ?? 1) * @@ -1207,7 +1280,7 @@ public abstract class Entity } - if (hullShape[int2(v.x, v.y + 1)]) + if (hullShape.GetCell(v.x, v.y + 1)) { var conductivity = (GearOccupancy[v.x, v.y]?.Conductivity ?? 1) * (GearOccupancy[v.x, v.y + 1]?.Conductivity ?? 1) * @@ -1223,7 +1296,7 @@ public abstract class Entity // For all cells on the border of the entity, radiate some heat into space, increasing the visibility of the ship if (Parent==null && !hullInterior[v]) { - var rad = pow(NewTemperature[v.x, v.y], ItemManager.GameplaySettings.HeatRadiationExponent) * + var rad = MathF.Pow(NewTemperature[v.x, v.y], ItemManager.GameplaySettings.HeatRadiationExponent) * ItemManager.GameplaySettings.HeatRadiationMultiplier; NewTemperature[v.x, v.y] -= rad * delta; r += rad; @@ -1278,7 +1351,7 @@ public class ConsumableItemEffect _duration = duration.GetValueOrDefault(RuntimeItem?.Duration > 0 ? (float)RuntimeItem.Duration : 0); _effectiveness = CreateEffectivenessCurve(RuntimeItem); RemainingDuration = remainingDuration.HasValue - ? clamp(remainingDuration.Value, 0, Duration) + ? AetheriaScalar.Clamp(remainingDuration.Value, 0, Duration) : _duration; Behaviors = entity.ItemManager.CreateRuntimeBehaviors(this); @@ -1300,37 +1373,101 @@ public class ConsumableItemEffect public float Evaluate(PerformanceStat stat) { + if (stat == null) + return 0; + var duration = Math.Max(_duration, float.Epsilon); var effectiveness = _effectiveness.Evaluate((duration - RemainingDuration) / duration); - var quality = pow(Item.Quality, stat.QualityExponent); - - var result = lerp(stat.Min, stat.Max, effectiveness * quality); + var result = stat.EvaluateConsumableBaseline(new StatEvaluationContext( + quality: Item.Quality, + durability: effectiveness, + heat: 1)); if (float.IsNaN(result)) return stat.Min; return result; } + public float Evaluate(PerformanceStat stat, StatConditionMask condition, float value) + { + if (stat == null) + return 0; + + if ((GetDependencyMask(stat) & condition) == 0) + return Evaluate(stat); + + var duration = Math.Max(_duration, float.Epsilon); + var effectiveness = _effectiveness.Evaluate((duration - RemainingDuration) / duration); + var context = new StatEvaluationContext( + quality: Item.Quality, + durability: effectiveness, + heat: 1).WithCondition(condition, value); + + var result = stat.EvaluateRecipeOrLegacy( + context, + durabilityExponent: 1, + thermalExponent: 0, + includeHeat: false); + + if (float.IsNaN(result)) + return stat.Min; + return result; + } + + public float Evaluate( + PerformanceStat stat, + StatConditionMask firstCondition, + float firstValue, + StatConditionMask secondCondition, + float secondValue) + { + if (stat == null) + return 0; + + var dependencyMask = GetDependencyMask(stat); + if ((dependencyMask & (firstCondition | secondCondition)) == 0) + return Evaluate(stat); + + var duration = Math.Max(_duration, float.Epsilon); + var effectiveness = _effectiveness.Evaluate((duration - RemainingDuration) / duration); + var context = new StatEvaluationContext( + quality: Item.Quality, + durability: effectiveness, + heat: 1) + .WithCondition(firstCondition, firstValue) + .WithCondition(secondCondition, secondValue); + + var result = stat.EvaluateRecipeOrLegacy( + context, + durabilityExponent: 1, + thermalExponent: 0, + includeHeat: false); + + if (float.IsNaN(result)) + return stat.Min; + return result; + } + + private static StatConditionMask GetDependencyMask(PerformanceStat stat) + { + return stat?.GetDependencyMask(includeHeat: false) ?? StatConditionMask.None; + } + private static BezierCurve CreateEffectivenessCurve(AetheriaRuntimeCatalogItem item) { if (item?.EffectivenessCurveKeys != null && item.EffectivenessCurveKeys.Count > 0) { - return new BezierCurve - { - Keys = item.EffectivenessCurveKeys - .Select(key => new float4( + return BezierCurve.FromKeys( + item.EffectivenessCurveKeys + .Select(key => ( (float)key.Time, (float)key.Value, (float)key.InTangent, (float)key.OutTangent)) - .ToArray() - }; + ); } - return new BezierCurve - { - Keys = new[] { new float4(0, 1, 0, 0), new float4(1, 1, 0, 0) } - }; + return BezierCurve.FromKeys(new[] { (0f, 1f, 0f, 0f), (1f, 1f, 0f, 0f) }); } } @@ -1371,6 +1508,26 @@ public class EquippedItem private readonly BezierCurve _thermalPerformanceCurve; private readonly EquippedItemAudioStatBinding[] _audioStats; + private readonly Dictionary _baseStatCache = + new Dictionary(); + private readonly Dictionary _statCache = + new Dictionary(); + private int _qualityVersion; + private int _durabilityVersion; + private int _thermalVersion; + private int _chargeVersion; + private int _ammoVersion; + private int _rangeVersion; + private int _integrityVersion; + private int _pilotSkillVersion; + private int _environmentVersion; + private float _lastQuality; + private float _statCharge; + private float _statAmmo; + private float _statRange; + private float _statIntegrity; + private float _statPilotSkill; + private float _statEnvironment; private float oldTemperature; public float Temperature { @@ -1465,14 +1622,15 @@ public class EquippedItem ThermalResilience = RuntimeItem?.ThermalResilience > 0 ? (float)RuntimeItem.ThermalResilience : 1; _thermalPerformanceCurve = CreateThermalPerformanceCurve(RuntimeItem); _audioStats = CreateAudioStatBindings(RuntimeItem); - ThermalExponent = lerp( + ThermalExponent = AetheriaScalar.Lerp( ItemManager.GameplaySettings.ThermalQualityMin, ItemManager.GameplaySettings.ThermalQualityMax, - pow(item.Quality, ItemManager.GameplaySettings.ThermalQualityExponent)); - DurabilityExponent = lerp( + MathF.Pow(item.Quality, ItemManager.GameplaySettings.ThermalQualityExponent)); + DurabilityExponent = AetheriaScalar.Lerp( ItemManager.GameplaySettings.DurabilityQualityMin, ItemManager.GameplaySettings.DurabilityQualityMax, - pow(item.Quality, ItemManager.GameplaySettings.DurabilityQualityExponent)); + MathF.Pow(item.Quality, ItemManager.GameplaySettings.DurabilityQualityExponent)); + _lastQuality = item.Quality; var hullShape = ItemManager.GetRuntimeShape(entity.Hull); var itemShape = ItemManager.GetRuntimeShape(item); InsetShape = hullShape.Inset(itemShape, position, item.Rotation); @@ -1503,25 +1661,183 @@ public class EquippedItem } } + internal void RegisterPerformanceStat(string behaviorKind, string statName, PerformanceStat stat) + { + if (stat == null || string.IsNullOrWhiteSpace(statName)) + return; + + if (!_statCache.TryGetValue(stat, out var entry)) + { + _statCache[stat] = new EquippedItemStatCacheEntry(stat, GetDependencyMask(stat)); + } + else + { + entry.Stat = stat; + entry.DependencyMask = GetDependencyMask(stat); + entry.Dirty = true; + } + } + + public float Evaluate(string behaviorKind, string statName, PerformanceStat stat) + { + RegisterPerformanceStat(behaviorKind, statName, stat); + return Evaluate(stat); + } + public float Evaluate(PerformanceStat stat) { - var heat = pow(ThermalPerformance, ThermalExponent * stat.HeatExponentMultiplier); - var durability = pow(DurabilityPerformance, DurabilityExponent * stat.DurabilityExponentMultiplier); - var quality = pow(EquippableItem.Quality, stat.QualityExponent); + if (stat == null) + { + return 0; + } - var scaleModifier = 1.0f; - var scaleModifiers = stat.GetScaleModifiers(Entity).Values; - foreach (var value in scaleModifiers) scaleModifier *= value; + if (_statCache.TryGetValue(stat, out var entry)) + { + return EvaluateCachedStat(entry); + } - float constantModifier = 0; - foreach (var value in stat.GetConstantModifiers(Entity).Values) constantModifier += value; + return EvaluateUncached(stat); + } - var result = lerp(stat.Min, stat.Max, durability * quality * heat) * scaleModifier + constantModifier; + public float Evaluate(PerformanceStat stat, StatConditionMask condition, float value) + { + if (stat == null) + return 0; + + var dependencyMask = GetDependencyMask(stat); + if ((dependencyMask & condition) == 0) + return Evaluate(stat); + + return EvaluateWithContext(stat, CreateStatEvaluationContext().WithCondition(condition, value)); + } + + public float Evaluate( + PerformanceStat stat, + StatConditionMask firstCondition, + float firstValue, + StatConditionMask secondCondition, + float secondValue) + { + if (stat == null) + return 0; + + var dependencyMask = GetDependencyMask(stat); + if ((dependencyMask & (firstCondition | secondCondition)) == 0) + return Evaluate(stat); + + var context = CreateStatEvaluationContext() + .WithCondition(firstCondition, firstValue) + .WithCondition(secondCondition, secondValue); + return EvaluateWithContext(stat, context); + } + + private float EvaluateCachedStat(EquippedItemStatCacheEntry entry) + { + var stamp = BuildCacheStamp(entry.DependencyMask); + if (!entry.Dirty && entry.InputStamp == stamp) + { + return entry.Value; + } + + var value = EvaluateUncached(entry.Stat); + entry.Value = value; + entry.InputStamp = stamp; + entry.Dirty = false; + return value; + } + + private float EvaluateUncached(PerformanceStat stat) + { + var dependencyMask = GetDependencyMask(stat); + var stamp = BuildCacheStamp(dependencyMask); + if (!_baseStatCache.TryGetValue(stat, out var baseEntry) || baseEntry.InputStamp != stamp) + { + float baseValue; + baseValue = stat.EvaluateRecipeOrLegacy( + CreateStatEvaluationContext(), + DurabilityExponent, + ThermalExponent, + includeHeat: true); + _baseStatCache[stat] = new EquippedItemBaseStatCacheEntry(baseValue, stamp); + } + var cachedBaseValue = _baseStatCache[stat].Value; + + var result = stat.ApplyEntityModifiers(Entity, cachedBaseValue); if (float.IsNaN(result)) return stat.Min; return result; } + private float EvaluateWithContext(PerformanceStat stat, in StatEvaluationContext context) + { + var baseValue = stat.EvaluateRecipeOrLegacy( + context, + DurabilityExponent, + ThermalExponent, + includeHeat: true); + + var result = stat.ApplyEntityModifiers(Entity, baseValue); + return float.IsNaN(result) ? stat.Min : result; + } + + internal void SetStatModifier( + PerformanceStat stat, + Behavior source, + StatModifierType type, + float value) + { + if (stat == null || source == null) + return; + + var modifiers = type == StatModifierType.Constant + ? stat.GetConstantModifiers(Entity) + : stat.GetScaleModifiers(Entity); + + if (modifiers.TryGetValue(source, out var current) && MathF.Abs(current - value) < 0.00001f) + return; + + modifiers[source] = value; + InvalidateStat(stat); + } + + internal void RemoveStatModifier(PerformanceStat stat, Behavior source, StatModifierType type) + { + if (stat == null || source == null) + return; + + var hasModifiers = type == StatModifierType.Constant + ? stat.TryGetConstantModifiers(Entity, out var modifiers) + : stat.TryGetScaleModifiers(Entity, out modifiers); + + if (hasModifiers && modifiers.Remove(source)) + InvalidateStat(stat); + } + + public void SetStatConditionValue(StatConditionMask condition, float value) + { + switch (condition) + { + case StatConditionMask.Charge: + SetTrackedStatCondition(ref _statCharge, ref _chargeVersion, value); + break; + case StatConditionMask.Ammo: + SetTrackedStatCondition(ref _statAmmo, ref _ammoVersion, value); + break; + case StatConditionMask.Range: + SetTrackedStatCondition(ref _statRange, ref _rangeVersion, value); + break; + case StatConditionMask.Integrity: + SetTrackedStatCondition(ref _statIntegrity, ref _integrityVersion, value); + break; + case StatConditionMask.PilotSkill: + SetTrackedStatCondition(ref _statPilotSkill, ref _pilotSkillVersion, value); + break; + case StatConditionMask.Environment: + SetTrackedStatCondition(ref _statEnvironment, ref _environmentVersion, value); + break; + } + } + public void AddHeat(float heat, bool ignoreThermalMass = false) { foreach(var hullCoord in InsetShape.Coordinates) @@ -1531,12 +1847,28 @@ public class EquippedItem public void UpdatePerformance() { var temp = Temperature; - ThermalPerformance = EvaluateThermalPerformance(temp); - var deltaTemp = math.abs(temp - oldTemperature); - DurabilityPerformance = EquippableItem.Durability / MaxDurability; + var nextThermalPerformance = EvaluateThermalPerformance(temp); + var deltaTemp = MathF.Abs(temp - oldTemperature); + var nextDurabilityPerformance = EquippableItem.Durability / MaxDurability; + var nextQuality = EquippableItem.Quality; + if (MathF.Abs(nextThermalPerformance - ThermalPerformance) > 0.00001f) + { + _thermalVersion++; + } + if (MathF.Abs(nextDurabilityPerformance - DurabilityPerformance) > 0.00001f) + { + _durabilityVersion++; + } + if (MathF.Abs(nextQuality - _lastQuality) > 0.00001f) + { + _qualityVersion++; + } + ThermalPerformance = nextThermalPerformance; + DurabilityPerformance = nextDurabilityPerformance; + _lastQuality = nextQuality; var performanceThreshold = Entity.Settings.ShutdownPerformance; - Wear = (1 - pow(ThermalPerformance, - (1 - pow(EquippableItem.Quality, ItemManager.GameplaySettings.QualityWearExponent)) * + Wear = (1 - MathF.Pow(ThermalPerformance, + (1 - MathF.Pow(EquippableItem.Quality, ItemManager.GameplaySettings.QualityWearExponent)) * ItemManager.GameplaySettings.ThermalWearExponent) + deltaTemp * ItemManager.GameplaySettings.DeltaTempWearExponent ) * MaxDurability / ThermalResilience; @@ -1545,6 +1877,67 @@ public class EquippedItem oldTemperature = temp; } + private void InvalidateStat(PerformanceStat stat) + { + if (stat != null && _statCache.TryGetValue(stat, out var entry)) + entry.Dirty = true; + } + + private StatConditionMask GetDependencyMask(PerformanceStat stat) + { + return stat?.GetDependencyMask(includeHeat: true) ?? StatConditionMask.None; + } + + private int BuildCacheStamp(StatConditionMask dependencyMask) + { + unchecked + { + var stamp = 17; + if ((dependencyMask & StatConditionMask.Quality) != 0) + stamp = stamp * 31 + _qualityVersion; + if ((dependencyMask & StatConditionMask.Durability) != 0) + stamp = stamp * 31 + _durabilityVersion; + if ((dependencyMask & StatConditionMask.Heat) != 0) + stamp = stamp * 31 + _thermalVersion; + if ((dependencyMask & StatConditionMask.Charge) != 0) + stamp = stamp * 31 + _chargeVersion; + if ((dependencyMask & StatConditionMask.Ammo) != 0) + stamp = stamp * 31 + _ammoVersion; + if ((dependencyMask & StatConditionMask.Range) != 0) + stamp = stamp * 31 + _rangeVersion; + if ((dependencyMask & StatConditionMask.Integrity) != 0) + stamp = stamp * 31 + _integrityVersion; + if ((dependencyMask & StatConditionMask.PilotSkill) != 0) + stamp = stamp * 31 + _pilotSkillVersion; + if ((dependencyMask & StatConditionMask.Environment) != 0) + stamp = stamp * 31 + _environmentVersion; + return stamp; + } + } + + private StatEvaluationContext CreateStatEvaluationContext() + { + return new StatEvaluationContext( + quality: EquippableItem.Quality, + durability: DurabilityPerformance, + heat: ThermalPerformance, + charge: _statCharge, + ammo: _statAmmo, + range: _statRange, + integrity: _statIntegrity, + pilotSkill: _statPilotSkill, + environment: _statEnvironment); + } + + private static void SetTrackedStatCondition(ref float current, ref int version, float next) + { + if (MathF.Abs(current - next) <= 0.00001f) + return; + + current = next; + version++; + } + private float EvaluateThermalPerformance(float temperature) { if (_thermalPerformanceCurve == null || @@ -1554,8 +1947,8 @@ public class EquippedItem return 1; } - var t = unlerp((float)RuntimeItem.MinimumTemperature, (float)RuntimeItem.MaximumTemperature, temperature); - return saturate(_thermalPerformanceCurve.Evaluate(t)); + var t = AetheriaScalar.Unlerp((float)RuntimeItem.MinimumTemperature, (float)RuntimeItem.MaximumTemperature, temperature); + return AetheriaScalar.Saturate(_thermalPerformanceCurve.Evaluate(t)); } private static BezierCurve CreateThermalPerformanceCurve(AetheriaRuntimeCatalogItem item) @@ -1565,12 +1958,9 @@ public class EquippedItem return null; } - return new BezierCurve - { - Keys = item.ThermalPerformanceCurveKeys - .Select(key => new float4((float)key.Time, (float)key.Value, (float)key.InTangent, (float)key.OutTangent)) - .ToArray() - }; + return BezierCurve.FromKeys( + item.ThermalPerformanceCurveKeys + .Select(key => ((float)key.Time, (float)key.Value, (float)key.InTangent, (float)key.OutTangent))); } public void Update(float delta) @@ -1626,10 +2016,50 @@ public class EquippedItem Max = (float)stat.Max, HeatExponentMultiplier = (float)stat.HeatExponentMultiplier, DurabilityExponentMultiplier = (float)stat.DurabilityExponentMultiplier, - QualityExponent = (float)stat.QualityExponent + QualityExponent = (float)stat.QualityExponent, + Recipe = CreateStatRecipe(stat.Recipe) }; } + private static StatRecipe CreateStatRecipe(AetheriaRuntimeStatRecipe recipe) + { + if (recipe == null) + return null; + + return new StatRecipe + { + BaseValue = (float)recipe.BaseValue, + Modifiers = recipe.Modifiers + .Select(CreateStatRecipeModifier) + .Where(modifier => modifier != null) + .ToArray() + }; + } + + private static StatRecipeModifier CreateStatRecipeModifier(AetheriaRuntimeStatRecipeModifier modifier) + { + if (modifier == null) + return null; + + return new StatRecipeModifier + { + Condition = StatRecipeTokens.ToConditionMask(modifier.Condition), + Operation = StatRecipeTokens.ToModifierOperation(modifier.Operation), + Amount = (float)modifier.Amount, + Curve = CreateCurve(modifier.CurveKeys), + Enabled = modifier.Enabled + }; + } + + private static BezierCurve CreateCurve(IReadOnlyList keys) + { + if (keys == null || keys.Count == 0) + return null; + + return BezierCurve.FromKeys( + keys.Select(key => ((float)key.Time, (float)key.Value, (float)key.InTangent, (float)key.OutTangent))); + } + private readonly struct EquippedItemAudioStatBinding { public EquippedItemAudioStatBinding(uint parameter, PerformanceStat stat) @@ -1641,6 +2071,34 @@ public class EquippedItem public uint Parameter { get; } public PerformanceStat Stat { get; } } + + private sealed class EquippedItemStatCacheEntry + { + public EquippedItemStatCacheEntry(PerformanceStat stat, StatConditionMask dependencyMask) + { + Stat = stat; + DependencyMask = dependencyMask; + Dirty = true; + } + + public PerformanceStat Stat { get; set; } + public StatConditionMask DependencyMask { get; set; } + public float Value { get; set; } + public int InputStamp { get; set; } + public bool Dirty { get; set; } + } + + private readonly struct EquippedItemBaseStatCacheEntry + { + public EquippedItemBaseStatCacheEntry(float value, int inputStamp) + { + Value = value; + InputStamp = inputStamp; + } + + public float Value { get; } + public int InputStamp { get; } + } } public class EquippedCargoBay : EquippedItem @@ -1735,7 +2193,7 @@ public class EquippedCargoBay : EquippedItem if (cargoCommodity.Quantity >= maxStack) continue; // Subtract remaining space in existing stack from remaining quantity - remainingQuantity -= min(maxStack - cargoCommodity.Quantity, remainingQuantity); + remainingQuantity -= Math.Min(maxStack - cargoCommodity.Quantity, remainingQuantity); positions.Add(Cargo[cargoItem]); // If we've moved all of the items into existing stacks, no need to search for empty space! @@ -1769,7 +2227,7 @@ public class EquippedCargoBay : EquippedItem } } - position = int2.zero; + position = new int2(0, 0); return false; } @@ -1978,7 +2436,7 @@ public class EquippedCargoBay : EquippedItem if (cells == null) return shape; foreach (var cell in cells) - shape[new int2(cell.X, cell.Y)] = true; + shape.SetCell(cell.X, cell.Y, true); return shape; } @@ -2025,7 +2483,7 @@ public class EquippedDockingBay : EquippedCargoBay var typedDockingBay = ItemManager.GetRuntimeItem(EquippableItem); MaxSize = typedDockingBay != null ? new int2(typedDockingBay.DockingMaxSizeX, typedDockingBay.DockingMaxSizeY) - : int2.zero; + : new int2(0, 0); } } diff --git a/Assets/Scripts/ServerShared/EntityConstructionBlueprintProjector.cs b/Assets/Scripts/ServerShared/EntityConstructionBlueprintProjector.cs index 14c61832f..866aa47cc 100644 --- a/Assets/Scripts/ServerShared/EntityConstructionBlueprintProjector.cs +++ b/Assets/Scripts/ServerShared/EntityConstructionBlueprintProjector.cs @@ -1,8 +1,8 @@ using System; -using System.Collections; -using System.Collections.Generic; using System.Linq; -using Unity.Mathematics; +using float2 = Unity.Mathematics.float2; +using float3 = Unity.Mathematics.float3; +using int2 = Unity.Mathematics.int2; public static class EntityConstructionBlueprintProjector { @@ -19,8 +19,8 @@ public static class EntityConstructionBlueprintProjector else if (entity is Ship ship) blueprint = new ShipConstructionBlueprint { - Position = ship.Position, - Direction = ship.Direction, + Position = AetheriaMath.ToUnity(ship.CultPosition), + Direction = AetheriaMath.ToUnity(ship.CultDirection), IsPlayerShip = ship.IsPlayerShip }; else throw new ArgumentException("Attempted to capture an instance of abstract class Entity!"); @@ -41,30 +41,40 @@ public static class EntityConstructionBlueprintProjector return blueprint; } - public static Entity InstantiateFromBlueprint(ItemManager itemManager, Zone zone, EntityConstructionBlueprint blueprint, bool instantiate = false) + public static Entity InstantiateAuthoritativeFromBlueprint(ItemManager itemManager, Zone zone, EntityConstructionBlueprint blueprint) + { + return BuildFromBlueprint(itemManager, zone, blueprint, false); + } + + public static Entity ProjectObservedFromBlueprint(ItemManager itemManager, Zone zone, EntityConstructionBlueprint blueprint) + { + return BuildFromBlueprint(itemManager, zone, blueprint, true); + } + + private static Entity BuildFromBlueprint(ItemManager itemManager, Zone zone, EntityConstructionBlueprint blueprint, bool instantiate) { blueprint.Settings ??= itemManager.GameplaySettings.DefaultEntitySettings.Copy(); return blueprint switch { - ShipConstructionBlueprint shipBlueprint => InstantiateFromBlueprint(itemManager, zone, shipBlueprint, instantiate), - OrbitalEntityConstructionBlueprint orbitalBlueprint => InstantiateFromBlueprint(itemManager, zone, orbitalBlueprint, instantiate), + ShipConstructionBlueprint shipBlueprint => BuildFromBlueprint(itemManager, zone, shipBlueprint, instantiate), + OrbitalEntityConstructionBlueprint orbitalBlueprint => BuildFromBlueprint(itemManager, zone, orbitalBlueprint, instantiate), _ => null }; } - private static Ship InstantiateFromBlueprint(ItemManager itemManager, Zone zone, ShipConstructionBlueprint blueprint, bool instantiate = false) + private static Ship BuildFromBlueprint(ItemManager itemManager, Zone zone, ShipConstructionBlueprint blueprint, bool instantiate) { var entity = new Ship(itemManager, zone, instantiate ? (EquippableItem) itemManager.Instantiate(blueprint.Hull) : blueprint.Hull, blueprint.Settings); Restore(itemManager, zone, blueprint, entity, instantiate); - entity.Position = blueprint.Position; - entity.Direction = blueprint.Direction; + entity.CultPosition = AetheriaMath.ToCult(blueprint.Position); + entity.CultDirection = AetheriaMath.ToCult(blueprint.Direction); entity.IsPlayerShip = blueprint.IsPlayerShip; return entity; } - private static OrbitalEntity InstantiateFromBlueprint(ItemManager itemManager, Zone zone, OrbitalEntityConstructionBlueprint blueprint, bool instantiate = false) + private static OrbitalEntity BuildFromBlueprint(ItemManager itemManager, Zone zone, OrbitalEntityConstructionBlueprint blueprint, bool instantiate) { var entity = new OrbitalEntity(itemManager, zone, instantiate ? (EquippableItem) itemManager.Instantiate(blueprint.Hull) : blueprint.Hull, blueprint.OrbitKey, blueprint.Settings); Restore(itemManager, zone, blueprint, entity, instantiate); @@ -80,7 +90,7 @@ public static class EntityConstructionBlueprintProjector entity.Faction = ResolveFaction(zone?.Galaxy, blueprint); entity.Children = blueprint.Children.Select(c => { - var child = InstantiateFromBlueprint(itemManager, zone, c, instantiate); + var child = BuildFromBlueprint(itemManager, zone, c, instantiate); child.Parent = entity; return child; }).ToList(); diff --git a/Assets/Scripts/ServerShared/Environment.cs b/Assets/Scripts/ServerShared/Environment.cs index 917e97272..d232a0bf5 100644 --- a/Assets/Scripts/ServerShared/Environment.cs +++ b/Assets/Scripts/ServerShared/Environment.cs @@ -1,8 +1,7 @@ using System; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using static Unity.Mathematics.noise; using float2 = Unity.Mathematics.float2; +using float3 = Unity.Mathematics.float3; +using unitynoise = Unity.Mathematics.noise; [Serializable] public class ZoneEnvironment @@ -78,8 +77,8 @@ public abstract class Brush float powerPulse( float x, float power ) { - x = saturate(abs(x))-.001f; - return pow((x + 1.0f) * (1.0f - x), power); + x = Saturate(MathF.Abs(x))-.001f; + return MathF.Pow((x + 1.0f) * (1.0f - x), power); } protected abstract float Evaluate(float2 world, float2 uv); @@ -87,10 +86,26 @@ public abstract class Brush public float Evaluate(float2 world, float2 pos, float2 radius) { var uv = (world - pos) / radius; - float dist = length(uv)*2; - float envelope = min(Cutoff, powerPulse(dist,EnvelopeExponent)) * smoothstep(1, .95f, dist); + float dist = Length(uv)*2; + float envelope = MathF.Min(Cutoff, powerPulse(dist,EnvelopeExponent)) * SmoothStep(1, .95f, dist); return Depth * Evaluate(world, uv) * envelope; } + + private static float Length(float2 value) + { + return MathF.Sqrt(value.x * value.x + value.y * value.y); + } + + private static float Saturate(float value) + { + return value < 0 ? 0 : value > 1 ? 1 : value; + } + + private static float SmoothStep(float edge0, float edge1, float x) + { + var t = Saturate((x - edge0) / (edge1 - edge0)); + return t * t * (3.0f - 2.0f * t); + } } [Serializable] @@ -124,8 +139,8 @@ public class SimplexBrush : TextureBrush protected override float Evaluate(float2 world, float2 uv) { - var noise = snoise(world * Frequency + Phase); - return AbsoluteValue ? abs(noise) : noise; + var noise = unitynoise.snoise(world * Frequency + Phase); + return AbsoluteValue ? MathF.Abs(noise) : noise; } } @@ -136,8 +151,8 @@ public class AnimatedSimplexBrush : AnimatedBrush protected override float Evaluate(float2 world, float2 uv) { - var noise = snoise(float3(float2(world * Frequency + Phase), AnimationSpeed * Time)); - return AbsoluteValue ? abs(noise) : noise; + var noise = unitynoise.snoise(new float3(world * Frequency + Phase, AnimationSpeed * Time)); + return AbsoluteValue ? MathF.Abs(noise) : noise; } } @@ -148,8 +163,8 @@ public class RadialWaveBrush : TextureBrush protected override float Evaluate(float2 world, float2 uv) { - float dist = length(uv); - float ang = atan2(uv.y,uv.x); - return cos((ang + Phase.x) * Frequency.x * PI + (pow(dist, WaveExponent) + Phase.y) * Frequency.y); + float dist = MathF.Sqrt(uv.x * uv.x + uv.y * uv.y); + float ang = MathF.Atan2(uv.y,uv.x); + return MathF.Cos((ang + Phase.x) * Frequency.x * MathF.PI + (MathF.Pow(dist, WaveExponent) + Phase.y) * Frequency.y); } } diff --git a/Assets/Scripts/ServerShared/ExponentialCurve.cs b/Assets/Scripts/ServerShared/ExponentialCurve.cs index 027319dae..15010f06c 100644 --- a/Assets/Scripts/ServerShared/ExponentialCurve.cs +++ b/Assets/Scripts/ServerShared/ExponentialCurve.cs @@ -1,12 +1,11 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public +/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; using System.Collections; using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using static CultMath.math; [Serializable] public class ExponentialCurve diff --git a/Assets/Scripts/ServerShared/Extensions.cs b/Assets/Scripts/ServerShared/Extensions.cs index 5950c3da0..93897b096 100644 --- a/Assets/Scripts/ServerShared/Extensions.cs +++ b/Assets/Scripts/ServerShared/Extensions.cs @@ -6,11 +6,8 @@ using System.Linq; using System.Numerics; using System.Reflection; using System.Text.RegularExpressions; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using Random = Unity.Mathematics.Random; -using Unity.Tiny; -using float2 = Unity.Mathematics.float2; +using float2 = CultMath.float2; +using Random = CultMath.Random; public static class Extensions { @@ -79,13 +76,13 @@ public static class Extensions switch (rotation) { case ItemRotation.None: - return float2(0, 1); + return new float2(0, 1); case ItemRotation.CounterClockwise: - return float2(-1, 0); + return new float2(-1, 0); case ItemRotation.Reversed: - return float2(0, -1); + return new float2(0, -1); case ItemRotation.Clockwise: - return float2(1, 0); + return new float2(1, 0); default: throw new ArgumentOutOfRangeException(nameof(rotation), rotation, null); } @@ -98,11 +95,11 @@ public static class Extensions case ItemRotation.None: return v; case ItemRotation.CounterClockwise: - return float2(-v.y, v.x); + return new float2(-v.y, v.x); case ItemRotation.Reversed: - return float2(-v.x, -v.y); + return new float2(-v.x, -v.y); case ItemRotation.Clockwise: - return float2(v.y, -v.x); + return new float2(v.y, -v.x); default: throw new ArgumentOutOfRangeException(nameof(rotation), rotation, null); } @@ -112,9 +109,10 @@ public static class Extensions //private static Random Random => (Random) (_random ??= new Random((uint) (DateTime.Now.Ticks%uint.MaxValue))); // public static T RandomElement(this IEnumerable enumerable) => enumerable.ElementAt(Random.NextInt(0, enumerable.Count())); public static float NextPowerDistribution(this ref Random random, float min, float max, float exp, float randexp) => - pow((pow(max, exp + 1) - pow(min, exp + 1)) * pow(random.NextFloat(), randexp) + pow(min, exp + 1), 1 / (exp + 1)); + MathF.Pow((MathF.Pow(max, exp + 1) - MathF.Pow(min, exp + 1)) * MathF.Pow(random.NextFloat(), randexp) + MathF.Pow(min, exp + 1), 1 / (exp + 1)); public static float NextUnbounded(this ref Random random) => 1 / (1 - random.NextFloat()) - 1; - public static float NextUnbounded(this ref Random random, float bias, float power, float ceiling) => 1 / (1 - pow(min(random.NextFloat(), ceiling), 1 - pow(clamp(bias,0,.99f), 1 / power))) - 1; + public static float NextUnbounded(this ref Random random, float bias, float power, float ceiling) => + 1 / (1 - MathF.Pow(MathF.Min(random.NextFloat(), ceiling), 1 - MathF.Pow(Clamp(bias, 0, .99f), 1 / power))) - 1; public static bool IsDefault(this T value) where T : struct { @@ -130,8 +128,23 @@ public static class Extensions public static float Angle(this float2 from, float2 to) { - var num = sqrt(lengthsq(from) * lengthsq(to)); - return num < 1.00000000362749E-15 ? 0.0f : acos(clamp(dot(from, to) / num, -1f, 1f)) * 57.29578f; + var num = MathF.Sqrt(LengthSquared(from) * LengthSquared(to)); + return num < 1.00000000362749E-15 ? 0.0f : MathF.Acos(Clamp(Dot(from, to) / num, -1f, 1f)) * 57.29578f; + } + + private static float Clamp(float value, float min, float max) + { + return value < min ? min : value > max ? max : value; + } + + private static float Dot(float2 a, float2 b) + { + return a.x * b.x + a.y * b.y; + } + + private static float LengthSquared(float2 value) + { + return Dot(value, value); } } diff --git a/Assets/Scripts/ServerShared/Galaxy.cs b/Assets/Scripts/ServerShared/Galaxy.cs index 7cbe6571d..8c74708d4 100644 --- a/Assets/Scripts/ServerShared/Galaxy.cs +++ b/Assets/Scripts/ServerShared/Galaxy.cs @@ -10,8 +10,7 @@ using Ink.Runtime; using MIConvexHull; using JM.LinqFaster; using UniRx; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using float2 = Unity.Mathematics.float2; using Random = Unity.Mathematics.Random; public class Galaxy @@ -85,11 +84,144 @@ public class Galaxy CalculateFactionInfluence(progressCallback); - GenerateNames(nameGeneratorSettings, ref random, progressCallback); + var nameRandom = new CultMath.Random((uint)random.NextInt(1, int.MaxValue)); + GenerateNames(nameGeneratorSettings, ref nameRandom, progressCallback); progressCallback?.Invoke("Done!"); if(progressCallback!=null) Thread.Sleep(500); // Inserting Delay to make it seem like it's doing more work lmao } + + public static Galaxy ProjectObservedDaemonRun( + AetheriaRuntimeRunCheckpointCommit run, + SectorBackgroundSettings background, + AetheriaRuntimeCatalogSnapshot runtimeCatalog, + Action log) + { + return new Galaxy(run, background, runtimeCatalog, log); + } + + private Galaxy( + AetheriaRuntimeRunCheckpointCommit run, + SectorBackgroundSettings background, + AetheriaRuntimeCatalogSnapshot runtimeCatalog, + Action log) + { + if (run == null) throw new ArgumentNullException(nameof(run)); + + _runtimeCatalog = runtimeCatalog ?? throw new InvalidOperationException("Daemon-observed galaxy requires the typed Aetheria runtime catalog."); + _allFactions = ProjectFactions(_runtimeCatalog); + IsPrelude = run.IsTutorial; + Background = background; + Log = log; + GenerationSeed = run.GenerationSeed; + NameGeneratorSettings = null; + Factions = ResolveDaemonFactions(run); + foreach (var faction in Factions) + FactionRelationships[faction] = ResolveDaemonFactionRelationship(run, faction); + + Zones = (run.Zones ?? Array.Empty()) + .Where(zone => zone != null && zone.ZoneIndex >= 0) + .OrderBy(zone => zone.ZoneIndex) + .Select(zone => new GalaxyZone + { + Name = string.IsNullOrWhiteSpace(zone.Name) ? $"Daemon Zone {zone.ZoneIndex}" : zone.Name, + Position = new float2((float)zone.PositionX, (float)zone.PositionY), + Factions = ResolveDaemonZoneFactions(zone), + Owner = ResolveDaemonFactionByIndex(zone.OwnerFactionIndex), + NamedZone = !string.IsNullOrWhiteSpace(zone.Name) + }) + .ToArray(); + + var zonesByIndex = (run.Zones ?? Array.Empty()) + .Where(zone => zone != null && zone.ZoneIndex >= 0) + .OrderBy(zone => zone.ZoneIndex) + .Select((zone, ordinal) => new { zone.ZoneIndex, Zone = Zones[ordinal] }) + .ToDictionary(pair => pair.ZoneIndex, pair => pair.Zone); + + foreach (var source in run.Zones ?? Array.Empty()) + { + if (source == null || !zonesByIndex.TryGetValue(source.ZoneIndex, out var sourceZone)) + continue; + + foreach (var adjacentIndex in source.AdjacentZoneIndices ?? Array.Empty()) + { + if (zonesByIndex.TryGetValue(adjacentIndex, out var adjacentZone) && + !sourceZone.AdjacentZones.Contains(adjacentZone)) + { + sourceZone.AdjacentZones.Add(adjacentZone); + } + } + } + + Entrance = ResolveDaemonZone(run.EntranceZoneIndex) ?? ResolveDaemonZone(run.CurrentZoneIndex) ?? Zones.FirstOrDefault(); + Exit = ResolveDaemonZone(run.ExitZoneIndex); + foreach (var zone in run.DiscoveredZoneIndices ?? Array.Empty()) + { + var discovered = ResolveDaemonZone(zone); + if (discovered != null) + DiscoveredZones.Add(discovered); + } + if (DiscoveredZones.Count == 0 && Entrance != null) + DiscoveredZones.Add(Entrance); + + CalculateDistanceMatrix(); + + GalaxyZone ResolveDaemonZone(int zoneIndex) + { + return zonesByIndex.TryGetValue(zoneIndex, out var zone) ? zone : null; + } + } + + private Faction[] ResolveDaemonFactions(AetheriaRuntimeRunCheckpointCommit run) + { + var factionKeys = new HashSet( + (run.FactionRelationships ?? Array.Empty()) + .Select(relationship => relationship?.FactionKey) + .Where(key => !string.IsNullOrWhiteSpace(key)), + StringComparer.OrdinalIgnoreCase); + + var factionIndices = (run.Zones ?? Array.Empty()) + .Where(zone => zone != null) + .SelectMany(zone => (zone.FactionIndices ?? Array.Empty()).Concat(new[] { zone.OwnerFactionIndex })) + .Where(index => index >= 0) + .Distinct() + .ToArray(); + + foreach (var index in factionIndices) + { + if (index >= 0 && index < _allFactions.Length) + factionKeys.Add(_allFactions[index].FactionKey); + } + + var factions = _allFactions + .Where(faction => factionKeys.Contains(faction.FactionKey)) + .ToArray(); + return factions.Length == 0 ? _allFactions : factions; + } + + private Faction[] ResolveDaemonZoneFactions(AetheriaRuntimeZoneSnapshotCommit zone) + { + return (zone.FactionIndices ?? Array.Empty()) + .Select(ResolveDaemonFactionByIndex) + .Where(faction => faction != null) + .ToArray(); + } + + private Faction ResolveDaemonFactionByIndex(int index) + { + return index >= 0 && index < _allFactions.Length ? _allFactions[index] : null; + } + + private FactionRelationship ResolveDaemonFactionRelationship( + AetheriaRuntimeRunCheckpointCommit run, + Faction faction) + { + var relationship = (run.FactionRelationships ?? Array.Empty()) + .FirstOrDefault(candidate => string.Equals(candidate?.FactionKey ?? "", faction?.FactionKey ?? "", StringComparison.OrdinalIgnoreCase)); + return Enum.TryParse(relationship?.Relationship ?? "", out var parsed) + ? parsed + : FactionRelationship.Neutral; + } public Faction ResolveFaction(string name) { @@ -161,15 +293,15 @@ public class Galaxy .MaxBy(z => ConnectedRegion(z, antagonistFaction.InfluenceDistance).Count * z.Distance[HomeZones[protagonistFaction]]); HomeZones[protagonistFaction] = Zones - .MaxBy(z => ConnectedRegion(z, protagonistFaction.InfluenceDistance).Count * sqrt(z.Distance[HomeZones[antagonistFaction]])); + .MaxBy(z => ConnectedRegion(z, protagonistFaction.InfluenceDistance).Count * MathF.Sqrt(z.Distance[HomeZones[antagonistFaction]])); // var antagonistRegion = ConnectedRegion(HomeZones[antagonistFaction], antagonistFaction.InfluenceDistance); // var protagonistRegion = ConnectedRegion(HomeZones[protagonistFaction], protagonistFaction.InfluenceDistance); // Place the buffer faction in a zone where it has equal distance to the pro/antagonist HQs and where it can control the most territory - var bufferDistance = Zones.Min(z => abs(z.Distance[HomeZones[antagonistFaction]] - z.Distance[HomeZones[protagonistFaction]])); + var bufferDistance = Zones.Min(z => Math.Abs(z.Distance[HomeZones[antagonistFaction]] - z.Distance[HomeZones[protagonistFaction]])); var potentialBufferZones = Zones - .Where(z => abs(z.Distance[HomeZones[antagonistFaction]] - z.Distance[HomeZones[protagonistFaction]]) == bufferDistance); + .Where(z => Math.Abs(z.Distance[HomeZones[antagonistFaction]] - z.Distance[HomeZones[protagonistFaction]]) == bufferDistance); HomeZones[bufferFaction] = potentialBufferZones.MaxBy(z => ConnectedRegion(z, bufferFaction.InfluenceDistance).Count); // Place neutral headquarters away from existing factions while also maximizing territory @@ -177,7 +309,7 @@ public class Galaxy { HomeZones[faction] = Zones.MaxBy(z => ConnectedRegion(z, faction.InfluenceDistance).Count * - HomeZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z]))); + HomeZones.Values.Aggregate(1f, (i, os) => i * MathF.Sqrt(os.Distance[z]))); } CalculateFactionInfluence(progressCallback); @@ -201,7 +333,8 @@ public class Galaxy CalculateFactionInfluence(progressCallback); - GenerateNames(nameGeneratorSettings, ref random, progressCallback); + var nameRandom = new CultMath.Random((uint)random.NextInt(1, int.MaxValue)); + GenerateNames(nameGeneratorSettings, ref nameRandom, progressCallback); // progressCallback?.Invoke("Weaving Narrative"); // var processor = new StoryProcessor(playerSettings, narrativeDirectory, this, ref random, Log); @@ -216,7 +349,7 @@ public class Galaxy var outputSamples = WeightedSampleElimination.GeneratePoints(zoneCount, ref random, Background.CloudDensity, - v => (.2f - lengthsq(v - float2(.5f))) * 4, + v => (.2f - LengthSquared(v - new float2(.5f, .5f))) * 4, progressCallback); return outputSamples.Select(v => new GalaxyZone {Position = v}).ToArray(); } @@ -305,17 +438,17 @@ public class Galaxy HomeZones[mega] = ConnectedRegion(BossZones[mega], mega.InfluenceDistance) .MaxBy(z => ConnectedRegion(z, mega.InfluenceDistance).Count * - HomeZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z]))); + HomeZones.Values.Aggregate(1f, (i, os) => i * MathF.Sqrt(os.Distance[z]))); } // Place remaining headquarters away from existing megas while also maximizing territory foreach (var mega in Factions.Where(m => !bossMegas.Contains(m))) { HomeZones[mega] = Zones.MaxBy(z => - pow(ConnectedRegion(z, mega.InfluenceDistance).Count, HomeZones.Count) * + MathF.Pow(ConnectedRegion(z, mega.InfluenceDistance).Count, HomeZones.Count) * Exit.Distance[z] * Entrance.Distance[z] * - HomeZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z])) * - BossZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z]))); + HomeZones.Values.Aggregate(1f, (i, os) => i * MathF.Sqrt(os.Distance[z])) * + BossZones.Values.Aggregate(1f, (i, os) => i * MathF.Sqrt(os.Distance[z]))); } } @@ -343,7 +476,7 @@ public class Galaxy } private void GenerateNames(NameGeneratorSettings nameGeneratorSettings, - ref Random random, + ref CultMath.Random random, Action progressCallback = null) { for (var i = 0; i < Factions.Length; i++) @@ -416,8 +549,8 @@ public class Galaxy float LinkWeight((GalaxyZone, GalaxyZone) link) { - return 1 / saturate(Background.CloudDensity((link.Item1.Position + link.Item2.Position) / 2)) * - lengthsq(link.Item1.Position - link.Item2.Position) * + return 1 / Saturate(Background.CloudDensity((link.Item1.Position + link.Item2.Position) / 2)) * + LengthSquared(link.Item1.Position - link.Item2.Position) * (link.Item1.AdjacentZones.Count - 1) * (link.Item2.AdjacentZones.Count - 1); } @@ -583,12 +716,22 @@ public class Galaxy if (!bestFirst) zonesToSearch = zonesToSearch.Where(z => !searched.Contains(z)); foreach (var dijkstraStar in zonesToSearch // Cost is parent cost plus distance squared - .Select(zone => new DijkstraVertex {Parent = s, Zone = zone, Cost = s.Cost + lengthsq(s.Zone.Position - zone.Position)})) + .Select(zone => new DijkstraVertex {Parent = s, Zone = zone, Cost = s.Cost + LengthSquared(s.Zone.Position - zone.Position)})) // Add new member to list, sorted by cost plus optional heuristic - unsearchedNodes.PushObj(dijkstraStar, bestFirst ? dijkstraStar.Cost + lengthsq(dijkstraStar.Zone.Position - target.Position) : dijkstraStar.Cost); + unsearchedNodes.PushObj(dijkstraStar, bestFirst ? dijkstraStar.Cost + LengthSquared(dijkstraStar.Zone.Position - target.Position) : dijkstraStar.Cost); searched.Add(s.Zone); } } + + private static float LengthSquared(float2 value) + { + return value.x * value.x + value.y * value.y; + } + + private static float Saturate(float value) + { + return value < 0 ? 0 : value > 1 ? 1 : value; + } } public class GalaxyZone diff --git a/Assets/Scripts/ServerShared/ItemInstance.cs b/Assets/Scripts/ServerShared/ItemInstance.cs index 7d6f5d110..9cdb53c0d 100644 --- a/Assets/Scripts/ServerShared/ItemInstance.cs +++ b/Assets/Scripts/ServerShared/ItemInstance.cs @@ -6,8 +6,6 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; -using Unity.Mathematics; -using static Unity.Mathematics.math; public abstract class ItemInstance { diff --git a/Assets/Scripts/ServerShared/ItemManager.cs b/Assets/Scripts/ServerShared/ItemManager.cs index 0891cd506..48398230b 100644 --- a/Assets/Scripts/ServerShared/ItemManager.cs +++ b/Assets/Scripts/ServerShared/ItemManager.cs @@ -6,9 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; using GameCult.Aetheria.State.Unity; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using Random = Unity.Mathematics.Random; +using Random = CultMath.Random; using JM.LinqFaster; using UniRx; @@ -157,7 +155,7 @@ public class ItemManager if (cells == null) return shape; foreach (var cell in cells) - shape[new int2(cell.X, cell.Y)] = true; + shape.SetCell(cell.X, cell.Y, true); return shape; } @@ -219,19 +217,30 @@ public class ItemManager maxDurability = Math.Max(item.Durability, 1f); } - var quality = pow(item.Quality, stat.QualityExponent); - var durabilityExponent = lerp( + var durabilityRatio = item.Durability / maxDurability; + var durabilityExponent = Lerp( GameplaySettings.DurabilityQualityMin, GameplaySettings.DurabilityQualityMax, - pow(item.Quality, GameplaySettings.DurabilityQualityExponent)); - var durability = pow(item.Durability / maxDurability, durabilityExponent * stat.DurabilityExponentMultiplier); - var result = lerp(stat.Min, stat.Max, quality * durability); + MathF.Pow(item.Quality, GameplaySettings.DurabilityQualityExponent)); + var result = stat.EvaluateRecipeOrLegacy( + new StatEvaluationContext( + quality: item.Quality, + durability: durabilityRatio, + heat: 1), + durabilityExponent, + thermalExponent: 0, + includeHeat: false); if (float.IsNaN(result)) throw new InvalidOperationException($"Performance Stat on {typedItem?.Name ?? item.ItemKey} evaluating as NaN: input data is invalid! Durability: {item.Durability} / {maxDurability}"); return result; } + private static float Lerp(float from, float to, float t) + { + return from + (to - from) * t; + } + public int GetPrice(CraftedItemInstance item) { var typedItem = GetRuntimeItem(item); diff --git a/Assets/Scripts/ServerShared/LoadoutGenerator.cs b/Assets/Scripts/ServerShared/LoadoutGenerator.cs index ee04c898c..a586febaa 100644 --- a/Assets/Scripts/ServerShared/LoadoutGenerator.cs +++ b/Assets/Scripts/ServerShared/LoadoutGenerator.cs @@ -2,9 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using GameCult.Aetheria.State.Unity; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using Random = Unity.Mathematics.Random; +using Random = CultMath.Random; public class LoadoutGenerator { @@ -170,8 +168,8 @@ public class LoadoutGenerator ? allegiance / ManufacturerDistancePenalty(manufacturerKey) : 0; return allegianceWeight * - pow(item.OccupiedCells, sizeExponent) / // Prioritize larger items - pow(item.Price, PriceExponent); // Penalize item price to a controllable degree + MathF.Pow(item.OccupiedCells, sizeExponent) / // Prioritize larger items + MathF.Pow(item.Price, PriceExponent); // Penalize item price to a controllable degree }, count ); @@ -289,7 +287,7 @@ public class LoadoutGenerator var shape = new Shape(Math.Max(width, 1), Math.Max(height, 1)); foreach (var cell in cells) { - shape[int2(cell.X, cell.Y)] = true; + shape.SetCell(cell.X, cell.Y, true); } return shape; diff --git a/Assets/Scripts/ServerShared/MarkovNameGenerator.cs b/Assets/Scripts/ServerShared/MarkovNameGenerator.cs index 5a770c7bf..b46fdf441 100644 --- a/Assets/Scripts/ServerShared/MarkovNameGenerator.cs +++ b/Assets/Scripts/ServerShared/MarkovNameGenerator.cs @@ -5,8 +5,8 @@ using System; using System.Collections.Generic; using System.Linq; -using static Unity.Mathematics.math; -using Random = Unity.Mathematics.Random; +using static CultMath.math; +using Random = CultMath.Random; //Generates random names based on the statistical weight of letter sequences //in a collection of sample names @@ -128,4 +128,4 @@ public class MarkovNameGenerator int n = _random.NextInt(0,letters.Count); return letters[n]; } -} \ No newline at end of file +} diff --git a/Assets/Scripts/ServerShared/Narrative/StoryProcessor.cs b/Assets/Scripts/ServerShared/Narrative/StoryProcessor.cs index 39418c4b7..a74a8a69c 100644 --- a/Assets/Scripts/ServerShared/Narrative/StoryProcessor.cs +++ b/Assets/Scripts/ServerShared/Narrative/StoryProcessor.cs @@ -6,7 +6,7 @@ using System.Linq; using Ink; using Ink.Parsed; using Path = System.IO.Path; -using Random = Unity.Mathematics.Random; +using Random = CultMath.Random; using Story = Ink.Runtime.Story; public class StoryProcessor : IZoneResolver, IFactionResolver diff --git a/Assets/Scripts/ServerShared/Narrative/ZoneSelectors.cs b/Assets/Scripts/ServerShared/Narrative/ZoneSelectors.cs index c4d2d60d3..e6ca5829b 100644 --- a/Assets/Scripts/ServerShared/Narrative/ZoneSelectors.cs +++ b/Assets/Scripts/ServerShared/Narrative/ZoneSelectors.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Random = Unity.Mathematics.Random; +using Random = CultMath.Random; public abstract class ZoneSelector { diff --git a/Assets/Scripts/ServerShared/Noise1D.cs b/Assets/Scripts/ServerShared/Noise1D.cs index 15d30ab76..2b979f632 100644 --- a/Assets/Scripts/ServerShared/Noise1D.cs +++ b/Assets/Scripts/ServerShared/Noise1D.cs @@ -1,5 +1,5 @@ -using Unity.Mathematics; -using static Unity.Mathematics.math; +using CultMath; +using static CultMath.math; public static class Noise1D { diff --git a/Assets/Scripts/ServerShared/OrbitalEntity.cs b/Assets/Scripts/ServerShared/OrbitalEntity.cs index 02a9f1144..ae1de84bc 100644 --- a/Assets/Scripts/ServerShared/OrbitalEntity.cs +++ b/Assets/Scripts/ServerShared/OrbitalEntity.cs @@ -4,9 +4,6 @@ using System.Collections; using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using float2 = Unity.Mathematics.float2; public class OrbitalEntity : Entity { @@ -25,8 +22,8 @@ public class OrbitalEntity : Entity { if (!string.IsNullOrWhiteSpace(OrbitKey)) { - Position.xz = Zone.GetOrbitPosition(OrbitKey); - Velocity = Zone.GetOrbitVelocity(OrbitKey); + CultPositionXZ = AetheriaMath.ToCult(Zone.GetOrbitPosition(OrbitKey)); + CultVelocity = AetheriaMath.ToCult(Zone.GetOrbitVelocity(OrbitKey)); } base.Update(delta); diff --git a/Assets/Scripts/ServerShared/Rect.cs b/Assets/Scripts/ServerShared/Rect.cs index 5ace687ef..6e660a309 100644 --- a/Assets/Scripts/ServerShared/Rect.cs +++ b/Assets/Scripts/ServerShared/Rect.cs @@ -1,6 +1,6 @@ -using Unity.Mathematics; -using static Unity.Mathematics.math; -using float2 = Unity.Mathematics.float2; +using CultMath; +using static CultMath.math; +using float2 = CultMath.float2; namespace Unity.Tiny { diff --git a/Assets/Scripts/ServerShared/RuntimeGeometry.cs b/Assets/Scripts/ServerShared/RuntimeGeometry.cs index 0032dcf16..55913fb62 100644 --- a/Assets/Scripts/ServerShared/RuntimeGeometry.cs +++ b/Assets/Scripts/ServerShared/RuntimeGeometry.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using float2 = Unity.Mathematics.float2; +using cfloat2 = CultMath.float2; +using cfloat3 = CultMath.float3; using int2 = Unity.Mathematics.int2; +using unityfloat2 = Unity.Mathematics.float2; public class Shape { @@ -58,10 +58,10 @@ public class Shape { return rotation switch { - ItemRotation.Clockwise => int2(position.y, Width - 1 - position.x), - ItemRotation.Reversed => int2(Width - 1 - position.x, Height - 1 - position.y), - ItemRotation.CounterClockwise => int2(Height - 1 - position.y, position.x), - _ => int2(position.x, position.y) + ItemRotation.Clockwise => new int2(position.y, Width - 1 - position.x), + ItemRotation.Reversed => new int2(Width - 1 - position.x, Height - 1 - position.y), + ItemRotation.CounterClockwise => new int2(Height - 1 - position.y, position.x), + _ => new int2(position.x, position.y) }; } @@ -88,7 +88,7 @@ public class Shape for (var x = 0; x < Width; x++) { if (Cells[x, y]) - yield return int2(x, y); + yield return new int2(x, y); } } } @@ -103,27 +103,35 @@ public class Shape { for (var x = 0; x < Width; x++) { - yield return int2(x, y); + yield return new int2(x, y); } } } - private float2? _centerOfMass; + private cfloat2? _centerOfMass; - public float2 CenterOfMass => _centerOfMass ??= Coordinates - .Aggregate(float2.zero, (total, coord) => total + coord) / Coordinates.Length; + public cfloat2 CenterOfMass => _centerOfMass ??= Coordinates + .Aggregate(new cfloat2(0, 0), (total, coord) => total + new cfloat2(coord.x, coord.y)) / Coordinates.Length; + + public bool GetCell(int x, int y) + { + return x >= 0 && y >= 0 && x < Width && y < Height && Cells[x, y]; + } + + public void SetCell(int x, int y, bool value) + { + if (x < 0 || y < 0 || x >= Width || y >= Height) + return; + + _dirty = true; + _centerOfMass = null; + Cells[x, y] = value; + } public bool this[int2 pos] { - get { return pos.x >= 0 && pos.y >= 0 && pos.x < Width && pos.y < Height && Cells[pos.x, pos.y]; } - set - { - if (pos.x < 0 || pos.y < 0 || pos.x >= Width || pos.y >= Height) - return; - - _dirty = true; - Cells[pos.x, pos.y] = value; - } + get { return GetCell(pos.x, pos.y); } + set { SetCell(pos.x, pos.y, value); } } public Shape Shrink() @@ -132,9 +140,9 @@ public class Shape foreach (var shapeCoord in Coordinates) { shape[shapeCoord] = ( - this[shapeCoord + int2(-1, -1)] && this[shapeCoord + int2(0, -1)] && this[shapeCoord + int2(1, -1)] && - this[shapeCoord + int2(-1, 0)] && this[shapeCoord + int2(1, 0)] && - this[shapeCoord + int2(-1, 1)] && this[shapeCoord + int2(0, 1)] && this[shapeCoord + int2(1, 1)] + this[shapeCoord + new int2(-1, -1)] && this[shapeCoord + new int2(0, -1)] && this[shapeCoord + new int2(1, -1)] && + this[shapeCoord + new int2(-1, 0)] && this[shapeCoord + new int2(1, 0)] && + this[shapeCoord + new int2(-1, 1)] && this[shapeCoord + new int2(0, 1)] && this[shapeCoord + new int2(1, 1)] ); } @@ -143,7 +151,7 @@ public class Shape public Shape Inset(Shape inset, int2 insetPosition, ItemRotation rotation = ItemRotation.None) { - var shape = new Shape(max(Width, insetPosition.x + inset.Width - 1), max(Height, insetPosition.y + inset.Height - 1)); + var shape = new Shape(Math.Max(Width, insetPosition.x + inset.Width - 1), Math.Max(Height, insetPosition.y + inset.Height - 1)); foreach (var v in inset.Coordinates) { var insetCoord = inset.Rotate(v, rotation) + insetPosition; @@ -159,9 +167,9 @@ public class Shape foreach (var shapeCoord in AllCoordinates) { shape[shapeCoord] = ( - this[shapeCoord + int2(-1, -1)] || this[shapeCoord + int2(0, -1)] || this[shapeCoord + int2(1, -1)] || - this[shapeCoord + int2(-1, 0)] || this[shapeCoord] || this[shapeCoord + int2(1, 0)] || - this[shapeCoord + int2(-1, 1)] || this[shapeCoord + int2(0, 1)] || this[shapeCoord + int2(1, 1)] + this[shapeCoord + new int2(-1, -1)] || this[shapeCoord + new int2(0, -1)] || this[shapeCoord + new int2(1, -1)] || + this[shapeCoord + new int2(-1, 0)] || this[shapeCoord] || this[shapeCoord + new int2(1, 0)] || + this[shapeCoord + new int2(-1, 1)] || this[shapeCoord + new int2(0, 1)] || this[shapeCoord + new int2(1, 1)] ); } @@ -194,7 +202,7 @@ public class Shape { for (var y = 0; y < other.Height - height + 1; y++) { - position = int2(x, y); + position = new int2(x, y); var fits = true; foreach (var v in Coordinates) { @@ -212,7 +220,10 @@ public class Shape return false; } - public void SetLine(float2 a, float2 b) + public void SetLine(unityfloat2 a, unityfloat2 b) => + SetLine(AetheriaMath.ToCult(a), AetheriaMath.ToCult(b)); + + public void SetLine(cfloat2 a, cfloat2 b) { if (a.Equals(b)) return; @@ -220,8 +231,8 @@ public class Shape var steep = Math.Abs(b.y - a.y) > Math.Abs(b.x - a.x); if (steep) { - a = new float2(a.y, a.x); - b = new float2(b.y, b.x); + a = new cfloat2(a.y, a.x); + b = new cfloat2(b.y, b.x); } if (a.x > b.x) @@ -270,57 +281,28 @@ public class HardpointData : ITintInspector return $"{Enum.GetName(typeof(HardpointType), Type)} Hardpoint {Rotation.Arrow()}"; } - public float3 TintColor + public cfloat3 TintColor { get { return GetColor(Type); } } - public static float3 GetColor(HardpointType type) + public static cfloat3 GetColor(HardpointType type) { if (_tintColors == null) { var hardpointTypes = (HardpointType[])Enum.GetValues(typeof(HardpointType)); _tintColors = hardpointTypes.ToDictionary( x => x, - x => ColorMath.HsvToRgb(float3(frac((float)(int)x / hardpointTypes.Length + .25f), 1, 1))); + x => ColorMath.HsvToRgb(new cfloat3(Fraction((float)(int)x / hardpointTypes.Length + .25f), 1, 1))); } return _tintColors.TryGetValue(type, out var color) ? color : _tintColors[HardpointType.Hull]; } - private static Dictionary _tintColors; -} - -public class PerformanceStat -{ - public float Min; - public float Max; - public float HeatExponentMultiplier; - public float DurabilityExponentMultiplier; - public float QualityExponent; - - private Dictionary> _scaleModifiers; - private Dictionary> _constantModifiers; - - private Dictionary> ScaleModifiers => - _scaleModifiers ??= new Dictionary>(); - - private Dictionary> ConstantModifiers => - _constantModifiers ??= new Dictionary>(); - - public Dictionary GetScaleModifiers(Entity entity) + private static float Fraction(float value) { - if (!ScaleModifiers.ContainsKey(entity)) - ScaleModifiers[entity] = new Dictionary(); - - return ScaleModifiers[entity]; + return value - MathF.Floor(value); } - public Dictionary GetConstantModifiers(Entity entity) - { - if (!ConstantModifiers.ContainsKey(entity)) - ConstantModifiers[entity] = new Dictionary(); - - return ConstantModifiers[entity]; - } + private static Dictionary _tintColors; } diff --git a/Assets/Scripts/ServerShared/SampleElimination.cs b/Assets/Scripts/ServerShared/SampleElimination.cs index 4827bd248..cbe74d4d4 100644 --- a/Assets/Scripts/ServerShared/SampleElimination.cs +++ b/Assets/Scripts/ServerShared/SampleElimination.cs @@ -3,15 +3,13 @@ using System.Collections.Generic; using System.Linq; using DataStructures.ViliWonka.Heap; using DataStructures.ViliWonka.KDTree; -using Unity.Mathematics; -using static Unity.Mathematics.math; using float2 = Unity.Mathematics.float2; using Random = Unity.Mathematics.Random; public static class WeightedSampleElimination { - private static float2 boundsMin = float2.zero; // The minimum bounds of the sampling domain - private static float2 boundsMax = float2(1); // The maximum bounds of the sampling domain + private static float2 boundsMin = new float2(0, 0); // The minimum bounds of the sampling domain + private static float2 boundsMax = new float2(1, 1); // The maximum bounds of the sampling domain // Returns the minimum bounds of the sampling domain. // The sampling domain boundaries are used for tiling and computing the maximum possible @@ -82,8 +80,8 @@ public static class WeightedSampleElimination if ( d_max < .001f ) d_max = 2 * GetMaxPoissonDiskRadius( outputPoints.Length ); Eliminate( inputPoints, outputPoints, (p0, p1, d2, dmax, density) => { - float d = sqrt(d2); - return pow( 1 - d / dmax, 5 + 6 * density ); + float d = MathF.Sqrt(d2); + return MathF.Pow( 1 - d / dmax, 5 + 6 * density ); }, densityFunc, d_max); } @@ -97,7 +95,7 @@ public static class WeightedSampleElimination while (sample < inputSamples.Length) { var v = random.NextFloat2(); - accumulator += pow(saturate(density(v)), 2f) * saturate(envelope(v)); + accumulator += MathF.Pow(Saturate(density(v)), 2f) * Saturate(envelope(v)); if (accumulator > .5f) { accumulator = 0; @@ -120,7 +118,7 @@ public static class WeightedSampleElimination var domainSize = boundsMax[0] - boundsMin[0]; domainSize *= domainSize; float sampleArea = domainSize / sampleCount; - return sqrt( sampleArea / ( 2 * sqrt(3) ) ); + return MathF.Sqrt( sampleArea / ( 2 * MathF.Sqrt(3) ) ); } // This is the method that performs weighted sample elimination. @@ -184,6 +182,11 @@ public static class WeightedSampleElimination static float GetWeightLimitFraction( int inputSize, int outputSize, float beta, float gamma ) { float ratio = (float) outputSize / inputSize; - return ( 1 - pow( ratio, gamma ) ) * beta; + return ( 1 - MathF.Pow( ratio, gamma ) ) * beta; } -} \ No newline at end of file + + private static float Saturate(float value) + { + return value < 0 ? 0 : value > 1 ? 1 : value; + } +} diff --git a/Assets/Scripts/ServerShared/Settings.cs b/Assets/Scripts/ServerShared/Settings.cs index 837870532..2bcb6bef1 100644 --- a/Assets/Scripts/ServerShared/Settings.cs +++ b/Assets/Scripts/ServerShared/Settings.cs @@ -5,9 +5,9 @@ using System; using System.Collections; using System.Collections.Generic; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using static Unity.Mathematics.noise; +using float2 = Unity.Mathematics.float2; +using float3 = Unity.Mathematics.float3; +using unitynoise = Unity.Mathematics.noise; [Serializable] public class PlanetSettings @@ -69,8 +69,8 @@ public class SectorBackgroundSettings for(int i = 0; i < octaves; i++) { if(i<4) - sum += (1-abs(snoise(p * freq))) * amp; - else sum += abs(snoise(p * freq)) * amp; + sum += (1 - MathF.Abs(unitynoise.snoise(p * freq))) * amp; + else sum += MathF.Abs(unitynoise.snoise(p * freq)) * amp; freq *= NoiseLacunarity; amp *= NoiseGain; } @@ -80,7 +80,7 @@ public class SectorBackgroundSettings public float CloudDensity(float2 uv) { float noise = fBm(uv + NoisePosition, 10); - return pow(noise, CloudExponent) * CloudAmplitude; + return MathF.Pow(noise, CloudExponent) * CloudAmplitude; } } diff --git a/Assets/Scripts/ServerShared/Ship.cs b/Assets/Scripts/ServerShared/Ship.cs index 93c138283..4bb9bbe6e 100644 --- a/Assets/Scripts/ServerShared/Ship.cs +++ b/Assets/Scripts/ServerShared/Ship.cs @@ -7,15 +7,17 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; +using CultMath; using UniRx; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using quaternion = Unity.Mathematics.quaternion; +using static CultMath.math; +using cfloat2 = CultMath.float2; +using cfloat3 = CultMath.float3; +using cquaternion = CultMath.quaternion; public class Ship : Entity { public Entity HomeEntity; - public float2 MovementDirection; + public cfloat2 MovementDirection; public bool IsPlayerShip; private HashSet _thrusterItems; @@ -32,10 +34,10 @@ public class Ship : Entity private bool _exitingWormhole = false; private bool _enteringWormhole = false; private float _wormholeAnimationProgress; - private float2 _wormholeEntryPosition; - private float2 _wormholeEntryDirection; - private float2 _wormholePosition; - private float2 _wormholeExitVelocity; + private cfloat2 _wormholeEntryPosition; + private cfloat2 _wormholeEntryDirection; + private cfloat2 _wormholePosition; + private cfloat2 _wormholeExitVelocity; public bool WormholeAnimationInProgress => _enteringWormhole || _exitingWormhole; public float ForwardThrust { get; private set; } @@ -49,35 +51,40 @@ public class Ship : Entity public float RightStrafeTotalTorque { get; private set; } private List RightStrafeTorqueThrusters = new List(); - public float TurnTime(float2 direction) + public float TurnTime(cfloat2 direction) { - var angleDiff = Direction.Angle(normalize(direction)); - var clockwise = dot(direction, Direction.Rotate(ItemRotation.Clockwise)) > 0; + var cultDirection = normalize(direction); + var shipDirection = CultDirection; + var angleDiff = AetheriaMath.AngleDegrees(shipDirection, cultDirection); + var clockwise = dot(cultDirection, AetheriaMath.Rotate(shipDirection, ItemRotation.Clockwise)) > 0; return angleDiff / ((clockwise ? ClockwiseTorque : CounterClockwiseTorque) / Mass); } public event Action OnExitedWormhole; public event Action OnEnteredWormhole; - public quaternion Rotation { get; private set; } + public cquaternion Rotation { get; private set; } - public void ExitWormhole(float2 wormholePosition, float2 exitVelocity) + private static cquaternion LookRotation(cfloat3 forward, cfloat3 up) => + cquaternion.LookRotation(forward, up); + + public void ExitWormhole(cfloat2 wormholePosition, cfloat2 exitVelocity) { _exitingWormhole = true; _wormholeAnimationProgress = 0; _wormholePosition = wormholePosition; _wormholeExitVelocity = exitVelocity; - Direction = normalize(exitVelocity); + CultDirection = normalize(_wormholeExitVelocity); } - public void EnterWormhole(float2 wormholePosition) + public void EnterWormhole(cfloat2 wormholePosition) { Target.Value = null; _wormholeAnimationProgress = 0; _enteringWormhole = true; - _wormholeEntryPosition = Position.xz; - _wormholeEntryDirection = normalize(_wormholeEntryPosition-wormholePosition); + _wormholeEntryPosition = CultPositionXZ; _wormholePosition = wormholePosition; + _wormholeEntryDirection = normalize(_wormholeEntryPosition - _wormholePosition); } public override void Activate() @@ -245,33 +252,35 @@ public class Ship : Entity { RecalculateThrust(); foreach (var thruster in _allThrusters) thruster.Axis = 0; + var movementDirection = MovementDirection; var rightThrusterTorqueCompensation = abs(RightStrafeTotalTorque) / RightStrafeTorqueThrusters.Count; foreach (var thruster in _rightThrusters) { var thrust = 0f; - thrust += MovementDirection.x; + thrust += movementDirection.x; if (RightStrafeTorqueThrusters.Contains(thruster)) - thrust -= MovementDirection.x * (rightThrusterTorqueCompensation / (abs(thruster.Torque) * thruster.Thrust)); + thrust -= movementDirection.x * (rightThrusterTorqueCompensation / (abs(thruster.Torque) * thruster.Thrust)); thruster.Axis = thrust; } var leftThrusterTorqueCompensation = abs(LeftStrafeTotalTorque) / LeftStrafeTorqueThrusters.Count; foreach (var thruster in _leftThrusters) { var thrust = 0f; - thrust += -MovementDirection.x; + thrust += -movementDirection.x; if (LeftStrafeTorqueThrusters.Contains(thruster)) - thrust += MovementDirection.x * (leftThrusterTorqueCompensation / (abs(thruster.Torque) * thruster.Thrust)); + thrust += movementDirection.x * (leftThrusterTorqueCompensation / (abs(thruster.Torque) * thruster.Thrust)); thruster.Axis = thrust; } - foreach (var thruster in _forwardThrusters) thruster.Axis += MovementDirection.y; - foreach (var thruster in _reverseThrusters) thruster.Axis += -MovementDirection.y; + foreach (var thruster in _forwardThrusters) thruster.Axis += movementDirection.y; + foreach (var thruster in _reverseThrusters) thruster.Axis += -movementDirection.y; - var look = normalize(LookDirection.xz); - var deltaRot = dot(look, normalize(Direction).Rotate(ItemRotation.Clockwise)); + var look = normalize(CultLookDirectionXZ); + var shipDirection = normalize(CultDirection); + var deltaRot = dot(look, AetheriaMath.Rotate(shipDirection, ItemRotation.Clockwise)); if (abs(deltaRot) < .01f) { deltaRot = 0; - Direction = lerp(Direction, look, min(delta, 1)); + CultDirection = lerp(shipDirection, look, min(delta, 1)); } deltaRot = pow(abs(deltaRot), .5f) * sign(deltaRot); @@ -279,58 +288,60 @@ public class Ship : Entity foreach (var thruster in _counterClockwiseThrusters) thruster.Axis += -deltaRot; foreach (var drive in _aetherDrives) - drive.Axis = float3(MovementDirection.y, MovementDirection.x, deltaRot); + drive.Axis = new cfloat3(movementDirection.y, movementDirection.x, deltaRot); } - var velocityMagnitude = length(Velocity); + var velocity = CultVelocity; + var velocityMagnitude = length(velocity); if(velocityMagnitude > .01f) - Velocity = normalize(Velocity) * AetheriaMath.Decay(velocityMagnitude, (float)(ItemManager.GetRuntimeItem(Hull)?.HullDrag ?? 0), delta); + CultVelocity = normalize(velocity) * AetheriaMath.Decay(velocityMagnitude, (float)(ItemManager.GetRuntimeItem(Hull)?.HullDrag ?? 0), delta); - Position.xz += Velocity * delta; + CultPositionXZ += CultVelocity * delta; - var normal = Zone.GetNormal(Position.xz); - var force = new float2(normal.x, normal.z); + var normal = Zone.GetNormal(CultPositionXZ); + var force = new cfloat2(normal.x, normal.z); var forceMagnitude = lengthsq(force); if (forceMagnitude > .001f) { var fa = 1 / (1 - forceMagnitude) - 1; - Velocity += normalize(force) * Zone.Settings.GravityStrength * fa; + CultVelocity += normalize(force) * Zone.Settings.GravityStrength * fa; } - var shipRight = Direction.Rotate(ItemRotation.Clockwise); - var forward = cross(float3(shipRight.x, 0, shipRight.y), normal); - Rotation = quaternion.LookRotation(forward, normal); + var shipRight = AetheriaMath.Rotate(CultDirection, ItemRotation.Clockwise); + var forward = cross(new cfloat3(shipRight.x, 0, shipRight.y), normal); + Rotation = LookRotation(forward, normal); base.Update(delta); if (_exitingWormhole) { + var inverseDirection = new cfloat3(-CultDirection.x, 0, -CultDirection.y); _wormholeAnimationProgress += delta / ItemManager.GameplaySettings.WormholeAnimationDuration; if(_wormholeAnimationProgress < 1) { if (_wormholeAnimationProgress < ItemManager.GameplaySettings.WormholeExitCurveStart) { - Position.xz = _wormholePosition; - Rotation = quaternion.LookRotation(float3(0, 1, 0), float3(-Direction.x, 0, -Direction.y)); + CultPositionXZ = _wormholePosition; + Rotation = LookRotation(new cfloat3(0, 1, 0), inverseDirection); } else { var exitLerp = (_wormholeAnimationProgress - ItemManager.GameplaySettings.WormholeExitCurveStart) / (1 - ItemManager.GameplaySettings.WormholeExitCurveStart); exitLerp = AetheriaMath.Smootherstep(exitLerp); // Square the interpolation variable to produce curve with zero slope at start - Position.xz = _wormholePosition + normalize(_wormholeExitVelocity) * exitLerp * ItemManager.GameplaySettings.WormholeExitRadius; - Rotation = quaternion.LookRotation( - lerp(float3(0, 1, 0), forward, exitLerp), - lerp(float3(-Direction.x, 0, -Direction.y), normal, exitLerp)); + CultPositionXZ = _wormholePosition + normalize(_wormholeExitVelocity) * exitLerp * ItemManager.GameplaySettings.WormholeExitRadius; + Rotation = LookRotation( + lerp(new cfloat3(0, 1, 0), forward, exitLerp), + lerp(inverseDirection, normal, exitLerp)); } - Position.y = Position.y - lerp(ItemManager.GameplaySettings.WormholeDepth, 0, _wormholeAnimationProgress); + CultPositionY -= lerp(ItemManager.GameplaySettings.WormholeDepth, 0, _wormholeAnimationProgress); } else { _exitingWormhole = false; OnExitedWormhole?.Invoke(); OnExitedWormhole = null; - Velocity = _wormholeExitVelocity; + CultVelocity = _wormholeExitVelocity; } } @@ -343,19 +354,19 @@ public class Ship : Entity { var enterLerp = _wormholeAnimationProgress / (1 - ItemManager.GameplaySettings.WormholeExitCurveStart); enterLerp = AetheriaMath.Smootherstep(enterLerp); // Square the interpolation variable to produce curve with zero slope at vertical - Position.xz = lerp(_wormholeEntryPosition, _wormholePosition, enterLerp); - Rotation = quaternion.LookRotation( - lerp(forward, float3(0, -1, 0), enterLerp), - lerp(normal, float3(-_wormholeEntryDirection.x, 0, -_wormholeEntryDirection.y), enterLerp)); + CultPositionXZ = lerp(_wormholeEntryPosition, _wormholePosition, enterLerp); + Rotation = LookRotation( + lerp(forward, new cfloat3(0, -1, 0), enterLerp), + lerp(normal, new cfloat3(-_wormholeEntryDirection.x, 0, -_wormholeEntryDirection.y), enterLerp)); } else { - Position.xz = _wormholePosition; - Rotation = quaternion.LookRotation(float3(0, -1, 0), - float3(-_wormholeEntryDirection.x, 0, -_wormholeEntryDirection.y)); + CultPositionXZ = _wormholePosition; + Rotation = LookRotation(new cfloat3(0, -1, 0), + new cfloat3(-_wormholeEntryDirection.x, 0, -_wormholeEntryDirection.y)); } - Position.y = Position.y - lerp(0, ItemManager.GameplaySettings.WormholeDepth, _wormholeAnimationProgress); + CultPositionY -= lerp(0, ItemManager.GameplaySettings.WormholeDepth, _wormholeAnimationProgress); } else { diff --git a/Assets/Scripts/ServerShared/StatRecipe.cs b/Assets/Scripts/ServerShared/StatRecipe.cs new file mode 100644 index 000000000..2320a08ec --- /dev/null +++ b/Assets/Scripts/ServerShared/StatRecipe.cs @@ -0,0 +1,496 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using System.Linq; +using GameCult.Aetheria.State.Unity; + +public class PerformanceStat +{ + public float Min; + public float Max; + public float HeatExponentMultiplier; + public float DurabilityExponentMultiplier; + public float QualityExponent; + public StatRecipe Recipe; + + private Dictionary> _scaleModifiers; + private Dictionary> _constantModifiers; + + private Dictionary> ScaleModifiers => + _scaleModifiers ??= new Dictionary>(); + + private Dictionary> ConstantModifiers => + _constantModifiers ??= new Dictionary>(); + + public Dictionary GetScaleModifiers(Entity entity) + { + if (!ScaleModifiers.ContainsKey(entity)) + ScaleModifiers[entity] = new Dictionary(); + + return ScaleModifiers[entity]; + } + + public bool TryGetScaleModifiers(Entity entity, out Dictionary modifiers) + { + modifiers = null; + return entity != null && + _scaleModifiers != null && + _scaleModifiers.TryGetValue(entity, out modifiers) && + modifiers.Count > 0; + } + + public Dictionary GetConstantModifiers(Entity entity) + { + if (!ConstantModifiers.ContainsKey(entity)) + ConstantModifiers[entity] = new Dictionary(); + + return ConstantModifiers[entity]; + } + + public bool TryGetConstantModifiers(Entity entity, out Dictionary modifiers) + { + modifiers = null; + return entity != null && + _constantModifiers != null && + _constantModifiers.TryGetValue(entity, out modifiers) && + modifiers.Count > 0; + } + + public float ApplyEntityModifiers(Entity entity, float baseValue) + { + var scaleModifier = 1.0f; + if (TryGetScaleModifiers(entity, out var scaleModifiers)) + { + foreach (var modifier in scaleModifiers.Values) + scaleModifier *= modifier; + } + + float constantModifier = 0; + if (TryGetConstantModifiers(entity, out var constantModifiers)) + { + foreach (var modifier in constantModifiers.Values) + constantModifier += modifier; + } + + return baseValue * scaleModifier + constantModifier; + } + + public StatConditionMask GetDependencyMask(bool includeHeat) + { + if (Recipe != null) + return Recipe.DependencyMask; + + var mask = StatConditionMask.None; + if (QualityExponent != 0) + mask |= StatConditionMask.Quality; + if (DurabilityExponentMultiplier != 0) + mask |= StatConditionMask.Durability; + if (includeHeat && HeatExponentMultiplier != 0) + mask |= StatConditionMask.Heat; + return mask; + } + + public float EvaluateRecipeOrLegacy( + in StatEvaluationContext context, + float durabilityExponent, + float thermalExponent, + bool includeHeat) + { + if (Recipe != null) + return Recipe.Evaluate(context); + + var heat = !includeHeat || HeatExponentMultiplier == 0 + ? 1 + : CultMath.math.pow(context.Heat, thermalExponent * HeatExponentMultiplier); + var durability = DurabilityExponentMultiplier == 0 + ? 1 + : CultMath.math.pow(context.Durability, durabilityExponent * DurabilityExponentMultiplier); + var quality = QualityExponent == 0 + ? 1 + : CultMath.math.pow(context.Quality, QualityExponent); + + return CultMath.math.lerp(Min, Max, durability * quality * heat); + } + + public float EvaluateConsumableBaseline(in StatEvaluationContext context) + { + if (Recipe != null) + return Recipe.Evaluate(context); + + return CultMath.math.lerp(Min, Max, context.Durability * CultMath.math.pow(context.Quality, QualityExponent)); + } +} + +[Flags] +public enum StatConditionMask +{ + None = 0, + Quality = 1 << 0, + Durability = 1 << 1, + Heat = 1 << 2, + Charge = 1 << 3, + Ammo = 1 << 4, + Range = 1 << 5, + Integrity = 1 << 6, + PilotSkill = 1 << 7, + Environment = 1 << 8 +} + +public enum StatModifierOperation +{ + Add, + Multiply, + Override +} + +public static class StatRecipeTokens +{ + public const string Quality = "quality"; + public const string Durability = "durability"; + public const string Heat = "heat"; + public const string Charge = "charge"; + public const string Ammo = "ammo"; + public const string Range = "range"; + public const string Integrity = "integrity"; + public const string PilotSkill = "pilotSkill"; + public const string Environment = "environment"; + + public const string Add = "add"; + public const string Multiply = "multiply"; + public const string Override = "override"; + + public static StatConditionMask ToConditionMask(string condition) + { + switch (condition ?? "") + { + case Quality: + return StatConditionMask.Quality; + case Durability: + return StatConditionMask.Durability; + case Heat: + return StatConditionMask.Heat; + case Charge: + return StatConditionMask.Charge; + case Ammo: + return StatConditionMask.Ammo; + case Range: + return StatConditionMask.Range; + case Integrity: + return StatConditionMask.Integrity; + case PilotSkill: + return StatConditionMask.PilotSkill; + case Environment: + return StatConditionMask.Environment; + default: + return StatConditionMask.None; + } + } + + public static StatModifierOperation ToModifierOperation(string operation) + { + switch (operation ?? "") + { + case Multiply: + return StatModifierOperation.Multiply; + case Override: + return StatModifierOperation.Override; + default: + return StatModifierOperation.Add; + } + } +} + +public static class AetheriaRuntimeBehaviorValueReader +{ + public static PerformanceStat ReadPerformanceStat(AetheriaRuntimeBehaviorValue value) + { + return new PerformanceStat + { + Min = ChildFloat(value, 0), + Max = ChildFloat(value, 1), + HeatExponentMultiplier = ChildFloat(value, 2), + DurabilityExponentMultiplier = ChildFloat(value, 3), + QualityExponent = ChildFloat(value, 4), + Recipe = ReadStatRecipe(ChildValue(value, 5)) + }; + } + + public static StatRecipe ReadStatRecipe(AetheriaRuntimeBehaviorValue value) + { + if (value == null || value.Children.Count == 0) + return null; + + return new StatRecipe + { + BaseValue = ChildFloat(value, 0), + Modifiers = ChildValue(value, 1)?.Children + .Select(ReadStatRecipeModifier) + .Where(modifier => modifier != null) + .ToArray() ?? Array.Empty() + }; + } + + public static StatRecipeModifier ReadStatRecipeModifier(AetheriaRuntimeBehaviorValue value) + { + if (value == null) + return null; + + return new StatRecipeModifier + { + Condition = StatRecipeTokens.ToConditionMask(ChildString(value, 0)), + Operation = StatRecipeTokens.ToModifierOperation(ChildString(value, 1)), + Amount = ChildFloat(value, 2), + Curve = ReadBezierCurve(ChildValue(value, 3)), + Enabled = value.Children.Count <= 4 || ChildBool(value, 4, fallback: true) + }; + } + + public static BezierCurve ReadBezierCurve(AetheriaRuntimeBehaviorValue value) + { + if (value == null || value.Children.Count == 0) + return null; + + return BezierCurve.FromKeys( + value.Children + .Where(key => key.Children.Count >= 4) + .Select(key => ( + ChildFloat(key, 0), + ChildFloat(key, 1), + ChildFloat(key, 2), + ChildFloat(key, 3)))); + } + + public static StatReference ReadStatReference(AetheriaRuntimeBehaviorValue value) + { + return new StatReference + { + Target = ChildString(value, 1), + Stat = ChildString(value, 2) + }; + } + + public static T ReadEnum(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 float ChildFloat(AetheriaRuntimeBehaviorValue value, int index) + { + return value != null && value.Children.Count > index ? (float)value.Children[index].NumberValue : 0; + } + + private static bool ChildBool(AetheriaRuntimeBehaviorValue value, int index, bool fallback = false) + { + return value != null && value.Children.Count > index ? value.Children[index].BoolValue : fallback; + } + + 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; + } +} + +public sealed class StatRecipe +{ + public float BaseValue; + public StatRecipeModifier[] Modifiers = Array.Empty(); + + private StatConditionMask? _dependencyMask; + private StatRecipeModifier[] _enabledModifiers; + + public StatConditionMask DependencyMask + { + get + { + if (_dependencyMask.HasValue) + return _dependencyMask.Value; + + var mask = StatConditionMask.None; + foreach (var modifier in EnabledModifiers) + mask |= modifier.Condition; + + _dependencyMask = mask; + return mask; + } + } + + private StatRecipeModifier[] EnabledModifiers + { + get + { + if (_enabledModifiers != null) + return _enabledModifiers; + + var modifiers = Modifiers ?? Array.Empty(); + var enabledCount = 0; + for (var i = 0; i < modifiers.Length; i++) + { + if (modifiers[i] != null && modifiers[i].Enabled && modifiers[i].Condition != StatConditionMask.None) + enabledCount++; + } + + if (enabledCount == 0) + { + _enabledModifiers = Array.Empty(); + return _enabledModifiers; + } + + _enabledModifiers = new StatRecipeModifier[enabledCount]; + var next = 0; + for (var i = 0; i < modifiers.Length; i++) + { + var modifier = modifiers[i]; + if (modifier != null && modifier.Enabled && modifier.Condition != StatConditionMask.None) + _enabledModifiers[next++] = modifier; + } + + return _enabledModifiers; + } + } + + public void InvalidateCache() + { + _dependencyMask = null; + _enabledModifiers = null; + } + + public float Evaluate(in StatEvaluationContext context) + { + var value = BaseValue; + foreach (var modifier in EnabledModifiers) + { + var sample = modifier.Sample(context.GetConditionValue(modifier.Condition)); + switch (modifier.Operation) + { + case StatModifierOperation.Multiply: + value *= 1 + ((modifier.Amount - 1) * sample); + break; + case StatModifierOperation.Override: + value = CultMath.math.lerp(value, modifier.Amount, sample); + break; + default: + value += modifier.Amount * sample; + break; + } + } + + return value; + } +} + +public sealed class StatRecipeModifier +{ + public StatConditionMask Condition; + public StatModifierOperation Operation; + public float Amount; + public BezierCurve Curve; + public bool Enabled = true; + + public float Sample(float conditionValue) + { + var value = CultMath.math.saturate(conditionValue); + return Curve == null ? value : CultMath.math.saturate(Curve.Evaluate(value)); + } +} + +public readonly struct StatEvaluationContext +{ + public StatEvaluationContext( + float quality, + float durability, + float heat, + float charge = 0, + float ammo = 0, + float range = 0, + float integrity = 0, + float pilotSkill = 0, + float environment = 0) + { + Quality = quality; + Durability = durability; + Heat = heat; + Charge = charge; + Ammo = ammo; + Range = range; + Integrity = integrity; + PilotSkill = pilotSkill; + Environment = environment; + } + + public float Quality { get; } + public float Durability { get; } + public float Heat { get; } + public float Charge { get; } + public float Ammo { get; } + public float Range { get; } + public float Integrity { get; } + public float PilotSkill { get; } + public float Environment { get; } + + public float GetConditionValue(StatConditionMask condition) + { + switch (condition) + { + case StatConditionMask.Quality: + return Quality; + case StatConditionMask.Durability: + return Durability; + case StatConditionMask.Heat: + return Heat; + case StatConditionMask.Charge: + return Charge; + case StatConditionMask.Ammo: + return Ammo; + case StatConditionMask.Range: + return Range; + case StatConditionMask.Integrity: + return Integrity; + case StatConditionMask.PilotSkill: + return PilotSkill; + case StatConditionMask.Environment: + return Environment; + default: + return 0; + } + } + + public StatEvaluationContext WithCondition(StatConditionMask condition, float value) + { + switch (condition) + { + case StatConditionMask.Quality: + return new StatEvaluationContext(value, Durability, Heat, Charge, Ammo, Range, Integrity, PilotSkill, Environment); + case StatConditionMask.Durability: + return new StatEvaluationContext(Quality, value, Heat, Charge, Ammo, Range, Integrity, PilotSkill, Environment); + case StatConditionMask.Heat: + return new StatEvaluationContext(Quality, Durability, value, Charge, Ammo, Range, Integrity, PilotSkill, Environment); + case StatConditionMask.Charge: + return new StatEvaluationContext(Quality, Durability, Heat, value, Ammo, Range, Integrity, PilotSkill, Environment); + case StatConditionMask.Ammo: + return new StatEvaluationContext(Quality, Durability, Heat, Charge, value, Range, Integrity, PilotSkill, Environment); + case StatConditionMask.Range: + return new StatEvaluationContext(Quality, Durability, Heat, Charge, Ammo, value, Integrity, PilotSkill, Environment); + case StatConditionMask.Integrity: + return new StatEvaluationContext(Quality, Durability, Heat, Charge, Ammo, Range, value, PilotSkill, Environment); + case StatConditionMask.PilotSkill: + return new StatEvaluationContext(Quality, Durability, Heat, Charge, Ammo, Range, Integrity, value, Environment); + case StatConditionMask.Environment: + return new StatEvaluationContext(Quality, Durability, Heat, Charge, Ammo, Range, Integrity, PilotSkill, value); + default: + return this; + } + } +} diff --git a/Assets/Scripts/ServerShared/StatRecipe.cs.meta b/Assets/Scripts/ServerShared/StatRecipe.cs.meta new file mode 100644 index 000000000..d93a46e86 --- /dev/null +++ b/Assets/Scripts/ServerShared/StatRecipe.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 04283d92f0302fc49978de4ee33c6a6c \ No newline at end of file diff --git a/Assets/Scripts/ServerShared/Stats.meta b/Assets/Scripts/ServerShared/Stats.meta new file mode 100644 index 000000000..ef376ab08 --- /dev/null +++ b/Assets/Scripts/ServerShared/Stats.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e4871bd17ece70646b5e8f3dbf42e923 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/ServerShared/Wormhole.cs b/Assets/Scripts/ServerShared/Wormhole.cs index 447cf174c..bacc4d12f 100644 --- a/Assets/Scripts/ServerShared/Wormhole.cs +++ b/Assets/Scripts/ServerShared/Wormhole.cs @@ -1,7 +1,7 @@ -using Unity.Mathematics; +using float2 = CultMath.float2; public class Wormhole { public float2 Position; public GalaxyZone Target; -} \ No newline at end of file +} diff --git a/Assets/Scripts/ServerShared/YmirPhysicsContracts.cs b/Assets/Scripts/ServerShared/YmirPhysicsContracts.cs new file mode 100644 index 000000000..211635e98 --- /dev/null +++ b/Assets/Scripts/ServerShared/YmirPhysicsContracts.cs @@ -0,0 +1,845 @@ +using System; +using System.Collections.Generic; + +[Serializable] +public sealed class YmirStepRequest +{ + public float deltaTime; + public YmirWorld world; +} + +[Serializable] +public sealed class YmirStepResult +{ + public YmirWorld world; + public YmirContactEvent[] contacts; +} + +[Serializable] +public sealed class YmirSphereOverlapRequest +{ + public YmirVec3 center; + public float radius; + public YmirSphereQueryBody[] bodies; +} + +[Serializable] +public sealed class YmirSphereOverlapResult +{ + public YmirSphereOverlapHit[] hits; +} + +[Serializable] +public sealed class YmirSphereOverlapHit +{ + public string bodyId; + public YmirVec3 point; + public YmirVec3 normal; + public float penetration; + public float distance; +} + +[Serializable] +public sealed class YmirCircleCastRequest +{ + public YmirVec2 origin; + public YmirVec2 direction; + public float distance; + public float radius; + public YmirWorld world; +} + +[Serializable] +public sealed class YmirCircleOverlapRequest +{ + public YmirVec2 center; + public float radius; + public YmirWorld world; +} + +[Serializable] +public sealed class YmirCircleOverlapResult +{ + public YmirCircleOverlapHit[] hits; +} + +[Serializable] +public sealed class YmirCircleOverlapHit +{ + public string bodyId; + public YmirVec2 point; + public YmirVec2 normal; + public float penetration; + public float distance; +} + +[Serializable] +public sealed class YmirCircleCastResult +{ + public YmirCircleCastHit[] hits; +} + +[Serializable] +public sealed class YmirCircleCastHit +{ + public string bodyId; + public YmirVec2 point; + public YmirVec2 normal; + public float distance; +} + +[Serializable] +public sealed class YmirSphereCastRequest +{ + public YmirVec3 origin; + public YmirVec3 direction; + public float distance; + public float radius; + public YmirSphereQueryBody[] bodies; +} + +[Serializable] +public sealed class YmirSphereCastResult +{ + public YmirSphereCastHit[] hits; +} + +[Serializable] +public sealed class YmirSphereCastHit +{ + public string bodyId; + public YmirVec3 point; + public YmirVec3 normal; + public float distance; +} + +[Serializable] +public sealed class YmirWorld +{ + public float time; + public YmirPhysicsBody[] bodies; + public YmirRadialField[] fields; +} + +[Serializable] +public sealed class YmirSphereQueryWorld +{ + public YmirSphereQueryBody[] bodies; +} + +[Serializable] +public sealed class YmirSphereQueryBody +{ + public string id; + public YmirVec3 position; + public float radius; +} + +[Serializable] +public sealed class YmirPhysicsBody +{ + public string id; + public YmirVec2 position; + public YmirVec2 velocity; + public YmirVec2 direction; + public float angularVelocity; + public float torque; + public float momentOfInertia; + public float radius; + public float mass; + public bool isStatic; + public float restitution; +} + +[Serializable] +public sealed class YmirRadialField +{ + public string id; + public YmirVec2 position; + public float strength; + public float radius; +} + +[Serializable] +public sealed class YmirContactEvent +{ + public string bodyA; + public string bodyB; + public YmirVec2 point; + public YmirVec2 normal; + public float penetration; + public float relativeSpeed; +} + +[Serializable] +public struct YmirVec2 +{ + public float x; + public float y; +} + +[Serializable] +public struct YmirVec3 +{ + public float x; + public float y; + public float z; +} + +public static class YmirPhysicsQueries +{ + private const float Epsilon = 0.000001f; + + public static YmirStepResult Step(YmirStepRequest request) + { + if (request == null || request.world == null) + { + return new YmirStepResult + { + world = new YmirWorld + { + time = 0, + bodies = Array.Empty(), + fields = Array.Empty() + }, + contacts = Array.Empty() + }; + } + + return Step(request.world, request.deltaTime); + } + + public static YmirStepResult Step(YmirWorld world, float deltaTime) + { + var bodies = CopyBodies(world?.bodies); + var fields = CopyFields(world?.fields); + var steppedWorld = new YmirWorld + { + time = world == null ? 0 : world.time + Math.Max(0, deltaTime), + bodies = bodies, + fields = fields + }; + + if (world == null || bodies.Length == 0 || deltaTime <= 0) + { + return new YmirStepResult + { + world = steppedWorld, + contacts = Array.Empty() + }; + } + + ApplyFields(bodies, fields, deltaTime); + IntegrateBodies(bodies, deltaTime); + var contacts = ResolveContacts(bodies); + return new YmirStepResult + { + world = steppedWorld, + contacts = contacts + }; + } + + public static YmirSphereOverlapResult OverlapSphere(YmirSphereOverlapRequest request) + { + return new YmirSphereOverlapResult + { + hits = request == null + ? Array.Empty() + : OverlapSphere(request.bodies, request.center, request.radius) + }; + } + + public static YmirSphereOverlapHit[] OverlapSphere( + IReadOnlyList bodies, + YmirVec3 center, + float radius) + { + var hits = new List(); + OverlapSphere(bodies, center, radius, hits); + return hits.Count == 0 ? Array.Empty() : hits.ToArray(); + } + + public static int OverlapSphere( + IReadOnlyList bodies, + YmirVec3 center, + float radius, + List hits) + { + if (hits == null) + throw new ArgumentNullException(nameof(hits)); + + hits.Clear(); + if (bodies == null || bodies.Count == 0 || radius < 0) + return 0; + + for (var i = 0; i < bodies.Count; i++) + { + var body = bodies[i]; + if (body == null || body.radius < 0) + continue; + + var delta = Subtract(body.position, center); + var centerDistanceSquared = LengthSquared(delta); + var combinedRadius = radius + body.radius; + if (centerDistanceSquared > combinedRadius * combinedRadius) + continue; + + var centerDistance = (float)Math.Sqrt(centerDistanceSquared); + var normal = centerDistance > Epsilon + ? Scale(delta, 1.0f / centerDistance) + : new YmirVec3 { x = 1, y = 0, z = 0 }; + + hits.Add(new YmirSphereOverlapHit + { + bodyId = body.id, + point = Subtract(body.position, Scale(normal, body.radius)), + normal = normal, + penetration = combinedRadius - centerDistance, + distance = Math.Max(0, centerDistance - body.radius) + }); + } + + hits.Sort((left, right) => CompareHitOrder(left.distance, left.bodyId, right.distance, right.bodyId)); + return hits.Count; + } + + public static YmirSphereCastResult CastSphere(YmirSphereCastRequest request) + { + return new YmirSphereCastResult + { + hits = request == null + ? Array.Empty() + : CastSphere(request.bodies, request.origin, request.direction, request.distance, request.radius) + }; + } + + public static YmirCircleCastResult CastCircle(YmirCircleCastRequest request) + { + return new YmirCircleCastResult + { + hits = request == null || request.world == null + ? Array.Empty() + : CastCircle(request.world.bodies, request.origin, request.direction, request.distance, request.radius) + }; + } + + public static YmirCircleOverlapResult OverlapCircle(YmirCircleOverlapRequest request) + { + return new YmirCircleOverlapResult + { + hits = request == null || request.world == null + ? Array.Empty() + : OverlapCircle(request.world.bodies, request.center, request.radius) + }; + } + + public static YmirCircleOverlapHit[] OverlapCircle( + IReadOnlyList bodies, + YmirVec2 center, + float radius) + { + var hits = new List(); + OverlapCircle(bodies, center, radius, hits); + return hits.Count == 0 ? Array.Empty() : hits.ToArray(); + } + + public static int OverlapCircle( + IReadOnlyList bodies, + YmirVec2 center, + float radius, + List hits) + { + if (hits == null) + throw new ArgumentNullException(nameof(hits)); + + hits.Clear(); + if (bodies == null || bodies.Count == 0 || radius < 0) + return 0; + + for (var i = 0; i < bodies.Count; i++) + { + var body = bodies[i]; + if (body == null || body.radius < 0) + continue; + + var delta = Subtract(body.position, center); + var centerDistanceSquared = LengthSquared(delta); + var combinedRadius = radius + body.radius; + if (centerDistanceSquared > combinedRadius * combinedRadius) + continue; + + var centerDistance = (float)Math.Sqrt(centerDistanceSquared); + var normal = centerDistance > Epsilon + ? Scale(delta, 1.0f / centerDistance) + : new YmirVec2 { x = 1, y = 0 }; + + hits.Add(new YmirCircleOverlapHit + { + bodyId = body.id, + point = Subtract(body.position, Scale(normal, body.radius)), + normal = normal, + penetration = combinedRadius - centerDistance, + distance = Math.Max(0, centerDistance - body.radius) + }); + } + + hits.Sort((left, right) => CompareHitOrder(left.distance, left.bodyId, right.distance, right.bodyId)); + return hits.Count; + } + + public static YmirCircleCastHit[] CastCircle( + IReadOnlyList bodies, + YmirVec2 origin, + YmirVec2 direction, + float distance, + float radius) + { + var hits = new List(); + CastCircle(bodies, origin, direction, distance, radius, hits); + return hits.Count == 0 ? Array.Empty() : hits.ToArray(); + } + + public static int CastCircle( + IReadOnlyList bodies, + YmirVec2 origin, + YmirVec2 direction, + float distance, + float radius, + List hits) + { + if (hits == null) + throw new ArgumentNullException(nameof(hits)); + + hits.Clear(); + var directionLengthSquared = LengthSquared(direction); + if (bodies == null || bodies.Count == 0 || distance < 0 || radius < 0 || directionLengthSquared <= Epsilon) + return 0; + + var directionLength = (float)Math.Sqrt(directionLengthSquared); + var rayDirection = Scale(direction, 1.0f / directionLength); + for (var i = 0; i < bodies.Count; i++) + { + var body = bodies[i]; + if (body == null || body.radius < 0) + continue; + + var toOrigin = Subtract(origin, body.position); + var combinedRadius = radius + body.radius; + var c = LengthSquared(toOrigin) - combinedRadius * combinedRadius; + float hitDistance; + if (c <= 0) + { + hitDistance = 0; + } + else + { + var b = Dot(toOrigin, rayDirection); + if (b > 0) + continue; + + var discriminant = b * b - c; + if (discriminant < 0) + continue; + + hitDistance = -b - (float)Math.Sqrt(discriminant); + if (hitDistance < 0 || hitDistance > distance) + continue; + } + + var castCenter = Add(origin, Scale(rayDirection, hitDistance)); + var delta = Subtract(body.position, castCenter); + var centerDistanceSquared = LengthSquared(delta); + var normal = centerDistanceSquared > Epsilon + ? Scale(delta, 1.0f / (float)Math.Sqrt(centerDistanceSquared)) + : Scale(rayDirection, -1); + + hits.Add(new YmirCircleCastHit + { + bodyId = body.id, + point = Subtract(body.position, Scale(normal, body.radius)), + normal = normal, + distance = hitDistance + }); + } + + hits.Sort((left, right) => CompareHitOrder(left.distance, left.bodyId, right.distance, right.bodyId)); + return hits.Count; + } + + public static YmirSphereCastHit[] CastSphere( + IReadOnlyList bodies, + YmirVec3 origin, + YmirVec3 direction, + float distance, + float radius) + { + var hits = new List(); + CastSphere(bodies, origin, direction, distance, radius, hits); + return hits.Count == 0 ? Array.Empty() : hits.ToArray(); + } + + public static int CastSphere( + IReadOnlyList bodies, + YmirVec3 origin, + YmirVec3 direction, + float distance, + float radius, + List hits) + { + if (hits == null) + throw new ArgumentNullException(nameof(hits)); + + hits.Clear(); + var directionLengthSquared = LengthSquared(direction); + if (bodies == null || bodies.Count == 0 || distance < 0 || radius < 0 || directionLengthSquared <= Epsilon) + return 0; + + var directionLength = (float)Math.Sqrt(directionLengthSquared); + var rayDirection = Scale(direction, 1.0f / directionLength); + for (var i = 0; i < bodies.Count; i++) + { + var body = bodies[i]; + if (body == null || body.radius < 0) + continue; + + var toOrigin = Subtract(origin, body.position); + var combinedRadius = radius + body.radius; + var c = LengthSquared(toOrigin) - combinedRadius * combinedRadius; + float hitDistance; + if (c <= 0) + { + hitDistance = 0; + } + else + { + var b = Dot(toOrigin, rayDirection); + if (b > 0) + continue; + + var discriminant = b * b - c; + if (discriminant < 0) + continue; + + hitDistance = -b - (float)Math.Sqrt(discriminant); + if (hitDistance < 0 || hitDistance > distance) + continue; + } + + var castCenter = Add(origin, Scale(rayDirection, hitDistance)); + var delta = Subtract(body.position, castCenter); + var centerDistanceSquared = LengthSquared(delta); + var normal = centerDistanceSquared > Epsilon + ? Scale(delta, 1.0f / (float)Math.Sqrt(centerDistanceSquared)) + : Scale(rayDirection, -1); + + hits.Add(new YmirSphereCastHit + { + bodyId = body.id, + point = Subtract(body.position, Scale(normal, body.radius)), + normal = normal, + distance = hitDistance + }); + } + + hits.Sort((left, right) => CompareHitOrder(left.distance, left.bodyId, right.distance, right.bodyId)); + return hits.Count; + } + + private static int CompareHitOrder(float leftDistance, string leftBodyId, float rightDistance, string rightBodyId) + { + var distanceOrder = leftDistance.CompareTo(rightDistance); + return distanceOrder != 0 + ? distanceOrder + : string.CompareOrdinal(leftBodyId, rightBodyId); + } + + private static void ApplyFields(IReadOnlyList bodies, IReadOnlyList fields, float deltaTime) + { + if (fields == null || fields.Count == 0) + return; + + for (var bodyIndex = 0; bodyIndex < bodies.Count; bodyIndex++) + { + var body = bodies[bodyIndex]; + if (body == null || body.isStatic) + continue; + + for (var fieldIndex = 0; fieldIndex < fields.Count; fieldIndex++) + { + var field = fields[fieldIndex]; + if (field == null || field.radius <= 0 || Math.Abs(field.strength) <= Epsilon) + continue; + + var offset = Subtract(body.position, field.position); + var distanceSquared = LengthSquared(offset); + var radiusSquared = field.radius * field.radius; + if (distanceSquared <= Epsilon || distanceSquared > radiusSquared) + continue; + + var distance = (float)Math.Sqrt(distanceSquared); + var falloff = 1.0f - Math.Min(1.0f, distance / field.radius); + var acceleration = Scale(offset, field.strength * falloff / distance); + body.velocity = Add(body.velocity, Scale(acceleration, deltaTime)); + } + } + } + + private static void IntegrateBodies(IReadOnlyList bodies, float deltaTime) + { + for (var i = 0; i < bodies.Count; i++) + { + var body = bodies[i]; + if (body == null || body.isStatic) + continue; + + if (body.momentOfInertia > Epsilon) + body.angularVelocity += body.torque / body.momentOfInertia * deltaTime; + + body.position = Add(body.position, Scale(body.velocity, deltaTime)); + if (Math.Abs(body.angularVelocity) > Epsilon) + body.direction = Rotate(body.direction, body.angularVelocity * deltaTime); + } + } + + private static YmirContactEvent[] ResolveContacts(IReadOnlyList bodies) + { + var contacts = new List(); + for (var leftIndex = 0; leftIndex < bodies.Count; leftIndex++) + { + var left = bodies[leftIndex]; + if (left == null || left.radius < 0) + continue; + + for (var rightIndex = leftIndex + 1; rightIndex < bodies.Count; rightIndex++) + { + var right = bodies[rightIndex]; + if (right == null || right.radius < 0 || left.isStatic && right.isStatic) + continue; + + var delta = Subtract(right.position, left.position); + var distanceSquared = LengthSquared(delta); + var combinedRadius = left.radius + right.radius; + if (distanceSquared > combinedRadius * combinedRadius) + continue; + + var distance = (float)Math.Sqrt(distanceSquared); + var normal = distance > Epsilon + ? Scale(delta, 1.0f / distance) + : new YmirVec2 { x = 1, y = 0 }; + var penetration = combinedRadius - distance; + var relativeVelocity = Subtract(left.velocity, right.velocity); + + contacts.Add(new YmirContactEvent + { + bodyA = left.id, + bodyB = right.id, + point = Add(left.position, Scale(normal, Math.Max(0, left.radius - penetration * 0.5f))), + normal = normal, + penetration = penetration, + relativeSpeed = Math.Max(0, Dot(relativeVelocity, normal)) + }); + + SeparateBodies(left, right, normal, penetration); + ResolveVelocity(left, right, normal); + } + } + + return contacts.ToArray(); + } + + private static void SeparateBodies(YmirPhysicsBody left, YmirPhysicsBody right, YmirVec2 normal, float penetration) + { + if (penetration <= 0) + return; + + if (left.isStatic) + { + right.position = Add(right.position, Scale(normal, penetration)); + return; + } + + if (right.isStatic) + { + left.position = Subtract(left.position, Scale(normal, penetration)); + return; + } + + var leftInverseMass = InverseMass(left); + var rightInverseMass = InverseMass(right); + var inverseMassTotal = leftInverseMass + rightInverseMass; + if (inverseMassTotal <= Epsilon) + return; + + var correction = Scale(normal, penetration / inverseMassTotal); + left.position = Subtract(left.position, Scale(correction, leftInverseMass)); + right.position = Add(right.position, Scale(correction, rightInverseMass)); + } + + private static void ResolveVelocity(YmirPhysicsBody left, YmirPhysicsBody right, YmirVec2 normal) + { + var leftInverseMass = InverseMass(left); + var rightInverseMass = InverseMass(right); + var inverseMassTotal = leftInverseMass + rightInverseMass; + if (inverseMassTotal <= Epsilon) + return; + + var relativeVelocity = Subtract(right.velocity, left.velocity); + var velocityAlongNormal = Dot(relativeVelocity, normal); + if (velocityAlongNormal > 0) + return; + + var restitution = Math.Max(0, Math.Min(1, Math.Max(left.restitution, right.restitution))); + var impulseMagnitude = -(1 + restitution) * velocityAlongNormal / inverseMassTotal; + var impulse = Scale(normal, impulseMagnitude); + if (!left.isStatic) + left.velocity = Subtract(left.velocity, Scale(impulse, leftInverseMass)); + if (!right.isStatic) + right.velocity = Add(right.velocity, Scale(impulse, rightInverseMass)); + } + + private static float InverseMass(YmirPhysicsBody body) + { + return body == null || body.isStatic || body.mass <= Epsilon ? 0 : 1.0f / body.mass; + } + + private static YmirPhysicsBody[] CopyBodies(IReadOnlyList bodies) + { + if (bodies == null || bodies.Count == 0) + return Array.Empty(); + + var copy = new YmirPhysicsBody[bodies.Count]; + for (var i = 0; i < copy.Length; i++) + { + var body = bodies[i]; + copy[i] = body == null + ? null + : new YmirPhysicsBody + { + id = body.id, + position = body.position, + velocity = body.velocity, + direction = body.direction, + angularVelocity = body.angularVelocity, + torque = body.torque, + momentOfInertia = body.momentOfInertia, + radius = body.radius, + mass = body.mass, + isStatic = body.isStatic, + restitution = body.restitution + }; + } + + return copy; + } + + private static YmirRadialField[] CopyFields(IReadOnlyList fields) + { + if (fields == null || fields.Count == 0) + return Array.Empty(); + + var copy = new YmirRadialField[fields.Count]; + for (var i = 0; i < copy.Length; i++) + { + var field = fields[i]; + copy[i] = field == null + ? null + : new YmirRadialField + { + id = field.id, + position = field.position, + strength = field.strength, + radius = field.radius + }; + } + + return copy; + } + + private static YmirVec3 Subtract(YmirVec3 left, YmirVec3 right) + { + return new YmirVec3 + { + x = left.x - right.x, + y = left.y - right.y, + z = left.z - right.z + }; + } + + private static YmirVec2 Subtract(YmirVec2 left, YmirVec2 right) + { + return new YmirVec2 + { + x = left.x - right.x, + y = left.y - right.y + }; + } + + private static YmirVec3 Add(YmirVec3 left, YmirVec3 right) + { + return new YmirVec3 + { + x = left.x + right.x, + y = left.y + right.y, + z = left.z + right.z + }; + } + + private static YmirVec2 Add(YmirVec2 left, YmirVec2 right) + { + return new YmirVec2 + { + x = left.x + right.x, + y = left.y + right.y + }; + } + + private static YmirVec3 Scale(YmirVec3 value, float scale) + { + return new YmirVec3 + { + x = value.x * scale, + y = value.y * scale, + z = value.z * scale + }; + } + + private static YmirVec2 Scale(YmirVec2 value, float scale) + { + return new YmirVec2 + { + x = value.x * scale, + y = value.y * scale + }; + } + + private static YmirVec2 Rotate(YmirVec2 value, float radians) + { + var sine = (float)Math.Sin(radians); + var cosine = (float)Math.Cos(radians); + return new YmirVec2 + { + x = value.x * cosine - value.y * sine, + y = value.x * sine + value.y * cosine + }; + } + + private static float LengthSquared(YmirVec3 value) + { + return value.x * value.x + value.y * value.y + value.z * value.z; + } + + private static float LengthSquared(YmirVec2 value) + { + return value.x * value.x + value.y * value.y; + } + + private static float Dot(YmirVec3 left, YmirVec3 right) + { + return left.x * right.x + left.y * right.y + left.z * right.z; + } + + private static float Dot(YmirVec2 left, YmirVec2 right) + { + return left.x * right.x + left.y * right.y; + } +} diff --git a/Assets/Scripts/ServerShared/YmirPhysicsContracts.cs.meta b/Assets/Scripts/ServerShared/YmirPhysicsContracts.cs.meta new file mode 100644 index 000000000..0c3e198f0 --- /dev/null +++ b/Assets/Scripts/ServerShared/YmirPhysicsContracts.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: eb6349d47a9ffb14381a03fe5fc21324 \ No newline at end of file diff --git a/Assets/Scripts/ServerShared/Zone.cs b/Assets/Scripts/ServerShared/Zone.cs index eb174879f..4a3474f35 100644 --- a/Assets/Scripts/ServerShared/Zone.cs +++ b/Assets/Scripts/ServerShared/Zone.cs @@ -9,9 +9,11 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using UniRx; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using cfloat2 = CultMath.float2; +using cfloat3 = CultMath.float3; using float2 = Unity.Mathematics.float2; +using float3 = Unity.Mathematics.float3; +using float4 = Unity.Mathematics.float4; using Random = Unity.Mathematics.Random; public class Zone @@ -54,7 +56,7 @@ public class Zone Mass = blueprint.Mass; _itemManager = itemManager; Settings = settings; - _random = new Random(Convert.ToUInt32(abs(galaxyZone?.Name.GetHashCode() ?? 1337))); + _random = new Random(Convert.ToUInt32(Math.Abs(galaxyZone?.Name.GetHashCode() ?? 1337))); foreach (var orbit in blueprint.Orbits) { @@ -87,14 +89,19 @@ public class Zone foreach (var entityBlueprint in blueprint.Entities) { - var entity = EntityConstructionBlueprintProjector.InstantiateFromBlueprint(_itemManager, this, entityBlueprint); + var entity = EntityConstructionBlueprintProjector.InstantiateAuthoritativeFromBlueprint(_itemManager, this, entityBlueprint); Entities.Add(entity); entity.Activate(); if (entity is Ship {IsPlayerShip: false} ship) { Agents.Add(CreateAgent(ship)); - if (lengthsq(ship.Position) < 1) - ship.Position = _itemManager.Random.NextFloat3(float3(-blueprint.Radius * .5f), float3(blueprint.Radius * .5f)); + if (CultMath.math.lengthsq(ship.CultPosition) < 1) + { + var halfRadius = blueprint.Radius * .5f; + ship.CultPosition = _itemManager.Random.NextFloat3( + new cfloat3(-halfRadius, -halfRadius, -halfRadius), + new cfloat3(halfRadius, halfRadius, halfRadius)); + } } } @@ -134,6 +141,11 @@ public class Zone return AsteroidBelts.TryGetValue(bodyKey ?? "", out belt); } + public bool AsteroidExists(string asteroidBeltKey, int asteroid) + { + return TryGetAsteroidBelt(asteroidBeltKey, out var belt) && belt.ContainsAsteroid(asteroid); + } + public bool TryGetOrbit(string orbitKey, out Orbit orbit) { return Orbits.TryGetValue(orbitKey ?? "", out orbit); @@ -171,25 +183,25 @@ public class Zone { // Root orbit is fixed at origin if (string.IsNullOrWhiteSpace(orbitKey)) - return float2.zero; + return new float2(0, 0); if (!Orbits.TryGetValue(orbitKey, out var orbit)) { Log?.Invoke("Requested orbit is not part of this zone!"); - return float2.zero; + return new float2(0, 0); } if (!_updatedOrbits.Contains(orbitKey)) { - float2 pos = float2.zero; + float2 pos = new float2(0, 0); if (orbit.Period > .01f) { - var phase = (float) frac(_time / orbit.Period); - pos = Orbit.Evaluate(frac(phase + orbit.Phase)) * orbit.Distance; + var phase = (float) Fraction(_time / orbit.Period); + pos = Orbit.Evaluate(Fraction(phase + orbit.Phase)) * orbit.Distance; if (float.IsNaN(pos.x)) { //_context.Log("Orbit position is NaN, something went very wrong!"); - pos = float2.zero; + pos = new float2(0, 0); } } @@ -208,6 +220,10 @@ public class Zone return TryGetOrbit(orbitKey, out var orbit) ? orbit.Velocity : float2.zero; } + public cfloat2 GetCultOrbitPosition(string orbitKey) => AetheriaMath.ToCult(GetOrbitPosition(orbitKey)); + + public cfloat2 GetCultOrbitVelocity(string orbitKey) => AetheriaMath.ToCult(GetOrbitVelocity(orbitKey)); + public int NearestAsteroid(string asteroidBeltKey, float2 position) { if (!TryGetAsteroidBelt(asteroidBeltKey, out var belt)) @@ -218,7 +234,7 @@ public class Zone float nearestDistance = Single.MaxValue; for (int i = 0; i < belt.AsteroidCount; i++) { - var dist = lengthsq(asteroidPositions[i].xz - position); + var dist = LengthSquared(asteroidPositions[i].xz - position); if (belt.ContainsAsteroid(i) && dist < nearestDistance) { nearest = i; @@ -229,9 +245,24 @@ public class Zone return nearest; } - public bool AsteroidExists(string asteroidBeltKey, int asteroid) + public int NearestAsteroid(string asteroidBeltKey, cfloat2 position) => + NearestAsteroid(asteroidBeltKey, AetheriaMath.ToUnity(position)); + + public bool TryGetCultAsteroidTransform( + string asteroidBeltKey, + int asteroid, + out cfloat2 position, + out float radius) { - return TryGetAsteroidBelt(asteroidBeltKey, out var belt) && belt.ContainsAsteroid(asteroid); + position = cfloat2.zero; + radius = 0; + if (!TryGetAsteroidBelt(asteroidBeltKey, out var belt) || !belt.ContainsAsteroid(asteroid)) + return false; + + var transform = belt.Transforms[asteroid]; + position = AetheriaMath.ToCult(transform.xy); + radius = transform.w; + return true; } private void UpdateAsteroidTransforms(AsteroidBelt belt) @@ -250,11 +281,11 @@ public class Zone } else size = Settings.AsteroidSize.Evaluate(asteroid.Size); - var rot = (float) (_time * asteroid.RotationSpeed % (PI * 2)); - var pos = Orbit.Evaluate((float) frac(_time / Settings.OrbitPeriod.Evaluate(asteroid.Distance) + + var rot = (float) (_time * asteroid.RotationSpeed % (MathF.PI * 2)); + var pos = Orbit.Evaluate((float) Fraction(_time / Settings.OrbitPeriod.Evaluate(asteroid.Distance) + asteroid.Phase)) * asteroid.Distance + belt.NewOrbitPosition; //belt.NewPositions[i] = float3(pos.x, GetHeight(pos) + Settings.AsteroidVerticalOffset, pos.y); - belt.NewTransforms[i] = float4(pos.x, pos.y, rot, size); + belt.NewTransforms[i] = new float4(pos.x, pos.y, rot, size); } } @@ -284,7 +315,7 @@ public class Zone } var resourceCount = belt.Resources.Sum(x => x.Value); - var resource = belt.Resources.MaxBy(x => pow(x.Value, 1f / penetration) * _random.NextFloat()); + var resource = belt.Resources.MaxBy(x => MathF.Pow(x.Value, 1f / penetration) * _random.NextFloat()); if (efficiency * _random.NextFloat() * belt.MiningAccumulator[(miner, asteroid)] * resourceCount / Settings.MiningDifficulty > 1) { belt.MiningAccumulator.Remove((miner, asteroid)); @@ -298,7 +329,9 @@ public class Zone } } - public SecurityLevel GetSecurityLevel(float2 pos) + public SecurityLevel GetSecurityLevel(float2 pos) => GetSecurityLevel(AetheriaMath.ToCult(pos)); + + public SecurityLevel GetSecurityLevel(cfloat2 pos) { if (GalaxyZone.Owner==null) return SecurityLevel.Open; @@ -307,7 +340,7 @@ public class Zone { if (entity is OrbitalEntity orbitalEntity && orbitalEntity.SecurityRadius > 1 && entity.Faction.HasSameKey(GalaxyZone.Owner)) { - if (orbitalEntity.SecurityLevel > security && length(orbitalEntity.Position.xz - pos) < orbitalEntity.SecurityRadius * Settings.SecureAreaRadiusMultiplier) + if (orbitalEntity.SecurityLevel > security && CultMath.math.length(orbitalEntity.CultPositionXZ - pos) < orbitalEntity.SecurityRadius * Settings.SecureAreaRadiusMultiplier) security = orbitalEntity.SecurityLevel; } } @@ -317,16 +350,16 @@ public class Zone public float GetHeight(float2 position) { - var result = -PowerPulse(length(position)/(Radius*2), Settings.ZoneDepthExponent) * Settings.ZoneDepth; + var result = -PowerPulse(Length(position)/(Radius*2), Settings.ZoneDepthExponent) * Settings.ZoneDepth; foreach (var body in PlanetInstances.Values) { var p = position - body.Orbit.Position; - var distSqr = lengthsq(p); + var distSqr = LengthSquared(p); var gravityRadius = body.GravityWellRadius; if (distSqr < gravityRadius*gravityRadius) { var depth = body.GravityWellDepth; - result -= PowerPulse(sqrt(distSqr) / gravityRadius, body.GravityDepthExponent) * depth; + result -= PowerPulse(MathF.Sqrt(distSqr) / gravityRadius, body.GravityDepthExponent) * depth; } if (body is GasGiant gas) @@ -337,7 +370,7 @@ public class Zone var depth = gas.GravityWavesDepth; var frequency = Settings.WaveFrequency.Evaluate(body.Mass); var speed = gas.GravityWavesSpeed; - result -= RadialWaves(sqrt(distSqr) / waveRadius, 8, 1.25f, frequency, (float) (_time * speed)) * depth; + result -= RadialWaves(MathF.Sqrt(distSqr) / waveRadius, 8, 1.25f, frequency, (float) (_time * speed)) * depth; } } } @@ -345,6 +378,8 @@ public class Zone return result; } + public float GetHeight(cfloat2 position) => GetHeight(AetheriaMath.ToUnity(position)); + public float GetLight(float2 position) { var light = 0f; @@ -353,11 +388,11 @@ public class Zone if (body is Sun sun) { var p = position - body.Orbit.Position; - var distSqr = lengthsq(p); + var distSqr = LengthSquared(p); var lightRadius = sun.LightRadius; if (distSqr < lightRadius * lightRadius) { - light += PowerPulse(sqrt(distSqr) / lightRadius, 8); + light += PowerPulse(MathF.Sqrt(distSqr) / lightRadius, 8); } } } @@ -365,24 +400,28 @@ public class Zone return light; } + public float GetLight(cfloat2 position) => GetLight(AetheriaMath.ToUnity(position)); + public float2 GetForce(float2 position) { var normal = GetNormal(position); var f = new float2(normal.x, normal.z); - return f * Settings.GravityStrength * lengthsq(f); + return f * Settings.GravityStrength * LengthSquared(f); } + public cfloat2 GetForce(cfloat2 position) => AetheriaMath.ToCult(GetForce(AetheriaMath.ToUnity(position))); + public static float PowerPulse(float x, float exponent) { x *= 2; - x = clamp(x, -1, 1); - return pow((x + 1) * (1 - x), exponent); + x = Clamp(x, -1, 1); + return MathF.Pow((x + 1) * (1 - x), exponent); } public static float RadialWaves(float x, float maskExponent, float sineExponent, float frequency, float phase) { //x *= 2; - return PowerPulse(x, maskExponent) * cos(pow(x*2, sineExponent) * frequency + phase); + return PowerPulse(x, maskExponent) * MathF.Cos(MathF.Pow(x*2, sineExponent) * frequency + phase); } public float3 GetNormal(float2 pos, float step = .1f, float mul = 1) @@ -394,7 +433,41 @@ public class Zone // Deduce terrain normal float3 normal = new float3((hL - hR), (hD - hU), step*2); - return normalize(normal).xzy; + return Normalize(normal).xzy; + } + + public cfloat3 GetNormal(cfloat2 pos, float step = .1f, float mul = 1) => + AetheriaMath.ToCult(GetNormal(AetheriaMath.ToUnity(pos), step, mul)); + + private static float Clamp(float value, float min, float max) + { + return value < min ? min : value > max ? max : value; + } + + private static double Fraction(double value) + { + return value - Math.Floor(value); + } + + private static float Fraction(float value) + { + return value - MathF.Floor(value); + } + + private static float Length(float2 value) + { + return MathF.Sqrt(LengthSquared(value)); + } + + private static float LengthSquared(float2 value) + { + return value.x * value.x + value.y * value.y; + } + + private static float3 Normalize(float3 value) + { + var length = MathF.Sqrt(value.x * value.x + value.y * value.y + value.z * value.z); + return length <= 0 ? new float3(0, 0, 0) : value / length; } } @@ -484,8 +557,8 @@ public class GasGiant : Planet public class Sun : GasGiant { - public float3 LightColor { get; } - public float3 FogTintColor { get; } + public cfloat3 LightColor { get; } + public cfloat3 FogTintColor { get; } public float LightRadiusMultiplier { get; } public float LightRadius; @@ -559,15 +632,15 @@ public class Orbit public float Distance { get; } public float Phase { get; } public float2 FixedPosition { get; } - public float2 Velocity = float2.zero; - public float2 Position = float2.zero; - public float2 PreviousPosition = float2.zero; + public float2 Velocity = new float2(0, 0); + public float2 Position = new float2(0, 0); + public float2 PreviousPosition = new float2(0, 0); public float Period; public static float2 Evaluate(float phase) { - phase *= PI * 2; - return new float2(cos(phase), sin(phase)); + phase *= MathF.PI * 2; + return new float2(MathF.Cos(phase), MathF.Sin(phase)); } public Orbit(PlanetSettings settings, OrbitConstructionData data) diff --git a/Assets/Scripts/ServerShared/ZoneConstructionData.cs b/Assets/Scripts/ServerShared/ZoneConstructionData.cs index 54f49e06c..9e78bb86b 100644 --- a/Assets/Scripts/ServerShared/ZoneConstructionData.cs +++ b/Assets/Scripts/ServerShared/ZoneConstructionData.cs @@ -5,10 +5,10 @@ using System.Collections; using System.Collections.Generic; using UniRx; -using Unity.Mathematics; -using static Unity.Mathematics.math; +using cfloat3 = CultMath.float3; using float2 = Unity.Mathematics.float2; using float3 = Unity.Mathematics.float3; +using float4 = Unity.Mathematics.float4; public class ZoneConstructionBlueprint { @@ -84,9 +84,9 @@ public class GasGiantConstructionData : BodyConstructionData public class SunConstructionData : GasGiantConstructionData { - public float3 LightColor = float3.zero; + public cfloat3 LightColor = cfloat3.zero; - public float3 FogTintColor = float3.zero; + public cfloat3 FogTintColor = cfloat3.zero; public float LightRadiusMultiplier = 1; } @@ -112,6 +112,6 @@ public class OrbitConstructionData public float Phase; - public float2 FixedPosition = float2.zero; + public float2 FixedPosition = new float2(0, 0); } diff --git a/Assets/Scripts/ServerShared/ZoneGenerator.cs b/Assets/Scripts/ServerShared/ZoneGenerator.cs index b99d499aa..22340f589 100644 --- a/Assets/Scripts/ServerShared/ZoneGenerator.cs +++ b/Assets/Scripts/ServerShared/ZoneGenerator.cs @@ -9,9 +9,9 @@ using System.Globalization; using System.Linq; using GameCult.Aetheria.State.Unity; using UniRx; -using Unity.Mathematics; -using static Unity.Mathematics.math; using float2 = Unity.Mathematics.float2; +using float3 = Unity.Mathematics.float3; +using float4 = Unity.Mathematics.float4; using Random = Unity.Mathematics.Random; public static class ZoneGenerator @@ -21,7 +21,7 @@ public static class ZoneGenerator public float2 Center; public float Radius; - public float Area => PI * Radius * Radius; + public float Area => MathF.PI * Radius * Radius; public Circle(float2 center, float radius) { @@ -29,8 +29,8 @@ public static class ZoneGenerator Radius = radius; } - public float DistanceTo(float2 point) => length(point - Center) - Radius; - public float DistanceTo(Circle other) => length(other.Center - Center) - Radius - other.Radius; + public float DistanceTo(float2 point) => ZoneGenerator.Length(point - Center) - Radius; + public float DistanceTo(Circle other) => ZoneGenerator.Length(other.Center - Center) - Radius - other.Radius; } private const int MaximumPlacementSamples = 32; @@ -45,9 +45,9 @@ public static class ZoneGenerator { var blueprint = new ZoneConstructionBlueprint(); - var random = new Random(unchecked((uint) galaxyZone.Name.GetHashCode()) ^ hash(galaxyZone.Position)); + var random = new Random(unchecked((uint) galaxyZone.Name.GetHashCode()) ^ ZoneGenerator.Hash(galaxyZone.Position)); - var density = saturate(galaxy.Background.CloudDensity(galaxyZone.Position)/2); + var density = ZoneGenerator.Saturate(galaxy.Background.CloudDensity(galaxyZone.Position)/2); blueprint.Radius = zoneSettings.ZoneRadius.Evaluate(density); blueprint.Mass = zoneSettings.ZoneMass.Evaluate(density); var targetSubzoneCount = zoneSettings.SubZoneCount.Evaluate(density); @@ -57,11 +57,11 @@ public static class ZoneGenerator var planets = new List(); if (targetSubzoneCount > 1) { - var zoneBoundary = new Circle(float2.zero, blueprint.Radius * zoneSettings.ZoneBoundaryRadius); + var zoneBoundary = new Circle(new float2(0, 0), blueprint.Radius * zoneSettings.ZoneBoundaryRadius); float boundaryTangentRadius(float2 point) => -zoneBoundary.DistanceTo(point); var occupiedAreas = new List(); - float tangentRadius(float2 point) => min(boundaryTangentRadius(point), occupiedAreas.Min(circle => circle.DistanceTo(point))); + float tangentRadius(float2 point) => MathF.Min(boundaryTangentRadius(point), occupiedAreas.Min(circle => circle.DistanceTo(point))); var startPosition = random.NextFloat(blueprint.Radius * .25f, blueprint.Radius * .5f) * random.NextFloat2Direction(); occupiedAreas.Add(new Circle(startPosition, boundaryTangentRadius(startPosition))); @@ -91,7 +91,7 @@ public static class ZoneGenerator } } else - planets.AddRange(GenerateEntities(zoneSettings, ref random, blueprint.Mass, blueprint.Radius, float2.zero)); + planets.AddRange(GenerateEntities(zoneSettings, ref random, blueprint.Mass, blueprint.Radius, new float2(0, 0))); // Create collections to map between zone generator output and database entries var orbitMap = new Dictionary(); @@ -186,24 +186,24 @@ public static class ZoneGenerator else if (planetData is GasGiantConstructionData gas) { if (gas is SunConstructionData sun) - { + { float primary = random.NextFloat(); - float secondary = frac(primary + 1 + zoneSettings.SunSecondaryColorDistance * (random.NextFloat() > .5 ? 1 : -1)); + float secondary = ZoneGenerator.Fraction(primary + 1 + zoneSettings.SunSecondaryColorDistance * (random.NextFloat() > .5 ? 1 : -1)); gas.Colors = new [] { - float4(ColorMath.HsvToRgb(float3(primary, zoneSettings.SunColorSaturation, .5f)), 0), - float4(ColorMath.HsvToRgb(float3(secondary, zoneSettings.SunColorSaturation, 1)), 1) + new float4(AetheriaMath.ToUnity(ColorMath.HsvToRgb(new CultMath.float3(primary, zoneSettings.SunColorSaturation, .5f))), 0), + new float4(AetheriaMath.ToUnity(ColorMath.HsvToRgb(new CultMath.float3(secondary, zoneSettings.SunColorSaturation, 1))), 1) }; - sun.FogTintColor = ColorMath.HsvToRgb(float3(primary, zoneSettings.SunFogTintSaturation, 1)); - sun.LightColor = ColorMath.HsvToRgb(float3(primary, zoneSettings.SunLightSaturation, 1)); + sun.FogTintColor = ColorMath.HsvToRgb(new CultMath.float3(primary, zoneSettings.SunFogTintSaturation, 1)); + sun.LightColor = ColorMath.HsvToRgb(new CultMath.float3(primary, zoneSettings.SunLightSaturation, 1)); gas.FirstOffsetDomainRotationSpeed = 5; } else { // Define primary color and two adjacent colors float primary = random.NextFloat(); - float right = frac(primary + zoneSettings.GasGiantBandColorSeparation); - float left = frac(primary + 1 - zoneSettings.GasGiantBandColorSeparation); + float right = ZoneGenerator.Fraction(primary + zoneSettings.GasGiantBandColorSeparation); + float left = ZoneGenerator.Fraction(primary + 1 - zoneSettings.GasGiantBandColorSeparation); // Create n time keys from 0 to 1 var bandCount = (int) (zoneSettings.GasGiantBandCount.Evaluate(random.NextFloat()) + .5f); @@ -213,10 +213,10 @@ public static class ZoneGenerator // Each band has a chance of being either the primary or one of the adjacent hues // Saturation and Value are random with curves applied gas.Colors = times - .Select(time => float4(ColorMath.HsvToRgb(float3( + .Select(time => new float4(AetheriaMath.ToUnity(ColorMath.HsvToRgb(new CultMath.float3( random.NextFloat() > zoneSettings.GasGiantBandAltColorChance ? primary : (random.NextFloat() > .5f ? right : left), zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat()), - zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat()))),time)) + zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat())))),time)) .ToArray(); gas.FirstOffsetDomainRotationSpeed = 0; @@ -237,7 +237,7 @@ public static class ZoneGenerator var stationCount = (int)(random.NextFloat() * (factionPresence + 1)) + storyStations.Length; var potentialLagrangePoints = planets .Where(p => p.Parent != null && p.Parent.Children - .TrueForAll(c => !(c != p && abs(c.Distance - p.Distance) < .1f))) // Filter Rosettes + .TrueForAll(c => !(c != p && MathF.Abs(c.Distance - p.Distance) < .1f))) // Filter Rosettes .OrderBy(p => p.Distance) .ToArray(); // Pick a selection from the middle of the distribution @@ -252,7 +252,10 @@ public static class ZoneGenerator LoadoutGenerator GetLoadoutGenerator(Faction faction) { if (!loadoutGenerators.ContainsKey(faction)) - loadoutGenerators[faction] = new LoadoutGenerator(ref random, itemManager, runtimeCatalog, galaxy, galaxyZone, faction, .5f); + { + var loadoutRandom = new CultMath.Random((uint)random.NextInt(1, int.MaxValue)); + loadoutGenerators[faction] = new LoadoutGenerator(ref loadoutRandom, itemManager, runtimeCatalog, galaxy, galaxyZone, faction, .5f); + } return loadoutGenerators[faction]; } @@ -263,7 +266,7 @@ public static class ZoneGenerator OrbitKey = Zone.OrbitKey(Guid.NewGuid()), ParentOrbitKey = baseOrbit.ParentOrbitKey, Distance = baseOrbit.Distance, - Phase = baseOrbit.Phase + PI / 3 * sign(random.NextFloat() - .5f) + Phase = baseOrbit.Phase + MathF.PI / 3 * ZoneGenerator.Sign(random.NextFloat() - .5f) }; blueprint.Orbits.Add(lagrangeOrbit); return lagrangeOrbit; @@ -315,7 +318,7 @@ public static class ZoneGenerator for (var i = storyStations.Length; i < selectedStationOrbits.Length; i++) { var orbit = selectedStationOrbits[i]; - var security = (SecurityLevel) ((int) ((1f - pow(random.NextFloat(), factionPresence / 2f)) * 3f)); + var security = (SecurityLevel) ((int) ((1f - MathF.Pow(random.NextFloat(), factionPresence / 2f)) * 3f)); var lagrangeOrbit = CreateLagrangeOrbit(orbit); var station = GetLoadoutGenerator(nearestFaction).GenerateStationLoadout(); @@ -463,6 +466,31 @@ public static class ZoneGenerator return root.AllPlanets().ToArray(); } + + private static uint Hash(float2 value) + { + return unchecked((uint)HashCode.Combine(value.x, value.y)); + } + + private static float Fraction(float value) + { + return value - MathF.Floor(value); + } + + private static float Length(float2 value) + { + return MathF.Sqrt(value.x * value.x + value.y * value.y); + } + + private static float Saturate(float value) + { + return value < 0 ? 0 : value > 1 ? 1 : value; + } + + private static float Sign(float value) + { + return value < 0 ? -1 : value > 0 ? 1 : 0; + } } // All orbiting bodies are referred to here as "Planets" @@ -514,8 +542,9 @@ public class GeneratorPlanet var p1 = Orbit.Evaluate(1.0f / vertices) * dist; // Maximum child distance is half the distance to the neighbor minus the neighbor's radius - var p0ChildDist = (distance(p0, p1) * proportion - Settings.PlanetSafetyRadius.Evaluate(sharedMass * (1 - proportion))) * .75f; - var p1ChildDist = (distance(p0, p1) * (1 - proportion) - Settings.PlanetSafetyRadius.Evaluate(sharedMass * proportion)) * .75f; + var neighborDistance = Length(p0 - p1); + var p0ChildDist = (neighborDistance * proportion - Settings.PlanetSafetyRadius.Evaluate(sharedMass * (1 - proportion))) * .75f; + var p1ChildDist = (neighborDistance * (1 - proportion) - Settings.PlanetSafetyRadius.Evaluate(sharedMass * proportion)) * .75f; for (int i = 0; i < vertices; i++) { @@ -573,7 +602,7 @@ public class GeneratorPlanet { var oldDistances = (float[]) distances.Clone(); for (var i = 0; i < count; i++) - distances[i] = lerp(ChildDistanceMinimum, ChildDistanceMaximum, + distances[i] = Lerp(ChildDistanceMinimum, ChildDistanceMaximum, (oldDistances[i] - oldDistances[0]) / (oldDistances[count - 1] - oldDistances[0])) + Settings.PlanetSafetyRadius.Evaluate(masses[i]); } @@ -593,7 +622,7 @@ public class GeneratorPlanet //child.Period = Context.GlobalData.OrbitalPeriod(child.Distance); child.ChildDistanceMinimum = Settings.PlanetSafetyRadius.Evaluate(child.Mass) * 2; // Maximum child distance of child is the smallest distance to either of its neighbors - child.ChildDistanceMaximum = min(i == 0 ? child.Distance - ChildDistanceMinimum : child.Distance - distances[i - 1], + child.ChildDistanceMaximum = MathF.Min(i == 0 ? child.Distance - ChildDistanceMinimum : child.Distance - distances[i - 1], i < count - 1 ? distances[i + 1] - child.Distance : float.PositiveInfinity); if (float.IsNaN(child.Distance)) throw new NotFiniteNumberException($"Planet created with NaN distance, something went very wrong!"); @@ -602,4 +631,34 @@ public class GeneratorPlanet } } } + + private static uint Hash(float2 value) + { + return unchecked((uint)HashCode.Combine(value.x, value.y)); + } + + private static float Fraction(float value) + { + return value - MathF.Floor(value); + } + + private static float Length(float2 value) + { + return MathF.Sqrt(value.x * value.x + value.y * value.y); + } + + private static float Lerp(float from, float to, float t) + { + return from + (to - from) * t; + } + + private static float Saturate(float value) + { + return value < 0 ? 0 : value > 1 ? 1 : value; + } + + private static float Sign(float value) + { + return value < 0 ? -1 : value > 0 ? 1 : 0; + } } diff --git a/Assets/Scripts/Tests/CultMathPrimitiveTests.cs b/Assets/Scripts/Tests/CultMathPrimitiveTests.cs new file mode 100644 index 000000000..638711163 --- /dev/null +++ b/Assets/Scripts/Tests/CultMathPrimitiveTests.cs @@ -0,0 +1,61 @@ +using CultMath; +using NUnit.Framework; +using static CultMath.math; + +public sealed class CultMathPrimitiveTests +{ + [Test] + public void Int2SupportsAetheriaGridArithmetic() + { + var cell = int2(2, 3); + + Assert.AreEqual(new int2(3, 5), cell + int2(1, 2)); + Assert.AreEqual(new float2(2.0f, 3.0f), (float2)cell); + } + + [Test] + public void AetheriaMathRotatesItemDirectionsOnShipPlane() + { + var forward = float2(0, 1); + + Assert.AreEqual(float2(-1, 0), AetheriaMath.Rotate(forward, ItemRotation.CounterClockwise)); + Assert.AreEqual(float2(0, -1), AetheriaMath.Rotate(forward, ItemRotation.Reversed)); + Assert.AreEqual(float2(1, 0), AetheriaMath.Rotate(forward, ItemRotation.Clockwise)); + } + + [Test] + public void ItemRotationDirectionsAreCultVectors() + { + Assert.AreEqual(float2(0, 1), ItemRotation.None.Direction()); + Assert.AreEqual(float2(1, 0), ItemRotation.Clockwise.Direction()); + Assert.AreEqual(float2(0, -1), ItemRotation.Reversed.Direction()); + Assert.AreEqual(float2(-1, 0), ItemRotation.CounterClockwise.Direction()); + } + + [Test] + public void AetheriaMathProjectsUnityVectorsOntoShipPlane() + { + var world = new Unity.Mathematics.float3(2, 99, 3); + + Assert.AreEqual(float2(2, 3), AetheriaMath.ToCultXZ(world)); + Assert.AreEqual(new Unity.Mathematics.float3(2, 0, 3), AetheriaMath.ToUnityXZ(float2(2, 3))); + } + + [Test] + public void AetheriaMathCalculatesStableAngles() + { + Assert.That(AetheriaMath.AngleDegrees(float2(1, 0), float2(0, 1)), Is.EqualTo(90).Within(0.0001f)); + Assert.That(AetheriaMath.AngleRadians(float2(1, 0), float2(0, 1)), Is.EqualTo(PI / 2).Within(0.0001f)); + Assert.That(AetheriaMath.AngleDegrees(float3(1, 0, 0), float3(1, 0, 0)), Is.EqualTo(0).Within(0.0001f)); + Assert.That(AetheriaMath.AngleDegrees(float3(0, 0, 0), float3(1, 0, 0)), Is.EqualTo(0).Within(0.0001f)); + } + + [Test] + public void ColorMathUsesCultVectorsAndSharedUnityBridge() + { + var rgb = ColorMath.HsvToRgb(float3(0, 1, 1)); + + Assert.AreEqual(float3(1, 0, 0), rgb); + Assert.AreEqual(new Unity.Mathematics.float3(1, 0, 0), AetheriaMath.ToUnity(rgb)); + } +} diff --git a/Assets/Scripts/Tests/CultMathPrimitiveTests.cs.meta b/Assets/Scripts/Tests/CultMathPrimitiveTests.cs.meta new file mode 100644 index 000000000..7460e046c --- /dev/null +++ b/Assets/Scripts/Tests/CultMathPrimitiveTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d4fc4667d3155a843b610a24d40fa6f0 \ No newline at end of file diff --git a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs index 057317735..ede3189ee 100644 --- a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs +++ b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs @@ -196,6 +196,8 @@ public class DaemonRuntimeDocumentTests CollectionAssert.Contains(frame.Capabilities, AetheriaRuntimeDaemonSchemas.GameSurface); CollectionAssert.Contains(frame.Capabilities, AetheriaRuntimeDaemonSchemas.EditorSurface); CollectionAssert.AreEquivalent(result.OperationResult.AppliedCommandIds, frame.AppliedCommandIds); + CollectionAssert.Contains(frame.AccountedCommandIds, targetCommand.CommandId); + CollectionAssert.Contains(frame.AccountedCommandIds, movementCommand.CommandId); Assert.AreEqual(1, frame.Run.Zones[0].Entities[0].TargetEntityIndex); Assert.AreEqual( AetheriaRuntimeDaemonPublicationStore.GetProviderAdvertisementPath(statePath), @@ -336,6 +338,47 @@ public class DaemonRuntimeDocumentTests Assert.AreEqual(AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId, genericEditorTuiSurface.Surface.Id); } + [Test] + public void TickRunnerSkipsPreviouslyAccountedObservedCommands() + { + var statePath = Path.Combine( + Path.GetTempPath(), + "aetheria-daemon-tick-runner-tests", + Path.GetRandomFileName(), + "state.cc"); + var run = RunWithTwoEntities(); + var targetCommand = AetheriaRuntimeDaemonCommandDocument.Create( + AetheriaRuntimeDaemonCommandKinds.SetTarget, + "codex", + "session-tick", + 40, + "zone.0.entity.0"); + targetCommand.TargetEntityKey = "zone.0.entity.1"; + + var result = AetheriaRuntimeDaemonTickRunner.Tick( + statePath, + run, + new AetheriaRuntimeDaemonTickOptions + { + DaemonId = "test-daemon", + SessionId = "session-tick", + FrameId = 43, + SimulationTimeSeconds = 12.52, + FixedDeltaSeconds = 0.02, + ObservedCommands = new[] { targetCommand }, + AccountedCommandIds = new[] { targetCommand.CommandId } + }); + + Assert.AreEqual(0, result.OperationResult.AppliedCommandIds.Count); + Assert.AreEqual(0, result.OperationResult.RejectedCommandIds.Count); + Assert.AreEqual(-1, run.Zones[0].Entities[0].TargetEntityIndex); + CollectionAssert.Contains(result.Frame.AccountedCommandIds, targetCommand.CommandId); + Assert.IsTrue(AetheriaRuntimeDaemonPublicationStore.TryReadHealth(statePath, out var health)); + Assert.AreEqual(1, health.ObservedCommandCount); + Assert.AreEqual(0, health.AppliedCommandCount); + Assert.AreEqual(0, health.RejectedCommandCount); + } + [Test] public void SoaViewFactoryDescribesDaemonOwnedObserverReadOnlySlabs() { diff --git a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs.meta b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs.meta new file mode 100644 index 000000000..8c06f58a4 --- /dev/null +++ b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: da50d4a5bedc04148a6912cff6d6f02d \ No newline at end of file diff --git a/Assets/Scripts/Tests/StatRecipeSurfaceCommandTests.cs b/Assets/Scripts/Tests/StatRecipeSurfaceCommandTests.cs new file mode 100644 index 000000000..b74d87a3c --- /dev/null +++ b/Assets/Scripts/Tests/StatRecipeSurfaceCommandTests.cs @@ -0,0 +1,108 @@ +using System.Linq; +using GameCult.Aetheria.State.Unity; +using NUnit.Framework; + +public class StatRecipeSurfaceCommandTests +{ + private const string TestUpdatedAtUtc = "2026-06-18T00:00:00.0000000Z"; + + [Test] + public void ToggleConditionCreatesEnabledInfluenceForSelectedRecipe() + { + var state = StateWithRecipe(); + + var next = AetheriaRuntimeStatRecipes.ToggleCondition( + state, + "weapon.damage", + AetheriaRuntimeStatRecipeConditions.Range, + updatedAtUtc: TestUpdatedAtUtc); + + var influence = next.SelectedRecipe.Influences.Single(); + Assert.AreEqual(AetheriaRuntimeStatRecipeConditions.Range, influence.Condition); + Assert.AreEqual(AetheriaRuntimeStatRecipeOperations.Add, influence.Operation); + Assert.IsTrue(influence.Enabled); + } + + [Test] + public void CycleInfluenceOperationAdvancesAddMultiplyOverride() + { + var state = AetheriaRuntimeStatRecipes.ToggleCondition( + StateWithRecipe(), + "weapon.damage", + AetheriaRuntimeStatRecipeConditions.Quality, + updatedAtUtc: TestUpdatedAtUtc); + + state = AetheriaRuntimeStatRecipes.CycleInfluenceOperation( + state, + "weapon.damage", + AetheriaRuntimeStatRecipeConditions.Quality, + updatedAtUtc: TestUpdatedAtUtc); + + Assert.AreEqual(AetheriaRuntimeStatRecipeOperations.Multiply, state.SelectedRecipe.Influences[0].Operation); + + state = AetheriaRuntimeStatRecipes.CycleInfluenceOperation( + state, + "weapon.damage", + AetheriaRuntimeStatRecipeConditions.Quality, + updatedAtUtc: TestUpdatedAtUtc); + + Assert.AreEqual(AetheriaRuntimeStatRecipeOperations.Override, state.SelectedRecipe.Influences[0].Operation); + } + + [Test] + public void SetInfluenceAmountAndCurveUpdateOnlyTargetInfluence() + { + var state = AetheriaRuntimeStatRecipes.ToggleCondition( + StateWithRecipe(), + "weapon.damage", + AetheriaRuntimeStatRecipeConditions.Heat, + updatedAtUtc: TestUpdatedAtUtc); + + state = AetheriaRuntimeStatRecipes.SetInfluenceAmount( + state, + "weapon.damage", + AetheriaRuntimeStatRecipeConditions.Heat, + 3.5, + updatedAtUtc: TestUpdatedAtUtc); + + state = AetheriaRuntimeStatRecipes.SetInfluenceCurve( + state, + "weapon.damage", + AetheriaRuntimeStatRecipeConditions.Heat, + "easeOut", + updatedAtUtc: TestUpdatedAtUtc); + + var influence = state.SelectedRecipe.Influences.Single(); + Assert.AreEqual(3.5, influence.Amount, 0.0001); + Assert.AreEqual("easeOut", influence.CurveLabel); + } + + [Test] + public void SetPreviewConditionUpdatesRequestedPreviewAxis() + { + var next = AetheriaRuntimeStatRecipes.SetPreviewCondition( + StateWithRecipe(), + AetheriaRuntimeStatRecipeConditions.Charge, + 0.25, + TestUpdatedAtUtc); + + Assert.AreEqual(0.25, next.Preview.Charge, 0.0001); + Assert.AreEqual(AetheriaRuntimeStatRecipePreviewState.Default.Quality, next.Preview.Quality, 0.0001); + } + + private static AetheriaRuntimeStatRecipeSurfaceState StateWithRecipe() + { + return new AetheriaRuntimeStatRecipeSurfaceState( + new[] + { + new AetheriaRuntimeStatRecipeState( + "weapon.damage", + "Weapon Damage", + 10, + new AetheriaRuntimeStatInfluenceState[0]) + }, + "Weapon Damage", + AetheriaRuntimeStatRecipePreviewState.Default, + ""); + } +} diff --git a/Assets/Scripts/Tests/StatRecipeSurfaceCommandTests.cs.meta b/Assets/Scripts/Tests/StatRecipeSurfaceCommandTests.cs.meta new file mode 100644 index 000000000..303ab893a --- /dev/null +++ b/Assets/Scripts/Tests/StatRecipeSurfaceCommandTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 45bf0c07c6a63554a949f3605ff81d13 \ No newline at end of file diff --git a/Assets/Scripts/Tests/StatRecipeTests.cs b/Assets/Scripts/Tests/StatRecipeTests.cs new file mode 100644 index 000000000..4ea896bfc --- /dev/null +++ b/Assets/Scripts/Tests/StatRecipeTests.cs @@ -0,0 +1,254 @@ +using System; +using GameCult.Aetheria.State.Unity; +using NUnit.Framework; + +public class StatRecipeTests +{ + [Test] + public void AddModifierScalesByConditionSample() + { + var recipe = new StatRecipe + { + BaseValue = 10, + Modifiers = new[] + { + new StatRecipeModifier + { + Condition = StatConditionMask.Quality, + Operation = StatModifierOperation.Add, + Amount = 6 + } + } + }; + + var value = recipe.Evaluate(new StatEvaluationContext(quality: 0.5f, durability: 1, heat: 1)); + + Assert.AreEqual(13, value, 0.0001f); + } + + [Test] + public void MultiplyModifierInterpolatesFromIdentity() + { + var recipe = new StatRecipe + { + BaseValue = 10, + Modifiers = new[] + { + new StatRecipeModifier + { + Condition = StatConditionMask.Durability, + Operation = StatModifierOperation.Multiply, + Amount = 2 + } + } + }; + + var value = recipe.Evaluate(new StatEvaluationContext(quality: 1, durability: 0.25f, heat: 1)); + + Assert.AreEqual(12.5f, value, 0.0001f); + } + + [Test] + public void OverrideModifierInterpolatesTowardTargetValue() + { + var recipe = new StatRecipe + { + BaseValue = 10, + Modifiers = new[] + { + new StatRecipeModifier + { + Condition = StatConditionMask.Heat, + Operation = StatModifierOperation.Override, + Amount = 20 + } + } + }; + + var value = recipe.Evaluate(new StatEvaluationContext(quality: 1, durability: 1, heat: 0.25f)); + + Assert.AreEqual(12.5f, value, 0.0001f); + } + + [Test] + public void DependencyMaskOnlyIncludesEnabledConditions() + { + var recipe = new StatRecipe + { + Modifiers = new[] + { + new StatRecipeModifier + { + Condition = StatConditionMask.Quality, + Enabled = false + }, + new StatRecipeModifier + { + Condition = StatConditionMask.Range, + Enabled = true + } + } + }; + + Assert.AreEqual(StatConditionMask.Range, recipe.DependencyMask); + } + + [Test] + public void DisabledAndConditionlessModifiersDoNotAffectEvaluation() + { + var recipe = new StatRecipe + { + BaseValue = 10, + Modifiers = new[] + { + new StatRecipeModifier + { + Condition = StatConditionMask.Quality, + Operation = StatModifierOperation.Add, + Amount = 100, + Enabled = false + }, + new StatRecipeModifier + { + Condition = StatConditionMask.None, + Operation = StatModifierOperation.Add, + Amount = 100, + Enabled = true + }, + new StatRecipeModifier + { + Condition = StatConditionMask.Heat, + Operation = StatModifierOperation.Add, + Amount = 4, + Enabled = true + } + } + }; + + var value = recipe.Evaluate(new StatEvaluationContext(quality: 1, durability: 1, heat: 0.25f)); + + Assert.AreEqual(11, value, 0.0001f); + Assert.AreEqual(StatConditionMask.Heat, recipe.DependencyMask); + } + + [Test] + public void ContextOverrideReplacesSingleCondition() + { + var context = new StatEvaluationContext(quality: 0.2f, durability: 0.3f, heat: 0.4f, range: 0.5f); + + var next = context.WithCondition(StatConditionMask.Range, 0.75f); + + Assert.AreEqual(0.2f, next.Quality, 0.0001f); + Assert.AreEqual(0.3f, next.Durability, 0.0001f); + Assert.AreEqual(0.4f, next.Heat, 0.0001f); + Assert.AreEqual(0.75f, next.Range, 0.0001f); + } + + [Test] + public void RecipeTokensMapCatalogStringsToRuntimeEnums() + { + Assert.AreEqual(StatConditionMask.Quality, StatRecipeTokens.ToConditionMask("quality")); + Assert.AreEqual(StatConditionMask.PilotSkill, StatRecipeTokens.ToConditionMask("pilotSkill")); + Assert.AreEqual(StatConditionMask.None, StatRecipeTokens.ToConditionMask("unknown")); + Assert.AreEqual(StatModifierOperation.Multiply, StatRecipeTokens.ToModifierOperation("multiply")); + Assert.AreEqual(StatModifierOperation.Override, StatRecipeTokens.ToModifierOperation("override")); + Assert.AreEqual(StatModifierOperation.Add, StatRecipeTokens.ToModifierOperation("unknown")); + } + + [Test] + public void LegacyStatDependencyMaskIncludesOnlyActiveLegacyInputs() + { + var stat = new PerformanceStat + { + QualityExponent = 2, + DurabilityExponentMultiplier = 0, + HeatExponentMultiplier = 3 + }; + + Assert.AreEqual(StatConditionMask.Quality, stat.GetDependencyMask(includeHeat: false)); + Assert.AreEqual(StatConditionMask.Quality | StatConditionMask.Heat, stat.GetDependencyMask(includeHeat: true)); + } + + [Test] + public void LegacyStatEvaluationUsesExplicitContextExponents() + { + var stat = new PerformanceStat + { + Min = 0, + Max = 100, + QualityExponent = 2, + DurabilityExponentMultiplier = 1, + HeatExponentMultiplier = 1 + }; + + var value = stat.EvaluateRecipeOrLegacy( + new StatEvaluationContext(quality: 0.5f, durability: 0.5f, heat: 0.5f), + durabilityExponent: 2, + thermalExponent: 2, + includeHeat: true); + + Assert.AreEqual(3.125f, value, 0.0001f); + } + + [Test] + public void ConsumableBaselinePreservesLinearEffectivenessCompatibility() + { + var stat = new PerformanceStat + { + Min = 10, + Max = 30, + QualityExponent = 2, + DurabilityExponentMultiplier = 10 + }; + + var value = stat.EvaluateConsumableBaseline( + new StatEvaluationContext(quality: 0.5f, durability: 0.5f, heat: 1)); + + Assert.AreEqual(12.5f, value, 0.0001f); + } + + [Test] + public void EntityModifierApplicationLeavesBaseValueWhenNoEntityModifiersExist() + { + var stat = new PerformanceStat(); + + Assert.AreEqual(42f, stat.ApplyEntityModifiers(null, 42f), 0.0001f); + } + + [Test] + public void BehaviorValueReaderReadsEnumFromString() + { + var value = BehaviorValue("multiply", 0); + + var operation = AetheriaRuntimeBehaviorValueReader.ReadEnum(value, StatModifierOperation.Add); + + Assert.AreEqual(StatModifierOperation.Multiply, operation); + } + + [Test] + public void BehaviorValueReaderReadsEnumFromNumberOrFallback() + { + var value = BehaviorValue("", (int)StatModifierOperation.Override); + var unknown = BehaviorValue("", 999); + + Assert.AreEqual( + StatModifierOperation.Override, + AetheriaRuntimeBehaviorValueReader.ReadEnum(value, StatModifierOperation.Add)); + Assert.AreEqual( + StatModifierOperation.Add, + AetheriaRuntimeBehaviorValueReader.ReadEnum(unknown, StatModifierOperation.Add)); + } + + private static AetheriaRuntimeBehaviorValue BehaviorValue(string stringValue, double numberValue) + { + return new AetheriaRuntimeBehaviorValue( + "", + stringValue, + numberValue, + false, + "", + "", + Array.Empty(), + Array.Empty()); + } +} diff --git a/Assets/Scripts/Tests/StatRecipeTests.cs.meta b/Assets/Scripts/Tests/StatRecipeTests.cs.meta new file mode 100644 index 000000000..1ee70bc0f --- /dev/null +++ b/Assets/Scripts/Tests/StatRecipeTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 53a35efb3a0d3d646a1ea27845e4391f \ No newline at end of file diff --git a/Assets/Scripts/Tests/Tests.asmdef b/Assets/Scripts/Tests/Tests.asmdef index ede58b507..a3d91b58d 100644 --- a/Assets/Scripts/Tests/Tests.asmdef +++ b/Assets/Scripts/Tests/Tests.asmdef @@ -5,7 +5,9 @@ "UnityEngine.TestRunner", "UnityEditor.TestRunner", "Unity.Mathematics", - "Aetheria.Shared.Unity" + "CultMath", + "Aetheria.Shared.Unity", + "GameCult.Aetheria.State.Unity" ], "includePlatforms": [ "Editor" @@ -22,4 +24,4 @@ ], "versionDefines": [], "noEngineReferences": false -} \ No newline at end of file +} diff --git a/Assets/Scripts/Tests/YmirPhysicsQueryTests.cs b/Assets/Scripts/Tests/YmirPhysicsQueryTests.cs new file mode 100644 index 000000000..cee7fe1d7 --- /dev/null +++ b/Assets/Scripts/Tests/YmirPhysicsQueryTests.cs @@ -0,0 +1,482 @@ +using System.Collections.Generic; +using NUnit.Framework; + +public sealed class YmirPhysicsQueryTests +{ + [Test] + public void StepIntegratesDynamicBodiesWithoutMutatingInputWorld() + { + var body = PhysicsBody("ship", 1, 2, 1); + body.velocity = Vec2(3, -4); + var world = World(body); + + var result = YmirPhysicsQueries.Step(world, 0.5f); + + Assert.That(result.world.time, Is.EqualTo(0.5f).Within(0.0001f)); + Assert.That(result.world.bodies[0].position.x, Is.EqualTo(2.5f).Within(0.0001f)); + Assert.That(result.world.bodies[0].position.y, Is.EqualTo(0.0f).Within(0.0001f)); + Assert.That(world.bodies[0].position.x, Is.EqualTo(1.0f).Within(0.0001f)); + Assert.That(world.bodies[0].position.y, Is.EqualTo(2.0f).Within(0.0001f)); + } + + [Test] + public void StepAppliesRadialFieldsWithLinearFalloff() + { + var body = PhysicsBody("ship", 5, 0, 1); + var world = World(body); + world.fields = new[] + { + new YmirRadialField + { + id = "push", + position = Vec2(0, 0), + strength = 10, + radius = 10 + } + }; + + var result = YmirPhysicsQueries.Step(world, 1.0f); + + Assert.That(result.world.bodies[0].velocity.x, Is.EqualTo(5.0f).Within(0.0001f)); + Assert.That(result.world.bodies[0].position.x, Is.EqualTo(10.0f).Within(0.0001f)); + } + + [Test] + public void StepReportsContactsAndSeparatesDynamicBodyFromStaticBody() + { + var mover = PhysicsBody("mover", 0, 0, 1); + mover.velocity = Vec2(2, 0); + mover.mass = 1; + var wall = PhysicsBody("wall", 1.5f, 0, 1); + wall.isStatic = true; + var world = World(mover, wall); + + var result = YmirPhysicsQueries.Step(world, 0.1f); + + Assert.That(result.contacts, Has.Length.EqualTo(1)); + Assert.AreEqual("mover", result.contacts[0].bodyA); + Assert.AreEqual("wall", result.contacts[0].bodyB); + Assert.That(result.contacts[0].normal.x, Is.EqualTo(1.0f).Within(0.0001f)); + Assert.That(result.contacts[0].penetration, Is.GreaterThan(0)); + Assert.That(result.world.bodies[0].position.x, Is.EqualTo(-0.5f).Within(0.0001f)); + } + + [Test] + public void StepSkipsInvalidInput() + { + var result = YmirPhysicsQueries.Step((YmirStepRequest)null); + + Assert.That(result.world.bodies, Is.Empty); + Assert.That(result.contacts, Is.Empty); + Assert.That(YmirPhysicsQueries.Step(World(PhysicsBody("ship", 1, 0, 1)), -1).contacts, Is.Empty); + } + + [Test] + public void OverlapSphereReturnsHitsSortedBySurfaceDistance() + { + var hits = YmirPhysicsQueries.OverlapSphere( + new[] + { + Body("far", 4, 0, 0, 2), + Body("near", 1, 0, 0, 0.25f), + Body("miss", 10, 0, 0, 1) + }, + Vec3(0, 0, 0), + 3); + + Assert.That(hits, Has.Length.EqualTo(2)); + Assert.AreEqual("near", hits[0].bodyId); + Assert.AreEqual("far", hits[1].bodyId); + Assert.That(hits[0].distance, Is.EqualTo(0.75f).Within(0.0001f)); + Assert.That(hits[1].distance, Is.EqualTo(2.0f).Within(0.0001f)); + } + + [Test] + public void OverlapSphereBreaksEqualDistanceTiesByBodyId() + { + var hits = YmirPhysicsQueries.OverlapSphere( + new[] + { + Body("b", -2, 0, 0, 1), + Body("a", 2, 0, 0, 1) + }, + Vec3(0, 0, 0), + 3); + + Assert.That(hits, Has.Length.EqualTo(2)); + Assert.AreEqual("a", hits[0].bodyId); + Assert.AreEqual("b", hits[1].bodyId); + } + + [Test] + public void OverlapSphereReportsPenetrationAndContactPoint() + { + var hits = YmirPhysicsQueries.OverlapSphere( + new[] { Body("target", 2, 0, 0, 1) }, + Vec3(0, 0, 0), + 3); + + Assert.That(hits, Has.Length.EqualTo(1)); + Assert.That(hits[0].penetration, Is.EqualTo(2.0f).Within(0.0001f)); + Assert.That(hits[0].normal.x, Is.EqualTo(1.0f).Within(0.0001f)); + Assert.That(hits[0].point.x, Is.EqualTo(1.0f).Within(0.0001f)); + } + + [Test] + public void OverlapSphereCanReuseCallerOwnedHitBuffer() + { + var hits = new List { new YmirSphereOverlapHit { bodyId = "stale" } }; + + var count = YmirPhysicsQueries.OverlapSphere( + new[] + { + Body("b", -2, 0, 0, 1), + Body("a", 2, 0, 0, 1) + }, + Vec3(0, 0, 0), + 3, + hits); + + Assert.That(count, Is.EqualTo(2)); + Assert.That(hits, Has.Count.EqualTo(2)); + Assert.AreEqual("a", hits[0].bodyId); + Assert.AreEqual("b", hits[1].bodyId); + } + + [Test] + public void OverlapSphereSkipsInvalidInput() + { + Assert.That(YmirPhysicsQueries.OverlapSphere(null, Vec3(0, 0, 0), 1), Is.Empty); + Assert.That(YmirPhysicsQueries.OverlapSphere(new[] { Body("bad", 0, 0, 0, -1) }, Vec3(0, 0, 0), 1), Is.Empty); + Assert.That(YmirPhysicsQueries.OverlapSphere(new[] { Body("target", 0, 0, 0, 1) }, Vec3(0, 0, 0), -1), Is.Empty); + } + + [Test] + public void OverlapCircleReturnsHitsSortedBySurfaceDistance() + { + var hits = YmirPhysicsQueries.OverlapCircle( + new[] + { + PhysicsBody("far", 4, 0, 2), + PhysicsBody("near", 1, 0, 0.25f), + PhysicsBody("miss", 10, 0, 1) + }, + Vec2(0, 0), + 3); + + Assert.That(hits, Has.Length.EqualTo(2)); + Assert.AreEqual("near", hits[0].bodyId); + Assert.AreEqual("far", hits[1].bodyId); + Assert.That(hits[0].distance, Is.EqualTo(0.75f).Within(0.0001f)); + Assert.That(hits[1].distance, Is.EqualTo(2.0f).Within(0.0001f)); + } + + [Test] + public void OverlapCircleBreaksEqualDistanceTiesByBodyId() + { + var hits = YmirPhysicsQueries.OverlapCircle( + new[] + { + PhysicsBody("b", -2, 0, 1), + PhysicsBody("a", 2, 0, 1) + }, + Vec2(0, 0), + 3); + + Assert.That(hits, Has.Length.EqualTo(2)); + Assert.AreEqual("a", hits[0].bodyId); + Assert.AreEqual("b", hits[1].bodyId); + } + + [Test] + public void OverlapCircleReportsPenetrationAndContactPoint() + { + var hits = YmirPhysicsQueries.OverlapCircle( + new[] { PhysicsBody("target", 2, 0, 1) }, + Vec2(0, 0), + 3); + + Assert.That(hits, Has.Length.EqualTo(1)); + Assert.That(hits[0].penetration, Is.EqualTo(2.0f).Within(0.0001f)); + Assert.That(hits[0].normal.x, Is.EqualTo(1.0f).Within(0.0001f)); + Assert.That(hits[0].point.x, Is.EqualTo(1.0f).Within(0.0001f)); + } + + [Test] + public void OverlapCircleCanReuseCallerOwnedHitBuffer() + { + var hits = new List { new YmirCircleOverlapHit { bodyId = "stale" } }; + + var count = YmirPhysicsQueries.OverlapCircle( + new[] + { + PhysicsBody("b", -2, 0, 1), + PhysicsBody("a", 2, 0, 1) + }, + Vec2(0, 0), + 3, + hits); + + Assert.That(count, Is.EqualTo(2)); + Assert.That(hits, Has.Count.EqualTo(2)); + Assert.AreEqual("a", hits[0].bodyId); + Assert.AreEqual("b", hits[1].bodyId); + } + + [Test] + public void OverlapCircleSkipsInvalidInput() + { + Assert.That(YmirPhysicsQueries.OverlapCircle(null, Vec2(0, 0), 1), Is.Empty); + Assert.That(YmirPhysicsQueries.OverlapCircle(new[] { PhysicsBody("bad", 0, 0, -1) }, Vec2(0, 0), 1), Is.Empty); + Assert.That(YmirPhysicsQueries.OverlapCircle(new[] { PhysicsBody("target", 0, 0, 1) }, Vec2(0, 0), -1), Is.Empty); + } + + [Test] + public void CastSphereReturnsHitsSortedByImpactDistance() + { + var hits = YmirPhysicsQueries.CastSphere( + new[] + { + Body("far", 8, 0, 0, 1), + Body("near", 4, 0, 0, 1), + Body("miss", 4, 4, 0, 1) + }, + Vec3(0, 0, 0), + Vec3(1, 0, 0), + 10, + 1); + + Assert.That(hits, Has.Length.EqualTo(2)); + Assert.AreEqual("near", hits[0].bodyId); + Assert.AreEqual("far", hits[1].bodyId); + Assert.That(hits[0].distance, Is.EqualTo(2.0f).Within(0.0001f)); + Assert.That(hits[1].distance, Is.EqualTo(6.0f).Within(0.0001f)); + } + + [Test] + public void CastSphereBreaksEqualDistanceTiesByBodyId() + { + var hits = YmirPhysicsQueries.CastSphere( + new[] + { + Body("b", 4, 1, 0, 1), + Body("a", 4, -1, 0, 1) + }, + Vec3(0, 0, 0), + Vec3(1, 0, 0), + 10, + 0); + + Assert.That(hits, Has.Length.EqualTo(2)); + Assert.AreEqual("a", hits[0].bodyId); + Assert.AreEqual("b", hits[1].bodyId); + } + + [Test] + public void CastSphereReportsImpactPointAndNormal() + { + var hits = YmirPhysicsQueries.CastSphere( + new[] { Body("target", 4, 0, 0, 1) }, + Vec3(0, 0, 0), + Vec3(2, 0, 0), + 10, + 1); + + Assert.That(hits, Has.Length.EqualTo(1)); + Assert.That(hits[0].distance, Is.EqualTo(2.0f).Within(0.0001f)); + Assert.That(hits[0].normal.x, Is.EqualTo(1.0f).Within(0.0001f)); + Assert.That(hits[0].point.x, Is.EqualTo(3.0f).Within(0.0001f)); + } + + [Test] + public void CastSphereReportsInitialOverlapAtZeroDistance() + { + var hits = YmirPhysicsQueries.CastSphere( + new[] { Body("inside", 0.5f, 0, 0, 1) }, + Vec3(0, 0, 0), + Vec3(1, 0, 0), + 10, + 1); + + Assert.That(hits, Has.Length.EqualTo(1)); + Assert.That(hits[0].distance, Is.EqualTo(0.0f).Within(0.0001f)); + } + + [Test] + public void CastSphereCanReuseCallerOwnedHitBuffer() + { + var hits = new List { new YmirSphereCastHit { bodyId = "stale" } }; + + var count = YmirPhysicsQueries.CastSphere( + new[] + { + Body("far", 8, 0, 0, 1), + Body("near", 4, 0, 0, 1) + }, + Vec3(0, 0, 0), + Vec3(1, 0, 0), + 10, + 1, + hits); + + Assert.That(count, Is.EqualTo(2)); + Assert.That(hits, Has.Count.EqualTo(2)); + Assert.AreEqual("near", hits[0].bodyId); + Assert.AreEqual("far", hits[1].bodyId); + } + + [Test] + public void CastSphereSkipsInvalidInput() + { + Assert.That(YmirPhysicsQueries.CastSphere(null, Vec3(0, 0, 0), Vec3(1, 0, 0), 1, 1), Is.Empty); + Assert.That(YmirPhysicsQueries.CastSphere(new[] { Body("target", 0, 0, 0, 1) }, Vec3(0, 0, 0), Vec3(0, 0, 0), 1, 1), Is.Empty); + Assert.That(YmirPhysicsQueries.CastSphere(new[] { Body("target", 0, 0, 0, 1) }, Vec3(0, 0, 0), Vec3(1, 0, 0), -1, 1), Is.Empty); + Assert.That(YmirPhysicsQueries.CastSphere(new[] { Body("bad", 0, 0, 0, -1) }, Vec3(0, 0, 0), Vec3(1, 0, 0), 1, 1), Is.Empty); + } + + [Test] + public void CastCircleReturnsHitsSortedByImpactDistance() + { + var hits = YmirPhysicsQueries.CastCircle( + new[] + { + PhysicsBody("far", 8, 0, 1), + PhysicsBody("near", 4, 0, 1), + PhysicsBody("miss", 4, 4, 1) + }, + Vec2(0, 0), + Vec2(1, 0), + 10, + 1); + + Assert.That(hits, Has.Length.EqualTo(2)); + Assert.AreEqual("near", hits[0].bodyId); + Assert.AreEqual("far", hits[1].bodyId); + Assert.That(hits[0].distance, Is.EqualTo(2.0f).Within(0.0001f)); + Assert.That(hits[1].distance, Is.EqualTo(6.0f).Within(0.0001f)); + } + + [Test] + public void CastCircleBreaksEqualDistanceTiesByBodyId() + { + var hits = YmirPhysicsQueries.CastCircle( + new[] + { + PhysicsBody("b", 4, 1, 1), + PhysicsBody("a", 4, -1, 1) + }, + Vec2(0, 0), + Vec2(1, 0), + 10, + 0); + + Assert.That(hits, Has.Length.EqualTo(2)); + Assert.AreEqual("a", hits[0].bodyId); + Assert.AreEqual("b", hits[1].bodyId); + } + + [Test] + public void CastCircleReportsImpactPointAndNormal() + { + var hits = YmirPhysicsQueries.CastCircle( + new[] { PhysicsBody("target", 4, 0, 1) }, + Vec2(0, 0), + Vec2(2, 0), + 10, + 1); + + Assert.That(hits, Has.Length.EqualTo(1)); + Assert.That(hits[0].distance, Is.EqualTo(2.0f).Within(0.0001f)); + Assert.That(hits[0].normal.x, Is.EqualTo(1.0f).Within(0.0001f)); + Assert.That(hits[0].point.x, Is.EqualTo(3.0f).Within(0.0001f)); + } + + [Test] + public void CastCircleReportsInitialOverlapAtZeroDistance() + { + var hits = YmirPhysicsQueries.CastCircle( + new[] { PhysicsBody("inside", 0.5f, 0, 1) }, + Vec2(0, 0), + Vec2(1, 0), + 10, + 1); + + Assert.That(hits, Has.Length.EqualTo(1)); + Assert.That(hits[0].distance, Is.EqualTo(0.0f).Within(0.0001f)); + } + + [Test] + public void CastCircleCanReuseCallerOwnedHitBuffer() + { + var hits = new List { new YmirCircleCastHit { bodyId = "stale" } }; + + var count = YmirPhysicsQueries.CastCircle( + new[] + { + PhysicsBody("far", 8, 0, 1), + PhysicsBody("near", 4, 0, 1) + }, + Vec2(0, 0), + Vec2(1, 0), + 10, + 1, + hits); + + Assert.That(count, Is.EqualTo(2)); + Assert.That(hits, Has.Count.EqualTo(2)); + Assert.AreEqual("near", hits[0].bodyId); + Assert.AreEqual("far", hits[1].bodyId); + } + + [Test] + public void CastCircleSkipsInvalidInput() + { + Assert.That(YmirPhysicsQueries.CastCircle(null, Vec2(0, 0), Vec2(1, 0), 1, 1), Is.Empty); + Assert.That(YmirPhysicsQueries.CastCircle(new[] { PhysicsBody("target", 0, 0, 1) }, Vec2(0, 0), Vec2(0, 0), 1, 1), Is.Empty); + Assert.That(YmirPhysicsQueries.CastCircle(new[] { PhysicsBody("target", 0, 0, 1) }, Vec2(0, 0), Vec2(1, 0), -1, 1), Is.Empty); + Assert.That(YmirPhysicsQueries.CastCircle(new[] { PhysicsBody("bad", 0, 0, -1) }, Vec2(0, 0), Vec2(1, 0), 1, 1), Is.Empty); + } + + private static YmirSphereQueryBody Body(string id, float x, float y, float z, float radius) + { + return new YmirSphereQueryBody + { + id = id, + position = Vec3(x, y, z), + radius = radius + }; + } + + private static YmirPhysicsBody PhysicsBody(string id, float x, float y, float radius) + { + return new YmirPhysicsBody + { + id = id, + position = Vec2(x, y), + direction = Vec2(1, 0), + radius = radius, + mass = 1, + momentOfInertia = 1 + }; + } + + private static YmirWorld World(params YmirPhysicsBody[] bodies) + { + return new YmirWorld + { + time = 0, + bodies = bodies, + fields = new YmirRadialField[0] + }; + } + + private static YmirVec3 Vec3(float x, float y, float z) + { + return new YmirVec3 { x = x, y = y, z = z }; + } + + private static YmirVec2 Vec2(float x, float y) + { + return new YmirVec2 { x = x, y = y }; + } +} diff --git a/Assets/Scripts/Tests/YmirPhysicsQueryTests.cs.meta b/Assets/Scripts/Tests/YmirPhysicsQueryTests.cs.meta new file mode 100644 index 000000000..77f66b60e --- /dev/null +++ b/Assets/Scripts/Tests/YmirPhysicsQueryTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 97ea5fc21683ebf409292b94fcb4cf6a \ No newline at end of file diff --git a/Assets/Scripts/UI/ClickRaycaster.cs b/Assets/Scripts/UI/ClickRaycaster.cs index 79968d156..f76687f5f 100644 --- a/Assets/Scripts/UI/ClickRaycaster.cs +++ b/Assets/Scripts/UI/ClickRaycaster.cs @@ -3,7 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using System.Collections; using System.Collections.Generic; using UniRx; using UnityEngine; @@ -21,17 +20,20 @@ public class ClickRaycaster : MonoBehaviour _clickCatcher = GetComponent(); _clickCatcher.OnDown.Subscribe(pointer => { - RaycastHit hit; var ray = RayCamera.ScreenPointToRay(pointer.position); - Physics.Raycast(ray, out hit, 1000, Layers.value); - if (hit.collider != null) + var clickables = new List(); + foreach (var clickable in ClickableCollider.Active) { - //Debug.Log($"Clicked on gameobject {hit.collider.gameObject}"); - var clickable = hit.collider.GetComponent(); - if (clickable != null) - clickable.Click(pointer, ray, hit); + if (clickable == null || ((1 << clickable.gameObject.layer) & Layers.value) == 0) + continue; + + clickables.Add(clickable); } - else OnClickMiss?.Invoke(pointer); + + if (AetheriaYmirPhysicsBridge.Instance.TryCastClickables(clickables, ray, 1000, out var hit)) + hit.Clickable.Click(pointer, ray, hit); + else + OnClickMiss?.Invoke(pointer); }); } } diff --git a/Assets/Scripts/UI/ClickableCollider.cs b/Assets/Scripts/UI/ClickableCollider.cs index 2d2ae6ffd..020cca7e5 100644 --- a/Assets/Scripts/UI/ClickableCollider.cs +++ b/Assets/Scripts/UI/ClickableCollider.cs @@ -3,26 +3,94 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; public class ClickableCollider : MonoBehaviour { - public event Action OnClick; + private static readonly List ActiveClickables = new List(); - public void Click(PointerEventData eventData, Ray ray, RaycastHit raycastHit) => OnClick?.Invoke(this, eventData, ray, raycastHit); + private Renderer[] _renderers; + + public event Action OnClick; + + public static IReadOnlyList Active => ActiveClickables; + + public Bounds ClickBounds + { + get + { + if (_renderers == null) + RefreshYmirBoundsSource(); + + if (_renderers == null || _renderers.Length == 0) + return new Bounds(transform.position, Vector3.one * 0.5f); + + var firstRenderer = FirstLiveRenderer(); + if (firstRenderer == null) + return new Bounds(transform.position, Vector3.one * 0.5f); + + var bounds = firstRenderer.bounds; + for (var i = 0; i < _renderers.Length; i++) + { + var renderer = _renderers[i]; + if (renderer == null || renderer == firstRenderer) + continue; + + bounds.Encapsulate(renderer.bounds); + } + + return bounds; + } + } + + public void Click(PointerEventData eventData, Ray ray, AetheriaYmirClickableHit hit) => OnClick?.Invoke(this, eventData, ray, hit); public void Clear() { OnClick = null; } + public void RefreshYmirBoundsSource() + { + _renderers = GetComponentsInChildren(); + } + + private void Awake() + { + RefreshYmirBoundsSource(); + } + + private void OnEnable() + { + if (_renderers == null || _renderers.Length == 0) + RefreshYmirBoundsSource(); + + if (!ActiveClickables.Contains(this)) + ActiveClickables.Add(this); + } + + private void OnDisable() + { + ActiveClickables.Remove(this); + } + void Start() { var proto = GetComponent(); if(proto != null) proto.OnReturnToPool += () => OnClick = null; } + + private Renderer FirstLiveRenderer() + { + for (var i = 0; i < _renderers.Length; i++) + { + if (_renderers[i] != null) + return _renderers[i]; + } + + return null; + } } diff --git a/Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs b/Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs index 43ff44df9..debd3bdeb 100644 --- a/Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs +++ b/Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; +using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Unity; using GameCult.Eve.Surface; -using GameCult.Eve.UnityUIToolkit; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Utilities; @@ -34,6 +33,28 @@ public class InputDisplayLayout : MonoBehaviour private string _captureActionName = ""; private int _captureBindingIndex = -1; private string _captureBindingLabel = ""; + private readonly AetheriaEveUnitySurfaceChrome _surfaceChrome = new AetheriaEveUnitySurfaceChrome + { + RootAlignItems = Align.Center, + RootJustifyContent = Justify.Center, + RootPaddingLeft = 24f, + RootPaddingRight = 24f, + RootPaddingTop = 24f, + RootPaddingBottom = 24f, + RootBackgroundColor = new Color(0f, 0f, 0f, 0.72f), + Width = 1080f, + MinWidth = 0f, + MaxWidth = 1080f, + MaxHeight = 900f, + FlexGrow = 1f, + PaddingLeft = 20f, + PaddingRight = 20f, + PaddingTop = 20f, + PaddingBottom = 20f, + BorderRadius = 0f, + BorderWidth = 0f, + BackgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.96f) + }; public InputActionAsset Input { @@ -51,7 +72,6 @@ public class InputDisplayLayout : MonoBehaviour private void Start() { EnsureInputAsset(); - EnsureSurfaceDocument(); EnsureCaptureAction(); HideLegacyChildren(); RenderSurface(); @@ -60,7 +80,6 @@ public class InputDisplayLayout : MonoBehaviour private void OnEnable() { EnsureInputAsset(); - EnsureSurfaceDocument(); EnsureCaptureAction(); HideLegacyChildren(); RenderSurface(); @@ -76,7 +95,7 @@ public class InputDisplayLayout : MonoBehaviour if (_surfaceDocument != null) { - _surfaceDocument.rootVisualElement.Clear(); + AetheriaEveUnitySurfaceHost.Hide(_surfaceDocument); } } @@ -90,7 +109,7 @@ public class InputDisplayLayout : MonoBehaviour if (_surfaceDocument != null) { - Destroy(_surfaceDocument.gameObject); + AetheriaEveUnitySurfaceHost.DestroyDocument(_surfaceDocument); _surfaceDocument = null; } } @@ -106,20 +125,6 @@ public class InputDisplayLayout : MonoBehaviour Input = _ownedInput.asset; } - private void EnsureSurfaceDocument() - { - if (_surfaceDocument != null) - { - return; - } - - var host = new GameObject("Aetheria Input Surface"); - host.transform.SetParent(transform, false); - host.layer = gameObject.layer; - _surfaceDocument = host.AddComponent(); - _surfaceDocument.sortingOrder = 1000; - } - private void HideLegacyChildren() { for (var index = 0; index < transform.childCount; index++) @@ -159,38 +164,17 @@ public class InputDisplayLayout : MonoBehaviour } EnsureInputAsset(); - EnsureSurfaceDocument(); EnsureCaptureAction(); - var document = ToEveSurfaceDocument( - AetheriaRuntimeInputSettingsSurfaceBuilder.Build(ProjectSurfaceState())); + var document = AetheriaRuntimeInputSettingsSurfaceBuilder.Build(ProjectSurfaceState()); - var root = _surfaceDocument.rootVisualElement; - root.Clear(); - root.style.flexGrow = 1; - root.style.justifyContent = Justify.Center; - root.style.alignItems = Align.Center; - root.style.paddingLeft = 24; - root.style.paddingRight = 24; - root.style.paddingTop = 24; - root.style.paddingBottom = 24; - root.style.backgroundColor = new Color(0f, 0f, 0f, 0.72f); - - var shell = new VisualElement(); - shell.style.flexDirection = FlexDirection.Column; - shell.style.width = 1080; - shell.style.maxWidth = 1080; - shell.style.maxHeight = 900; - shell.style.flexGrow = 1; - shell.style.paddingLeft = 20; - shell.style.paddingRight = 20; - shell.style.paddingTop = 20; - shell.style.paddingBottom = 20; - shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.96f); - root.Add(shell); - - var lowerer = new EveUiToolkitSurfaceLowerer(); - shell.Add(lowerer.Lower(document, HandleSurfaceCommand)); + _surfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _surfaceDocument, + "Aetheria Input Surface", + document, + HandleSurfaceCommand, + _surfaceChrome); } private AetheriaRuntimeInputSettingsSurfaceState ProjectSurfaceState() @@ -312,59 +296,59 @@ public class InputDisplayLayout : MonoBehaviour private void HandleSurfaceCommand(EveSurfaceCommandRequest request) { - switch (request.Command) + if (!AetheriaRuntimeInputSettingsSurfaceCommands.TryRead(request, out var command)) { - case var command when string.Equals(command, AetheriaRuntimeInputSettingsCommands.Refresh, StringComparison.Ordinal): + Debug.LogWarning($"Unknown input-settings command: {request?.Command}"); + return; + } + + switch (command.Kind) + { + case AetheriaRuntimeInputSettingsCommandKind.Refresh: RenderSurface(); return; - case var command when string.Equals(command, AetheriaRuntimeInputSettingsCommands.CancelCapture, StringComparison.Ordinal): + case AetheriaRuntimeInputSettingsCommandKind.CancelCapture: ClearCapture(); RenderSurface(); return; - case var command when string.Equals(command, AetheriaRuntimeInputSettingsCommands.BeginCapture, StringComparison.Ordinal): - BeginCapture(request.Payload); + case AetheriaRuntimeInputSettingsCommandKind.BeginCapture: + BeginCapture(command); return; - case var command when string.Equals(command, AetheriaRuntimeInputSettingsCommands.ToggleActionBar, StringComparison.Ordinal): - ToggleActionBarInput(request.Payload); + case AetheriaRuntimeInputSettingsCommandKind.ToggleActionBar: + ToggleActionBarInput(command); return; default: - Debug.LogWarning($"Unknown input-settings command: {request.Command}"); + Debug.LogWarning($"Unknown input-settings command: {request?.Command}"); return; } } - private void BeginCapture(IReadOnlyDictionary payload) + private void BeginCapture(AetheriaRuntimeInputSettingsSurfaceCommand command) { - if (payload == null || - !payload.TryGetValue("actionName", out var actionName) || - !payload.TryGetValue("bindingIndex", out var bindingIndexText) || - !int.TryParse(bindingIndexText, NumberStyles.Integer, CultureInfo.InvariantCulture, out var bindingIndex)) + if (string.IsNullOrWhiteSpace(command.ActionName) || command.BindingIndex < 0) { Debug.LogWarning("Input capture requested without a valid action name and binding index."); return; } - _captureActionName = actionName; - _captureBindingIndex = bindingIndex; - _captureBindingLabel = payload.TryGetValue("bindingLabel", out var bindingLabel) - ? bindingLabel ?? actionName - : actionName; + _captureActionName = command.ActionName; + _captureBindingIndex = command.BindingIndex; + _captureBindingLabel = string.IsNullOrWhiteSpace(command.BindingLabel) + ? command.ActionName + : command.BindingLabel; _captureAction?.Enable(); RenderSurface(); } - private void ToggleActionBarInput(IReadOnlyDictionary payload) + private void ToggleActionBarInput(AetheriaRuntimeInputSettingsSurfaceCommand command) { - if (payload == null || - !payload.TryGetValue("inputPath", out var inputPath) || - !payload.TryGetValue("enabled", out var enabledText) || - !bool.TryParse(enabledText, out var enabled)) + if (string.IsNullOrWhiteSpace(command.InputPath)) { Debug.LogWarning("Action-bar toggle requested without a valid input path and enabled state."); return; } - ActionGameManager.CommitRuntimeActionBarInput(inputPath, enabled); + ActionGameManager.RequestRuntimeActionBarInput(command.InputPath, command.Enabled); RenderSurface(); } @@ -394,7 +378,7 @@ public class InputDisplayLayout : MonoBehaviour } action.ApplyBindingOverride(_captureBindingIndex, inputPath); - ActionGameManager.CommitRuntimeInputBindingOverride(action.name, _captureBindingIndex, inputPath); + ActionGameManager.RequestRuntimeInputBindingOverride(action.name, _captureBindingIndex, inputPath); ClearCapture(); RenderSurface(); } @@ -406,33 +390,4 @@ public class InputDisplayLayout : MonoBehaviour _captureBindingLabel = ""; } - private static EveSurfaceDocument ToEveSurfaceDocument(AetheriaRuntimeSurfaceDocument document) - { - return new EveSurfaceDocument( - "surface-state", - "gamecult.eve.surface.v1", - document.ProviderId, - document.ProviderKind, - document.Title, - document.Version, - document.UpdatedAtUtc, - new EveSurfaceTree( - document.Surface.Id, - ToEveSurfaceComponent(document.Surface.Root), - document.Surface.Styles - .Select(style => new EveStyleToken(style.Name, style.Value)) - .ToArray()), - document.Commands - .Select(command => new EveCommandTemplate(command.Command, command.Label, command.Transport)) - .ToArray()); - } - - private static EveSurfaceComponent ToEveSurfaceComponent(AetheriaRuntimeSurfaceComponent component) - { - return new EveSurfaceComponent( - component.Id, - component.Kind, - new Dictionary(component.Props, StringComparer.Ordinal), - component.Children.Select(ToEveSurfaceComponent).ToArray()); - } } diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index 704e44f1d..906e5f74b 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -1,51 +1,43 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Unity; using GameCult.Eve.Surface; -using GameCult.Eve.UnityUIToolkit; using TMPro; using UniRx; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityEngine.UIElements; -using static Unity.Mathematics.math; -using float2 = Unity.Mathematics.float2; -using Random = UnityEngine.Random; public class MainMenu : MonoBehaviour { - private const string MenuSurfaceType = "surface-state"; - private const string MenuSurfaceSchema = "gamecult.eve.surface.v1"; - private const string MenuSurfaceProviderId = "aetheria"; - private const string MenuSurfaceProviderKind = "game.menu"; - private const string MainSurfaceId = "aetheria.main_menu.root"; - private const string SettingsSurfaceId = "aetheria.main_menu.settings"; - private const string InputSettingsSurfaceId = "aetheria.main_menu.input_settings"; - private const string PlayerSettingsShellSurfaceId = "aetheria.main_menu.player_settings"; - private const string VerseSettingsShellSurfaceId = "aetheria.main_menu.verse_settings"; - private const string ContinueRunCommand = "aetheria.main_menu.root.continue"; - private const string NewGameCommand = "aetheria.main_menu.root.new_game"; - private const string ShowSettingsCommand = "aetheria.main_menu.root.show_settings"; - private const string QuitCommand = "aetheria.main_menu.root.quit"; - private const string OpenRuntimeInputScreenCommand = "aetheria.main_menu.input_settings.open_runtime_screen"; - private const string ShowPlayerSettingsCommand = "aetheria.main_menu.settings.show_player_settings"; - private const string ShowVerseSettingsCommand = "aetheria.main_menu.settings.show_verse_settings"; - private const string ShowInputSettingsCommand = "aetheria.main_menu.settings.show_input_settings"; - private const string BackToMainCommand = "aetheria.main_menu.settings.back_to_main"; - private const string BackToSettingsCommand = "aetheria.main_menu.settings.back_to_settings"; - public VolumeCloudRenderer CloudRenderer; public GameSettings Settings; public ConfirmationDialog Dialog; public bool InGame; private UIDocument _menuSurfaceDocument; + private readonly AetheriaEveUnitySurfaceChrome _menuSurfaceChrome = new AetheriaEveUnitySurfaceChrome + { + RootAlignItems = Align.Center, + RootJustifyContent = Justify.Center, + RootPaddingLeft = 24f, + RootPaddingRight = 24f, + RootPaddingTop = 24f, + RootPaddingBottom = 24f, + RootBackgroundColor = new Color(0f, 0f, 0f, 0.6f), + Width = 560f, + MinWidth = 0f, + MaxWidth = 560f, + PaddingLeft = 20f, + PaddingRight = 20f, + PaddingTop = 20f, + PaddingBottom = 20f, + BorderRadius = 0f, + BorderWidth = 0f, + BackgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.96f) + }; void Start() { @@ -55,114 +47,96 @@ public class MainMenu : MonoBehaviour private void ShowMain() { var stateBoot = CurrentStateBoot(); + var frame = LatestDaemonFrame(stateBoot); + var verseHost = LatestVerseHostSettings(stateBoot); RenderMenuSurface( - BuildMainSurfaceDefinition( - stateBoot, - LatestContinueRun(stateBoot), - LatestVerseHostSettings(stateBoot), - InGame), + AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot( + ProjectMainMenuSurfaceState( + stateBoot, + frame, + verseHost, + CanOpenRuntimeInputScreen(), + InGame)), HandleMainSurfaceCommand); } private void HandleMainSurfaceCommand(EveSurfaceCommandRequest request) { - switch (request.Command) + if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)) { - case ContinueRunCommand: - var continueRun = LatestContinueRun(CurrentStateBoot()); - if (continueRun == null) + Debug.LogWarning("Unknown main menu command."); + return; + } + + switch (command.Kind) + { + case AetheriaRuntimeMainMenuCommandKind.ContinueRun: + if (LatestDaemonFrame(CurrentStateBoot()) == null) { - Debug.LogWarning("Main-menu Continue requested without a typed run state."); + Debug.LogWarning("Main-menu Continue requested without an authoritative daemon frame."); ShowMain(); return; } - ContinueGame(continueRun); + ContinueGame(); return; - case NewGameCommand: + case AetheriaRuntimeMainMenuCommandKind.NewGame: StartNewGame(); return; - case ShowSettingsCommand: + case AetheriaRuntimeMainMenuCommandKind.ShowSettings: ShowSettings(); return; - case QuitCommand: + case AetheriaRuntimeMainMenuCommandKind.Quit: Application.Quit(); return; default: - Debug.LogWarning($"Unknown main menu command: {request.Command}"); + Debug.LogWarning($"Unhandled main menu command kind: {command.Kind}"); return; } } private void StartNewGame() { - var generatorState = "Loading typed catalog"; + if (!TryStartDaemonObservedGame("Starting Daemon Run")) + { + ShowMain(); + } + } + + private bool TryStartDaemonObservedGame(string title) + { + var stateBoot = CurrentStateBoot(); + if (!AetheriaRuntimeStateReader.TryReadDaemonFrame(stateBoot.StateFilePath, out var frame) || + frame == null || + !frame.IsAuthoritative || + frame.Run == null || + frame.Run.Zones == null || + frame.Run.Zones.Count == 0) + { + Debug.LogWarning($"Cannot start Aetheria observer scene without an authoritative daemon frame at {AetheriaRuntimeDaemonFrameStore.GetFramePath(stateBoot.StateFilePath)}."); + return false; + } + + var generatorState = "Loading runtime catalog"; Action setState = s => generatorState = s; HideMenuSurface(); Dialog.Clear(); - Dialog.Title.text = "Generating Galaxy"; + Dialog.Title.text = title; Dialog.AddProperty(() => generatorState); Dialog.Show(); - if (ActionGameManager.RuntimePlayerSettings.TutorialPassed) - { - Settings.SectorBackgroundSettings.NoisePosition = Random.value * 1000; - ActionGameManager.IsTutorial = false; - var generationSeed = NextGenerationSeed(); - Task.Run(() => - { - var sector = new Galaxy( - Settings.SectorGenerationSettings, - Settings.SectorBackgroundSettings, - Settings.NameGeneratorSettings, - ActionGameManager.RuntimeCatalog, - Debug.Log, - setState, - generationSeed); - Observable.NextFrame().Subscribe(_ => - { - ActionGameManager.CurrentGalaxy = sector; - SceneManager.LoadScene("ARPG"); - }); - }).WrapErrors(); - } - else - { - int iteration = 1; - do - { - Settings.TutorialBackgroundSettings.NoisePosition = Random.value * 1000; - setState($"Finding Galaxy Position: iteration {iteration++}"); - } while (Settings.TutorialBackgroundSettings.CloudDensity(float2(0.5f)) < .5f); - - ActionGameManager.IsTutorial = true; - var generationSeed = NextGenerationSeed(); - Task.Run(() => - { - var sector = new Galaxy( - Settings.TutorialGenerationSettings, - Settings.TutorialBackgroundSettings, - Settings.NameGeneratorSettings, - ActionGameManager.RuntimeCatalog, - ActionGameManager.RuntimePlayerSettings, - ActionGameManager.GameDataDirectory.CreateSubdirectory("Narrative"), - Debug.Log, - setState, - generationSeed); - Observable.NextFrame().Subscribe(_ => - { - ActionGameManager.CurrentGalaxy = sector; - SceneManager.LoadScene("ARPG"); - }); - }).WrapErrors(); - } - } - - private static uint NextGenerationSeed() - { - var seed = (uint)Random.Range(1, int.MaxValue); - return seed == 0 ? 1u : seed; + setState($"Observing daemon frame {frame.FrameId}"); + ActionGameManager.IsTutorial = frame.Run.IsTutorial; + var backgroundSettings = frame.Run.IsTutorial ? Settings.TutorialBackgroundSettings : Settings.SectorBackgroundSettings; + backgroundSettings.NoisePosition = frame.Run.GenerationSeed == 0 ? 1 : frame.Run.GenerationSeed; + ActionGameManager.ObservedGalaxy = Galaxy.ProjectObservedDaemonRun( + frame.Run, + backgroundSettings, + ActionGameManager.RuntimeCatalog, + Debug.Log); + SceneManager.LoadScene("ARPG"); + return true; } private static AetheriaRuntimeStateBootReport CurrentStateBoot() @@ -170,25 +144,19 @@ public class MainMenu : MonoBehaviour return AetheriaRuntimeStateBoot.Inspect(ActionGameManager.GameDataDirectory); } - private static AetheriaRuntimeRunStateSnapshot LatestContinueRun(AetheriaRuntimeStateBootReport stateBoot) + private static AetheriaRuntimeDaemonFrameDocument LatestDaemonFrame(AetheriaRuntimeStateBootReport stateBoot) { - if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists) + if (!AetheriaRuntimeStateReader.TryReadDaemonFrame(stateBoot.StateFilePath, out var frame) || + frame == null || + !frame.IsAuthoritative || + frame.Run == null || + frame.Run.Zones == null || + frame.Run.Zones.Count == 0) + { return null; + } - try - { - return AetheriaRuntimeStateReader - .ReadRunStates(stateBoot.StateFilePath) - .Where(run => !string.IsNullOrWhiteSpace(run.RunId)) - .OrderByDescending(run => run.UpdatedAtUtc, StringComparer.Ordinal) - .ThenByDescending(run => run.RunId, StringComparer.Ordinal) - .FirstOrDefault(); - } - catch (Exception ex) - { - Debug.LogError($"Failed to read typed Aetheria run state for Continue: {ex}"); - return null; - } + return frame; } private static AetheriaRuntimeVerseHostSettingsSnapshot LatestVerseHostSettings(AetheriaRuntimeStateBootReport stateBoot) @@ -207,72 +175,41 @@ public class MainMenu : MonoBehaviour } } - private void ContinueGame(AetheriaRuntimeRunStateSnapshot run) + private void ContinueGame() { - if (run == null) - return; - - var generatorState = "Loading typed run"; - Action setState = s => generatorState = s; - - HideMenuSurface(); - Dialog.Clear(); - Dialog.Title.text = "Continuing Run"; - Dialog.AddProperty(() => generatorState); - Dialog.Show(); - - ActionGameManager.IsTutorial = run.IsTutorial; - ActionGameManager.ContinueRunState = run; - var generationSeed = run.GenerationSeed == 0 ? NextGenerationSeed() : run.GenerationSeed; - var backgroundSettings = run.IsTutorial ? Settings.TutorialBackgroundSettings : Settings.SectorBackgroundSettings; - backgroundSettings.NoisePosition = generationSeed; - - Task.Run(() => + if (!TryStartDaemonObservedGame("Continuing Daemon Run")) { - var sector = run.IsTutorial - ? new Galaxy( - Settings.TutorialGenerationSettings, - Settings.TutorialBackgroundSettings, - Settings.NameGeneratorSettings, - ActionGameManager.RuntimeCatalog, - ActionGameManager.RuntimePlayerSettings, - ActionGameManager.GameDataDirectory.CreateSubdirectory("Narrative"), - Debug.Log, - setState, - generationSeed) - : new Galaxy( - Settings.SectorGenerationSettings, - backgroundSettings, - Settings.NameGeneratorSettings, - ActionGameManager.RuntimeCatalog, - Debug.Log, - setState, - generationSeed); - Observable.NextFrame().Subscribe(_ => - { - ActionGameManager.CurrentGalaxy = sector; - SceneManager.LoadScene("ARPG"); - }); - }).WrapErrors(); + ShowMain(); + } } private void ShowSettings() { - RenderMenuSurface(BuildSettingsSurfaceDefinition(), HandleSettingsSurfaceCommand); + RenderMenuSurface( + AetheriaRuntimeMainMenuSurfaceBuilder.BuildSettings(DateTime.UtcNow.ToString("O")), + HandleSettingsSurfaceCommand); } private void ShowInputSettings() { - RenderMenuSurface(BuildInputSettingsSurfaceDefinition(CanOpenRuntimeInputScreen(), InGame), HandleInputSettingsSurfaceCommand); + RenderMenuSurface( + AetheriaRuntimeMainMenuSurfaceBuilder.BuildInputSettings( + ProjectMainMenuSurfaceState( + CurrentStateBoot(), + null, + null, + CanOpenRuntimeInputScreen(), + InGame)), + HandleInputSettingsSurfaceCommand); } private void ShowPlayerSettingsSurface() { RenderMenuSurface( - WithBackAction( - ToEveSurfaceDocument(BuildPlayerSettingsSurfaceDefinition()), - PlayerSettingsShellSurfaceId, - BackToSettingsCommand, + AetheriaRuntimeMainMenuSurfaceBuilder.WithBackAction( + BuildPlayerSettingsSurfaceDefinition(), + AetheriaRuntimeMainMenuCommands.PlayerSettingsShellSurfaceId, + AetheriaRuntimeMainMenuCommands.BackToSettings, "Back"), HandlePlayerSettingsSurfaceCommand); } @@ -280,129 +217,140 @@ public class MainMenu : MonoBehaviour private void ShowVerseSettingsSurface() { RenderMenuSurface( - WithBackAction( - ToEveSurfaceDocument(BuildVerseSettingsSurfaceDefinition()), - VerseSettingsShellSurfaceId, - BackToSettingsCommand, + AetheriaRuntimeMainMenuSurfaceBuilder.WithBackAction( + BuildVerseSettingsSurfaceDefinition(), + AetheriaRuntimeMainMenuCommands.VerseSettingsShellSurfaceId, + AetheriaRuntimeMainMenuCommands.BackToSettings, "Back"), HandleVerseSettingsSurfaceCommand); } private void RenderMenuSurface( - EveSurfaceDocument document, + AetheriaRuntimeSurfaceDocument document, Action commandHandler) { - var surfaceDocument = ResolveMenuSurfaceDocument(); - surfaceDocument.gameObject.SetActive(true); - - var root = surfaceDocument.rootVisualElement; - root.Clear(); - root.style.flexGrow = 1; - root.style.justifyContent = Justify.Center; - root.style.alignItems = Align.Center; - root.style.paddingLeft = 24; - root.style.paddingRight = 24; - root.style.paddingTop = 24; - root.style.paddingBottom = 24; - root.style.backgroundColor = new Color(0f, 0f, 0f, 0.6f); - - var shell = new VisualElement(); - shell.style.flexDirection = FlexDirection.Column; - shell.style.width = 560; - shell.style.maxWidth = 560; - shell.style.paddingLeft = 20; - shell.style.paddingRight = 20; - shell.style.paddingTop = 20; - shell.style.paddingBottom = 20; - shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.96f); - root.Add(shell); - - var lowerer = new EveUiToolkitSurfaceLowerer(); - shell.Add(lowerer.Lower(document, commandHandler)); + _menuSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _menuSurfaceDocument, + "Aetheria Menu Surface", + document, + commandHandler, + _menuSurfaceChrome); } private void HandleSettingsSurfaceCommand(EveSurfaceCommandRequest request) { - switch (request.Command) + if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)) { - case ShowPlayerSettingsCommand: + Debug.LogWarning("Unknown settings menu command."); + return; + } + + switch (command.Kind) + { + case AetheriaRuntimeMainMenuCommandKind.ShowPlayerSettings: ShowPlayerSettingsSurface(); return; - case ShowVerseSettingsCommand: + case AetheriaRuntimeMainMenuCommandKind.ShowVerseSettings: ShowVerseSettingsSurface(); return; - case ShowInputSettingsCommand: + case AetheriaRuntimeMainMenuCommandKind.ShowInputSettings: ShowInputSettings(); return; - case BackToMainCommand: + case AetheriaRuntimeMainMenuCommandKind.BackToMain: ShowMain(); return; default: - Debug.LogWarning($"Unknown settings menu command: {request.Command}"); + Debug.LogWarning($"Unhandled settings menu command kind: {command.Kind}"); return; } } private void HandleInputSettingsSurfaceCommand(EveSurfaceCommandRequest request) { - if (string.Equals(request.Command, BackToSettingsCommand, StringComparison.Ordinal)) + if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)) { - ShowSettings(); + Debug.LogWarning("Unknown input settings command."); return; } - if (string.Equals(request.Command, OpenRuntimeInputScreenCommand, StringComparison.Ordinal)) + switch (command.Kind) { - if (!TryOpenRuntimeInputScreen()) - { - ShowInputSettings(); - } + case AetheriaRuntimeMainMenuCommandKind.BackToSettings: + ShowSettings(); + return; + case AetheriaRuntimeMainMenuCommandKind.OpenRuntimeInputScreen: + if (!TryOpenRuntimeInputScreen()) + { + ShowInputSettings(); + } - return; + return; + default: + Debug.LogWarning($"Unhandled input settings command kind: {command.Kind}"); + return; } - - Debug.LogWarning($"Unknown input settings command: {request.Command}"); } private void HandlePlayerSettingsSurfaceCommand(EveSurfaceCommandRequest request) { - if (string.Equals(request.Command, BackToSettingsCommand, StringComparison.Ordinal)) + if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)) { - ShowSettings(); + Debug.LogWarning("Unknown player-settings command."); return; } - if (!ActionGameManager.CommitRuntimePlayerSettingsCommand(request.Command, request.Payload)) + switch (command.Kind) { - Debug.LogWarning($"Unknown player-settings command: {request.Command}"); - return; - } + case AetheriaRuntimeMainMenuCommandKind.BackToSettings: + ShowSettings(); + return; + case AetheriaRuntimeMainMenuCommandKind.PlayerSettingsCommand: + if (!TrySendPlayerSettingsCommand(request, command.Command)) + { + Debug.LogWarning($"Unhandled player-settings command kind: {command.Kind}"); + return; + } - CloudRenderer.quality = ActionGameManager.RuntimePlayerSettings.GraphicsSettings.NebulaQuality; - ShowPlayerSettingsSurface(); + ShowPlayerSettingsSurface(); + return; + default: + Debug.LogWarning($"Unhandled player-settings command kind: {command.Kind}"); + return; + } } private void HandleVerseSettingsSurfaceCommand(EveSurfaceCommandRequest request) { - if (string.Equals(request.Command, BackToSettingsCommand, StringComparison.Ordinal)) + if (!AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)) { - ShowSettings(); + Debug.LogWarning("Unknown verse-settings command."); return; } - if (TryCommitClientTargetCommand(request.Command, request.Payload)) + switch (command.Kind) { - ShowVerseSettingsSurface(); - return; - } + case AetheriaRuntimeMainMenuCommandKind.BackToSettings: + ShowSettings(); + return; + case AetheriaRuntimeMainMenuCommandKind.ClientTargetCommand: + if (TryRequestClientTargetCommand(request)) + { + ShowVerseSettingsSurface(); + } - if (TryQueueVerseHostCommand(request.Command, request.Payload)) - { - ShowVerseSettingsSurface(); - return; - } + return; + case AetheriaRuntimeMainMenuCommandKind.VerseHostCommand: + if (TrySendVerseHostCommand(command.Command)) + { + ShowVerseSettingsSurface(); + } - Debug.LogWarning($"Unknown verse-settings command: {request.Command}"); + return; + default: + Debug.LogWarning($"Unhandled verse-settings command kind: {command.Kind}"); + return; + } } private void HideMenuSurface() @@ -410,22 +358,7 @@ public class MainMenu : MonoBehaviour if (_menuSurfaceDocument == null) return; - _menuSurfaceDocument.rootVisualElement.Clear(); - _menuSurfaceDocument.gameObject.SetActive(false); - } - - private UIDocument ResolveMenuSurfaceDocument() - { - if (_menuSurfaceDocument != null) - return _menuSurfaceDocument; - - var host = new GameObject("Aetheria Menu Surface"); - host.transform.SetParent(transform, false); - var document = host.AddComponent(); - document.sortingOrder = 1000; - host.SetActive(false); - _menuSurfaceDocument = document; - return document; + AetheriaEveUnitySurfaceHost.Hide(_menuSurfaceDocument); } private static AetheriaRuntimeSurfaceDocument BuildPlayerSettingsSurfaceDefinition() @@ -434,9 +367,9 @@ public class MainMenu : MonoBehaviour new AetheriaRuntimePlayerSettingsSurfaceState( ActionGameManager.RuntimePlayerSettings.Name, ActionGameManager.RuntimePlayerSettings.TutorialPassed, - ActionGameManager.ContinueRunState?.RunId ?? "", + "", ActionGameManager.RuntimePlayerSettings.GameplaySettings.TemperatureUnit.ToString(), - max(0, ActionGameManager.RuntimePlayerSettings.GameplaySettings.SignificantDigits), + Math.Max(0, ActionGameManager.RuntimePlayerSettings.GameplaySettings.SignificantDigits), ActionGameManager.RuntimePlayerSettings.GraphicsSettings.NebulaQuality.ToString(), ActionGameManager.RuntimePlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap, DateTime.UtcNow.ToString("O"))); @@ -470,124 +403,29 @@ public class MainMenu : MonoBehaviour DateTime.UtcNow.ToString("O"))); } - private static EveSurfaceDocument BuildMainSurfaceDefinition( + private static AetheriaRuntimeMainMenuSurfaceState ProjectMainMenuSurfaceState( AetheriaRuntimeStateBootReport stateBoot, - AetheriaRuntimeRunStateSnapshot continueRun, + AetheriaRuntimeDaemonFrameDocument daemonFrame, AetheriaRuntimeVerseHostSettingsSnapshot verseHost, + bool canOpenRuntimeInputScreen, bool inGame) { - var commands = new List(); - var actionButtons = new List(); - var cardChildren = new List(); - var targetLabel = stateBoot.TargetLabel; - var targetKind = string.IsNullOrWhiteSpace(stateBoot.TargetKind) ? "unknown" : stateBoot.TargetKind; - var targetSource = string.IsNullOrWhiteSpace(stateBoot.TargetSource) ? "unknown" : stateBoot.TargetSource; - var verseTitle = string.IsNullOrWhiteSpace(verseHost?.Title) ? "Unknown Verse" : verseHost.Title; - var verseId = string.IsNullOrWhiteSpace(verseHost?.VerseId) ? "unknown" : verseHost.VerseId; - var verseVisibility = string.IsNullOrWhiteSpace(verseHost?.Visibility) ? "unknown" : verseHost.Visibility; - var verseMeshAddress = string.IsNullOrWhiteSpace(verseHost?.CultMeshAddress) ? "unpublished" : verseHost.CultMeshAddress; - var verseLabel = string.Equals(verseTitle, verseId, StringComparison.Ordinal) - ? verseTitle - : $"{verseTitle} ({verseId})"; - - if (!inGame) - { - if (!stateBoot.SupportsLocalStateFileRead) - { - cardChildren.Add(Text( - $"{MainSurfaceId}.continue.note", - stateBoot.FailureMessage)); - } - else if (continueRun == null) - { - cardChildren.Add(Text( - $"{MainSurfaceId}.continue.note", - $"No typed run state is available in {verseLabel} yet. Start a new galaxy or connect to a Verse that already has one.")); - } - else - { - commands.Add(new EveCommandTemplate(ContinueRunCommand, "Continue", "unity-uitoolkit")); - actionButtons.Add(Button($"{MainSurfaceId}.continue", "Continue", ContinueRunCommand)); - cardChildren.Add(Metric( - $"{MainSurfaceId}.continue.run", - "Latest Run", - continueRun.RunId)); - } - } - - cardChildren.Add(Metric( - $"{MainSurfaceId}.target.title", - "Client Target", - targetLabel)); - cardChildren.Add(Metric( - $"{MainSurfaceId}.target.transport", - "Transport", - targetKind)); - cardChildren.Add(Metric( - $"{MainSurfaceId}.target.source", - "Target Source", - targetSource)); - - cardChildren.Add(Metric( - $"{MainSurfaceId}.verse.title", - "Verse", - verseLabel)); - cardChildren.Add(Metric( - $"{MainSurfaceId}.verse.visibility", - "Visibility", - verseVisibility)); - cardChildren.Add(Metric( - $"{MainSurfaceId}.verse.mesh", - "CultMesh", - verseMeshAddress)); - - commands.Add(new EveCommandTemplate(NewGameCommand, "New Game", "unity-uitoolkit")); - commands.Add(new EveCommandTemplate(ShowSettingsCommand, "Settings", "unity-uitoolkit")); - commands.Add(new EveCommandTemplate(QuitCommand, "Quit", "unity-uitoolkit")); - - actionButtons.Add(Button($"{MainSurfaceId}.newGame", "New Game", NewGameCommand)); - actionButtons.Add(Button($"{MainSurfaceId}.settings", "Settings", ShowSettingsCommand)); - actionButtons.Add(Button($"{MainSurfaceId}.quit", "Quit", QuitCommand)); - - cardChildren.Add(Text( - $"{MainSurfaceId}.note", - $"The client lowers this shell through Eve. The client target chooses which Verse it follows; game truth belongs to the daemon serving {verseLabel}.")); - cardChildren.Add(ButtonColumn($"{MainSurfaceId}.actions", actionButtons.ToArray())); - - return BuildMenuSurfaceDocument( - MainSurfaceId, - "Aetheria Terminus", - commands, - Card( - $"{MainSurfaceId}.card", - "Aetheria Terminus", - cardChildren.ToArray())); - } - - private static EveSurfaceDocument BuildSettingsSurfaceDefinition() - { - return BuildMenuSurfaceDocument( - SettingsSurfaceId, - "Aetheria Settings", - new[] - { - new EveCommandTemplate(ShowPlayerSettingsCommand, "Player Settings", "unity-uitoolkit"), - new EveCommandTemplate(ShowVerseSettingsCommand, "Verse", "unity-uitoolkit"), - new EveCommandTemplate(ShowInputSettingsCommand, "Input", "unity-uitoolkit"), - new EveCommandTemplate(BackToMainCommand, "Back", "unity-uitoolkit") - }, - Card( - "aetheria.mainMenu.settings.card", - "Settings", - Text( - "aetheria.mainMenu.settings.note", - "Player settings, client target selection, and input rebinding all lower through typed Eve surfaces. Audio still has no typed owner."), - ButtonRow( - "aetheria.mainMenu.settings.actions", - Button("aetheria.mainMenu.settings.playerSettings", "Player Settings", ShowPlayerSettingsCommand), - Button("aetheria.mainMenu.settings.verse", "Verse", ShowVerseSettingsCommand), - Button("aetheria.mainMenu.settings.input", "Input", ShowInputSettingsCommand), - Button("aetheria.mainMenu.settings.back", "Back", BackToMainCommand)))); + return new AetheriaRuntimeMainMenuSurfaceState( + stateBoot.TargetLabel, + stateBoot.TargetKind, + stateBoot.TargetSource, + verseHost?.Title ?? stateBoot.Title, + verseHost?.VerseId ?? stateBoot.VerseId, + verseHost?.Visibility ?? "unknown", + verseHost?.CultMeshAddress ?? stateBoot.CultMeshAddress, + inGame, + daemonFrame != null, + daemonFrame?.Run?.RunId ?? "", + daemonFrame?.FrameId ?? -1, + ActionGameManager.RuntimePlayerSettings.InputSettings.InputActionMap.Count, + ActionGameManager.RuntimePlayerSettings.InputSettings.ActionBarInputs.Count, + canOpenRuntimeInputScreen, + DateTime.UtcNow.ToString("O")); } private bool CanOpenRuntimeInputScreen() @@ -609,110 +447,18 @@ public class MainMenu : MonoBehaviour return true; } - private static bool TryCommitClientTargetCommand(string command, IReadOnlyDictionary payload) + private static bool TryRequestClientTargetCommand(EveSurfaceCommandRequest request) { - if (!AetheriaRuntimeClientTargetCommands.IsKnown(command)) - return false; - - if (string.Equals(command, AetheriaRuntimeClientTargetCommands.Refresh, StringComparison.Ordinal)) - return true; - try { - var gameDataDirectory = ActionGameManager.GameDataDirectory; - var clientTargetPath = AetheriaRuntimeStateBoundary.GetClientTargetPath(gameDataDirectory); - var defaultStateFilePath = AetheriaRuntimeStateBoundary.GetStateFilePath(gameDataDirectory); - if (string.Equals(command, AetheriaRuntimeClientTargetCommands.DiscoverVerses, StringComparison.Ordinal)) + if (!AetheriaRuntimeClientTargetSurfaceCommands.TryRequest( + AetheriaState.At(ActionGameManager.GameDataDirectory).ClientTarget, + request, + out _)) { - AetheriaRuntimeVerseDiscovery.RefreshClientTarget(clientTargetPath, defaultStateFilePath); - return true; + return false; } - if (string.Equals(command, AetheriaRuntimeClientTargetCommands.SyncReplica, StringComparison.Ordinal)) - { - var target = AetheriaRuntimeClientTargetStore.ReadOrInitialize(clientTargetPath, defaultStateFilePath); - var syncedAtUtc = DateTime.UtcNow.ToString("O"); - try - { - var result = AetheriaRuntimeVerseReplicaBridge.Sync(gameDataDirectory, target); - AetheriaRuntimeClientTargetStore.Update( - clientTargetPath, - defaultStateFilePath, - document => - { - document.ReplicaStateFilePath = result.ReplicaStateFilePath; - document.LastReplicaSyncAtUtc = syncedAtUtc; - document.LastReplicaSyncError = ""; - document.UpdatedAtUtc = syncedAtUtc; - }); - } - catch (Exception ex) - { - AetheriaRuntimeClientTargetStore.Update( - clientTargetPath, - defaultStateFilePath, - document => - { - document.LastReplicaSyncAtUtc = syncedAtUtc; - document.LastReplicaSyncError = ex.Message ?? ex.GetType().Name; - document.UpdatedAtUtc = syncedAtUtc; - }); - throw; - } - - return true; - } - - AetheriaRuntimeClientTargetStore.Update( - clientTargetPath, - defaultStateFilePath, - document => - { - switch (command) - { - case var _ when string.Equals(command, AetheriaRuntimeClientTargetCommands.CycleTargetKind, StringComparison.Ordinal): - document.TargetKind = string.Equals(document.TargetKind, AetheriaRuntimeClientTargetKinds.CultMeshVerse, StringComparison.Ordinal) - ? AetheriaRuntimeClientTargetKinds.StateFile - : AetheriaRuntimeClientTargetKinds.CultMeshVerse; - document.LastReplicaSyncError = ""; - break; - case var _ when string.Equals(command, AetheriaRuntimeClientTargetCommands.SetTitle, StringComparison.Ordinal): - document.Title = ReadPayloadValue(payload, "value"); - break; - case var _ when string.Equals(command, AetheriaRuntimeClientTargetCommands.SetVerseId, StringComparison.Ordinal): - document.VerseId = ReadPayloadValue(payload, "value"); - document.ReplicaStateFilePath = AetheriaRuntimeStateBoundary.GetReplicaStateFilePath(gameDataDirectory, document.VerseId); - document.LastReplicaSyncError = ""; - break; - case var _ when string.Equals(command, AetheriaRuntimeClientTargetCommands.SetCultMeshAddress, StringComparison.Ordinal): - document.CultMeshAddress = ReadPayloadValue(payload, "value"); - document.LastReplicaSyncError = ""; - break; - case var _ when string.Equals(command, AetheriaRuntimeClientTargetCommands.SetStateFilePath, StringComparison.Ordinal): - document.StateFilePath = ReadPayloadValue(payload, "value"); - break; - case var _ when string.Equals(command, AetheriaRuntimeClientTargetCommands.SetDiscoveryEndpoints, StringComparison.Ordinal): - document.DiscoveryEndpoints = ParseDiscoveryEndpoints(ReadPayloadValue(payload, "value")); - document.LastDiscoveryError = ""; - break; - case var _ when string.Equals(command, AetheriaRuntimeClientTargetCommands.SelectDiscoveredVerse, StringComparison.Ordinal): - document.TargetKind = AetheriaRuntimeClientTargetKinds.CultMeshVerse; - document.Title = ReadPayloadValue(payload, "title"); - document.VerseId = ReadPayloadValue(payload, "verseId"); - document.CultMeshAddress = ReadPayloadValue(payload, "cultMeshAddress"); - document.ReplicaStateFilePath = AetheriaRuntimeStateBoundary.GetReplicaStateFilePath(gameDataDirectory, document.VerseId); - document.DiscoveryEndpoints = ParseDiscoveryEndpoints(ReadPayloadValue(payload, "discoveryEndpoints")); - document.LastDiscoveryError = ""; - document.LastReplicaSyncError = ""; - break; - } - - if (string.IsNullOrWhiteSpace(document.TargetKind)) - document.TargetKind = AetheriaRuntimeClientTargetKinds.StateFile; - if (string.IsNullOrWhiteSpace(document.StateFilePath)) - document.StateFilePath = defaultStateFilePath; - document.UpdatedAtUtc = DateTime.UtcNow.ToString("O"); - }); return true; } catch (Exception ex) @@ -722,247 +468,82 @@ public class MainMenu : MonoBehaviour } } - private static bool TryQueueVerseHostCommand(string command, IReadOnlyDictionary payload) + private static bool TrySendVerseHostCommand(string command) { - if (!AetheriaRuntimeVerseHostCommands.IsKnown(command)) - return false; - var stateBoot = CurrentStateBoot(); - if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists) - { - Debug.LogWarning( - $"Cannot queue Verse-host command '{command}' because the active target is not a readable local Verse state file."); + if (!CanSendLocalEveCommand(stateBoot, "Verse-host", command)) return true; - } try { - var request = new EveSurfaceCommandRequest( - "aetheria", - AetheriaRuntimeVerseHostCommands.SurfaceId, - command, - payload?.ToDictionary(entry => entry.Key, entry => entry.Value ?? "", StringComparer.Ordinal) - ?? new Dictionary(StringComparer.Ordinal), - DateTimeOffset.UtcNow, - "unity-main-menu"); - var envelope = AetheriaRuntimeEveCommandLog.QueueCommand(stateBoot.StateFilePath, request); - Debug.Log($"Queued Aetheria Verse-host Eve command: {envelope.Path}"); + if (!AetheriaRuntimeEveCommands.TrySendVerseHostCommand( + stateBoot.StateFilePath, + command, + "unity-main-menu", + out var submitted, + out var error)) + { + Debug.LogError($"Failed to submit Aetheria Verse-host Eve command '{command}': {error}"); + return true; + } + + Debug.Log($"Submitted Aetheria Verse-host Eve command: {submitted!.CommandId}"); return true; } catch (Exception ex) { - Debug.LogError($"Failed to queue typed Aetheria Verse-host Eve command: {ex}"); + Debug.LogError($"Failed to send Aetheria Verse-host Eve command '{command}': {ex}"); return true; } } - private static string ReadPayloadValue(IReadOnlyDictionary payload, string key) + private static bool TrySendPlayerSettingsCommand( + EveSurfaceCommandRequest request, + string command) { - return payload != null && payload.TryGetValue(key, out var value) - ? value ?? "" - : ""; + if (request == null) + return false; + + var stateBoot = CurrentStateBoot(); + if (!CanSendLocalEveCommand(stateBoot, "player-settings", command)) + return true; + + try + { + if (!AetheriaRuntimeEveCommands.TrySendPlayerSettingsCommand( + stateBoot.StateFilePath, + request, + "unity-main-menu", + out var submitted, + out var error)) + { + Debug.LogError($"Failed to submit Aetheria player-settings Eve command '{command}': {error}"); + return true; + } + + Debug.Log($"Submitted Aetheria player-settings Eve command: {submitted!.CommandId}"); + return true; + } + catch (Exception ex) + { + Debug.LogError($"Failed to send Aetheria player-settings Eve command '{command}': {ex}"); + return true; + } } - private static string[] ParseDiscoveryEndpoints(string value) + private static bool CanSendLocalEveCommand( + AetheriaRuntimeStateBootReport stateBoot, + string label, + string command) { - return (value ?? "") - .Split(new[] { ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) - .Select(endpoint => endpoint.Trim()) - .Where(endpoint => !string.IsNullOrWhiteSpace(endpoint)) - .Distinct(StringComparer.Ordinal) - .ToArray(); - } - - private static EveSurfaceDocument BuildInputSettingsSurfaceDefinition(bool canOpenRuntimeInputScreen, bool inGame) - { - var commands = new List + if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists) { - new EveCommandTemplate(BackToSettingsCommand, "Back", "unity-uitoolkit") - }; - - var cardChildren = new List - { - Metric( - "aetheria.mainMenu.input.bindingOverrides", - "Binding Overrides", - ActionGameManager.RuntimePlayerSettings.InputSettings.InputActionMap.Count.ToString()), - Metric( - "aetheria.mainMenu.input.actionBarInputs", - "Action-Bar Inputs", - ActionGameManager.RuntimePlayerSettings.InputSettings.ActionBarInputs.Count.ToString()) - }; - - if (canOpenRuntimeInputScreen) - { - commands.Insert(0, new EveCommandTemplate(OpenRuntimeInputScreenCommand, "Open Remap Screen", "unity-uitoolkit")); - cardChildren.Add(Text( - "aetheria.mainMenu.input.note", - "The runtime Eve input screen owns low-level InputSystem rebinding and action-bar input edits. This title shell reports typed player-settings state and hands off to that owner.")); - } - else if (inGame) - { - cardChildren.Add(Text( - "aetheria.mainMenu.input.note", - "The runtime Eve input screen should own rebinding here, but this scene has no active input surface to hand off to.")); - } - else - { - cardChildren.Add(Text( - "aetheria.mainMenu.input.note", - "This title shell reports the typed player-settings state. Launch a run to open the runtime Eve input screen that owns low-level InputSystem rebinding.")); + Debug.LogWarning( + $"Cannot send {label} command '{command}' because the active target is not a readable local Verse state file."); + return false; } - var buttons = new List(); - if (canOpenRuntimeInputScreen) - { - buttons.Add(Button("aetheria.mainMenu.input.openRuntimeScreen", "Open Remap Screen", OpenRuntimeInputScreenCommand)); - } - - buttons.Add(Button("aetheria.mainMenu.input.back", "Back", BackToSettingsCommand)); - cardChildren.Add(ButtonRow("aetheria.mainMenu.input.actions", buttons.ToArray())); - - return BuildMenuSurfaceDocument( - InputSettingsSurfaceId, - "Aetheria Input Settings", - commands, - Card( - "aetheria.mainMenu.input.card", - "Input Settings", - cardChildren.ToArray())); - } - - private static EveSurfaceDocument WithBackAction( - EveSurfaceDocument document, - string surfaceId, - string backCommand, - string backLabel) - { - return new EveSurfaceDocument( - document.Type, - document.Schema, - document.ProviderId, - document.ProviderKind, - document.Title, - document.Version, - document.UpdatedAtUtc, - new EveSurfaceTree( - surfaceId, - Node( - $"{surfaceId}.root", - "surface", - Array.Empty<(string Key, string Value)>(), - document.Surface.Root, - ButtonRow( - $"{surfaceId}.actions", - Button($"{surfaceId}.back", backLabel, backCommand))), - document.Surface.Styles), - document.Commands - .Concat(new[] - { - new EveCommandTemplate(backCommand, backLabel, "unity-uitoolkit") - }) - .ToArray()); - } - - private static EveSurfaceDocument BuildMenuSurfaceDocument( - string surfaceId, - string title, - IReadOnlyList commands, - params EveSurfaceComponent[] children) - { - return new EveSurfaceDocument( - MenuSurfaceType, - MenuSurfaceSchema, - MenuSurfaceProviderId, - MenuSurfaceProviderKind, - title, - version: 1, - DateTime.UtcNow.ToString("O"), - new EveSurfaceTree( - surfaceId, - Node($"{surfaceId}.root", "surface", Array.Empty<(string Key, string Value)>(), children), - Array.Empty()), - commands); - } - - private static EveSurfaceComponent Card( - string id, - string title, - params EveSurfaceComponent[] children) - { - return Node(id, "card", new[] { ("title", title) }, children); - } - - private static EveSurfaceComponent Metric(string id, string label, string value) - { - return Node(id, "metric", new[] { ("label", label), ("value", value) }); - } - - private static EveSurfaceComponent Text(string id, string value) - { - return Node(id, "text", new[] { ("value", value) }); - } - - private static EveSurfaceComponent Button(string id, string label, string command) - { - return Node(id, "control.button", new[] { ("label", label), ("command", command) }); - } - - private static EveSurfaceComponent ButtonRow( - string id, - params EveSurfaceComponent[] children) - { - return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); - } - - private static EveSurfaceComponent ButtonColumn( - string id, - params EveSurfaceComponent[] children) - { - return Node(id, "column", Array.Empty<(string Key, string Value)>(), children); - } - - private static EveSurfaceComponent Node( - string id, - string kind, - IEnumerable<(string Key, string Value)> props, - params EveSurfaceComponent[] children) - { - return new EveSurfaceComponent( - id, - kind, - props.ToDictionary(prop => prop.Key, prop => prop.Value, StringComparer.Ordinal), - children ?? Array.Empty()); - } - - private static EveSurfaceDocument ToEveSurfaceDocument(AetheriaRuntimeSurfaceDocument document) - { - return new EveSurfaceDocument( - "surface-state", - "gamecult.eve.surface.v1", - document.ProviderId, - document.ProviderKind, - document.Title, - document.Version, - document.UpdatedAtUtc, - new EveSurfaceTree( - document.Surface.Id, - ToEveSurfaceComponent(document.Surface.Root), - document.Surface.Styles - .Select(style => new EveStyleToken(style.Name, style.Value)) - .ToArray()), - document.Commands - .Select(command => new EveCommandTemplate(command.Command, command.Label, command.Transport)) - .ToArray()); - } - - private static EveSurfaceComponent ToEveSurfaceComponent(AetheriaRuntimeSurfaceComponent component) - { - return new EveSurfaceComponent( - component.Id, - component.Kind, - new Dictionary(component.Props, StringComparer.Ordinal), - component.Children.Select(ToEveSurfaceComponent).ToArray()); + return true; } private void OnDestroy() diff --git a/Assets/Scripts/UI/Menu/InventoryMenu.cs b/Assets/Scripts/UI/Menu/InventoryMenu.cs index 2c97b166f..8485ee5c9 100644 --- a/Assets/Scripts/UI/Menu/InventoryMenu.cs +++ b/Assets/Scripts/UI/Menu/InventoryMenu.cs @@ -1,4 +1,4 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public +/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ @@ -6,9 +6,9 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Unity; using GameCult.Eve.Surface; -using GameCult.Eve.UnityUIToolkit; using UnityEngine; using UniRx; using UniRx.Triggers; @@ -17,24 +17,6 @@ using UnityEngine.UIElements; public class InventoryMenu : MonoBehaviour { - private const string ShipSettingsSurfaceType = "surface-state"; - private const string ShipSettingsSurfaceSchema = "gamecult.eve.surface.v1"; - private const string ShipSettingsSurfaceProviderId = "aetheria"; - private const string ShipSettingsSurfaceProviderKind = "inventory.menu"; - private const string ShipSettingsSurfaceId = "aetheria.inventory.current_ship_settings"; - private const string DecrementShutdownThresholdCommand = "aetheria.inventory.current_ship_settings.shutdown.decrement"; - private const string IncrementShutdownThresholdCommand = "aetheria.inventory.current_ship_settings.shutdown.increment"; - private const string ResetShutdownThresholdCommand = "aetheria.inventory.current_ship_settings.shutdown.reset"; - private const string CloseShipSettingsCommand = "aetheria.inventory.current_ship_settings.close"; - private const string CargoItemDetailsSurfaceId = "aetheria.inventory.cargo_item_details"; - private const string CloseCargoItemDetailsCommand = "aetheria.inventory.cargo_item_details.close"; - private const string EquippedItemDetailsSurfaceId = "aetheria.inventory.equipped_item_details"; - private const string CloseEquippedItemDetailsCommand = "aetheria.inventory.equipped_item_details.close"; - private const string ToggleEquippedItemOverrideShutdownCommand = "aetheria.inventory.equipped_item_details.override_shutdown.toggle"; - private const string SetEquippedItemTargetTemperatureCommand = "aetheria.inventory.equipped_item_details.target_temperature.set"; - private const string ToggleEquippedItemWeaponGroupCommand = "aetheria.inventory.equipped_item_details.weapon_group.toggle"; - private const string BindEquippedItemWeaponGroupCommand = "aetheria.inventory.equipped_item_details.weapon_group.bind"; - private const string ClearEquippedItemActionBarBindingCommand = "aetheria.inventory.equipped_item_details.action_bar.clear"; private const float ShutdownThresholdStep = 0.05f; public InventoryPanel[] InventoryPanels; @@ -42,8 +24,6 @@ public class InventoryMenu : MonoBehaviour public ConfirmationDialog Dialog; // public ClickCatcher Background; - private int _currentPanel = -1; - private int2 _selectedPosition; private InventoryPanel _selectedPanel; private ItemInstance _selectedItem; @@ -52,6 +32,9 @@ public class InventoryMenu : MonoBehaviour private UIDocument _shipSettingsSurfaceDocument; private UIDocument _cargoItemDetailsSurfaceDocument; private UIDocument _equippedItemDetailsSurfaceDocument; + private readonly AetheriaEveUnitySurfaceChrome _shipSettingsSurfaceChrome = PanelChrome(360f, 420f); + private readonly AetheriaEveUnitySurfaceChrome _cargoItemDetailsSurfaceChrome = PanelChrome(420f, 520f); + private readonly AetheriaEveUnitySurfaceChrome _equippedItemDetailsSurfaceChrome = PanelChrome(460f, 560f); // private List _backgroundSubscriptions; // private ItemInstance _dragItem; @@ -116,7 +99,7 @@ public class InventoryMenu : MonoBehaviour HideEquippedItemDetailsSurface(); ClearSelectedItemSelection(); var otherPanel = panel == InventoryPanels[0] ? InventoryPanels[1] : InventoryPanels[0]; - if (CommitCargoItemTransfer(cargoEvent.CargoBay, otherPanel, item)) + if (RequestCargoItemTransfer(cargoEvent.CargoBay, otherPanel, item)) { // TODO: SFX: Equip panel.RefreshCells(); @@ -152,7 +135,7 @@ public class InventoryMenu : MonoBehaviour HideEquippedItemDetailsSurface(); ClearSelectedItemSelection(); var otherPanel = panel == InventoryPanels[0] ? InventoryPanels[1] : InventoryPanels[0]; - if (CommitEquippedItemTransfer(entityEvent.Entity, item, otherPanel)) + if (RequestEquippedItemTransfer(entityEvent.Entity, item, otherPanel)) { // TODO: SFX: Unequip panel.RefreshCells(); @@ -197,46 +180,13 @@ public class InventoryMenu : MonoBehaviour HideEquippedItemDetailsSurface(); ClearSelectedItemSelection(); - var document = ResolveShipSettingsSurfaceDocument(); - document.gameObject.SetActive(true); - - var root = document.rootVisualElement; - 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 = Align.FlexStart; - root.style.justifyContent = Justify.FlexStart; - root.pickingMode = PickingMode.Ignore; - - var shell = new VisualElement(); - shell.style.width = 360; - shell.style.maxWidth = 420; - shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.94f); - shell.style.borderTopLeftRadius = 8; - shell.style.borderTopRightRadius = 8; - shell.style.borderBottomLeftRadius = 8; - shell.style.borderBottomRightRadius = 8; - shell.style.paddingLeft = 18; - shell.style.paddingRight = 18; - shell.style.paddingTop = 18; - shell.style.paddingBottom = 18; - shell.style.borderLeftWidth = 1; - shell.style.borderRightWidth = 1; - shell.style.borderTopWidth = 1; - shell.style.borderBottomWidth = 1; - shell.style.borderLeftColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderRightColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderTopColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderBottomColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.pickingMode = PickingMode.Position; - root.Add(shell); - - var lowerer = new EveUiToolkitSurfaceLowerer(); - shell.Add(lowerer.Lower(BuildCurrentShipSettingsSurfaceDefinition(entity), HandleCurrentShipSettingsSurfaceCommand)); + _shipSettingsSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _shipSettingsSurfaceDocument, + "Aetheria Inventory Ship Settings Surface", + AetheriaRuntimeShipSettingsSurfaceBuilder.Build(ProjectCurrentShipSettingsSurfaceState(entity)), + HandleCurrentShipSettingsSurfaceCommand, + _shipSettingsSurfaceChrome); } private void HandleCurrentShipSettingsSurfaceCommand(EveSurfaceCommandRequest request) @@ -248,31 +198,37 @@ public class InventoryMenu : MonoBehaviour return; } - switch (request.Command) + if (!AetheriaRuntimeShipSettingsSurfaceCommands.TryRead(request, out var command)) { - case DecrementShutdownThresholdCommand: - GameManager.CommitEntityShutdownPerformance( + Debug.LogWarning($"Unknown inventory ship settings command: {request?.Command}"); + return; + } + + switch (command.Kind) + { + case AetheriaRuntimeShipSettingsCommandKind.DecrementShutdownThreshold: + GameManager.RequestEntityShutdownPerformance( entity, Mathf.Clamp01(entity.Settings.ShutdownPerformance - ShutdownThresholdStep)); RenderCurrentShipSettingsSurface(entity); return; - case IncrementShutdownThresholdCommand: - GameManager.CommitEntityShutdownPerformance( + case AetheriaRuntimeShipSettingsCommandKind.IncrementShutdownThreshold: + GameManager.RequestEntityShutdownPerformance( entity, Mathf.Clamp01(entity.Settings.ShutdownPerformance + ShutdownThresholdStep)); RenderCurrentShipSettingsSurface(entity); return; - case ResetShutdownThresholdCommand: - GameManager.CommitEntityShutdownPerformance( + case AetheriaRuntimeShipSettingsCommandKind.ResetShutdownThreshold: + GameManager.RequestEntityShutdownPerformance( entity, Mathf.Clamp01(GameManager.Settings.GameplaySettings.DefaultShutdownPerformance)); RenderCurrentShipSettingsSurface(entity); return; - case CloseShipSettingsCommand: + case AetheriaRuntimeShipSettingsCommandKind.Close: HideCurrentShipSettingsSurface(); return; default: - Debug.LogWarning($"Unknown inventory ship settings command: {request.Command}"); + Debug.LogWarning($"Unknown inventory ship settings command: {request?.Command}"); return; } } @@ -282,8 +238,17 @@ public class InventoryMenu : MonoBehaviour if (_shipSettingsSurfaceDocument == null) return; - _shipSettingsSurfaceDocument.rootVisualElement.Clear(); - _shipSettingsSurfaceDocument.gameObject.SetActive(false); + AetheriaEveUnitySurfaceHost.Hide(_shipSettingsSurfaceDocument); + } + + private static AetheriaRuntimeShipSettingsSurfaceState ProjectCurrentShipSettingsSurfaceState(Entity entity) + { + return new AetheriaRuntimeShipSettingsSurfaceState( + entity?.Name ?? "", + entity == null + ? "" + : ActionGameManager.RuntimePlayerSettings.Format(entity.Settings.ShutdownPerformance), + DateTime.UtcNow.ToString("O")); } private void RenderCargoItemDetailsSurface(ItemInstance item) @@ -294,58 +259,32 @@ public class InventoryMenu : MonoBehaviour HideEquippedItemDetailsSurface(); - var document = ResolveCargoItemDetailsSurfaceDocument(); - document.gameObject.SetActive(true); - - var root = document.rootVisualElement; - 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 = Align.FlexStart; - root.style.justifyContent = Justify.FlexStart; - root.pickingMode = PickingMode.Ignore; - - var shell = new VisualElement(); - shell.style.width = 420; - shell.style.maxWidth = 520; - shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.94f); - shell.style.borderTopLeftRadius = 8; - shell.style.borderTopRightRadius = 8; - shell.style.borderBottomLeftRadius = 8; - shell.style.borderBottomRightRadius = 8; - shell.style.paddingLeft = 18; - shell.style.paddingRight = 18; - shell.style.paddingTop = 18; - shell.style.paddingBottom = 18; - shell.style.borderLeftWidth = 1; - shell.style.borderRightWidth = 1; - shell.style.borderTopWidth = 1; - shell.style.borderBottomWidth = 1; - shell.style.borderLeftColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderRightColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderTopColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderBottomColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.pickingMode = PickingMode.Position; - root.Add(shell); - - var lowerer = new EveUiToolkitSurfaceLowerer(); - shell.Add(lowerer.Lower(BuildCargoItemDetailsSurfaceDefinition(item, typedItem), HandleCargoItemDetailsSurfaceCommand)); + _cargoItemDetailsSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _cargoItemDetailsSurfaceDocument, + "Aetheria Inventory Cargo Item Details Surface", + AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Build(ProjectCargoItemDetailsSurfaceState(item, typedItem)), + HandleCargoItemDetailsSurfaceCommand, + _cargoItemDetailsSurfaceChrome, + sortingOrder: 1001); } private void HandleCargoItemDetailsSurfaceCommand(EveSurfaceCommandRequest request) { - if (string.Equals(request.Command, CloseCargoItemDetailsCommand, StringComparison.Ordinal)) + if (!AetheriaRuntimeCargoItemDetailsSurfaceCommands.TryRead(request, out var command)) + { + Debug.LogWarning($"Unknown inventory cargo item details command: {request?.Command}"); + return; + } + + if (command.Kind == AetheriaRuntimeCargoItemDetailsCommandKind.Close) { HideCargoItemDetailsSurface(); ClearSelectedItemSelection(); return; } - Debug.LogWarning($"Unknown inventory cargo item details command: {request.Command}"); + Debug.LogWarning($"Unknown inventory cargo item details command: {request?.Command}"); } private void HideCargoItemDetailsSurface() @@ -353,8 +292,7 @@ public class InventoryMenu : MonoBehaviour if (_cargoItemDetailsSurfaceDocument == null) return; - _cargoItemDetailsSurfaceDocument.rootVisualElement.Clear(); - _cargoItemDetailsSurfaceDocument.gameObject.SetActive(false); + AetheriaEveUnitySurfaceHost.Hide(_cargoItemDetailsSurfaceDocument); } private void RenderEquippedItemDetailsSurface(EquippedItem item) @@ -363,46 +301,14 @@ public class InventoryMenu : MonoBehaviour if (item?.EquippableItem == null || typedItem == null) return; - var document = ResolveEquippedItemDetailsSurfaceDocument(); - document.gameObject.SetActive(true); - - var root = document.rootVisualElement; - 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 = Align.FlexStart; - root.style.justifyContent = Justify.FlexStart; - root.pickingMode = PickingMode.Ignore; - - var shell = new VisualElement(); - shell.style.width = 460; - shell.style.maxWidth = 560; - shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.94f); - shell.style.borderTopLeftRadius = 8; - shell.style.borderTopRightRadius = 8; - shell.style.borderBottomLeftRadius = 8; - shell.style.borderBottomRightRadius = 8; - shell.style.paddingLeft = 18; - shell.style.paddingRight = 18; - shell.style.paddingTop = 18; - shell.style.paddingBottom = 18; - shell.style.borderLeftWidth = 1; - shell.style.borderRightWidth = 1; - shell.style.borderTopWidth = 1; - shell.style.borderBottomWidth = 1; - shell.style.borderLeftColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderRightColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderTopColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderBottomColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.pickingMode = PickingMode.Position; - root.Add(shell); - - var lowerer = new EveUiToolkitSurfaceLowerer(); - shell.Add(lowerer.Lower(BuildEquippedItemDetailsSurfaceDefinition(item, typedItem), HandleEquippedItemDetailsSurfaceCommand)); + _equippedItemDetailsSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _equippedItemDetailsSurfaceDocument, + "Aetheria Inventory Equipped Item Details Surface", + AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.Build(ProjectEquippedItemDetailsSurfaceState(item, typedItem)), + HandleEquippedItemDetailsSurfaceCommand, + _equippedItemDetailsSurfaceChrome, + sortingOrder: 1002); } private void HandleEquippedItemDetailsSurfaceCommand(EveSurfaceCommandRequest request) @@ -415,25 +321,30 @@ public class InventoryMenu : MonoBehaviour return; } - switch (request.Command) + if (!AetheriaRuntimeEquippedItemDetailsSurfaceCommands.TryRead(request, out var command)) { - case CloseEquippedItemDetailsCommand: + Debug.LogWarning($"Unknown inventory equipped item details command: {request?.Command}"); + return; + } + + switch (command.Kind) + { + case AetheriaRuntimeEquippedItemDetailsCommandKind.Close: HideEquippedItemDetailsSurface(); ClearSelectedItemSelection(); return; - case ToggleEquippedItemOverrideShutdownCommand: - GameManager.CommitEquippedItemOverrideShutdown(item, !item.EquippableItem.OverrideShutdown); + case AetheriaRuntimeEquippedItemDetailsCommandKind.ToggleOverrideShutdown: + GameManager.RequestEquippedItemOverrideShutdown(item, !item.EquippableItem.OverrideShutdown); RenderEquippedItemDetailsSurface(item); return; - case SetEquippedItemTargetTemperatureCommand: - if (TryReadPayloadInt(request, "behaviorIndex", out var behaviorIndex) && - behaviorIndex >= 0 && - behaviorIndex < (item.Behaviors?.Length ?? 0) && - item.Behaviors[behaviorIndex] is Thermotoggle thermotoggle && + case AetheriaRuntimeEquippedItemDetailsCommandKind.SetTargetTemperature: + if (command.BehaviorIndex >= 0 && + command.BehaviorIndex < (item.Behaviors?.Length ?? 0) && + item.Behaviors[command.BehaviorIndex] is Thermotoggle thermotoggle && thermotoggle.Adjustable && - TryReadPayloadFloat(request, "value", out var targetTemperature)) + GameManager != null) { - GameManager.CommitThermotoggleTargetTemperature(thermotoggle, targetTemperature); + GameManager.RequestThermotoggleTargetTemperature(thermotoggle, command.TargetTemperature); RenderEquippedItemDetailsSurface(item); return; } @@ -441,11 +352,11 @@ public class InventoryMenu : MonoBehaviour Debug.LogWarning("Unable to apply equipped-item target temperature command."); RenderEquippedItemDetailsSurface(item); return; - case ToggleEquippedItemWeaponGroupCommand: - if (TryReadPayloadInt(request, "group", out var groupIndex)) + case AetheriaRuntimeEquippedItemDetailsCommandKind.ToggleWeaponGroup: + if (command.GroupIndex >= 0) { - var assigned = !IsWeaponGroupAssigned(item, groupIndex); - if (GameManager.CommitWeaponGroupMembership(item, groupIndex, assigned)) + var assigned = !IsWeaponGroupAssigned(item, command.GroupIndex); + if (GameManager.RequestWeaponGroupMembership(item, command.GroupIndex, assigned)) { RenderEquippedItemDetailsSurface(item); return; @@ -454,10 +365,10 @@ public class InventoryMenu : MonoBehaviour Debug.LogWarning("Unable to toggle equipped-item weapon group membership."); return; - case BindEquippedItemWeaponGroupCommand: - if (TryReadPayloadInt(request, "group", out var bindGroupIndex) && - TryReadPayloadInt(request, "slot", out var slotIndex) && - GameManager.CommitWeaponGroupActionBarBinding(slotIndex, bindGroupIndex)) + case AetheriaRuntimeEquippedItemDetailsCommandKind.BindWeaponGroup: + if (command.GroupIndex >= 0 && + command.SlotIndex >= 0 && + GameManager.RequestWeaponGroupActionBarBinding(command.SlotIndex, command.GroupIndex)) { RenderEquippedItemDetailsSurface(item); return; @@ -465,9 +376,9 @@ public class InventoryMenu : MonoBehaviour Debug.LogWarning("Unable to bind equipped-item weapon group to action bar."); return; - case ClearEquippedItemActionBarBindingCommand: - if (TryReadPayloadInt(request, "slot", out var clearSlotIndex) && - GameManager.CommitClearActionBarBinding(clearSlotIndex)) + case AetheriaRuntimeEquippedItemDetailsCommandKind.ClearActionBarBinding: + if (command.SlotIndex >= 0 && + GameManager.RequestClearActionBarBinding(command.SlotIndex)) { RenderEquippedItemDetailsSurface(item); return; @@ -476,7 +387,7 @@ public class InventoryMenu : MonoBehaviour Debug.LogWarning("Unable to clear equipped-item action bar binding."); return; default: - Debug.LogWarning($"Unknown inventory equipped item details command: {request.Command}"); + Debug.LogWarning($"Unknown inventory equipped item details command: {request?.Command}"); return; } } @@ -486,317 +397,147 @@ public class InventoryMenu : MonoBehaviour if (_equippedItemDetailsSurfaceDocument == null) return; - _equippedItemDetailsSurfaceDocument.rootVisualElement.Clear(); - _equippedItemDetailsSurfaceDocument.gameObject.SetActive(false); + AetheriaEveUnitySurfaceHost.Hide(_equippedItemDetailsSurfaceDocument); } - private UIDocument ResolveEquippedItemDetailsSurfaceDocument() + private static AetheriaEveUnitySurfaceChrome PanelChrome(float width, float maxWidth) { - if (_equippedItemDetailsSurfaceDocument != null) - return _equippedItemDetailsSurfaceDocument; - - var host = new GameObject("Aetheria Inventory Equipped Item Details Surface"); - host.transform.SetParent(transform, false); - var document = host.AddComponent(); - document.sortingOrder = 1002; - host.SetActive(false); - _equippedItemDetailsSurfaceDocument = document; - return document; - } - - private EveSurfaceDocument BuildEquippedItemDetailsSurfaceDefinition(EquippedItem item, AetheriaRuntimeCatalogItem typedItem) - { - var children = new List + return new AetheriaEveUnitySurfaceChrome { - Card( - $"{EquippedItemDetailsSurfaceId}.summary", - BuildEquippedItemTitle(item, typedItem), - Text( - $"{EquippedItemDetailsSurfaceId}.description", - typedItem.Description ?? "No typed item description is available."), - Text( - $"{EquippedItemDetailsSurfaceId}.note", - "InventoryMenu still owns cell selection. This surface replaces the old equipped-item PropertiesPanel shell, including weapon-group and behavior controls."), - Metric( - $"{EquippedItemDetailsSurfaceId}.manufacturer", - "Manufacturer", - ActionGameManager.RuntimeCatalog?.GetManufacturer(typedItem)?.Name ?? "GameCult"), - Metric( - $"{EquippedItemDetailsSurfaceId}.mass", - "Mass", - ActionGameManager.RuntimePlayerSettings.Format((float)typedItem.Mass))), - Card( - $"{EquippedItemDetailsSurfaceId}.status.card", - "Status", - Metric( - $"{EquippedItemDetailsSurfaceId}.durability", - "Durability", - item.EquippableItem.Durability < .01f - ? "Item Destroyed!" - : $"{(int)(item.EquippableItem.Durability / GetMaxDurability(typedItem, item.EquippableItem) * 100)}%"), - Metric( - $"{EquippedItemDetailsSurfaceId}.temperature", - "Temperature", - ActionGameManager.RuntimePlayerSettings.FormatTemperature(item.Temperature)), - Metric( - $"{EquippedItemDetailsSurfaceId}.thermal_range", - "Thermal Range", - FormatTemperatureRange(typedItem))) + RootAlignItems = Align.FlexStart, + RootJustifyContent = Justify.FlexStart, + RootPaddingTop = 0f, + Width = width, + MinWidth = 0f, + MaxWidth = maxWidth, + PaddingLeft = 18f, + PaddingRight = 18f, + PaddingTop = 18f, + PaddingBottom = 18f }; - - foreach (var behaviorCard in BuildItemBehaviorCards(EquippedItemDetailsSurfaceId, typedItem, item.EquippableItem)) - { - children.Add(behaviorCard); - } - - children.Add(BuildEquippedItemControlCard(item)); - - if (item.GetBehavior() != null) - { - children.Add(BuildEquippedItemWeaponGroupCard(item)); - children.AddRange(BuildEquippedItemActionBarCards(item)); - } - - children.Add(ButtonRow( - $"{EquippedItemDetailsSurfaceId}.actions", - Button($"{EquippedItemDetailsSurfaceId}.close", "Close", CloseEquippedItemDetailsCommand))); - - return new EveSurfaceDocument( - ShipSettingsSurfaceType, - ShipSettingsSurfaceSchema, - ShipSettingsSurfaceProviderId, - ShipSettingsSurfaceProviderKind, - "Inventory Equipped Item Details", - version: 1, - DateTime.UtcNow.ToString("O"), - new EveSurfaceTree( - EquippedItemDetailsSurfaceId, - Node( - $"{EquippedItemDetailsSurfaceId}.root", - "surface", - Array.Empty<(string Key, string Value)>(), - children.ToArray()), - Array.Empty()), - new[] - { - new EveCommandTemplate(CloseEquippedItemDetailsCommand, "Close", "unity-uitoolkit"), - new EveCommandTemplate(ToggleEquippedItemOverrideShutdownCommand, "Toggle Override Shutdown", "unity-uitoolkit"), - new EveCommandTemplate(SetEquippedItemTargetTemperatureCommand, "Set Target Temperature", "unity-uitoolkit"), - new EveCommandTemplate(ToggleEquippedItemWeaponGroupCommand, "Toggle Weapon Group", "unity-uitoolkit"), - new EveCommandTemplate(BindEquippedItemWeaponGroupCommand, "Bind Weapon Group", "unity-uitoolkit"), - new EveCommandTemplate(ClearEquippedItemActionBarBindingCommand, "Clear Action Bar Binding", "unity-uitoolkit") - }); } - private EveSurfaceComponent BuildEquippedItemControlCard(EquippedItem item) + private AetheriaRuntimeEquippedItemDetailsSurfaceState ProjectEquippedItemDetailsSurfaceState( + EquippedItem item, + AetheriaRuntimeCatalogItem typedItem) { - var children = new List - { - Metric( - $"{EquippedItemDetailsSurfaceId}.controls.override_shutdown.metric", - "Override Shutdown", - item.EquippableItem.OverrideShutdown ? "Enabled" : "Disabled"), - ButtonRow( - $"{EquippedItemDetailsSurfaceId}.controls.override_shutdown.actions", - Button( - $"{EquippedItemDetailsSurfaceId}.controls.override_shutdown.toggle", - item.EquippableItem.OverrideShutdown ? "Disable Override" : "Enable Override", - ToggleEquippedItemOverrideShutdownCommand)) - }; + var durability = item.EquippableItem.Durability < .01f + ? "Item Destroyed!" + : $"{(int)(item.EquippableItem.Durability / GetMaxDurability(typedItem, item.EquippableItem) * 100)}%"; + var hasWeapon = item.GetBehavior() != null; + return new AetheriaRuntimeEquippedItemDetailsSurfaceState( + BuildEquippedItemTitle(item, typedItem), + typedItem.Description ?? "", + ActionGameManager.RuntimeCatalog?.GetManufacturer(typedItem)?.Name ?? "GameCult", + ActionGameManager.RuntimePlayerSettings.Format((float)typedItem.Mass), + durability, + ActionGameManager.RuntimePlayerSettings.FormatTemperature(item.Temperature), + FormatTemperatureRange(typedItem), + item.EquippableItem.OverrideShutdown ? "Enabled" : "Disabled", + item.EquippableItem.OverrideShutdown ? "Disable Override" : "Enable Override", + ProjectEquippedItemTemperatureControls(item).ToArray(), + ProjectEquippedItemBehaviorSections(typedItem, item.EquippableItem).ToArray(), + hasWeapon ? ProjectEquippedItemWeaponGroupControls(item).ToArray() : Array.Empty(), + hasWeapon ? ProjectEquippedItemActionBarSlots(item).ToArray() : Array.Empty(), + DateTime.UtcNow.ToString("O")); + } + + private IEnumerable ProjectEquippedItemTemperatureControls( + EquippedItem item) + { for (var i = 0; i < (item.Behaviors?.Length ?? 0); i++) { if (item.Behaviors[i] is not Thermotoggle thermotoggle || !thermotoggle.Adjustable) continue; - children.Add(Metric( - $"{EquippedItemDetailsSurfaceId}.controls.thermotoggle.{i}.metric", + yield return new AetheriaRuntimeEquippedItemTemperatureControl( + $"{AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SurfaceId}.controls.thermotoggle.{i}.input", $"Target Temperature {i + 1}", - ActionGameManager.RuntimePlayerSettings.FormatTemperature(thermotoggle.TargetTemperature))); - children.Add(TextField( - $"{EquippedItemDetailsSurfaceId}.controls.thermotoggle.{i}.input", - $"Target Temperature {i + 1}", - SetEquippedItemTargetTemperatureCommand, thermotoggle.TargetTemperature.ToString(CultureInfo.InvariantCulture), - ("behaviorIndex", i.ToString(CultureInfo.InvariantCulture)))); + AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.Payload( + ("behaviorIndex", i.ToString(CultureInfo.InvariantCulture)))); } - - return Card( - $"{EquippedItemDetailsSurfaceId}.controls.card", - "Controls", - children.ToArray()); } - private EveSurfaceComponent BuildEquippedItemWeaponGroupCard(EquippedItem item) + private IEnumerable ProjectEquippedItemWeaponGroupControls( + EquippedItem item) { - var groupButtons = Enumerable.Range(0, item.Entity?.WeaponGroups?.Length ?? 0) - .Select(groupIndex => CommandButton( - $"{EquippedItemDetailsSurfaceId}.weapon_groups.{groupIndex}", + return Enumerable.Range(0, item.Entity?.WeaponGroups?.Length ?? 0) + .Select(groupIndex => new AetheriaRuntimeEquippedItemControl( + $"{AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SurfaceId}.weapon_groups.{groupIndex}", IsWeaponGroupAssigned(item, groupIndex) ? $"G{groupIndex + 1} On" : $"G{groupIndex + 1} Off", - ToggleEquippedItemWeaponGroupCommand, - ("group", groupIndex.ToString(CultureInfo.InvariantCulture)))) - .ToArray(); - - return Card( - $"{EquippedItemDetailsSurfaceId}.weapon_groups.card", - "Weapon Groups", - Text( - $"{EquippedItemDetailsSurfaceId}.weapon_groups.note", - "Toggle membership directly; action-bar group binding is handled below."), - ButtonRow($"{EquippedItemDetailsSurfaceId}.weapon_groups.actions", groupButtons)); + AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.ToggleWeaponGroup, + AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.Payload( + ("group", groupIndex.ToString(CultureInfo.InvariantCulture))))); } - private IEnumerable BuildEquippedItemActionBarCards(EquippedItem item) + private IEnumerable ProjectEquippedItemActionBarSlots( + EquippedItem item) { for (var slotIndex = 0; slotIndex < GameManager.GetActionBarSlotCount(); slotIndex++) { - var bindButtons = Enumerable.Range(0, item.Entity?.WeaponGroups?.Length ?? 0) - .Select(groupIndex => CommandButton( - $"{EquippedItemDetailsSurfaceId}.action_bar.{slotIndex}.group.{groupIndex}", + var controls = Enumerable.Range(0, item.Entity?.WeaponGroups?.Length ?? 0) + .Select(groupIndex => new AetheriaRuntimeEquippedItemControl( + $"{AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SurfaceId}.action_bar.{slotIndex}.group.{groupIndex}", $"G{groupIndex + 1}", - BindEquippedItemWeaponGroupCommand, - ("slot", slotIndex.ToString(CultureInfo.InvariantCulture)), - ("group", groupIndex.ToString(CultureInfo.InvariantCulture)))) + AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.BindWeaponGroup, + AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.Payload( + ("slot", slotIndex.ToString(CultureInfo.InvariantCulture)), + ("group", groupIndex.ToString(CultureInfo.InvariantCulture))))) .Concat(new[] { - CommandButton( - $"{EquippedItemDetailsSurfaceId}.action_bar.{slotIndex}.clear", + new AetheriaRuntimeEquippedItemControl( + $"{AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SurfaceId}.action_bar.{slotIndex}.clear", "Clear", - ClearEquippedItemActionBarBindingCommand, - ("slot", slotIndex.ToString(CultureInfo.InvariantCulture))) + AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.ClearActionBarBinding, + AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.Payload( + ("slot", slotIndex.ToString(CultureInfo.InvariantCulture)))) }) .ToArray(); - yield return Card( - $"{EquippedItemDetailsSurfaceId}.action_bar.{slotIndex}.card", + yield return new AetheriaRuntimeEquippedItemActionBarSlot( + $"{AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SurfaceId}.action_bar.{slotIndex}.card", $"Action Bar {GameManager.GetActionBarSlotLabel(slotIndex)}", - Metric( - $"{EquippedItemDetailsSurfaceId}.action_bar.{slotIndex}.binding", - "Current", - GameManager.GetActionBarBindingLabel(slotIndex)), - ButtonRow($"{EquippedItemDetailsSurfaceId}.action_bar.{slotIndex}.actions", bindButtons)); + GameManager.GetActionBarBindingLabel(slotIndex), + controls); } } - private UIDocument ResolveCargoItemDetailsSurfaceDocument() + private AetheriaRuntimeCargoItemDetailsSurfaceState ProjectCargoItemDetailsSurfaceState( + ItemInstance item, + AetheriaRuntimeCatalogItem typedItem) { - if (_cargoItemDetailsSurfaceDocument != null) - return _cargoItemDetailsSurfaceDocument; - - var host = new GameObject("Aetheria Inventory Cargo Item Details Surface"); - host.transform.SetParent(transform, false); - var document = host.AddComponent(); - document.sortingOrder = 1001; - host.SetActive(false); - _cargoItemDetailsSurfaceDocument = document; - return document; - } - - private EveSurfaceDocument BuildCargoItemDetailsSurfaceDefinition(ItemInstance item, AetheriaRuntimeCatalogItem typedItem) - { - var children = new List - { - Card( - $"{CargoItemDetailsSurfaceId}.summary", - typedItem.Name, - Text( - $"{CargoItemDetailsSurfaceId}.description", - typedItem.Description ?? "No typed item description is available."), - Text( - $"{CargoItemDetailsSurfaceId}.note", - "InventoryMenu still owns cell selection. This surface replaces the old cargo-item PropertiesPanel shell."), - Metric( - $"{CargoItemDetailsSurfaceId}.manufacturer", - "Manufacturer", - ActionGameManager.RuntimeCatalog?.GetManufacturer(typedItem)?.Name ?? "GameCult"), - Metric( - $"{CargoItemDetailsSurfaceId}.mass", - "Mass", - ActionGameManager.RuntimePlayerSettings.Format(GetCargoItemMass(item, typedItem)))) - }; - - if (typedItem.Price > 0) - { - children.Add(Card( - $"{CargoItemDetailsSurfaceId}.market.card", - "Market", - Metric( - $"{CargoItemDetailsSurfaceId}.price", - "Price", - typedItem.Price.ToString("N0")))); - } - - if (item is SimpleCommodity simpleCommodity) - { - children.Add(Card( - $"{CargoItemDetailsSurfaceId}.quantity.card", - "Quantity", - Metric( - $"{CargoItemDetailsSurfaceId}.quantity", - "Units", - simpleCommodity.Quantity.ToString()))); - } + var quantity = item is SimpleCommodity simpleCommodity ? simpleCommodity.Quantity : 0; + var tier = ""; + var durability = ""; + var thermalRange = ""; + var behaviorSections = Array.Empty(); if (item is EquippableItem equippableItem) { - var (tier, upgrades) = GameManager.ItemManager.GetTier(equippableItem); - children.Add(Card( - $"{CargoItemDetailsSurfaceId}.status.card", - "Status", - Metric( - $"{CargoItemDetailsSurfaceId}.tier", - "Tier", - $"{tier.Name}{new string('+', upgrades)}"), - Metric( - $"{CargoItemDetailsSurfaceId}.durability", - "Durability", - $"{(int)(equippableItem.Durability / GetMaxDurability(typedItem, equippableItem) * 100)}%"), - Metric( - $"{CargoItemDetailsSurfaceId}.temperature_range", - "Thermal Range", - FormatTemperatureRange(typedItem)))); - - foreach (var behaviorCard in BuildCargoItemBehaviorCards(typedItem, equippableItem)) - { - children.Add(behaviorCard); - } + var (itemTier, upgrades) = GameManager.ItemManager.GetTier(equippableItem); + tier = $"{itemTier.Name}{new string('+', upgrades)}"; + durability = $"{(int)(equippableItem.Durability / GetMaxDurability(typedItem, equippableItem) * 100)}%"; + thermalRange = FormatTemperatureRange(typedItem); + behaviorSections = ProjectCargoItemBehaviorSections(typedItem, equippableItem).ToArray(); } - children.Add(ButtonRow( - $"{CargoItemDetailsSurfaceId}.actions", - Button($"{CargoItemDetailsSurfaceId}.close", "Close", CloseCargoItemDetailsCommand))); - - return new EveSurfaceDocument( - ShipSettingsSurfaceType, - ShipSettingsSurfaceSchema, - ShipSettingsSurfaceProviderId, - ShipSettingsSurfaceProviderKind, - "Inventory Cargo Item Details", - version: 1, - DateTime.UtcNow.ToString("O"), - new EveSurfaceTree( - CargoItemDetailsSurfaceId, - Node( - $"{CargoItemDetailsSurfaceId}.root", - "surface", - Array.Empty<(string Key, string Value)>(), - children.ToArray()), - Array.Empty()), - new[] - { - new EveCommandTemplate(CloseCargoItemDetailsCommand, "Close", "unity-uitoolkit") - }); + return new AetheriaRuntimeCargoItemDetailsSurfaceState( + typedItem.Name, + typedItem.Description ?? "", + ActionGameManager.RuntimeCatalog?.GetManufacturer(typedItem)?.Name ?? "GameCult", + ActionGameManager.RuntimePlayerSettings.Format(GetCargoItemMass(item, typedItem)), + typedItem.Price, + quantity, + tier, + durability, + thermalRange, + behaviorSections, + DateTime.UtcNow.ToString("O")); } - private IEnumerable BuildCargoItemBehaviorCards( - AetheriaRuntimeCatalogItem typedItem, - EquippableItem equippableItem) - { - return BuildItemBehaviorCards(CargoItemDetailsSurfaceId, typedItem, equippableItem); - } - - private IEnumerable BuildItemBehaviorCards( - string surfaceId, + private IEnumerable ProjectCargoItemBehaviorSections( AetheriaRuntimeCatalogItem typedItem, EquippableItem equippableItem) { @@ -804,16 +545,21 @@ public class InventoryMenu : MonoBehaviour { if (string.Equals(behavior.Kind, AetheriaRuntimeBehaviorKinds.StatModifier, StringComparison.Ordinal)) { - var statReference = ReadTypedStatReference(FindTypedBehaviorField(behavior, 1)?.Value); - var modifier = ReadTypedPerformanceStat(FindTypedBehaviorField(behavior, 2)?.Value); - var modifierType = ReadTypedEnum(FindTypedBehaviorField(behavior, 3)?.Value, StatModifierType.Constant); - yield return Card( - $"{surfaceId}.behavior.{behavior.Kind}.stat_modifier", + var statReference = AetheriaRuntimeBehaviorValueReader.ReadStatReference(FindTypedBehaviorField(behavior, 1)?.Value); + var modifier = AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(FindTypedBehaviorField(behavior, 2)?.Value); + var modifierType = AetheriaRuntimeBehaviorValueReader.ReadEnum( + FindTypedBehaviorField(behavior, 3)?.Value, + StatModifierType.Constant); + yield return new AetheriaRuntimeCargoItemSection( + $"{AetheriaRuntimeCargoItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.stat_modifier", "Stat Modifier", - Metric( - $"{surfaceId}.behavior.{behavior.Kind}.target", - $"{statReference.target.SplitCamelCase()}:{statReference.stat.SplitCamelCase()}", - $"{(modifierType == StatModifierType.Constant ? "+" : "x")}{ActionGameManager.RuntimePlayerSettings.Format(GameManager.ItemManager.Evaluate(modifier, equippableItem))}")); + new[] + { + new AetheriaRuntimeCargoItemMetric( + $"{AetheriaRuntimeCargoItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.target", + $"{statReference.Target.SplitCamelCase()}:{statReference.Stat.SplitCamelCase()}", + $"{(modifierType == StatModifierType.Constant ? "+" : "x")}{ActionGameManager.RuntimePlayerSettings.Format(GameManager.ItemManager.Evaluate(modifier, equippableItem))}") + }); continue; } @@ -822,30 +568,21 @@ public class InventoryMenu : MonoBehaviour continue; var fields = metadata.DisplayFields - .Select(field => BuildItemBehaviorMetric(surfaceId, behavior, field, equippableItem)) + .Select(field => ProjectCargoItemBehaviorMetric(behavior, field, equippableItem)) .Where(metric => metric != null) .ToArray(); if (fields.Length == 0) continue; - yield return Card( - $"{surfaceId}.behavior.{behavior.Kind}", + yield return new AetheriaRuntimeCargoItemSection( + $"{AetheriaRuntimeCargoItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}", behavior.Kind.FormatTypeName(), fields); } } - private EveSurfaceComponent BuildCargoItemBehaviorMetric( - AetheriaRuntimeBehaviorPayload behavior, - AetheriaRuntimeBehaviorFieldMetadata field, - EquippableItem equippableItem) - { - return BuildItemBehaviorMetric(CargoItemDetailsSurfaceId, behavior, field, equippableItem); - } - - private EveSurfaceComponent BuildItemBehaviorMetric( - string surfaceId, + private AetheriaRuntimeCargoItemMetric ProjectCargoItemBehaviorMetric( AetheriaRuntimeBehaviorPayload behavior, AetheriaRuntimeBehaviorFieldMetadata field, EquippableItem equippableItem) @@ -867,156 +604,98 @@ public class InventoryMenu : MonoBehaviour value = ((int)payloadField.Value.NumberValue).ToString(); break; case AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat: - value = ActionGameManager.RuntimePlayerSettings.Format(GameManager.ItemManager.Evaluate(ReadTypedPerformanceStat(payloadField.Value), equippableItem)); + value = ActionGameManager.RuntimePlayerSettings.Format(GameManager.ItemManager.Evaluate(AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(payloadField.Value), equippableItem)); break; default: return null; } - return Metric( - $"{surfaceId}.behavior.{behavior.Kind}.{field.Key}", + return new AetheriaRuntimeCargoItemMetric( + $"{AetheriaRuntimeCargoItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.{field.Key}", field.Name.SplitCamelCase(), value); } - private UIDocument ResolveShipSettingsSurfaceDocument() + private IEnumerable ProjectEquippedItemBehaviorSections( + AetheriaRuntimeCatalogItem typedItem, + EquippableItem equippableItem) { - if (_shipSettingsSurfaceDocument != null) - return _shipSettingsSurfaceDocument; - - var host = new GameObject("Aetheria Inventory Ship Settings Surface"); - host.transform.SetParent(transform, false); - var document = host.AddComponent(); - document.sortingOrder = 1000; - host.SetActive(false); - _shipSettingsSurfaceDocument = document; - return document; - } - - private EveSurfaceDocument BuildCurrentShipSettingsSurfaceDefinition(Entity entity) - { - return new EveSurfaceDocument( - ShipSettingsSurfaceType, - ShipSettingsSurfaceSchema, - ShipSettingsSurfaceProviderId, - ShipSettingsSurfaceProviderKind, - "Current Ship Settings", - version: 1, - DateTime.UtcNow.ToString("O"), - new EveSurfaceTree( - ShipSettingsSurfaceId, - Node( - $"{ShipSettingsSurfaceId}.root", - "surface", - Array.Empty<(string Key, string Value)>(), - Card( - $"{ShipSettingsSurfaceId}.card", - entity.Name, - Metric( - $"{ShipSettingsSurfaceId}.shutdown.metric", - "Shutdown Threshold", - ActionGameManager.RuntimePlayerSettings.Format(entity.Settings.ShutdownPerformance)), - Text( - $"{ShipSettingsSurfaceId}.note", - "Gameplay still owns the mutation. This surface only lowers the current ship setting."), - ButtonRow( - $"{ShipSettingsSurfaceId}.shutdown.buttons", - Button($"{ShipSettingsSurfaceId}.shutdown.decrement", "Threshold -", DecrementShutdownThresholdCommand), - Button($"{ShipSettingsSurfaceId}.shutdown.increment", "Threshold +", IncrementShutdownThresholdCommand), - Button($"{ShipSettingsSurfaceId}.shutdown.reset", "Default", ResetShutdownThresholdCommand), - Button($"{ShipSettingsSurfaceId}.close", "Close", CloseShipSettingsCommand)))), - Array.Empty()), - new[] + foreach (var behavior in typedItem.BehaviorPayloads ?? Array.Empty()) + { + if (string.Equals(behavior.Kind, AetheriaRuntimeBehaviorKinds.StatModifier, StringComparison.Ordinal)) { - new EveCommandTemplate(DecrementShutdownThresholdCommand, "Threshold -", "unity-uitoolkit"), - new EveCommandTemplate(IncrementShutdownThresholdCommand, "Threshold +", "unity-uitoolkit"), - new EveCommandTemplate(ResetShutdownThresholdCommand, "Default", "unity-uitoolkit"), - new EveCommandTemplate(CloseShipSettingsCommand, "Close", "unity-uitoolkit") - }); + var statReference = AetheriaRuntimeBehaviorValueReader.ReadStatReference(FindTypedBehaviorField(behavior, 1)?.Value); + var modifier = AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(FindTypedBehaviorField(behavior, 2)?.Value); + var modifierType = AetheriaRuntimeBehaviorValueReader.ReadEnum( + FindTypedBehaviorField(behavior, 3)?.Value, + StatModifierType.Constant); + yield return new AetheriaRuntimeEquippedItemSection( + $"{AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.stat_modifier", + "Stat Modifier", + new[] + { + new AetheriaRuntimeEquippedItemMetric( + $"{AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.target", + $"{statReference.Target.SplitCamelCase()}:{statReference.Stat.SplitCamelCase()}", + $"{(modifierType == StatModifierType.Constant ? "+" : "x")}{ActionGameManager.RuntimePlayerSettings.Format(GameManager.ItemManager.Evaluate(modifier, equippableItem))}") + }); + continue; + } + + var metadata = AetheriaRuntimeBehaviorMetadataCatalog.Get(behavior.Kind); + if (metadata == null) + continue; + + var fields = metadata.DisplayFields + .Select(field => ProjectEquippedItemBehaviorMetric(behavior, field, equippableItem)) + .Where(metric => metric != null) + .ToArray(); + + if (fields.Length == 0) + continue; + + yield return new AetheriaRuntimeEquippedItemSection( + $"{AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}", + behavior.Kind.FormatTypeName(), + fields); + } } - private static EveSurfaceComponent Card( - string id, - string title, - params EveSurfaceComponent[] children) + private AetheriaRuntimeEquippedItemMetric ProjectEquippedItemBehaviorMetric( + AetheriaRuntimeBehaviorPayload behavior, + AetheriaRuntimeBehaviorFieldMetadata field, + EquippableItem equippableItem) { - return Node(id, "card", new[] { ("title", title) }, children); - } + var payloadField = FindTypedBehaviorField(behavior, field.Key); + if (payloadField == null) + return null; - private static EveSurfaceComponent Metric(string id, string label, string value) - { - return Node(id, "metric", new[] { ("label", label), ("value", value) }); - } - - private static EveSurfaceComponent Text(string id, string value) - { - return Node(id, "text", new[] { ("value", value) }); - } - - private static EveSurfaceComponent Button(string id, string label, string command) - { - return Node(id, "control.button", new[] { ("label", label), ("command", command) }); - } - - private static EveSurfaceComponent CommandButton( - string id, - string label, - string command, - params (string Key, string Value)[] props) - { - var buttonProps = new List<(string Key, string Value)> + string value; + switch (field.ValueKind) { - ("label", label), - ("command", command) - }; + case AetheriaRuntimeBehaviorFieldValueKind.Number: + value = ActionGameManager.RuntimePlayerSettings.Format((float)payloadField.Value.NumberValue); + break; + case AetheriaRuntimeBehaviorFieldValueKind.Temperature: + value = ActionGameManager.RuntimePlayerSettings.FormatTemperature((float)payloadField.Value.NumberValue); + break; + case AetheriaRuntimeBehaviorFieldValueKind.Integer: + value = ((int)payloadField.Value.NumberValue).ToString(); + break; + case AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat: + value = ActionGameManager.RuntimePlayerSettings.Format(GameManager.ItemManager.Evaluate(AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(payloadField.Value), equippableItem)); + break; + default: + return null; + } - if (props != null && props.Length > 0) - buttonProps.AddRange(props); - - return Node(id, "control.button", buttonProps); + return new AetheriaRuntimeEquippedItemMetric( + $"{AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.{field.Key}", + field.Name.SplitCamelCase(), + value); } - private static EveSurfaceComponent TextField( - string id, - string label, - string command, - string value, - params (string Key, string Value)[] props) - { - var fieldProps = new List<(string Key, string Value)> - { - ("label", label), - ("command", command), - ("value", value ?? "") - }; - - if (props != null && props.Length > 0) - fieldProps.AddRange(props); - - return Node(id, "control.text", fieldProps); - } - - private static EveSurfaceComponent ButtonRow( - string id, - params EveSurfaceComponent[] children) - { - return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); - } - - private static EveSurfaceComponent Node( - string id, - string kind, - IEnumerable<(string Key, string Value)> props, - params EveSurfaceComponent[] children) - { - return new EveSurfaceComponent( - id, - kind, - props.ToDictionary(prop => prop.Key, prop => prop.Value, StringComparer.Ordinal), - children ?? Array.Empty()); - } - - private string BuildEquippedItemTitle(EquippedItem item, AetheriaRuntimeCatalogItem typedItem) +private string BuildEquippedItemTitle(EquippedItem item, AetheriaRuntimeCatalogItem typedItem) { var (tier, upgrades) = GameManager.ItemManager.GetTier(item.EquippableItem); return $"{typedItem?.Name ?? "Unknown Item"} ({tier.Name}{new string('+', upgrades)})"; @@ -1030,29 +709,13 @@ public class InventoryMenu : MonoBehaviour item.Entity.WeaponGroups[groupIndex].items.Contains(item); } - private static bool TryReadPayloadInt(EveSurfaceCommandRequest request, string key, out int value) - { - value = 0; - return request?.Payload != null && - request.Payload.TryGetValue(key, out var raw) && - int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out value); - } - - private static bool TryReadPayloadFloat(EveSurfaceCommandRequest request, string key, out float value) - { - value = 0f; - return request?.Payload != null && - request.Payload.TryGetValue(key, out var raw) && - float.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out value); - } - - private bool CommitCargoItemTransfer(EquippedCargoBay origin, InventoryPanel destination, ItemInstance item) + private bool RequestCargoItemTransfer(EquippedCargoBay origin, InventoryPanel destination, ItemInstance item) { if (destination.DisplayedEntity != null && item is EquippableItem equippableItem) - return GameManager.CommitCargoItemEquip(origin, destination.DisplayedEntity, equippableItem); + return GameManager.RequestCargoItemEquip(origin, destination.DisplayedEntity, equippableItem); if (destination.DisplayedCargo != null) - return GameManager.CommitCargoItemTransfer(origin, destination.DisplayedCargo, item); + return GameManager.RequestCargoItemTransfer(origin, destination.DisplayedCargo, item); return false; } @@ -1067,10 +730,10 @@ public class InventoryMenu : MonoBehaviour _selectedPosition = default; } - private bool CommitEquippedItemTransfer(Entity origin, EquippedItem item, InventoryPanel destination) + private bool RequestEquippedItemTransfer(Entity origin, EquippedItem item, InventoryPanel destination) { if (destination.DisplayedCargo != null) - return GameManager.CommitEquippedItemStore(origin, item, destination.DisplayedCargo); + return GameManager.RequestEquippedItemStore(origin, item, destination.DisplayedCargo); return false; } @@ -1164,51 +827,6 @@ public class InventoryMenu : MonoBehaviour : behavior.Fields.FirstOrDefault(field => field.Key == key.Value); } - private static PerformanceStat ReadTypedPerformanceStat(AetheriaRuntimeBehaviorValue value) - { - return new PerformanceStat - { - Min = ReadTypedChildNumber(value, 0), - Max = ReadTypedChildNumber(value, 1), - HeatExponentMultiplier = ReadTypedChildNumber(value, 2), - DurabilityExponentMultiplier = ReadTypedChildNumber(value, 3), - QualityExponent = ReadTypedChildNumber(value, 4) - }; - } - - private static (string target, string stat) ReadTypedStatReference(AetheriaRuntimeBehaviorValue value) - { - return ( - ReadTypedChildString(value, 1), - ReadTypedChildString(value, 2)); - } - - private static float ReadTypedChildNumber(AetheriaRuntimeBehaviorValue value, int index) - { - return value != null && value.Children.Count > index - ? (float)value.Children[index].NumberValue - : 0f; - } - - private static string ReadTypedChildString(AetheriaRuntimeBehaviorValue value, int index) - { - return value != null && value.Children.Count > index - ? value.Children[index].StringValue ?? "" - : ""; - } - - private static T ReadTypedEnum(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 int2 RotateTypedShapeCell( AetheriaRuntimeShapeCell cell, AetheriaRuntimeCatalogItem item, @@ -1227,19 +845,19 @@ public class InventoryMenu : MonoBehaviour { if (_shipSettingsSurfaceDocument != null) { - Destroy(_shipSettingsSurfaceDocument.gameObject); + AetheriaEveUnitySurfaceHost.DestroyDocument(_shipSettingsSurfaceDocument); _shipSettingsSurfaceDocument = null; } if (_cargoItemDetailsSurfaceDocument != null) { - Destroy(_cargoItemDetailsSurfaceDocument.gameObject); + AetheriaEveUnitySurfaceHost.DestroyDocument(_cargoItemDetailsSurfaceDocument); _cargoItemDetailsSurfaceDocument = null; } if (_equippedItemDetailsSurfaceDocument != null) { - Destroy(_equippedItemDetailsSurfaceDocument.gameObject); + AetheriaEveUnitySurfaceHost.DestroyDocument(_equippedItemDetailsSurfaceDocument); _equippedItemDetailsSurfaceDocument = null; } } diff --git a/Assets/Scripts/UI/Menu/InventoryPanel.cs b/Assets/Scripts/UI/Menu/InventoryPanel.cs index 832997f28..5a2be014b 100644 --- a/Assets/Scripts/UI/Menu/InventoryPanel.cs +++ b/Assets/Scripts/UI/Menu/InventoryPanel.cs @@ -6,9 +6,9 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Unity; using GameCult.Eve.Surface; -using GameCult.Eve.UnityUIToolkit; using TMPro; using UniRx; using UniRx.Triggers; @@ -24,14 +24,6 @@ using int2 = Unity.Mathematics.int2; public class InventoryPanel : MonoBehaviour, IPointerClickHandler { - private const string DropdownSurfaceType = "surface-state"; - private const string DropdownSurfaceSchema = "gamecult.eve.surface.v1"; - private const string DropdownSurfaceProviderId = "aetheria"; - private const string DropdownSurfaceProviderKind = "inventory.panel"; - private const string DropdownSurfaceId = "aetheria.inventory.panel.dropdown"; - private const string CloseDropdownSurfaceCommand = "aetheria.inventory.panel.dropdown.close"; - private const string SaveLoadoutCommand = "aetheria.inventory.panel.dropdown.save_loadout"; - public ConfirmationDialog Dialog; public RectTransform DragParent; public bool Flip; @@ -92,6 +84,19 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler private InventoryCell _clickCell; private float _clickTime; private readonly Dictionary _dropdownCommands = new Dictionary(StringComparer.Ordinal); + private readonly AetheriaEveUnitySurfaceChrome _dropdownSurfaceChrome = new AetheriaEveUnitySurfaceChrome + { + RootAlignItems = UIE.Align.FlexStart, + RootJustifyContent = UIE.Justify.FlexStart, + RootPaddingTop = 0f, + Width = 420f, + MinWidth = 0f, + MaxWidth = 520f, + PaddingLeft = 18f, + PaddingRight = 18f, + PaddingTop = 18f, + PaddingBottom = 18f + }; private UIE.UIDocument _dropdownSurfaceDocument; private bool _hud; @@ -133,7 +138,7 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler { if(_displayedEntity is Ship ship) { - if (GameManager.CommitDockedCurrentShip(ship)) + if (GameManager.RequestDockedCurrentShip(ship)) Current.targetGraphic.color = ToggleEnabledColor; } else @@ -157,7 +162,7 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler Dialog.AddField("Name", () => entityName, s => entityName = s); Dialog.Show(() => { - GameManager.CommitEntityName(_displayedEntity, entityName); + GameManager.RequestEntityName(_displayedEntity, entityName); Title.text = _displayedEntity.Name; }); Dialog.MoveToCursor(); @@ -182,55 +187,22 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler var price = blueprint.Price(GameManager.ItemManager); return ($"{template.Name} - {price:n0}", () => { - if (GameManager.CommitRuntimeLoadoutRestore(template, out var entity)) + if (GameManager.RequestRuntimeLoadoutRestore(template, out var entity) && entity != null) Display(entity); - }, price < GameManager.Credits); + }, true); } private void RenderDropdownSurface() { BuildDropdownCommands(); - var document = ResolveDropdownSurfaceDocument(); - document.gameObject.SetActive(true); - - var root = document.rootVisualElement; - root.Clear(); - root.style.flexGrow = 1; - root.style.position = UIE.Position.Absolute; - root.style.left = 0; - root.style.top = 0; - root.style.right = 0; - root.style.bottom = 0; - root.style.alignItems = UIE.Align.FlexStart; - root.style.justifyContent = UIE.Justify.FlexStart; - root.pickingMode = UIE.PickingMode.Ignore; - - var shell = new UIE.VisualElement(); - shell.style.width = 420; - shell.style.maxWidth = 520; - shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.94f); - shell.style.borderTopLeftRadius = 8; - shell.style.borderTopRightRadius = 8; - shell.style.borderBottomLeftRadius = 8; - shell.style.borderBottomRightRadius = 8; - shell.style.paddingLeft = 18; - shell.style.paddingRight = 18; - shell.style.paddingTop = 18; - shell.style.paddingBottom = 18; - shell.style.borderLeftWidth = 1; - shell.style.borderRightWidth = 1; - shell.style.borderTopWidth = 1; - shell.style.borderBottomWidth = 1; - shell.style.borderLeftColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderRightColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderTopColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderBottomColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.pickingMode = UIE.PickingMode.Position; - root.Add(shell); - - var lowerer = new EveUiToolkitSurfaceLowerer(); - shell.Add(lowerer.Lower(BuildDropdownSurfaceDefinition(), HandleDropdownSurfaceCommand)); + _dropdownSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _dropdownSurfaceDocument, + "Aetheria Inventory Dropdown Surface", + AetheriaRuntimeInventoryDropdownSurfaceBuilder.Build(ProjectDropdownSurfaceState()), + HandleDropdownSurfaceCommand, + _dropdownSurfaceChrome); } private void BuildDropdownCommands() @@ -243,7 +215,7 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler { if (entityEntry.entity != _displayedEntity) { - var equipmentCommand = $"aetheria.inventory.panel.dropdown.entity_{entityEntry.entityIndex}.equipment"; + var equipmentCommand = AetheriaRuntimeInventoryDropdownSurfaceBuilder.EntityEquipmentCommand(entityEntry.entityIndex); _dropdownCommands[equipmentCommand] = () => Display(entityEntry.entity); } @@ -251,27 +223,29 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler .Where(bay => bay != _displayedCargo) .Select((bay, bayIndex) => (bay, bayIndex))) { - var bayCommand = $"aetheria.inventory.panel.dropdown.entity_{entityEntry.entityIndex}.bay_{bayEntry.bayIndex}"; + var bayCommand = AetheriaRuntimeInventoryDropdownSurfaceBuilder.EntityBayCommand( + entityEntry.entityIndex, + bayEntry.bayIndex); _dropdownCommands[bayCommand] = () => Display(bayEntry.bay); } } else if (entityEntry.entity != _displayedEntity) { - var entityCommand = $"aetheria.inventory.panel.dropdown.entity_{entityEntry.entityIndex}"; + var entityCommand = AetheriaRuntimeInventoryDropdownSurfaceBuilder.EntityCommand(entityEntry.entityIndex); _dropdownCommands[entityCommand] = () => Display(entityEntry.entity); } } if (GameManager.DockingBay != null && _displayedCargo != GameManager.DockingBay) { - _dropdownCommands["aetheria.inventory.panel.dropdown.docking_bay"] = () => Display(GameManager.DockingBay); + _dropdownCommands[AetheriaRuntimeInventoryDropdownSurfaceBuilder.DockingBay] = () => Display(GameManager.DockingBay); } if (_displayedEntity != null) { - _dropdownCommands[SaveLoadoutCommand] = () => + _dropdownCommands[AetheriaRuntimeInventoryDropdownSurfaceBuilder.SaveLoadout] = () => { - GameManager.QueueRuntimeLoadoutTemplateCommit(EntityConstructionBlueprintProjector.CaptureBlueprint(_displayedEntity)); + GameManager.RequestLoadoutTemplateSave(EntityConstructionBlueprintProjector.CaptureBlueprint(_displayedEntity)); }; } @@ -281,14 +255,10 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler if (blueprint == null) continue; - var price = blueprint.Price(GameManager.ItemManager); - if (price >= GameManager.Credits) - continue; - - var restoreCommand = $"aetheria.inventory.panel.dropdown.loadout_{loadoutEntry.templateIndex}"; + var restoreCommand = AetheriaRuntimeInventoryDropdownSurfaceBuilder.LoadoutCommand(loadoutEntry.templateIndex); _dropdownCommands[restoreCommand] = () => { - if (GameManager.CommitRuntimeLoadoutRestore(loadoutEntry.template, out var entity)) + if (GameManager.RequestRuntimeLoadoutRestore(loadoutEntry.template, out var entity) && entity != null) Display(entity); }; } @@ -296,20 +266,27 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler private void HandleDropdownSurfaceCommand(EveSurfaceCommandRequest request) { - if (string.Equals(request.Command, CloseDropdownSurfaceCommand, StringComparison.Ordinal)) + if (!AetheriaRuntimeInventoryDropdownSurfaceCommands.TryRead(request, out var command)) + { + Debug.LogWarning($"Unknown inventory dropdown command: {request?.Command}"); + return; + } + + if (command.Kind == AetheriaRuntimeInventoryDropdownCommandKind.Close) { HideDropdownSurface(); return; } - if (_dropdownCommands.TryGetValue(request.Command, out var action)) + if (command.Kind == AetheriaRuntimeInventoryDropdownCommandKind.Select && + _dropdownCommands.TryGetValue(command.Command, out var action)) { action(); HideDropdownSurface(); return; } - Debug.LogWarning($"Unknown inventory dropdown command: {request.Command}"); + Debug.LogWarning($"Unknown inventory dropdown command: {request?.Command}"); } private void HideDropdownSurface() @@ -317,217 +294,125 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler if (_dropdownSurfaceDocument == null) return; - _dropdownSurfaceDocument.rootVisualElement.Clear(); - _dropdownSurfaceDocument.gameObject.SetActive(false); + AetheriaEveUnitySurfaceHost.Hide(_dropdownSurfaceDocument); } - private UIE.UIDocument ResolveDropdownSurfaceDocument() + private AetheriaRuntimeInventoryDropdownSurfaceState ProjectDropdownSurfaceState() { - if (_dropdownSurfaceDocument != null) - return _dropdownSurfaceDocument; - - var host = new GameObject("Aetheria Inventory Dropdown Surface"); - host.transform.SetParent(transform, false); - var document = host.AddComponent(); - document.sortingOrder = 1000; - host.SetActive(false); - _dropdownSurfaceDocument = document; - return document; - } - - private EveSurfaceDocument BuildDropdownSurfaceDefinition() - { - var children = new List - { - Card( - $"{DropdownSurfaceId}.summary", - "Inventory Actions", - Metric( - $"{DropdownSurfaceId}.current", - "Current View", - Title?.text ?? "None"), - Text( - $"{DropdownSurfaceId}.note", - "Gameplay still owns entity and cargo selection plus loadout restore. This surface just kills the old context-menu shell.")) - }; + var groups = new List(); foreach (var entityEntry in GameManager.AvailableEntities().Select((entity, entityIndex) => (entity, entityIndex))) { - var buttons = new List(); - var equipmentCommand = $"aetheria.inventory.panel.dropdown.entity_{entityEntry.entityIndex}.equipment"; + var options = new List(); + var equipmentCommand = AetheriaRuntimeInventoryDropdownSurfaceBuilder.EntityEquipmentCommand(entityEntry.entityIndex); if (_dropdownCommands.ContainsKey(equipmentCommand)) { - buttons.Add(Button( - $"{DropdownSurfaceId}.entity_{entityEntry.entityIndex}.equipment", + options.Add(new AetheriaRuntimeInventoryDropdownOption( + $"{AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId}.entity_{entityEntry.entityIndex}.equipment", "Equipment", equipmentCommand)); } foreach (var bayEntry in entityEntry.entity.CargoBays.Select((bay, bayIndex) => (bay, bayIndex))) { - var bayCommand = $"aetheria.inventory.panel.dropdown.entity_{entityEntry.entityIndex}.bay_{bayEntry.bayIndex}"; + var bayCommand = AetheriaRuntimeInventoryDropdownSurfaceBuilder.EntityBayCommand( + entityEntry.entityIndex, + bayEntry.bayIndex); if (_dropdownCommands.ContainsKey(bayCommand)) { - buttons.Add(Button( - $"{DropdownSurfaceId}.entity_{entityEntry.entityIndex}.bay_{bayEntry.bayIndex}", + options.Add(new AetheriaRuntimeInventoryDropdownOption( + $"{AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId}.entity_{entityEntry.entityIndex}.bay_{bayEntry.bayIndex}", $"Bay {bayEntry.bayIndex + 1}", bayCommand)); } } - var entityCommand = $"aetheria.inventory.panel.dropdown.entity_{entityEntry.entityIndex}"; + var entityCommand = AetheriaRuntimeInventoryDropdownSurfaceBuilder.EntityCommand(entityEntry.entityIndex); if (_dropdownCommands.ContainsKey(entityCommand)) { - buttons.Add(Button( - $"{DropdownSurfaceId}.entity_{entityEntry.entityIndex}.select", + options.Add(new AetheriaRuntimeInventoryDropdownOption( + $"{AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId}.entity_{entityEntry.entityIndex}.select", entityEntry.entity.Name, entityCommand)); } - if (buttons.Count > 0) + if (options.Count > 0) { - children.Add(Card( - $"{DropdownSurfaceId}.entity_{entityEntry.entityIndex}.card", + groups.Add(new AetheriaRuntimeInventoryDropdownGroup( + $"{AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId}.entity_{entityEntry.entityIndex}.card", entityEntry.entity.Name, - ButtonColumn($"{DropdownSurfaceId}.entity_{entityEntry.entityIndex}.options", buttons.ToArray()))); + options)); } } - if (_dropdownCommands.ContainsKey("aetheria.inventory.panel.dropdown.docking_bay")) + if (_dropdownCommands.ContainsKey(AetheriaRuntimeInventoryDropdownSurfaceBuilder.DockingBay)) { - children.Add(Card( - $"{DropdownSurfaceId}.dockingBay.card", + groups.Add(new AetheriaRuntimeInventoryDropdownGroup( + $"{AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId}.dockingBay.card", "Docking Bay", - ButtonColumn( - $"{DropdownSurfaceId}.dockingBay.options", - Button( - $"{DropdownSurfaceId}.dockingBay.select", + new[] + { + new AetheriaRuntimeInventoryDropdownOption( + $"{AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId}.dockingBay.select", GameManager.DockingBay.Name, - "aetheria.inventory.panel.dropdown.docking_bay")))); + AetheriaRuntimeInventoryDropdownSurfaceBuilder.DockingBay) + })); } - var loadoutChildren = new List(); - if (_dropdownCommands.ContainsKey(SaveLoadoutCommand)) + var loadoutOptions = new List(); + if (_dropdownCommands.ContainsKey(AetheriaRuntimeInventoryDropdownSurfaceBuilder.SaveLoadout)) { - loadoutChildren.Add(Button( - $"{DropdownSurfaceId}.loadouts.save", + loadoutOptions.Add(new AetheriaRuntimeInventoryDropdownOption( + $"{AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId}.loadouts.save", "Save Loadout", - SaveLoadoutCommand)); + AetheriaRuntimeInventoryDropdownSurfaceBuilder.SaveLoadout)); } foreach (var loadoutEntry in GameManager.LoadoutTemplates.Select((template, templateIndex) => (template, templateIndex))) { - var restoreCommand = $"aetheria.inventory.panel.dropdown.loadout_{loadoutEntry.templateIndex}"; + var restoreCommand = AetheriaRuntimeInventoryDropdownSurfaceBuilder.LoadoutCommand(loadoutEntry.templateIndex); var blueprint = GameManager.CreateEntityConstructionBlueprint(loadoutEntry.template); if (blueprint == null) { - loadoutChildren.Add(Text( - $"{DropdownSurfaceId}.loadouts.unavailable_{loadoutEntry.templateIndex}", - $"{loadoutEntry.template.Name} - unavailable")); + loadoutOptions.Add(new AetheriaRuntimeInventoryDropdownOption( + $"{AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId}.loadouts.unavailable_{loadoutEntry.templateIndex}", + $"{loadoutEntry.template.Name} - unavailable", + "")); continue; } var price = blueprint.Price(GameManager.ItemManager); if (_dropdownCommands.ContainsKey(restoreCommand)) { - loadoutChildren.Add(Button( - $"{DropdownSurfaceId}.loadouts.restore_{loadoutEntry.templateIndex}", + loadoutOptions.Add(new AetheriaRuntimeInventoryDropdownOption( + $"{AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId}.loadouts.restore_{loadoutEntry.templateIndex}", $"{loadoutEntry.template.Name} - {price:n0}", restoreCommand)); } else { - loadoutChildren.Add(Text( - $"{DropdownSurfaceId}.loadouts.locked_{loadoutEntry.templateIndex}", - $"{loadoutEntry.template.Name} - {price:n0} (unavailable)")); + loadoutOptions.Add(new AetheriaRuntimeInventoryDropdownOption( + $"{AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId}.loadouts.locked_{loadoutEntry.templateIndex}", + $"{loadoutEntry.template.Name} - {price:n0} (unavailable)", + "")); } } - if (loadoutChildren.Count > 0) + if (loadoutOptions.Count > 0) { - children.Add(Card( - $"{DropdownSurfaceId}.loadouts.card", + groups.Add(new AetheriaRuntimeInventoryDropdownGroup( + $"{AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId}.loadouts.card", "Loadouts", - ButtonColumn($"{DropdownSurfaceId}.loadouts.options", loadoutChildren.ToArray()))); + loadoutOptions)); } - children.Add(ButtonRow( - $"{DropdownSurfaceId}.actions", - Button($"{DropdownSurfaceId}.close", "Close", CloseDropdownSurfaceCommand))); - - return new EveSurfaceDocument( - DropdownSurfaceType, - DropdownSurfaceSchema, - DropdownSurfaceProviderId, - DropdownSurfaceProviderKind, - "Inventory Dropdown", - version: 1, - DateTime.UtcNow.ToString("O"), - new EveSurfaceTree( - DropdownSurfaceId, - Node( - $"{DropdownSurfaceId}.root", - "surface", - Array.Empty<(string Key, string Value)>(), - children.ToArray()), - Array.Empty()), - _dropdownCommands - .Select(pair => new EveCommandTemplate(pair.Key, pair.Key.Split('.').Last(), "unity-uitoolkit")) - .Append(new EveCommandTemplate(CloseDropdownSurfaceCommand, "Close", "unity-uitoolkit")) - .ToArray()); + return new AetheriaRuntimeInventoryDropdownSurfaceState( + Title?.text ?? "None", + groups, + DateTime.UtcNow.ToString("O")); } - private static EveSurfaceComponent Card( - string id, - string title, - params EveSurfaceComponent[] children) - { - return Node(id, "card", new[] { ("title", title) }, children); - } - - private static EveSurfaceComponent Metric(string id, string label, string value) - { - return Node(id, "metric", new[] { ("label", label), ("value", value) }); - } - - private static EveSurfaceComponent Text(string id, string value) - { - return Node(id, "text", new[] { ("value", value) }); - } - - private static EveSurfaceComponent Button(string id, string label, string command) - { - return Node(id, "control.button", new[] { ("label", label), ("command", command) }); - } - - private static EveSurfaceComponent ButtonRow( - string id, - params EveSurfaceComponent[] children) - { - return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); - } - - private static EveSurfaceComponent ButtonColumn( - string id, - params EveSurfaceComponent[] children) - { - return Node(id, "column", Array.Empty<(string Key, string Value)>(), children); - } - - private static EveSurfaceComponent Node( - string id, - string kind, - IEnumerable<(string Key, string Value)> props, - params EveSurfaceComponent[] children) - { - return new EveSurfaceComponent( - id, - kind, - props.ToDictionary(prop => prop.Key, prop => prop.Value, StringComparer.Ordinal), - children ?? Array.Empty()); - } - - private void Update() +private void Update() { if (_displayedEntity != null && TemperatureDisplay) { @@ -747,24 +632,21 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler var item = itemDragObject.Item; if (!(item is EquippableItem equippableItem)) return; var placementPosition = v + itemDragObject.OriginCellOffset; - if (entity.ItemFits(equippableItem, placementPosition)) + //foreach (var cell in _dragCells) cell.gameObject.SetActive(false); + FakeItem = item; + FakeOccupancy = _displayedHullShape.Inset(GetItemShape(equippableItem), placementPosition, item.Rotation); + RefreshCells(); + GameManager.RegisterDragTarget(drag => { - //foreach (var cell in _dragCells) cell.gameObject.SetActive(false); - FakeItem = item; - FakeOccupancy = _displayedHullShape.Inset(GetItemShape(item), placementPosition, item.Rotation); + //Debug.Log("Entity Drag Callback"); + FakeOccupancy = null; + var success = RequestDraggedItemToEntity(drag, entity, placementPosition); + if (!success) + ShowUnableToMoveItemDialog(); RefreshCells(); - GameManager.RegisterDragTarget(drag => - { - //Debug.Log("Entity Drag Callback"); - FakeOccupancy = null; - var success = CommitDraggedItemToEntity(drag, entity, placementPosition); - if (!success) - ShowUnableToMoveItemDialog(); - RefreshCells(); - // TODO: SFX: Equip - return success; - }); - } + // TODO: SFX: Equip + return success; + }); }); cell.PointerExitTrigger.OnPointerExitAsObservable() .Subscribe(data => @@ -786,22 +668,22 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler // Debug.Log($"Clicked at pos {data.position}, normalized {point}"); if (_displayedHullShape[int2(v.x - 1, v.y)] && point.x < ThermalToggleRegionSize) { - if (GameManager.CommitHullConductivityToggle(entity, int2(v.x - 1, v.y), 0)) + if (GameManager.RequestHullConductivityToggle(entity, int2(v.x - 1, v.y), 0)) RefreshCells(new []{v,int2(v.x - 1, v.y)}); } if (_displayedHullShape[int2(v.x + 1, v.y)] && point.x > 1 - ThermalToggleRegionSize) { - if (GameManager.CommitHullConductivityToggle(entity, int2(v.x, v.y), 0)) + if (GameManager.RequestHullConductivityToggle(entity, int2(v.x, v.y), 0)) RefreshCells(new []{v,int2(v.x + 1, v.y)}); } if (_displayedHullShape[int2(v.x, v.y - 1)] && point.y < ThermalToggleRegionSize) { - if (GameManager.CommitHullConductivityToggle(entity, int2(v.x, v.y - 1), 1)) + if (GameManager.RequestHullConductivityToggle(entity, int2(v.x, v.y - 1), 1)) RefreshCells(new []{v,int2(v.x, v.y - 1)}); } if (_displayedHullShape[int2(v.x, v.y + 1)] && point.y > 1 - ThermalToggleRegionSize) { - if (GameManager.CommitHullConductivityToggle(entity, int2(v.x, v.y), 1)) + if (GameManager.RequestHullConductivityToggle(entity, int2(v.x, v.y), 1)) RefreshCells(new []{v,int2(v.x, v.y + 1)}); } }); @@ -950,23 +832,20 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler if (!(GameManager.DragObject is ItemDragObject itemDragObject)) return; var item = itemDragObject.Item; var placementPosition = v + itemDragObject.OriginCellOffset; - if (cargo.ItemFits(item, placementPosition)) + //foreach (var cell in _dragCells) cell.gameObject.SetActive(false); + FakeItem = item; + FakeOccupancy = cargo.InteriorShape.Inset(GetItemShape(item), placementPosition, item.Rotation); + RefreshCells(); + GameManager.RegisterDragTarget(drag => { - //foreach (var cell in _dragCells) cell.gameObject.SetActive(false); - FakeItem = item; - FakeOccupancy = cargo.InteriorShape.Inset(GetItemShape(item), placementPosition, item.Rotation); - RefreshCells(); - GameManager.RegisterDragTarget(drag => - { - //Debug.Log("Inventory Drag Callback"); - FakeOccupancy = null; - var success = CommitDraggedItemToCargo(drag, cargo, placementPosition); - if (!success) - ShowUnableToMoveItemDialog(); - // TODO: SFX: Drop - return success; - }); - } + //Debug.Log("Inventory Drag Callback"); + FakeOccupancy = null; + var success = RequestDraggedItemToCargo(drag, cargo, placementPosition); + if (!success) + ShowUnableToMoveItemDialog(); + // TODO: SFX: Drop + return success; + }); }); cell.PointerExitTrigger.OnPointerExitAsObservable() .Subscribe(data => @@ -1150,7 +1029,7 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler var c = float3(1); if (TryGetTypedHardpointType(item, out var typedHardpoint)) - c = HardpointData.GetColor(typedHardpoint); + c = HardpointData.GetColor(typedHardpoint).ToUnityFloat3(); if(!highlight) c *= .7071f; @@ -1224,18 +1103,18 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler return ActionGameManager.RuntimeCatalog?.FindItem(item?.ItemKey ?? ""); } - private bool CommitDraggedItemToEntity(DragObject drag, Entity destination, int2 destinationPosition) + private bool RequestDraggedItemToEntity(DragObject drag, Entity destination, int2 destinationPosition) { switch (drag) { case EquippedItemDragObject equippedItemDragObject: - return GameManager.CommitEquippedItemEquip( + return GameManager.RequestEquippedItemEquip( equippedItemDragObject.OriginEntity, equippedItemDragObject.EquippedItem, destination, destinationPosition); case ItemInstanceDragObject itemInstanceDragObject when itemInstanceDragObject.Item is EquippableItem equippableItem: - return GameManager.CommitCargoItemEquip( + return GameManager.RequestCargoItemEquip( itemInstanceDragObject.OriginInventory, destination, equippableItem, @@ -1245,18 +1124,18 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler } } - private bool CommitDraggedItemToCargo(DragObject drag, EquippedCargoBay destination, int2 destinationPosition) + private bool RequestDraggedItemToCargo(DragObject drag, EquippedCargoBay destination, int2 destinationPosition) { switch (drag) { case EquippedItemDragObject equippedItemDragObject: - return GameManager.CommitEquippedItemStore( + return GameManager.RequestEquippedItemStore( equippedItemDragObject.OriginEntity, equippedItemDragObject.EquippedItem, destination, destinationPosition); case ItemInstanceDragObject itemInstanceDragObject: - return GameManager.CommitCargoItemTransfer( + return GameManager.RequestCargoItemTransfer( itemInstanceDragObject.OriginInventory, destination, itemInstanceDragObject.Item, @@ -1279,7 +1158,7 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler { if (_dropdownSurfaceDocument != null) { - Destroy(_dropdownSurfaceDocument.gameObject); + AetheriaEveUnitySurfaceHost.DestroyDocument(_dropdownSurfaceDocument); _dropdownSurfaceDocument = null; } } diff --git a/Assets/Scripts/UI/Menu/MenuPanel.cs b/Assets/Scripts/UI/Menu/MenuPanel.cs index 211cc0e2b..0ca42decb 100644 --- a/Assets/Scripts/UI/Menu/MenuPanel.cs +++ b/Assets/Scripts/UI/Menu/MenuPanel.cs @@ -1,12 +1,13 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public +/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; using System.Collections.Generic; using System.Linq; +using GameCult.Aetheria.EveRuntime; +using GameCult.Aetheria.State.Unity; using GameCult.Eve.Surface; -using GameCult.Eve.UnityUIToolkit; using UnityEngine; using UnityEngine.UIElements; @@ -21,12 +22,6 @@ public class MenuPanel : MonoBehaviour public bool RequireDock; } - private const string MenuTabsSurfaceType = "surface-state"; - private const string MenuTabsSurfaceSchema = "gamecult.eve.surface.v1"; - private const string MenuTabsSurfaceProviderId = "aetheria"; - private const string MenuTabsSurfaceProviderKind = "runtime.menu"; - private const string MenuTabsSurfaceId = "aetheria.runtime_menu.tabs"; - public ActionGameManager GameManager; public RectTransform TabButtons; [SerializeField] private MenuTabBinding[] TabBindings = Array.Empty(); @@ -36,6 +31,7 @@ public class MenuPanel : MonoBehaviour private readonly Dictionary _tabs = new Dictionary(); private MenuTabBinding _current; private UIDocument _tabSurfaceDocument; + private readonly AetheriaEveUnitySurfaceChrome _tabSurfaceChrome = new AetheriaEveUnitySurfaceChrome(); public MenuTab CurrentTab { get; private set; } @@ -95,7 +91,7 @@ public class MenuPanel : MonoBehaviour { if (_tabSurfaceDocument != null) { - Destroy(_tabSurfaceDocument.gameObject); + AetheriaEveUnitySurfaceHost.DestroyDocument(_tabSurfaceDocument); _tabSurfaceDocument = null; } } @@ -108,61 +104,34 @@ public class MenuPanel : MonoBehaviour if (TabButtons != null) TabButtons.gameObject.SetActive(false); - var document = ResolveTabSurfaceDocument(); - document.gameObject.SetActive(true); - - var root = document.rootVisualElement; - 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 = Align.Center; - root.style.justifyContent = Justify.FlexStart; - root.style.paddingTop = 16; - root.pickingMode = PickingMode.Ignore; - - var shell = new VisualElement(); - shell.style.minWidth = 420; - shell.style.maxWidth = 760; - shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.94f); - shell.style.borderTopLeftRadius = 8; - shell.style.borderTopRightRadius = 8; - shell.style.borderBottomLeftRadius = 8; - shell.style.borderBottomRightRadius = 8; - shell.style.paddingLeft = 16; - shell.style.paddingRight = 16; - shell.style.paddingTop = 12; - shell.style.paddingBottom = 12; - shell.style.borderLeftWidth = 1; - shell.style.borderRightWidth = 1; - shell.style.borderTopWidth = 1; - shell.style.borderBottomWidth = 1; - shell.style.borderLeftColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderRightColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderTopColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderBottomColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.pickingMode = PickingMode.Position; - root.Add(shell); - - var lowerer = new EveUiToolkitSurfaceLowerer(); - shell.Add(lowerer.Lower(BuildTabSurfaceDefinition(), HandleTabSurfaceCommand)); + _tabSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _tabSurfaceDocument, + "Aetheria Runtime Menu Tabs Surface", + AetheriaRuntimeMenuTabsSurfaceBuilder.Build(ProjectTabSurfaceState()), + HandleTabSurfaceCommand, + _tabSurfaceChrome); } private void HandleTabSurfaceCommand(EveSurfaceCommandRequest request) { + if (!AetheriaRuntimeMenuTabsSurfaceCommands.TryRead(request, out var command)) + { + Debug.LogWarning($"Unknown runtime menu tab command: {request?.Command}"); + return; + } + foreach (var tab in _tabs.Keys) { - if (string.Equals(request.Command, GetTabCommand(tab), StringComparison.Ordinal)) + if (command.Kind == AetheriaRuntimeMenuTabCommandKind.SelectTab && + string.Equals(command.TabKey, TabKey(tab), StringComparison.Ordinal)) { ShowTab(tab); return; } } - Debug.LogWarning($"Unknown runtime menu tab command: {request.Command}"); + Debug.LogWarning($"Unknown runtime menu tab command: {request?.Command}"); } private void HideTabSurface() @@ -170,68 +139,21 @@ public class MenuPanel : MonoBehaviour if (_tabSurfaceDocument == null) return; - _tabSurfaceDocument.rootVisualElement.Clear(); - _tabSurfaceDocument.gameObject.SetActive(false); + AetheriaEveUnitySurfaceHost.Hide(_tabSurfaceDocument); } - private UIDocument ResolveTabSurfaceDocument() - { - if (_tabSurfaceDocument != null) - return _tabSurfaceDocument; - - var host = new GameObject("Aetheria Runtime Menu Tabs Surface"); - host.transform.SetParent(transform, false); - var document = host.AddComponent(); - document.sortingOrder = 1000; - host.SetActive(false); - _tabSurfaceDocument = document; - return document; - } - - private EveSurfaceDocument BuildTabSurfaceDefinition() + private AetheriaRuntimeMenuTabsSurfaceState ProjectTabSurfaceState() { var visibleTabs = ResolveVisibleTabs(); - var commands = visibleTabs - .Select(tabBinding => new EveCommandTemplate( - GetTabCommand(tabBinding.Tab), - GetTabLabel(tabBinding), - "unity-uitoolkit")) - .ToArray(); - - var buttons = visibleTabs - .Select(tabBinding => - { - var label = GetTabLabel(tabBinding); - if (tabBinding.Tab == CurrentTab) - label = $"{label} *"; - - return Button( - $"{MenuTabsSurfaceId}.{tabBinding.Tab.ToString().ToLowerInvariant()}", - label, - GetTabCommand(tabBinding.Tab)); - }) - .ToArray(); - - return new EveSurfaceDocument( - MenuTabsSurfaceType, - MenuTabsSurfaceSchema, - MenuTabsSurfaceProviderId, - MenuTabsSurfaceProviderKind, - "Runtime Menu Tabs", - version: 1, - DateTime.UtcNow.ToString("O"), - new EveSurfaceTree( - MenuTabsSurfaceId, - Node( - $"{MenuTabsSurfaceId}.root", - "surface", - Array.Empty<(string Key, string Value)>(), - Text( - $"{MenuTabsSurfaceId}.current", - $"Current: {CurrentTab}"), - ButtonRow($"{MenuTabsSurfaceId}.tabs", buttons)), - Array.Empty()), - commands); + return new AetheriaRuntimeMenuTabsSurfaceState( + TabKey(CurrentTab), + visibleTabs + .Select(tabBinding => new AetheriaRuntimeMenuTabSurfaceEntry( + TabKey(tabBinding.Tab), + GetTabLabel(tabBinding), + tabBinding.Tab == CurrentTab)) + .ToArray(), + DateTime.UtcNow.ToString("O")); } private MenuTabBinding[] ResolveVisibleTabs() @@ -250,42 +172,12 @@ public class MenuPanel : MonoBehaviour : tabBinding.Label; } - private static string GetTabCommand(MenuTab tab) + private static string TabKey(MenuTab tab) { - return $"aetheria.runtime_menu.tab.{tab.ToString().ToLowerInvariant()}"; + return tab.ToString().ToLowerInvariant(); } - private static EveSurfaceComponent Text(string id, string value) - { - return Node(id, "text", new[] { ("value", value) }); - } - - private static EveSurfaceComponent Button(string id, string label, string command) - { - return Node(id, "control.button", new[] { ("label", label), ("command", command) }); - } - - private static EveSurfaceComponent ButtonRow( - string id, - params EveSurfaceComponent[] children) - { - return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); - } - - private static EveSurfaceComponent Node( - string id, - string kind, - IEnumerable<(string Key, string Value)> props, - params EveSurfaceComponent[] children) - { - return new EveSurfaceComponent( - id, - kind, - props.ToDictionary(prop => prop.Key, prop => prop.Value, StringComparer.Ordinal), - children ?? Array.Empty()); - } - - // void Start() +// void Start() // { // ShowTab(MenuTab.Inventory); // } diff --git a/Assets/Scripts/UI/Menu/SectorMap.cs b/Assets/Scripts/UI/Menu/SectorMap.cs index a0134bb70..ca92be71f 100644 --- a/Assets/Scripts/UI/Menu/SectorMap.cs +++ b/Assets/Scripts/UI/Menu/SectorMap.cs @@ -100,7 +100,7 @@ public class SectorMap : MonoBehaviour if (!_revealedZones.Contains(zone)) { var zoneInstance = ZonePrototype.Instantiate(); - zoneInstance.Clickable.OnClick += (clickableCollider, data, ray, hit) => ZoneClicked.OnNext(zone); + zoneInstance.Clickable.OnClick += (_, _, _, _) => ZoneClicked.OnNext(zone); if(zone == _currentPlayerLocation) MarkPlayerLocation(zoneInstance); _zoneInstances[zone] = zoneInstance; @@ -111,7 +111,7 @@ public class SectorMap : MonoBehaviour { zoneInstance.Primary.sharedMaterial = _factionMaterials[zone.Owner].primaryMaterial; zoneInstance.Secondary.sharedMaterial = _factionMaterials[zone.Owner].secondaryMaterial; - if(zone == ActionGameManager.CurrentGalaxy.HomeZones[zone.Owner]) + if(zone == ActionGameManager.ObservedGalaxy.HomeZones[zone.Owner]) zoneInstance.Secondary.transform.localScale = Vector3.one * 4; } else @@ -119,7 +119,7 @@ public class SectorMap : MonoBehaviour zoneInstance.Secondary.gameObject.SetActive(false); } - var isOnCriticalPath = ActionGameManager.CurrentGalaxy.ExitPath?.Contains(zone) ?? false; + var isOnCriticalPath = ActionGameManager.ObservedGalaxy.ExitPath?.Contains(zone) ?? false; foreach (var adjacentZone in zone.AdjacentZones) { // If the adjacent zone is in the set already revealed, then show the link @@ -128,7 +128,7 @@ public class SectorMap : MonoBehaviour _revealedLinks.Contains((adjacentZone, zone)))) { var link = LinkPrototype.Instantiate(); - var critical = isOnCriticalPath && (ActionGameManager.CurrentGalaxy.ExitPath?.Contains(adjacentZone) ?? false); + var critical = isOnCriticalPath && (ActionGameManager.ObservedGalaxy.ExitPath?.Contains(adjacentZone) ?? false); linksToReveal.Add((zone.Position, adjacentZone.Position, link, critical)); if (zone.Owner != null && zone.Owner.HasSameKey(adjacentZone.Owner)) link.GetComponent().sharedMaterial = _factionMaterials[zone.Owner].linkMaterial; @@ -151,7 +151,7 @@ public class SectorMap : MonoBehaviour zoneTextTransform.pivot = new Vector2(sign(linkDirection.x)/2+.5f,sign(linkDirection.y)/2+.5f); zoneTextTransform.localPosition = new Vector3(-linkDirection.x * LabelDistance, -linkDirection.y * LabelDistance, -1); - if (zone == ActionGameManager.CurrentGalaxy.Entrance) + if (zone == ActionGameManager.ObservedGalaxy.Entrance) { var iconInstance = IconPrototype.Instantiate(); iconInstance.material.mainTexture = EntranceIcon; @@ -161,7 +161,7 @@ public class SectorMap : MonoBehaviour iconTransform.localPosition = new Vector3(-linkDirection.x * IconDistance, -linkDirection.y * IconDistance); } - if (zone == ActionGameManager.CurrentGalaxy.Exit) + if (zone == ActionGameManager.ObservedGalaxy.Exit) { var iconInstance = IconPrototype.Instantiate(); iconInstance.material.mainTexture = ExitIcon; @@ -171,8 +171,8 @@ public class SectorMap : MonoBehaviour iconTransform.localPosition = new Vector3(-linkDirection.x * IconDistance, -linkDirection.y * IconDistance); } - var homeMega = ActionGameManager.CurrentGalaxy.HomeZones.Keys - .FirstOrDefault(m => ActionGameManager.CurrentGalaxy.HomeZones[m] == zone); + var homeMega = ActionGameManager.ObservedGalaxy.HomeZones.Keys + .FirstOrDefault(m => ActionGameManager.ObservedGalaxy.HomeZones[m] == zone); if (homeMega != null) { var backgroundInstance = IconBackgroundPrototype.Instantiate(); @@ -195,8 +195,8 @@ public class SectorMap : MonoBehaviour iconTransform.localPosition = new Vector3(-linkDirection.x * IconDistance, -linkDirection.y * IconDistance); } - var bossMega = ActionGameManager.CurrentGalaxy.BossZones.Keys - .FirstOrDefault(m => ActionGameManager.CurrentGalaxy.BossZones[m] == zone); + var bossMega = ActionGameManager.ObservedGalaxy.BossZones.Keys + .FirstOrDefault(m => ActionGameManager.ObservedGalaxy.BossZones[m] == zone); if (bossMega != null) { var backgroundInstance = IconBackgroundPrototype.Instantiate(); @@ -268,12 +268,12 @@ public class SectorMap : MonoBehaviour public void Start() { - if (ActionGameManager.CurrentGalaxy == null) + if (ActionGameManager.ObservedGalaxy == null) { gameObject.SetActive(false); return; } - foreach (var mega in ActionGameManager.CurrentGalaxy.Factions) + foreach (var mega in ActionGameManager.ObservedGalaxy.Factions) { var influenceTexture = new RenderTexture(1024, 1024, 0, RenderTextureFormat.RHalf); var influenceRenderer = InfluenceRendererPrototype.Instantiate(); @@ -300,22 +300,22 @@ public class SectorMap : MonoBehaviour legendElement.Label.text = mega.ShortName; } - SectorRenderer.material.SetFloat("CloudAmplitude", ActionGameManager.CurrentGalaxy.Background.CloudAmplitude); - SectorRenderer.material.SetFloat("CloudExponent", ActionGameManager.CurrentGalaxy.Background.CloudExponent); - SectorRenderer.material.SetFloat("NoisePosition", ActionGameManager.CurrentGalaxy.Background.NoisePosition); - SectorRenderer.material.SetFloat("NoiseAmplitude", ActionGameManager.CurrentGalaxy.Background.NoiseAmplitude); - SectorRenderer.material.SetFloat("NoiseOffset", ActionGameManager.CurrentGalaxy.Background.NoiseOffset); - SectorRenderer.material.SetFloat("NoiseGain", ActionGameManager.CurrentGalaxy.Background.NoiseGain); - SectorRenderer.material.SetFloat("NoiseLacunarity", ActionGameManager.CurrentGalaxy.Background.NoiseLacunarity); - SectorRenderer.material.SetFloat("NoiseFrequency", ActionGameManager.CurrentGalaxy.Background.NoiseFrequency); + SectorRenderer.material.SetFloat("CloudAmplitude", ActionGameManager.ObservedGalaxy.Background.CloudAmplitude); + SectorRenderer.material.SetFloat("CloudExponent", ActionGameManager.ObservedGalaxy.Background.CloudExponent); + SectorRenderer.material.SetFloat("NoisePosition", ActionGameManager.ObservedGalaxy.Background.NoisePosition); + SectorRenderer.material.SetFloat("NoiseAmplitude", ActionGameManager.ObservedGalaxy.Background.NoiseAmplitude); + SectorRenderer.material.SetFloat("NoiseOffset", ActionGameManager.ObservedGalaxy.Background.NoiseOffset); + SectorRenderer.material.SetFloat("NoiseGain", ActionGameManager.ObservedGalaxy.Background.NoiseGain); + SectorRenderer.material.SetFloat("NoiseLacunarity", ActionGameManager.ObservedGalaxy.Background.NoiseLacunarity); + SectorRenderer.material.SetFloat("NoiseFrequency", ActionGameManager.ObservedGalaxy.Background.NoiseFrequency); } private void RenderInfluence() { // Render Influence Textures - foreach (var mega in ActionGameManager.CurrentGalaxy.Factions) + foreach (var mega in ActionGameManager.ObservedGalaxy.Factions) { - foreach (var zone in ActionGameManager.CurrentGalaxy.Zones) + foreach (var zone in ActionGameManager.ObservedGalaxy.Zones) { if(_zoneInstances.ContainsKey(zone)) { diff --git a/Assets/Scripts/UI/Menu/SectorRenderer.cs b/Assets/Scripts/UI/Menu/SectorRenderer.cs index b769c03ad..468baaa1a 100644 --- a/Assets/Scripts/UI/Menu/SectorRenderer.cs +++ b/Assets/Scripts/UI/Menu/SectorRenderer.cs @@ -2,8 +2,9 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using GameCult.Aetheria.EveRuntime; +using GameCult.Aetheria.State.Unity; using GameCult.Eve.Surface; -using GameCult.Eve.UnityUIToolkit; using UniRx; using UnityEngine; using UnityEngine.EventSystems; @@ -15,13 +16,6 @@ using float2 = Unity.Mathematics.float2; public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IScrollHandler { - private const string ZoneDetailsSurfaceType = "surface-state"; - private const string ZoneDetailsSurfaceSchema = "gamecult.eve.surface.v1"; - private const string ZoneDetailsSurfaceProviderId = "aetheria"; - private const string ZoneDetailsSurfaceProviderKind = "sector.map"; - private const string ZoneDetailsSurfaceId = "aetheria.sector_map.zone_details"; - private const string CloseZoneDetailsCommand = "aetheria.sector_map.zone_details.close"; - public ClickRaycaster Raycaster; public Canvas Canvas; public SectorMap Map; @@ -51,6 +45,19 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS private float _sectorBackgroundDepth; private float _sectorCameraDepth; private UIDocument _zoneDetailsSurfaceDocument; + private readonly AetheriaEveUnitySurfaceChrome _zoneDetailsSurfaceChrome = new AetheriaEveUnitySurfaceChrome + { + RootAlignItems = Align.FlexEnd, + RootPaddingTop = 24f, + RootPaddingRight = 24f, + Width = 360f, + MinWidth = 0f, + MaxWidth = 420f, + PaddingLeft = 18f, + PaddingRight = 18f, + PaddingTop = 18f, + PaddingBottom = 18f + }; private float2 _position = float2(0.5f); private float _viewSize = .5f; @@ -86,59 +93,30 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS private void RenderZoneDetailsSurface(GalaxyZone zone) { - var document = ResolveZoneDetailsSurfaceDocument(); - document.gameObject.SetActive(true); - - var root = document.rootVisualElement; - 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 = Align.FlexEnd; - root.style.justifyContent = Justify.FlexStart; - root.style.paddingTop = 24; - root.style.paddingRight = 24; - root.pickingMode = PickingMode.Ignore; - - var shell = new VisualElement(); - shell.style.width = 360; - shell.style.maxWidth = 420; - shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.94f); - shell.style.borderTopLeftRadius = 8; - shell.style.borderTopRightRadius = 8; - shell.style.borderBottomLeftRadius = 8; - shell.style.borderBottomRightRadius = 8; - shell.style.paddingLeft = 18; - shell.style.paddingRight = 18; - shell.style.paddingTop = 18; - shell.style.paddingBottom = 18; - shell.style.borderLeftWidth = 1; - shell.style.borderRightWidth = 1; - shell.style.borderTopWidth = 1; - shell.style.borderBottomWidth = 1; - shell.style.borderLeftColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderRightColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderTopColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderBottomColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.pickingMode = PickingMode.Position; - root.Add(shell); - - var lowerer = new EveUiToolkitSurfaceLowerer(); - shell.Add(lowerer.Lower(BuildZoneDetailsSurfaceDefinition(zone), HandleZoneDetailsSurfaceCommand)); + _zoneDetailsSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _zoneDetailsSurfaceDocument, + "Aetheria Sector Zone Details Surface", + AetheriaRuntimeZoneDetailsSurfaceBuilder.Build(ProjectZoneDetailsSurfaceState(zone)), + HandleZoneDetailsSurfaceCommand, + _zoneDetailsSurfaceChrome); } private void HandleZoneDetailsSurfaceCommand(EveSurfaceCommandRequest request) { - if (string.Equals(request.Command, CloseZoneDetailsCommand, StringComparison.Ordinal)) + if (!AetheriaRuntimeZoneDetailsSurfaceCommands.TryRead(request, out var command)) + { + Debug.LogWarning($"Unknown sector-map zone details command: {request?.Command}"); + return; + } + + if (command.Kind == AetheriaRuntimeZoneDetailsCommandKind.Close) { HideZoneDetailsSurface(); return; } - Debug.LogWarning($"Unknown sector-map zone details command: {request.Command}"); + Debug.LogWarning($"Unknown sector-map zone details command: {request?.Command}"); } private void HideZoneDetailsSurface() @@ -146,27 +124,12 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS if (_zoneDetailsSurfaceDocument == null) return; - _zoneDetailsSurfaceDocument.rootVisualElement.Clear(); - _zoneDetailsSurfaceDocument.gameObject.SetActive(false); + AetheriaEveUnitySurfaceHost.Hide(_zoneDetailsSurfaceDocument); } - private UIDocument ResolveZoneDetailsSurfaceDocument() + private AetheriaRuntimeZoneDetailsSurfaceState ProjectZoneDetailsSurfaceState(GalaxyZone zone) { - if (_zoneDetailsSurfaceDocument != null) - return _zoneDetailsSurfaceDocument; - - var host = new GameObject("Aetheria Sector Zone Details Surface"); - host.transform.SetParent(transform, false); - var document = host.AddComponent(); - document.sortingOrder = 1000; - host.SetActive(false); - _zoneDetailsSurfaceDocument = document; - return document; - } - - private EveSurfaceDocument BuildZoneDetailsSurfaceDefinition(GalaxyZone zone) - { - var density = saturate(ActionGameManager.CurrentGalaxy.Background.CloudDensity(zone.Position) / 2); + var density = saturate(ActionGameManager.ObservedGalaxy.Background.CloudDensity(zone.Position) / 2); var radius = GameManager.Settings.ZoneSettings.ZoneRadius.Evaluate(density); var mass = GameManager.Settings.ZoneSettings.ZoneMass.Evaluate(density); var otherFactions = zone.Factions @@ -175,135 +138,44 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS .Where(name => !string.IsNullOrWhiteSpace(name)) .ToArray(); - var children = new List - { - Card( - $"{ZoneDetailsSurfaceId}.card", - zone.Name, - Metric($"{ZoneDetailsSurfaceId}.owner", "Owner", zone.Owner?.Name ?? "None"), - Metric($"{ZoneDetailsSurfaceId}.mass", "Mass", ActionGameManager.RuntimePlayerSettings.Format(mass)), - Metric($"{ZoneDetailsSurfaceId}.radius", "Radius", ActionGameManager.RuntimePlayerSettings.Format(radius))) - }; - - if (otherFactions.Length > 0) - { - children.Add(Text( - $"{ZoneDetailsSurfaceId}.factions", - $"Factions Present: {string.Join(", ", otherFactions)}")); - } - if (zone.Contents == null) { - children.Add(Text( - $"{ZoneDetailsSurfaceId}.unvisited", - "Has not been visited.")); - } - else - { - var runtimeZone = zone.Contents; - children.Add(Card( - $"{ZoneDetailsSurfaceId}.contents", - "Contents", - Metric( - $"{ZoneDetailsSurfaceId}.planets", - "Planets", - runtimeZone.PlanetInstances.Values.Count(body => !(body is GasGiant)).ToString()), - Metric( - $"{ZoneDetailsSurfaceId}.belts", - "Asteroid Belts", - runtimeZone.AsteroidBelts.Count.ToString()), - Metric( - $"{ZoneDetailsSurfaceId}.giants", - "Gas Giants", - runtimeZone.PlanetInstances.Values.Count(body => body is GasGiant && !(body is Sun)).ToString()), - Metric( - $"{ZoneDetailsSurfaceId}.stars", - "Stars", - runtimeZone.PlanetInstances.Values.Count(body => body is Sun).ToString()), - Metric( - $"{ZoneDetailsSurfaceId}.stations", - "Stations", - runtimeZone.Entities.Count(entity => HasHullType(entity, HullType.Station)).ToString()), - Metric( - $"{ZoneDetailsSurfaceId}.turrets", - "Turrets", - runtimeZone.Entities.Count(entity => HasHullType(entity, HullType.Turret)).ToString()), - Metric( - $"{ZoneDetailsSurfaceId}.ships", - "Ships", - runtimeZone.Entities.Count(entity => HasHullType(entity, HullType.Ship)).ToString()))); + return new AetheriaRuntimeZoneDetailsSurfaceState( + zone.Name, + zone.Owner?.Name ?? "None", + ActionGameManager.RuntimePlayerSettings.Format(mass), + ActionGameManager.RuntimePlayerSettings.Format(radius), + otherFactions, + hasContents: false, + planets: "", + asteroidBelts: "", + gasGiants: "", + stars: "", + stations: "", + turrets: "", + ships: "", + updatedAtUtc: DateTime.UtcNow.ToString("O")); } - children.Add(ButtonRow( - $"{ZoneDetailsSurfaceId}.actions", - Button($"{ZoneDetailsSurfaceId}.close", "Close", CloseZoneDetailsCommand))); - - return new EveSurfaceDocument( - ZoneDetailsSurfaceType, - ZoneDetailsSurfaceSchema, - ZoneDetailsSurfaceProviderId, - ZoneDetailsSurfaceProviderKind, + var runtimeZone = zone.Contents; + return new AetheriaRuntimeZoneDetailsSurfaceState( zone.Name, - version: 1, - DateTime.UtcNow.ToString("O"), - new EveSurfaceTree( - ZoneDetailsSurfaceId, - Node( - $"{ZoneDetailsSurfaceId}.root", - "surface", - Array.Empty<(string Key, string Value)>(), - children.ToArray()), - Array.Empty()), - new[] - { - new EveCommandTemplate(CloseZoneDetailsCommand, "Close", "unity-uitoolkit") - }); + zone.Owner?.Name ?? "None", + ActionGameManager.RuntimePlayerSettings.Format(mass), + ActionGameManager.RuntimePlayerSettings.Format(radius), + otherFactions, + hasContents: true, + planets: runtimeZone.PlanetInstances.Values.Count(body => !(body is GasGiant)).ToString(), + asteroidBelts: runtimeZone.AsteroidBelts.Count.ToString(), + gasGiants: runtimeZone.PlanetInstances.Values.Count(body => body is GasGiant && !(body is Sun)).ToString(), + stars: runtimeZone.PlanetInstances.Values.Count(body => body is Sun).ToString(), + stations: runtimeZone.Entities.Count(entity => HasHullType(entity, HullType.Station)).ToString(), + turrets: runtimeZone.Entities.Count(entity => HasHullType(entity, HullType.Turret)).ToString(), + ships: runtimeZone.Entities.Count(entity => HasHullType(entity, HullType.Ship)).ToString(), + updatedAtUtc: DateTime.UtcNow.ToString("O")); } - private static EveSurfaceComponent Card( - string id, - string title, - params EveSurfaceComponent[] children) - { - return Node(id, "card", new[] { ("title", title) }, children); - } - - private static EveSurfaceComponent Metric(string id, string label, string value) - { - return Node(id, "metric", new[] { ("label", label), ("value", value) }); - } - - private static EveSurfaceComponent Text(string id, string value) - { - return Node(id, "text", new[] { ("value", value) }); - } - - private static EveSurfaceComponent Button(string id, string label, string command) - { - return Node(id, "control.button", new[] { ("label", label), ("command", command) }); - } - - private static EveSurfaceComponent ButtonRow( - string id, - params EveSurfaceComponent[] children) - { - return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); - } - - private static EveSurfaceComponent Node( - string id, - string kind, - IEnumerable<(string Key, string Value)> props, - params EveSurfaceComponent[] children) - { - return new EveSurfaceComponent( - id, - kind, - props.ToDictionary(prop => prop.Key, prop => prop.Value, StringComparer.Ordinal), - children ?? Array.Empty()); - } - - // private IEnumerator AnimatePath() +// private IEnumerator AnimatePath() // { // var pathZones = ActionGameManager.CurrentSector.ExitPath; // LegendPanel.SetActive(false); @@ -358,7 +230,7 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS { if (_zoneDetailsSurfaceDocument != null) { - Destroy(_zoneDetailsSurfaceDocument.gameObject); + AetheriaEveUnitySurfaceHost.DestroyDocument(_zoneDetailsSurfaceDocument); _zoneDetailsSurfaceDocument = null; } } diff --git a/Assets/Scripts/UI/Menu/TradeMenu.cs b/Assets/Scripts/UI/Menu/TradeMenu.cs index 6cb9d7fc8..d0b76ddd8 100644 --- a/Assets/Scripts/UI/Menu/TradeMenu.cs +++ b/Assets/Scripts/UI/Menu/TradeMenu.cs @@ -2,9 +2,9 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Unity; using GameCult.Eve.Surface; -using GameCult.Eve.UnityUIToolkit; using TMPro; using UnityEngine; using UnityEngine.UI; @@ -14,19 +14,6 @@ using static Unity.Mathematics.math; public class TradeMenu : MonoBehaviour { - private const string CargoSelectorSurfaceType = "surface-state"; - private const string CargoSelectorSurfaceSchema = "gamecult.eve.surface.v1"; - private const string CargoSelectorSurfaceProviderId = "aetheria"; - private const string CargoSelectorSurfaceProviderKind = "trade.menu"; - private const string CargoSelectorSurfaceId = "aetheria.trade.target_cargo_selector"; - private const string CloseCargoSelectorCommand = "aetheria.trade.target_cargo_selector.close"; - private const string FilterSurfaceId = "aetheria.trade.filter_selector"; - private const string CloseFilterSurfaceCommand = "aetheria.trade.filter_selector.close"; - private const string RowActionSurfaceId = "aetheria.trade.row_actions"; - private const string CloseRowActionSurfaceCommand = "aetheria.trade.row_actions.close"; - private const string TradeItemSurfaceId = "aetheria.trade.item_details"; - private const string CloseTradeItemDetailsCommand = "aetheria.trade.item_details.close"; - public ActionGameManager GameManager; public ConfirmationDialog Dialog; public UnityEngine.UI.Button NewFilterButton; @@ -51,6 +38,10 @@ public class TradeMenu : MonoBehaviour private UIDocument _filterSurfaceDocument; private UIDocument _rowActionSurfaceDocument; private UIDocument _tradeItemSurfaceDocument; + private readonly AetheriaEveUnitySurfaceChrome _cargoSelectorSurfaceChrome = PanelChrome(360f, 420f, Align.FlexEnd); + private readonly AetheriaEveUnitySurfaceChrome _filterSurfaceChrome = PanelChrome(420f, 520f, Align.FlexStart); + private readonly AetheriaEveUnitySurfaceChrome _rowActionSurfaceChrome = PanelChrome(320f, 360f, Align.FlexStart); + private readonly AetheriaEveUnitySurfaceChrome _tradeItemSurfaceChrome = PanelChrome(420f, 520f, Align.FlexStart); private string _rowActionTitle = "Trade Actions"; public EquippedCargoBay Inventory { get; set; } @@ -428,15 +419,9 @@ public class TradeMenu : MonoBehaviour return; } - if (price > GameManager.Credits) + if (!GameManager.RequestTradePurchase(Inventory, _targetCargo, item, price, isShipHull)) { - ShowUnableToBuy("Insufficient Credits!"); - return; - } - - if (!GameManager.CommitTradePurchase(Inventory, _targetCargo, item, price, isShipHull)) - { - ShowUnableToBuy(isShipHull ? "Unable to create ship!" : "Insufficient Cargo Space!"); + ShowUnableToBuy("Purchase request rejected!"); return; } @@ -454,16 +439,10 @@ public class TradeMenu : MonoBehaviour var price = typedItem.Price; var clampedQuantity = min(quantity, simpleCommodity.Quantity); - var totalPrice = (long)clampedQuantity * price; - if (totalPrice > GameManager.Credits) - { - ShowUnableToBuy("Insufficient Credits!"); - return; - } - if (!GameManager.CommitTradePurchase(Inventory, _targetCargo, simpleCommodity, clampedQuantity, price)) + if (!GameManager.RequestTradePurchase(Inventory, _targetCargo, simpleCommodity, clampedQuantity, price)) { - ShowUnableToBuy("Insufficient Cargo Space!"); + ShowUnableToBuy("Purchase request rejected!"); return; } @@ -491,7 +470,7 @@ public class TradeMenu : MonoBehaviour Dialog.AddField( "Quantity", () => quantity, - q => quantity = min(min(q, GameManager.Credits / price), simpleCommodity.Quantity)); + q => quantity = min(q, simpleCommodity.Quantity)); Dialog.Show(() => { Buy(simpleCommodity, quantity); @@ -505,59 +484,31 @@ public class TradeMenu : MonoBehaviour if (item == null) return; - var document = ResolveTradeItemDetailsSurfaceDocument(); - document.gameObject.SetActive(true); - - var root = document.rootVisualElement; - 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 = Align.FlexStart; - root.style.justifyContent = Justify.FlexStart; - root.style.paddingTop = 16; - root.style.paddingLeft = 16; - root.pickingMode = PickingMode.Ignore; - - var shell = new VisualElement(); - shell.style.width = 420; - shell.style.maxWidth = 520; - shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.94f); - shell.style.borderTopLeftRadius = 8; - shell.style.borderTopRightRadius = 8; - shell.style.borderBottomLeftRadius = 8; - shell.style.borderBottomRightRadius = 8; - shell.style.paddingLeft = 18; - shell.style.paddingRight = 18; - shell.style.paddingTop = 18; - shell.style.paddingBottom = 18; - shell.style.borderLeftWidth = 1; - shell.style.borderRightWidth = 1; - shell.style.borderTopWidth = 1; - shell.style.borderBottomWidth = 1; - shell.style.borderLeftColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderRightColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderTopColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderBottomColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.pickingMode = PickingMode.Position; - root.Add(shell); - - var lowerer = new EveUiToolkitSurfaceLowerer(); - shell.Add(lowerer.Lower(BuildTradeItemDetailsSurfaceDefinition(item), HandleTradeItemDetailsSurfaceCommand)); + _tradeItemSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _tradeItemSurfaceDocument, + "Aetheria Trade Item Details Surface", + AetheriaRuntimeTradeItemDetailsSurfaceBuilder.Build(ProjectTradeItemDetailsSurfaceState(item)), + HandleTradeItemDetailsSurfaceCommand, + _tradeItemSurfaceChrome, + sortingOrder: 1003); } private void HandleTradeItemDetailsSurfaceCommand(EveSurfaceCommandRequest request) { - if (string.Equals(request.Command, CloseTradeItemDetailsCommand, StringComparison.Ordinal)) + if (!AetheriaRuntimeTradeItemDetailsSurfaceCommands.TryRead(request, out var command)) + { + Debug.LogWarning($"Unknown trade item details command: {request?.Command}"); + return; + } + + if (command.Kind == AetheriaRuntimeTradeItemDetailsCommandKind.Close) { HideTradeItemDetailsSurface(); return; } - Debug.LogWarning($"Unknown trade item details command: {request.Command}"); + Debug.LogWarning($"Unknown trade item details command: {request?.Command}"); } private void HideTradeItemDetailsSurface() @@ -565,111 +516,54 @@ public class TradeMenu : MonoBehaviour if (_tradeItemSurfaceDocument == null) return; - _tradeItemSurfaceDocument.rootVisualElement.Clear(); - _tradeItemSurfaceDocument.gameObject.SetActive(false); + AetheriaEveUnitySurfaceHost.Hide(_tradeItemSurfaceDocument); } - private UIDocument ResolveTradeItemDetailsSurfaceDocument() + private AetheriaRuntimeTradeItemDetailsSurfaceState ProjectTradeItemDetailsSurfaceState(AetheriaRuntimeCatalogItem item) { - if (_tradeItemSurfaceDocument != null) - return _tradeItemSurfaceDocument; - - var host = new GameObject("Aetheria Trade Item Details Surface"); - host.transform.SetParent(transform, false); - var document = host.AddComponent(); - document.sortingOrder = 1003; - host.SetActive(false); - _tradeItemSurfaceDocument = document; - return document; - } - - private EveSurfaceDocument BuildTradeItemDetailsSurfaceDefinition(AetheriaRuntimeCatalogItem item) - { - var children = new List - { - Card( - $"{TradeItemSurfaceId}.summary", - item.Name, - Text($"{TradeItemSurfaceId}.description", item.Description ?? "No typed item description is available."), - Text( - $"{TradeItemSurfaceId}.note", - "Trade still owns purchase and row population. This surface replaces the old PropertiesPanel item-inspection shell."), - Metric( - $"{TradeItemSurfaceId}.manufacturer", - "Manufacturer", - ActionGameManager.RuntimeCatalog?.GetManufacturer(item)?.Name ?? "GameCult"), - Metric( - $"{TradeItemSurfaceId}.mass", - "Mass", - ActionGameManager.RuntimePlayerSettings.Format((float)item.Mass)), - Metric( - $"{TradeItemSurfaceId}.price", - "Price", - item.Price.ToString("N0"))) - }; - + var durability = ""; + var thermalRange = ""; + var behaviorSections = Array.Empty(); if (!string.IsNullOrWhiteSpace(item.HardpointType)) { - children.Add(Card( - $"{TradeItemSurfaceId}.durability.card", - "Durability", - Metric( - $"{TradeItemSurfaceId}.durability", - "Max Durability", - ActionGameManager.RuntimePlayerSettings.Format((float)item.Durability)), - Metric( - $"{TradeItemSurfaceId}.temperature_range", - "Thermal Range", - FormatTemperatureRange(item)))); - - foreach (var behaviorCard in BuildTradeItemBehaviorCards(item)) - { - children.Add(behaviorCard); - } + durability = ActionGameManager.RuntimePlayerSettings.Format((float)item.Durability); + thermalRange = FormatTemperatureRange(item); + behaviorSections = ProjectTradeItemBehaviorSections(item).ToArray(); } - children.Add(ButtonRow( - $"{TradeItemSurfaceId}.actions", - Button($"{TradeItemSurfaceId}.close", "Close", CloseTradeItemDetailsCommand))); - - return new EveSurfaceDocument( - CargoSelectorSurfaceType, - CargoSelectorSurfaceSchema, - CargoSelectorSurfaceProviderId, - CargoSelectorSurfaceProviderKind, - "Trade Item Details", - version: 1, - DateTime.UtcNow.ToString("O"), - new EveSurfaceTree( - TradeItemSurfaceId, - Node( - $"{TradeItemSurfaceId}.root", - "surface", - Array.Empty<(string Key, string Value)>(), - children.ToArray()), - Array.Empty()), - new[] - { - new EveCommandTemplate(CloseTradeItemDetailsCommand, "Close", "unity-uitoolkit") - }); + return new AetheriaRuntimeTradeItemDetailsSurfaceState( + item.Name, + item.Description ?? "", + ActionGameManager.RuntimeCatalog?.GetManufacturer(item)?.Name ?? "GameCult", + ActionGameManager.RuntimePlayerSettings.Format((float)item.Mass), + item.Price, + durability, + thermalRange, + behaviorSections, + DateTime.UtcNow.ToString("O")); } - private IEnumerable BuildTradeItemBehaviorCards(AetheriaRuntimeCatalogItem item) + private IEnumerable ProjectTradeItemBehaviorSections(AetheriaRuntimeCatalogItem item) { foreach (var behavior in item.BehaviorPayloads ?? Array.Empty()) { if (string.Equals(behavior.Kind, AetheriaRuntimeBehaviorKinds.StatModifier, StringComparison.Ordinal)) { - var statReference = ReadTypedStatReference(FindTypedBehaviorField(behavior, 1)?.Value); - var modifier = ReadTypedPerformanceStat(FindTypedBehaviorField(behavior, 2)?.Value); - var modifierType = ReadTypedEnum(FindTypedBehaviorField(behavior, 3)?.Value, StatModifierType.Constant); - yield return Card( - $"{TradeItemSurfaceId}.behavior.{behavior.Kind}.stat_modifier", + var statReference = AetheriaRuntimeBehaviorValueReader.ReadStatReference(FindTypedBehaviorField(behavior, 1)?.Value); + var modifier = AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(FindTypedBehaviorField(behavior, 2)?.Value); + var modifierType = AetheriaRuntimeBehaviorValueReader.ReadEnum( + FindTypedBehaviorField(behavior, 3)?.Value, + StatModifierType.Constant); + yield return new AetheriaRuntimeTradeItemSection( + $"{AetheriaRuntimeTradeItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.stat_modifier", "Stat Modifier", - Metric( - $"{TradeItemSurfaceId}.behavior.{behavior.Kind}.target", - $"{statReference.target.SplitCamelCase()}:{statReference.stat.SplitCamelCase()}", - $"{(modifierType == StatModifierType.Constant ? "+" : "x")}{ActionGameManager.RuntimePlayerSettings.Format(modifier.Min)}")); + new[] + { + new AetheriaRuntimeTradeItemMetric( + $"{AetheriaRuntimeTradeItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.target", + $"{statReference.Target.SplitCamelCase()}:{statReference.Stat.SplitCamelCase()}", + $"{(modifierType == StatModifierType.Constant ? "+" : "x")}{ActionGameManager.RuntimePlayerSettings.Format(modifier.Min)}") + }); continue; } @@ -678,21 +572,21 @@ public class TradeMenu : MonoBehaviour continue; var fields = metadata.DisplayFields - .Select(field => BuildTradeItemBehaviorMetric(behavior, field)) + .Select(field => ProjectTradeItemBehaviorMetric(behavior, field)) .Where(metric => metric != null) .ToArray(); if (fields.Length == 0) continue; - yield return Card( - $"{TradeItemSurfaceId}.behavior.{behavior.Kind}", + yield return new AetheriaRuntimeTradeItemSection( + $"{AetheriaRuntimeTradeItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}", behavior.Kind.FormatTypeName(), fields); } } - private EveSurfaceComponent BuildTradeItemBehaviorMetric( + private AetheriaRuntimeTradeItemMetric ProjectTradeItemBehaviorMetric( AetheriaRuntimeBehaviorPayload behavior, AetheriaRuntimeBehaviorFieldMetadata field) { @@ -713,14 +607,14 @@ public class TradeMenu : MonoBehaviour value = ((int)payloadField.Value.NumberValue).ToString(); break; case AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat: - value = ActionGameManager.RuntimePlayerSettings.Format(ReadTypedPerformanceStat(payloadField.Value).Min); + value = ActionGameManager.RuntimePlayerSettings.Format(AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(payloadField.Value).Min); break; default: return null; } - return Metric( - $"{TradeItemSurfaceId}.behavior.{behavior.Kind}.{field.Key}", + return new AetheriaRuntimeTradeItemMetric( + $"{AetheriaRuntimeTradeItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.{field.Key}", field.Name.SplitCamelCase(), value); } @@ -744,51 +638,6 @@ public class TradeMenu : MonoBehaviour : behavior.Fields.FirstOrDefault(field => field.Key == key.Value); } - private static PerformanceStat ReadTypedPerformanceStat(AetheriaRuntimeBehaviorValue value) - { - return new PerformanceStat - { - Min = ReadTypedChildNumber(value, 0), - Max = ReadTypedChildNumber(value, 1), - HeatExponentMultiplier = ReadTypedChildNumber(value, 2), - DurabilityExponentMultiplier = ReadTypedChildNumber(value, 3), - QualityExponent = ReadTypedChildNumber(value, 4) - }; - } - - private static (string target, string stat) ReadTypedStatReference(AetheriaRuntimeBehaviorValue value) - { - return ( - ReadTypedChildString(value, 1), - ReadTypedChildString(value, 2)); - } - - private static float ReadTypedChildNumber(AetheriaRuntimeBehaviorValue value, int index) - { - return value != null && value.Children.Count > index - ? (float)value.Children[index].NumberValue - : 0f; - } - - private static string ReadTypedChildString(AetheriaRuntimeBehaviorValue value, int index) - { - return value != null && value.Children.Count > index - ? value.Children[index].StringValue ?? "" - : ""; - } - - private static T ReadTypedEnum(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; - } - void Start() { FoldoutButton.onClick.AddListener(() => @@ -806,48 +655,14 @@ public class TradeMenu : MonoBehaviour { BuildFilterSurfaceCommands(); - var document = ResolveFilterSurfaceDocument(); - document.gameObject.SetActive(true); - - var root = document.rootVisualElement; - 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 = Align.FlexStart; - root.style.justifyContent = Justify.FlexStart; - root.style.paddingTop = 16; - root.style.paddingLeft = 16; - root.pickingMode = PickingMode.Ignore; - - var shell = new VisualElement(); - shell.style.width = 420; - shell.style.maxWidth = 520; - shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.94f); - shell.style.borderTopLeftRadius = 8; - shell.style.borderTopRightRadius = 8; - shell.style.borderBottomLeftRadius = 8; - shell.style.borderBottomRightRadius = 8; - shell.style.paddingLeft = 18; - shell.style.paddingRight = 18; - shell.style.paddingTop = 18; - shell.style.paddingBottom = 18; - shell.style.borderLeftWidth = 1; - shell.style.borderRightWidth = 1; - shell.style.borderTopWidth = 1; - shell.style.borderBottomWidth = 1; - shell.style.borderLeftColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderRightColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderTopColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderBottomColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.pickingMode = PickingMode.Position; - root.Add(shell); - - var lowerer = new EveUiToolkitSurfaceLowerer(); - shell.Add(lowerer.Lower(BuildFilterSurfaceDefinition(), HandleFilterSurfaceCommand)); + _filterSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _filterSurfaceDocument, + "Aetheria Trade Filter Surface", + AetheriaRuntimeTradeInteractionSurfaceBuilder.BuildFilter(ProjectTradeFilterSurfaceState()), + HandleFilterSurfaceCommand, + _filterSurfaceChrome, + sortingOrder: 1001); } private void BuildFilterSurfaceCommands() @@ -857,21 +672,21 @@ public class TradeMenu : MonoBehaviour foreach (var hardpointType in ((HardpointType[])Enum.GetValues(typeof(HardpointType))) .Where(type => _hardpointFilter.filter == null || type != _hardpointFilter.type)) { - var command = $"{FilterSurfaceId}.hardpoint.{hardpointType}"; + var command = AetheriaRuntimeTradeInteractionSurfaceBuilder.HardpointFilterCommand(hardpointType.ToString()); _filterSurfaceCommands[command] = () => ApplyHardpointFilter(hardpointType); } foreach (var commodityType in ((SimpleCommodityCategory[])Enum.GetValues(typeof(SimpleCommodityCategory))) .Where(type => _commodityFilter.filter == null || type != _commodityFilter.type)) { - var command = $"{FilterSurfaceId}.simple.{commodityType}"; + var command = AetheriaRuntimeTradeInteractionSurfaceBuilder.SimpleCommodityFilterCommand(commodityType.ToString()); _filterSurfaceCommands[command] = () => ApplySimpleCommodityFilter(commodityType); } foreach (var commodityType in ((CompoundCommodityCategory[])Enum.GetValues(typeof(CompoundCommodityCategory))) .Where(type => _compoundCommodityFilter.filter == null || type != _compoundCommodityFilter.type)) { - var command = $"{FilterSurfaceId}.compound.{commodityType}"; + var command = AetheriaRuntimeTradeInteractionSurfaceBuilder.CompoundCommodityFilterCommand(commodityType.ToString()); _filterSurfaceCommands[command] = () => ApplyCompoundCommodityFilter(commodityType); } @@ -879,37 +694,44 @@ public class TradeMenu : MonoBehaviour .Where(option => _behaviorFilters.All(filter => filter.Kind != option.Kind)) .OrderBy(option => option.Kind, StringComparer.Ordinal)) { - var command = $"{FilterSurfaceId}.behavior.{metadata.Kind}"; + var command = AetheriaRuntimeTradeInteractionSurfaceBuilder.BehaviorFilterCommand(metadata.Kind); _filterSurfaceCommands[command] = () => ApplyBehaviorFilter(metadata); } if (!MinimumSizeFilter.gameObject.activeSelf) { - _filterSurfaceCommands[$"{FilterSurfaceId}.size.minimum"] = EnableMinimumSizeFilter; + _filterSurfaceCommands[AetheriaRuntimeTradeInteractionSurfaceBuilder.MinimumSizeFilterCommand()] = EnableMinimumSizeFilter; } if (!MaximumSizeFilter.gameObject.activeSelf) { - _filterSurfaceCommands[$"{FilterSurfaceId}.size.maximum"] = EnableMaximumSizeFilter; + _filterSurfaceCommands[AetheriaRuntimeTradeInteractionSurfaceBuilder.MaximumSizeFilterCommand()] = EnableMaximumSizeFilter; } } private void HandleFilterSurfaceCommand(EveSurfaceCommandRequest request) { - if (string.Equals(request.Command, CloseFilterSurfaceCommand, StringComparison.Ordinal)) + if (!AetheriaRuntimeTradeInteractionSurfaceCommands.TryReadFilter(request, out var command)) + { + Debug.LogWarning($"Unknown trade filter command: {request?.Command}"); + return; + } + + if (command.Kind == AetheriaRuntimeTradeInteractionCommandKind.Close) { HideFilterSurface(); return; } - if (_filterSurfaceCommands.TryGetValue(request.Command, out var action)) + if (command.Kind == AetheriaRuntimeTradeInteractionCommandKind.Select && + _filterSurfaceCommands.TryGetValue(command.Command, out var action)) { action(); HideFilterSurface(); return; } - Debug.LogWarning($"Unknown trade filter command: {request.Command}"); + Debug.LogWarning($"Unknown trade filter command: {request?.Command}"); } private void HideFilterSurface() @@ -917,126 +739,52 @@ public class TradeMenu : MonoBehaviour if (_filterSurfaceDocument == null) return; - _filterSurfaceDocument.rootVisualElement.Clear(); - _filterSurfaceDocument.gameObject.SetActive(false); + AetheriaEveUnitySurfaceHost.Hide(_filterSurfaceDocument); } - private UIDocument ResolveFilterSurfaceDocument() + private AetheriaRuntimeTradeFilterSurfaceState ProjectTradeFilterSurfaceState() { - if (_filterSurfaceDocument != null) - return _filterSurfaceDocument; + var groups = new List(); + AddTradeFilterGroup(groups, "hardpoint", "Gear Type", command => command.Contains(".hardpoint.")); + AddTradeFilterGroup(groups, "simple", "Simple Commodity", command => command.Contains(".simple.")); + AddTradeFilterGroup(groups, "compound", "Compound Commodity", command => command.Contains(".compound.")); + AddTradeFilterGroup(groups, "behavior", "Item Behavior", command => command.Contains(".behavior.")); + AddTradeFilterGroup( + groups, + "size", + "Size", + command => command.Contains(".size."), + command => command.EndsWith(".minimum", StringComparison.Ordinal) ? "Minimum Size" : "Maximum Size"); - var host = new GameObject("Aetheria Trade Filter Surface"); - host.transform.SetParent(transform, false); - var document = host.AddComponent(); - document.sortingOrder = 1001; - host.SetActive(false); - _filterSurfaceDocument = document; - return document; + return new AetheriaRuntimeTradeFilterSurfaceState( + BuildFilterSummary(), + groups, + DateTime.UtcNow.ToString("O")); } - private EveSurfaceDocument BuildFilterSurfaceDefinition() + private void AddTradeFilterGroup( + List groups, + string key, + string title, + Func commandFilter, + Func labelFactory = null) { - var cards = new List - { - Card( - $"{FilterSurfaceId}.summary", - "Trade Filters", - Text( - $"{FilterSurfaceId}.note", - "Trade still owns filter state and population. This surface just removes the old context-menu shell."), - Text( - $"{FilterSurfaceId}.active", - BuildFilterSummary())) - }; - - var hardpointButtons = _filterSurfaceCommands.Keys - .Where(command => command.StartsWith($"{FilterSurfaceId}.hardpoint.", StringComparison.Ordinal)) + var options = _filterSurfaceCommands.Keys + .Where(commandFilter) .OrderBy(command => command, StringComparer.Ordinal) - .Select(command => Button( + .Select(command => new AetheriaRuntimeTradeSurfaceOption( command, - command.Split('.').Last().FormatTypeName(), + labelFactory?.Invoke(command) ?? command.Split('.').Last().FormatTypeName(), command)) .ToArray(); - if (hardpointButtons.Length > 0) - { - cards.Add(Card($"{FilterSurfaceId}.hardpoint.card", "Gear Type", ButtonColumn($"{FilterSurfaceId}.hardpoint.options", hardpointButtons))); - } - var simpleButtons = _filterSurfaceCommands.Keys - .Where(command => command.StartsWith($"{FilterSurfaceId}.simple.", StringComparison.Ordinal)) - .OrderBy(command => command, StringComparer.Ordinal) - .Select(command => Button( - command, - command.Split('.').Last().FormatTypeName(), - command)) - .ToArray(); - if (simpleButtons.Length > 0) - { - cards.Add(Card($"{FilterSurfaceId}.simple.card", "Simple Commodity", ButtonColumn($"{FilterSurfaceId}.simple.options", simpleButtons))); - } + if (options.Length == 0) + return; - var compoundButtons = _filterSurfaceCommands.Keys - .Where(command => command.StartsWith($"{FilterSurfaceId}.compound.", StringComparison.Ordinal)) - .OrderBy(command => command, StringComparer.Ordinal) - .Select(command => Button( - command, - command.Split('.').Last().FormatTypeName(), - command)) - .ToArray(); - if (compoundButtons.Length > 0) - { - cards.Add(Card($"{FilterSurfaceId}.compound.card", "Compound Commodity", ButtonColumn($"{FilterSurfaceId}.compound.options", compoundButtons))); - } - - var behaviorButtons = _filterSurfaceCommands.Keys - .Where(command => command.StartsWith($"{FilterSurfaceId}.behavior.", StringComparison.Ordinal)) - .OrderBy(command => command, StringComparer.Ordinal) - .Select(command => Button( - command, - command.Split('.').Last().FormatTypeName(), - command)) - .ToArray(); - if (behaviorButtons.Length > 0) - { - cards.Add(Card($"{FilterSurfaceId}.behavior.card", "Item Behavior", ButtonColumn($"{FilterSurfaceId}.behavior.options", behaviorButtons))); - } - - var sizeButtons = _filterSurfaceCommands.Keys - .Where(command => command.StartsWith($"{FilterSurfaceId}.size.", StringComparison.Ordinal)) - .OrderBy(command => command, StringComparer.Ordinal) - .Select(command => Button( - command, - command.EndsWith(".minimum", StringComparison.Ordinal) ? "Minimum Size" : "Maximum Size", - command)) - .ToArray(); - if (sizeButtons.Length > 0) - { - cards.Add(Card($"{FilterSurfaceId}.size.card", "Size", ButtonColumn($"{FilterSurfaceId}.size.options", sizeButtons))); - } - - cards.Add(ButtonRow($"{FilterSurfaceId}.actions", Button($"{FilterSurfaceId}.close", "Close", CloseFilterSurfaceCommand))); - - return new EveSurfaceDocument( - CargoSelectorSurfaceType, - CargoSelectorSurfaceSchema, - CargoSelectorSurfaceProviderId, - CargoSelectorSurfaceProviderKind, - "Trade Filter Selector", - version: 1, - DateTime.UtcNow.ToString("O"), - new EveSurfaceTree( - FilterSurfaceId, - Node( - $"{FilterSurfaceId}.root", - "surface", - Array.Empty<(string Key, string Value)>(), - cards.ToArray()), - Array.Empty()), - _filterSurfaceCommands.Keys - .Select(command => new EveCommandTemplate(command, command.Split('.').Last().FormatTypeName(), "unity-uitoolkit")) - .Append(new EveCommandTemplate(CloseFilterSurfaceCommand, "Close", "unity-uitoolkit")) - .ToArray()); + groups.Add(new AetheriaRuntimeTradeSurfaceGroup( + $"{AetheriaRuntimeTradeInteractionSurfaceBuilder.FilterSurfaceId}.{key}.card", + title, + options)); } private void RenderRowActionSurface(string title, params (string Label, Action Action)[] actions) @@ -1044,48 +792,14 @@ public class TradeMenu : MonoBehaviour _rowActionTitle = title; BuildRowActionSurfaceCommands(actions); - var document = ResolveRowActionSurfaceDocument(); - document.gameObject.SetActive(true); - - var root = document.rootVisualElement; - 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 = Align.FlexStart; - root.style.justifyContent = Justify.FlexStart; - root.style.paddingTop = 16; - root.style.paddingLeft = 16; - root.pickingMode = PickingMode.Ignore; - - var shell = new VisualElement(); - shell.style.width = 320; - shell.style.maxWidth = 360; - shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.94f); - shell.style.borderTopLeftRadius = 8; - shell.style.borderTopRightRadius = 8; - shell.style.borderBottomLeftRadius = 8; - shell.style.borderBottomRightRadius = 8; - shell.style.paddingLeft = 18; - shell.style.paddingRight = 18; - shell.style.paddingTop = 18; - shell.style.paddingBottom = 18; - shell.style.borderLeftWidth = 1; - shell.style.borderRightWidth = 1; - shell.style.borderTopWidth = 1; - shell.style.borderBottomWidth = 1; - shell.style.borderLeftColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderRightColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderTopColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderBottomColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.pickingMode = PickingMode.Position; - root.Add(shell); - - var lowerer = new EveUiToolkitSurfaceLowerer(); - shell.Add(lowerer.Lower(BuildRowActionSurfaceDefinition(), HandleRowActionSurfaceCommand)); + _rowActionSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _rowActionSurfaceDocument, + "Aetheria Trade Row Action Surface", + AetheriaRuntimeTradeInteractionSurfaceBuilder.BuildRowActions(ProjectTradeRowActionSurfaceState()), + HandleRowActionSurfaceCommand, + _rowActionSurfaceChrome, + sortingOrder: 1002); } private void BuildRowActionSurfaceCommands(IEnumerable<(string Label, Action Action)> actions) @@ -1094,27 +808,34 @@ public class TradeMenu : MonoBehaviour foreach (var actionEntry in actions.Select((entry, index) => (entry, index))) { - var command = $"{RowActionSurfaceId}.action_{actionEntry.index}"; + var command = AetheriaRuntimeTradeInteractionSurfaceBuilder.RowActionCommand(actionEntry.index); _rowActionSurfaceCommands[command] = actionEntry.entry.Action; } } private void HandleRowActionSurfaceCommand(EveSurfaceCommandRequest request) { - if (string.Equals(request.Command, CloseRowActionSurfaceCommand, StringComparison.Ordinal)) + if (!AetheriaRuntimeTradeInteractionSurfaceCommands.TryReadRowAction(request, out var command)) + { + Debug.LogWarning($"Unknown trade row action command: {request?.Command}"); + return; + } + + if (command.Kind == AetheriaRuntimeTradeInteractionCommandKind.Close) { HideRowActionSurface(); return; } - if (_rowActionSurfaceCommands.TryGetValue(request.Command, out var action)) + if (command.Kind == AetheriaRuntimeTradeInteractionCommandKind.Select && + _rowActionSurfaceCommands.TryGetValue(command.Command, out var action)) { action(); HideRowActionSurface(); return; } - Debug.LogWarning($"Unknown trade row action command: {request.Command}"); + Debug.LogWarning($"Unknown trade row action command: {request?.Command}"); } private void HideRowActionSurface() @@ -1122,59 +843,23 @@ public class TradeMenu : MonoBehaviour if (_rowActionSurfaceDocument == null) return; - _rowActionSurfaceDocument.rootVisualElement.Clear(); - _rowActionSurfaceDocument.gameObject.SetActive(false); + AetheriaEveUnitySurfaceHost.Hide(_rowActionSurfaceDocument); } - private UIDocument ResolveRowActionSurfaceDocument() + private AetheriaRuntimeTradeRowActionSurfaceState ProjectTradeRowActionSurfaceState() { - if (_rowActionSurfaceDocument != null) - return _rowActionSurfaceDocument; - - var host = new GameObject("Aetheria Trade Row Action Surface"); - host.transform.SetParent(transform, false); - var document = host.AddComponent(); - document.sortingOrder = 1002; - host.SetActive(false); - _rowActionSurfaceDocument = document; - return document; - } - - private EveSurfaceDocument BuildRowActionSurfaceDefinition() - { - var commands = _rowActionSurfaceCommands.Keys.OrderBy(command => command, StringComparer.Ordinal).ToArray(); - var buttons = commands - .Select(command => Button(command, command.EndsWith("action_0", StringComparison.Ordinal) ? "Buy Quantity" : command.Split('.').Last(), command)) + var actions = _rowActionSurfaceCommands.Keys + .OrderBy(command => command, StringComparer.Ordinal) + .Select(command => new AetheriaRuntimeTradeSurfaceOption( + command, + command.EndsWith("action_0", StringComparison.Ordinal) ? "Buy Quantity" : command.Split('.').Last(), + command)) .ToArray(); - return new EveSurfaceDocument( - CargoSelectorSurfaceType, - CargoSelectorSurfaceSchema, - CargoSelectorSurfaceProviderId, - CargoSelectorSurfaceProviderKind, - "Trade Row Actions", - version: 1, - DateTime.UtcNow.ToString("O"), - new EveSurfaceTree( - RowActionSurfaceId, - Node( - $"{RowActionSurfaceId}.root", - "surface", - Array.Empty<(string Key, string Value)>(), - Card( - $"{RowActionSurfaceId}.card", - "Trade Action", - Text($"{RowActionSurfaceId}.title", _rowActionTitle), - Text( - $"{RowActionSurfaceId}.note", - "Trade still owns the quantity dialog and purchase commit. This surface just removes the old right-click context-menu shell."), - ButtonColumn($"{RowActionSurfaceId}.options", buttons), - ButtonRow($"{RowActionSurfaceId}.actions", Button($"{RowActionSurfaceId}.close", "Close", CloseRowActionSurfaceCommand)))), - Array.Empty()), - commands - .Select(command => new EveCommandTemplate(command, command.EndsWith("action_0", StringComparison.Ordinal) ? "Buy Quantity" : command.Split('.').Last(), "unity-uitoolkit")) - .Append(new EveCommandTemplate(CloseRowActionSurfaceCommand, "Close", "unity-uitoolkit")) - .ToArray()); + return new AetheriaRuntimeTradeRowActionSurfaceState( + _rowActionTitle, + actions, + DateTime.UtcNow.ToString("O")); } private void ApplyHardpointFilter(HardpointType hardpointType) @@ -1353,48 +1038,13 @@ public class TradeMenu : MonoBehaviour { BuildCargoSelectionCommands(); - var document = ResolveCargoSelectorSurfaceDocument(); - document.gameObject.SetActive(true); - - var root = document.rootVisualElement; - 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 = Align.FlexEnd; - root.style.justifyContent = Justify.FlexStart; - root.style.paddingTop = 16; - root.style.paddingRight = 16; - root.pickingMode = PickingMode.Ignore; - - var shell = new VisualElement(); - shell.style.width = 360; - shell.style.maxWidth = 420; - shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.94f); - shell.style.borderTopLeftRadius = 8; - shell.style.borderTopRightRadius = 8; - shell.style.borderBottomLeftRadius = 8; - shell.style.borderBottomRightRadius = 8; - shell.style.paddingLeft = 18; - shell.style.paddingRight = 18; - shell.style.paddingTop = 18; - shell.style.paddingBottom = 18; - shell.style.borderLeftWidth = 1; - shell.style.borderRightWidth = 1; - shell.style.borderTopWidth = 1; - shell.style.borderBottomWidth = 1; - shell.style.borderLeftColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderRightColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderTopColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.style.borderBottomColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); - shell.pickingMode = PickingMode.Position; - root.Add(shell); - - var lowerer = new EveUiToolkitSurfaceLowerer(); - shell.Add(lowerer.Lower(BuildCargoSelectorSurfaceDefinition(), HandleCargoSelectorSurfaceCommand)); + _cargoSelectorSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _cargoSelectorSurfaceDocument, + "Aetheria Trade Cargo Selector Surface", + AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.Build(ProjectTradeCargoSelectorSurfaceState()), + HandleCargoSelectorSurfaceCommand, + _cargoSelectorSurfaceChrome); } private void BuildCargoSelectionCommands() @@ -1403,7 +1053,7 @@ public class TradeMenu : MonoBehaviour if (GameManager.DockingBay != null && _targetCargo != GameManager.DockingBay) { - _cargoSelectionCommands["aetheria.trade.target_cargo_selector.docking_bay"] = + _cargoSelectionCommands[AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.DockingBay] = (GameManager.DockingBay, "Docking Bay"); } @@ -1420,7 +1070,7 @@ public class TradeMenu : MonoBehaviour if (_targetCargo == bay.cargoBay) continue; - var command = $"aetheria.trade.target_cargo_selector.ship_{ship.shipIndex}_bay_{bay.index}"; + var command = AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.ShipBayCommand(ship.shipIndex, bay.index); _cargoSelectionCommands[command] = (bay.cargoBay, $"{ship.ship.Name} Bay {bay.index + 1}"); } } @@ -1428,13 +1078,20 @@ public class TradeMenu : MonoBehaviour private void HandleCargoSelectorSurfaceCommand(EveSurfaceCommandRequest request) { - if (string.Equals(request.Command, CloseCargoSelectorCommand, StringComparison.Ordinal)) + if (!AetheriaRuntimeTradeCargoSelectorSurfaceCommands.TryRead(request, out var command)) + { + Debug.LogWarning($"Unknown trade cargo selector command: {request?.Command}"); + return; + } + + if (command.Kind == AetheriaRuntimeTradeCargoSelectorCommandKind.Close) { HideCargoSelectorSurface(); return; } - if (_cargoSelectionCommands.TryGetValue(request.Command, out var option)) + if (command.Kind == AetheriaRuntimeTradeCargoSelectorCommandKind.Select && + _cargoSelectionCommands.TryGetValue(command.Command, out var option)) { _targetCargo = option.Cargo; TargetCargoLabel.text = option.Label; @@ -1443,7 +1100,7 @@ public class TradeMenu : MonoBehaviour return; } - Debug.LogWarning($"Unknown trade cargo selector command: {request.Command}"); + Debug.LogWarning($"Unknown trade cargo selector command: {request?.Command}"); } private void HideCargoSelectorSurface() @@ -1451,64 +1108,42 @@ public class TradeMenu : MonoBehaviour if (_cargoSelectorSurfaceDocument == null) return; - _cargoSelectorSurfaceDocument.rootVisualElement.Clear(); - _cargoSelectorSurfaceDocument.gameObject.SetActive(false); + AetheriaEveUnitySurfaceHost.Hide(_cargoSelectorSurfaceDocument); } - private UIDocument ResolveCargoSelectorSurfaceDocument() + private static AetheriaEveUnitySurfaceChrome PanelChrome(float width, float maxWidth, Align alignItems) { - if (_cargoSelectorSurfaceDocument != null) - return _cargoSelectorSurfaceDocument; - - var host = new GameObject("Aetheria Trade Cargo Selector Surface"); - host.transform.SetParent(transform, false); - var document = host.AddComponent(); - document.sortingOrder = 1000; - host.SetActive(false); - _cargoSelectorSurfaceDocument = document; - return document; + return new AetheriaEveUnitySurfaceChrome + { + RootAlignItems = alignItems, + RootJustifyContent = Justify.FlexStart, + RootPaddingTop = 16f, + RootPaddingLeft = alignItems == Align.FlexStart ? 16f : 0f, + RootPaddingRight = alignItems == Align.FlexEnd ? 16f : 0f, + Width = width, + MinWidth = 0f, + MaxWidth = maxWidth, + PaddingLeft = 18f, + PaddingRight = 18f, + PaddingTop = 18f, + PaddingBottom = 18f + }; } - private EveSurfaceDocument BuildCargoSelectorSurfaceDefinition() + private AetheriaRuntimeTradeCargoSelectorSurfaceState ProjectTradeCargoSelectorSurfaceState() { - var buttons = _cargoSelectionCommands + var targets = _cargoSelectionCommands .OrderBy(pair => pair.Value.Label, StringComparer.Ordinal) - .Select(pair => Button( - $"{CargoSelectorSurfaceId}.{pair.Key.Split('.').Last()}", + .Select(pair => new AetheriaRuntimeTradeCargoTargetOption( + $"{AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.SurfaceId}.{pair.Key.Split('.').Last()}", pair.Value.Label, pair.Key)) .ToArray(); - return new EveSurfaceDocument( - CargoSelectorSurfaceType, - CargoSelectorSurfaceSchema, - CargoSelectorSurfaceProviderId, - CargoSelectorSurfaceProviderKind, - "Trade Target Cargo Selector", - version: 1, - DateTime.UtcNow.ToString("O"), - new EveSurfaceTree( - CargoSelectorSurfaceId, - Node( - $"{CargoSelectorSurfaceId}.root", - "surface", - Array.Empty<(string Key, string Value)>(), - Card( - $"{CargoSelectorSurfaceId}.card", - "Target Cargo", - Metric($"{CargoSelectorSurfaceId}.current", "Current", TargetCargoLabel.text ?? ""), - Text( - $"{CargoSelectorSurfaceId}.note", - "Trade still owns local presentation state here; this surface cuts out the old context-menu option list."), - ButtonColumn($"{CargoSelectorSurfaceId}.options", buttons), - ButtonRow( - $"{CargoSelectorSurfaceId}.actions", - Button($"{CargoSelectorSurfaceId}.close", "Close", CloseCargoSelectorCommand)))), - Array.Empty()), - _cargoSelectionCommands.Keys - .Select(command => new EveCommandTemplate(command, _cargoSelectionCommands[command].Label, "unity-uitoolkit")) - .Append(new EveCommandTemplate(CloseCargoSelectorCommand, "Close", "unity-uitoolkit")) - .ToArray()); + return new AetheriaRuntimeTradeCargoSelectorSurfaceState( + TargetCargoLabel.text ?? "", + targets, + DateTime.UtcNow.ToString("O")); } private static EveSurfaceComponent Card( @@ -1561,7 +1196,7 @@ public class TradeMenu : MonoBehaviour children ?? Array.Empty()); } - private void OnDisable() +private void OnDisable() { HideCargoSelectorSurface(); HideFilterSurface(); @@ -1573,25 +1208,25 @@ public class TradeMenu : MonoBehaviour { if (_cargoSelectorSurfaceDocument != null) { - Destroy(_cargoSelectorSurfaceDocument.gameObject); + AetheriaEveUnitySurfaceHost.DestroyDocument(_cargoSelectorSurfaceDocument); _cargoSelectorSurfaceDocument = null; } if (_filterSurfaceDocument != null) { - Destroy(_filterSurfaceDocument.gameObject); + AetheriaEveUnitySurfaceHost.DestroyDocument(_filterSurfaceDocument); _filterSurfaceDocument = null; } if (_rowActionSurfaceDocument != null) { - Destroy(_rowActionSurfaceDocument.gameObject); + AetheriaEveUnitySurfaceHost.DestroyDocument(_rowActionSurfaceDocument); _rowActionSurfaceDocument = null; } if (_tradeItemSurfaceDocument != null) { - Destroy(_tradeItemSurfaceDocument.gameObject); + AetheriaEveUnitySurfaceHost.DestroyDocument(_tradeItemSurfaceDocument); _tradeItemSurfaceDocument = null; } } diff --git a/Assets/Scripts/UnityExtensions.cs b/Assets/Scripts/UnityExtensions.cs index 174e3d2c1..eb244c282 100644 --- a/Assets/Scripts/UnityExtensions.cs +++ b/Assets/Scripts/UnityExtensions.cs @@ -36,7 +36,7 @@ public static class UnitySceneExtensions } public static void BroadcastAll(string fun, System.Object msg) { - GameObject[] gos = (GameObject[])GameObject.FindObjectsOfType(typeof(GameObject)); + var gos = GameObject.FindObjectsByType(); foreach (GameObject go in gos) { if (go && go.transform.parent == null) { go.gameObject.BroadcastMessageExt(fun, msg, SendMessageOptions.DontRequireReceiver); diff --git a/Assets/Scripts/UnitySupport/UnityExtensions.cs b/Assets/Scripts/UnitySupport/UnityExtensions.cs index aaebc4106..16243294b 100644 --- a/Assets/Scripts/UnitySupport/UnityExtensions.cs +++ b/Assets/Scripts/UnitySupport/UnityExtensions.cs @@ -40,14 +40,24 @@ public static class UnityExtensions return new AnimationCurve(keys.Select(v => new Keyframe(v.x, v.y, v.z, v.w)).ToArray()); } + public static AnimationCurve ToCurve(this CultMath.float4[] keys) + { + return new AnimationCurve(keys.Select(v => new Keyframe(v.x, v.y, v.z, v.w)).ToArray()); + } + public static AnimationCurve ToCurve(this BezierCurve curve) { return new AnimationCurve(curve.Keys.Select(v => new Keyframe(v.x, v.y, v.z, v.w)).ToArray()); } public static Color ToColor(this float3 v) => new Color(v.x,v.y,v.z); + public static Color ToColor(this CultMath.float3 v) => new Color(v.x,v.y,v.z); + public static float3 ToUnityFloat3(this CultMath.float3 v) => float3(v.x, v.y, v.z); public static float3 ToFloat3(this Color c) => float3(c.r,c.g,c.b); + public static CultMath.float3 ToCultFloat3(this Color c) => new(c.r, c.g, c.b); public static Color ToColor(this float4 v) => new Color(v.x,v.y,v.z, v.w); + public static Color ToColor(this CultMath.float4 v) => new Color(v.x,v.y,v.z, v.w); public static float4 ToFloat4(this Color c) => float4(c.r, c.g, c.b, c.a); -} \ No newline at end of file + public static CultMath.float4 ToCultFloat4(this Color c) => new(c.r, c.g, c.b, c.a); +} diff --git a/Assets/Scripts/Zone Display/SnapToGrid.cs b/Assets/Scripts/Zone Display/SnapToGrid.cs index b2bf4e979..c14e81116 100644 --- a/Assets/Scripts/Zone Display/SnapToGrid.cs +++ b/Assets/Scripts/Zone Display/SnapToGrid.cs @@ -2,8 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -using System.Collections; -using System.Collections.Generic; using UnityEngine; public class SnapToGrid : MonoBehaviour @@ -19,31 +17,27 @@ public class SnapToGrid : MonoBehaviour public float NormalMultiplier = 100; public float GravityGradientStep = .01f; - private Rigidbody _body; - - // Use this for initialization - void Start () { - _body = GetComponent(); - } + private Vector2 _velocity; // Update is called once per frame void LateUpdate () { var pos = new Vector2(transform.position.x,transform.position.z); + if (ApplyForces) + { + var force = Gravity.GetForce(pos) * ForceScale; + _velocity += force * Time.deltaTime; + pos += _velocity * Time.deltaTime; + transform.position = new Vector3(pos.x, transform.position.y, pos.y); + } + if(SnapPosition) transform.position = new Vector3(pos.x, Gravity.GetHeight(pos) + Offset,pos.y); if (SnapRotation) { var normal = Gravity.GetNormal(pos, GravityGradientStep, NormalMultiplier); - var forward = Vector3.Cross(transform.right, normal); + var forward = Vector3.Cross(transform.right, normal); transform.rotation = Quaternion.LookRotation(forward, normal); } - - if (ApplyForces) - { - //var currentVelocity = _body.velocity; - var force = Gravity.GetForce(pos) * ForceScale; - _body.AddForce(force.x,0,force.y,ForceMode.Acceleration); - } } } diff --git a/Assets/Scripts/Zone Display/ZoneRenderer.cs b/Assets/Scripts/Zone Display/ZoneRenderer.cs index d4cbc66e1..c39a945b6 100644 --- a/Assets/Scripts/Zone Display/ZoneRenderer.cs +++ b/Assets/Scripts/Zone Display/ZoneRenderer.cs @@ -75,7 +75,6 @@ public class ZoneRenderer : MonoBehaviour //private float _maxDepth; private float _minimapDistance; - private int _tourIndex = -1; private float _tourTimer; private List<(Transform, Transform)> _tourPlanets = new List<(Transform, Transform)>(); private CinemachineTransposer _transposer; @@ -229,7 +228,7 @@ public class ZoneRenderer : MonoBehaviour AddWormhole(new Wormhole { Target = adjacentZone, - Position = dir * zone.Radius * Settings.WormholeDistanceRatio + Position = AetheriaMath.ToCult(dir * zone.Radius * Settings.WormholeDistanceRatio) }); } } @@ -360,11 +359,9 @@ public class ZoneRenderer : MonoBehaviour } var beltObject = Instantiate(AsteroidBeltUI, ZoneRoot); - var collider = beltObject.GetComponent(); var belt = new AsteroidBeltUI(Zone, runtimeBelt, beltObject, - collider, AsteroidSpritesheetWidth, AsteroidSpritesheetHeight, Settings.MinimapAsteroidSize); @@ -455,8 +452,8 @@ public class ZoneRenderer : MonoBehaviour var maxDepth = 0f; foreach (var loot in _loot) { - loot.ViewOrigin = PerspectiveEntity.Position; - loot.ViewDirection = PerspectiveEntity.LookDirection; + loot.ViewOrigin = PerspectiveEntity.CultPosition; + loot.ViewDirection = PerspectiveEntity.CultLookDirection; } // if (SlimeRenderer.SpawnPositions.Length != _suns.Length) @@ -528,8 +525,8 @@ public class ZoneRenderer : MonoBehaviour { if(entityInstance.CompassIcon) { - var difference = entityInstance.Entity.Position.xz - PerspectiveEntity.Position.xz; - var distance = length(difference); + var difference = entityInstance.Entity.CultPositionXZ - PerspectiveEntity.CultPositionXZ; + var distance = CultMath.math.length(difference); entityInstance.CompassIcon.gameObject.SetActive( PerspectiveEntity.EntityInfoGathered.ContainsKey(entityInstance.Entity) && @@ -541,7 +538,7 @@ public class ZoneRenderer : MonoBehaviour foreach (var wormhole in WormholeInstances.Values) { - var difference = wormhole.gravity.transform.position.Flatland() - (Vector2)PerspectiveEntity.Position.xz; + var difference = wormhole.gravity.transform.position.Flatland() - (Vector2)AetheriaMath.ToUnity(PerspectiveEntity.CultPositionXZ); var distance = difference.magnitude; wormhole.icon.gameObject.SetActive(distance > _minimapDistance); wormhole.icon.transform.rotation = Quaternion.Euler(0, 0, Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg - 90); @@ -597,7 +594,6 @@ public class ZoneRenderer : MonoBehaviour itemPickup.ScanLabel.color = c; } else itemPickup.ScanLabel.color = new Color(.75f, .75f, .75f, 0); - gridObject.gameObject.AddComponent().Duration = Settings.PickupLifetime; _loot.Add(itemPickup); } @@ -623,7 +619,6 @@ public class InstancedMesh public class AsteroidBeltUI { public MeshFilter Filter; - private MeshCollider _collider; private Vector3[] _vertices; private Vector3[] _normals; private Vector2[] _uvs; @@ -637,7 +632,6 @@ public class AsteroidBeltUI public AsteroidBeltUI(Zone zone, AsteroidBelt belt, MeshFilter meshFilter, - MeshCollider collider, int spritesheetWidth, int spritesheetHeight, float scale) @@ -645,7 +639,6 @@ public class AsteroidBeltUI _belt = belt; _zone = zone; Filter = meshFilter; - _collider = collider; _vertices = new Vector3[_belt.AsteroidCount * 4]; _normals = new Vector3[_belt.AsteroidCount * 4]; _uvs = new Vector2[_belt.AsteroidCount * 4]; diff --git a/Assets/Settings/Rendering.meta b/Assets/Settings/Rendering.meta new file mode 100644 index 000000000..7405a1c23 --- /dev/null +++ b/Assets/Settings/Rendering.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e06e6922fe6f2ed4fa265a0c5b4d376e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Rendering/AetheriaURPAsset.asset b/Assets/Settings/Rendering/AetheriaURPAsset.asset new file mode 100644 index 000000000..1adf09ad5 --- /dev/null +++ b/Assets/Settings/Rendering/AetheriaURPAsset.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f56d18c0c7b03350f805d7ffad26309ff9c396ce2484229c1eef0853b2faebe4 +size 4695 diff --git a/Assets/Settings/Rendering/AetheriaURPAsset.asset.meta b/Assets/Settings/Rendering/AetheriaURPAsset.asset.meta new file mode 100644 index 000000000..f6a2bfa02 --- /dev/null +++ b/Assets/Settings/Rendering/AetheriaURPAsset.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 41c6acd9154b160419f90b65621bebbf +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Rendering/AetheriaUniversalRenderer.asset b/Assets/Settings/Rendering/AetheriaUniversalRenderer.asset new file mode 100644 index 000000000..4870b2512 --- /dev/null +++ b/Assets/Settings/Rendering/AetheriaUniversalRenderer.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2c5bb55c31c6c1db5b7f94033ee6e6763c8c307a3a37fa49ca2534aecff285c +size 1914 diff --git a/Assets/Settings/Rendering/AetheriaUniversalRenderer.asset.meta b/Assets/Settings/Rendering/AetheriaUniversalRenderer.asset.meta new file mode 100644 index 000000000..00ffeb892 --- /dev/null +++ b/Assets/Settings/Rendering/AetheriaUniversalRenderer.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a0dc26efef6a9c3419c47aa2ef4e71e2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Shaders/AetheriaDaemonIndirect.shader b/Assets/Shaders/AetheriaDaemonIndirect.shader new file mode 100644 index 000000000..9e7b67423 --- /dev/null +++ b/Assets/Shaders/AetheriaDaemonIndirect.shader @@ -0,0 +1,123 @@ +Shader "Aetheria/Daemon Indirect" +{ + Properties + { + [MainTexture] _BaseMap ("Texture", 2D) = "white" {} + [MainColor] _BaseColor ("Color", Color) = (1, 1, 1, 1) + } + + SubShader + { + Tags + { + "RenderType" = "Opaque" + "RenderPipeline" = "UniversalPipeline" + } + + Pass + { + Name "Forward" + Tags { "LightMode" = "UniversalForward" } + + HLSLPROGRAM + #pragma target 4.5 + #pragma vertex Vert + #pragma fragment Frag + + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + + TEXTURE2D(_BaseMap); + SAMPLER(sampler_BaseMap); + float4 _BaseMap_ST; + half4 _BaseColor; + StructuredBuffer _AetheriaObjectToWorld; + int _AetheriaInstanceCount; + + struct Attributes + { + float3 positionOS : POSITION; + float2 uv : TEXCOORD0; + uint instanceId : SV_InstanceID; + }; + + struct Varyings + { + float4 positionCS : SV_POSITION; + float2 uv : TEXCOORD0; + }; + + Varyings Vert(Attributes input) + { + Varyings output; + const uint instanceCount = (uint)max(1, _AetheriaInstanceCount); + const uint instanceId = min(input.instanceId, instanceCount - 1); + const float4x4 objectToWorld = _AetheriaObjectToWorld[instanceId]; + const float3 worldPosition = mul(objectToWorld, float4(input.positionOS, 1.0)).xyz; + + output.positionCS = TransformWorldToHClip(worldPosition); + output.uv = input.uv * _BaseMap_ST.xy + _BaseMap_ST.zw; + return output; + } + + half4 Frag(Varyings input) : SV_Target + { + return SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor; + } + ENDHLSL + } + } + + SubShader + { + Tags { "RenderType" = "Opaque" } + + Pass + { + Tags { "LightMode" = "ForwardBase" } + + CGPROGRAM + #pragma target 4.5 + #pragma vertex vert + #pragma fragment frag + #include "UnityCG.cginc" + + sampler2D _BaseMap; + float4 _BaseMap_ST; + fixed4 _BaseColor; + StructuredBuffer _AetheriaObjectToWorld; + int _AetheriaInstanceCount; + + struct appdata + { + float3 vertex : POSITION; + float2 uv : TEXCOORD0; + uint instanceId : SV_InstanceID; + }; + + struct v2f + { + float4 vertex : SV_POSITION; + float2 uv : TEXCOORD0; + }; + + v2f vert(appdata input) + { + v2f output; + const uint instanceCount = (uint)max(1, _AetheriaInstanceCount); + const uint instanceId = min(input.instanceId, instanceCount - 1); + const float4x4 objectToWorld = _AetheriaObjectToWorld[instanceId]; + const float3 worldPosition = mul(objectToWorld, float4(input.vertex, 1.0)).xyz; + + output.vertex = mul(UNITY_MATRIX_VP, float4(worldPosition, 1.0)); + output.uv = input.uv * _BaseMap_ST.xy + _BaseMap_ST.zw; + return output; + } + + fixed4 frag(v2f input) : SV_Target + { + return tex2D(_BaseMap, input.uv) * _BaseColor; + } + ENDCG + } + } +} diff --git a/Assets/Shaders/AetheriaDaemonIndirect.shader.meta b/Assets/Shaders/AetheriaDaemonIndirect.shader.meta new file mode 100644 index 000000000..6e2021cce --- /dev/null +++ b/Assets/Shaders/AetheriaDaemonIndirect.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: e3bfa4a4c3e3f564392a45c3dba24513 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniversalRenderPipelineGlobalSettings.asset b/Assets/UniversalRenderPipelineGlobalSettings.asset new file mode 100644 index 000000000..36576e146 --- /dev/null +++ b/Assets/UniversalRenderPipelineGlobalSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dfc5f040929f6498abddc44a1fd817fdcca92f43f4ac2a1727a736e05ebb4e5 +size 27389 diff --git a/Assets/UniversalRenderPipelineGlobalSettings.asset.meta b/Assets/UniversalRenderPipelineGlobalSettings.asset.meta new file mode 100644 index 000000000..32a3702ac --- /dev/null +++ b/Assets/UniversalRenderPipelineGlobalSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ddaa69bcd0e396543bf96556e1841b69 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/csc.rsp b/Assets/csc.rsp index 12dab3ad7..aafdd9933 100644 --- a/Assets/csc.rsp +++ b/Assets/csc.rsp @@ -1 +1 @@ --define:TEST_MSAGL \ No newline at end of file +-langversion:latest diff --git a/CultMath.csproj b/CultMath.csproj new file mode 100644 index 000000000..f6a1de74d --- /dev/null +++ b/CultMath.csproj @@ -0,0 +1,545 @@ + + + + latest + <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package + <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package + true + false + + + Debug + AnyCPU + 10.0.20506 + 2.0 + CultMath + {6f7a47c4-21dc-64ef-0969-96b956a000f4} + {E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + CultMath + v4.7.1 + 512 + . + + + true + full + false + Temp\Bin\Debug\CultMath\ + UNITY_6000_4_2;UNITY_6000_4;UNITY_6000;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;UNITY_2019_4_OR_NEWER;UNITY_2020_1_OR_NEWER;UNITY_2020_2_OR_NEWER;UNITY_2020_3_OR_NEWER;UNITY_2021_1_OR_NEWER;UNITY_2021_2_OR_NEWER;UNITY_2021_3_OR_NEWER;UNITY_2022_1_OR_NEWER;UNITY_2022_2_OR_NEWER;UNITY_2022_3_OR_NEWER;UNITY_2023_1_OR_NEWER;UNITY_2023_2_OR_NEWER;UNITY_2023_3_OR_NEWER;UNITY_6000_0_OR_NEWER;UNITY_6000_1_OR_NEWER;UNITY_6000_2_OR_NEWER;UNITY_6000_3_OR_NEWER;UNITY_6000_4_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AR;ENABLE_AUDIO;ENABLE_AUDIO_SCRIPTABLE_PIPELINE;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_EVENT_QUEUE;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_VIRTUALTEXTURING;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_UNITY_CONSENT;ENABLE_UNITY_CLOUD_IDENTIFIERS;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_NATIVE_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_EDITOR_GAME_SERVICES;ENABLE_UNITY_GAME_SERVICES_ANALYTICS_SUPPORT;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;ENABLE_MANAGED_UNITYTLS;INCLUDE_DYNAMIC_GI;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_MARSHALLING_TESTS;ENABLE_VIDEO;ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK;ENABLE_ACCELERATOR_CLIENT_DEBUGGING;ENABLE_ACCESSIBILITY_SCREEN_READER;TEXTCORE_1_0_OR_NEWER;EDITOR_ONLY_NAVMESH_BUILDER_DEPRECATED;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_NVIDIA;ENABLE_AMD;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLOUD_SERVICES_ENGINE_DIAGNOSTICS;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;PLATFORM_UPDATES_TIME_OUTSIDE_OF_PLAYER_LOOP;GFXDEVICE_WAITFOREVENT_MESSAGEPUMP;PLATFORM_USES_EXPLICIT_MEMORY_MANAGER_INITIALIZER;PLATFORM_SUPPORTS_WAIT_FOR_PRESENTATION;PLATFORM_SUPPORTS_SPLIT_GRAPHICS_JOBS;ENABLE_MONO;NET_4_6;NET_UNITY_4_8;ENABLE_PROFILER;ENABLE_PROFILER_ASSISTANT_INTEGRATION;DEBUG;TRACE;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_INPUT_SYSTEM;TEXTCORE_FONT_ENGINE_1_5_OR_NEWER;TEXTCORE_TEXT_ENGINE_1_5_OR_NEWER;TEXTCORE_FONT_ENGINE_1_6_OR_NEWER;UNITY_POST_PROCESSING_STACK_V2;TEST_MSAGL;REPORTING;PERSISTENCE;SUBSTANCE_PLUGIN_ENABLED;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + prompt + 4 + 0169,0649 + True + False + + + true + true + false + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.entities@637eb689ab47\Unity.Entities.CodeGen\Unity.Cecil.Awesome.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.collections@05d86f76b771\Unity.Collections.LowLevel.ILSupport\Unity.Collections.LowLevel.ILSupport.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.ext.nunit@d8c07649098d\net40\unity-custom\nunit.framework.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Collections.Immutable.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.IO.Pipelines.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.TimeProvider.dll + + + E:\Projects\Aetheria\Assets\Allegorithmic\Plugins\Substance\Substance Engine\Editor\Substance.Editor.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\R3.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Networking.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Argon2.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.SecureArray.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Runtime.CompilerServices.Unsafe.dll + + + E:\Projects\Aetheria\Assets\Allegorithmic\Plugins\Substance\Substance Source\Editor\SourceUnityLib.dll + + + E:\Projects\Aetheria\Assets\Allegorithmic\Plugins\Substance.Game.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\ConcurrentCollections.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Encodings.Web.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.NET.StringTools.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.collections@05d86f76b771\Unity.Collections.Tests\System.IO.Hashing\System.IO.Hashing.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.dll + + + E:\Projects\Aetheria\Assets\Plugins\QuickGraph.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.nuget.mono-cecil@ecb9724e46ff\Mono.Cecil.dll + + + E:\Projects\Aetheria\Assets\Plugins\Remotion.Linq.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.AsyncInterfaces.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Threading.Channels.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Logging.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Mesh.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Json.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Blake2b.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.MessagePack.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\LiteNetLib.dll + + + E:\Projects\Aetheria\Assets\Plugins\Microsoft.Extensions.Logging.Abstractions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\mscorlib.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Core.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Runtime.Serialization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.Vectors.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Net.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Microsoft.CSharp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.DataSetExtensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Drawing.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.ComponentModel.Composition.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Transactions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\Microsoft.Win32.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\netstandard.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.AppContext.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Buffers.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Concurrent.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.NonGeneric.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Specialized.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Annotations.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.EventBasedAsync.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.TypeConverter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Console.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Data.Common.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Contracts.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Debug.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.FileVersionInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Process.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.StackTrace.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TextWriterTraceListener.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Tools.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TraceSource.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Drawing.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Dynamic.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Calendars.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Compression.ZipFile.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.DriveInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Watcher.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.IsolatedStorage.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.MemoryMappedFiles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Pipes.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.UnmanagedMemoryStream.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Expressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Queryable.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Memory.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Http.Rtc.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NameResolution.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NetworkInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Ping.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Requests.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Sockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebHeaderCollection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.Client.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ObjectModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.DispatchProxy.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.ILGeneration.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.Lightweight.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Reader.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.ResourceManager.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Writer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.CompilerServices.VisualC.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Handles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.RuntimeInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.WindowsRuntime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Formatters.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Json.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Claims.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Algorithms.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Csp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.X509Certificates.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Principal.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.SecureString.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Duplex.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.NetTcp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.RegularExpressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Overlapped.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Thread.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.ThreadPool.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Timer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ValueTuple.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.ReaderWriter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlSerializer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.XDocument.dll + + + + + diff --git a/GameCult.Aetheria.EveRuntime.csproj b/GameCult.Aetheria.EveRuntime.csproj new file mode 100644 index 000000000..eac824ca0 --- /dev/null +++ b/GameCult.Aetheria.EveRuntime.csproj @@ -0,0 +1,968 @@ + + + + latest + <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package + <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package + true + false + + + Debug + AnyCPU + 10.0.20506 + 2.0 + GameCult.Aetheria.EveRuntime + {a1a8b65b-ca79-2fa2-3803-5f4833778bdb} + {E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + GameCult.Aetheria.EveRuntime + v4.7.1 + 512 + . + + + true + full + false + Temp\Bin\Debug\GameCult.Aetheria.EveRuntime\ + UNITY_6000_4_2;UNITY_6000_4;UNITY_6000;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;UNITY_2019_4_OR_NEWER;UNITY_2020_1_OR_NEWER;UNITY_2020_2_OR_NEWER;UNITY_2020_3_OR_NEWER;UNITY_2021_1_OR_NEWER;UNITY_2021_2_OR_NEWER;UNITY_2021_3_OR_NEWER;UNITY_2022_1_OR_NEWER;UNITY_2022_2_OR_NEWER;UNITY_2022_3_OR_NEWER;UNITY_2023_1_OR_NEWER;UNITY_2023_2_OR_NEWER;UNITY_2023_3_OR_NEWER;UNITY_6000_0_OR_NEWER;UNITY_6000_1_OR_NEWER;UNITY_6000_2_OR_NEWER;UNITY_6000_3_OR_NEWER;UNITY_6000_4_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AR;ENABLE_AUDIO;ENABLE_AUDIO_SCRIPTABLE_PIPELINE;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_EVENT_QUEUE;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_VIRTUALTEXTURING;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_UNITY_CONSENT;ENABLE_UNITY_CLOUD_IDENTIFIERS;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_NATIVE_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_EDITOR_GAME_SERVICES;ENABLE_UNITY_GAME_SERVICES_ANALYTICS_SUPPORT;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;ENABLE_MANAGED_UNITYTLS;INCLUDE_DYNAMIC_GI;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_MARSHALLING_TESTS;ENABLE_VIDEO;ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK;ENABLE_ACCELERATOR_CLIENT_DEBUGGING;ENABLE_ACCESSIBILITY_SCREEN_READER;TEXTCORE_1_0_OR_NEWER;EDITOR_ONLY_NAVMESH_BUILDER_DEPRECATED;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_NVIDIA;ENABLE_AMD;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLOUD_SERVICES_ENGINE_DIAGNOSTICS;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;PLATFORM_UPDATES_TIME_OUTSIDE_OF_PLAYER_LOOP;GFXDEVICE_WAITFOREVENT_MESSAGEPUMP;PLATFORM_USES_EXPLICIT_MEMORY_MANAGER_INITIALIZER;PLATFORM_SUPPORTS_WAIT_FOR_PRESENTATION;PLATFORM_SUPPORTS_SPLIT_GRAPHICS_JOBS;ENABLE_MONO;NET_4_6;NET_UNITY_4_8;ENABLE_PROFILER;ENABLE_PROFILER_ASSISTANT_INTEGRATION;DEBUG;TRACE;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_INPUT_SYSTEM;TEXTCORE_FONT_ENGINE_1_5_OR_NEWER;TEXTCORE_TEXT_ENGINE_1_5_OR_NEWER;TEXTCORE_FONT_ENGINE_1_6_OR_NEWER;UNITY_POST_PROCESSING_STACK_V2;TEST_MSAGL;REPORTING;PERSISTENCE;SUBSTANCE_PLUGIN_ENABLED;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + prompt + 4 + 0169,0649 + False + False + + + true + true + false + false + false + + + + + + + + + + + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\Unity.Scripting.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ARModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AccessibilityModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AdaptivePerformanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AndroidJNIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AnimationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AssetBundleModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AudioModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ClothModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ClusterInputModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ClusterRendererModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ContentLoadModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.CoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.CrashReportingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.DSPGraphModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.DirectorModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GameCenterModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GraphicsStateCollectionSerializerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GridModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.HierarchyCoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.HotReloadModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.IMGUIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.IdentifiersModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ImageConversionModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InputModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InputForUIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InputLegacyModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InsightsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.JSONSerializeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.LocalizationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.MarshallingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.MultiplayerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ParticleSystemModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PerformanceReportingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PhysicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.Physics2DModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PhysicsBackendPhysXModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PropertiesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.RenderAs2DModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ScreenCaptureModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ScriptingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ShaderVariantAnalyticsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SharedInternalsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SpriteMaskModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SpriteShapeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.StreamingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SubstanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SubsystemsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TLSModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TerrainModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TerrainPhysicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextCoreFontEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextCoreTextEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextRenderingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TilemapModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UIElementsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UmbraModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityAnalyticsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityAnalyticsCommonModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityConnectModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityConsentModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityCurlModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestAudioModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestTextureModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestWWWModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VFXModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VRModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VectorGraphicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VehiclesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VideoModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VirtualTexturingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.WindModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.XRModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.AccessibilityModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.AdaptivePerformanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.AssetComplianceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.BuildProfileModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ClothModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.CoreBusinessMetricsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.CoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.DeviceSimulatorModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.DiagnosticsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.EditorToolbarModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.EmbreeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GraphToolkitModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GraphViewModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GraphicsStateCollectionSerializerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GridAndSnapModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GridModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.HierarchyModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.MediaModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.MultiplayerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.Physics2DModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PhysicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PlayModeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PresetsUIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ProjectAuditorModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PropertiesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.QuickInstallModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.QuickSearchModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SafeModeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SceneTemplateModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SceneViewModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ShaderBuildSettingsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ShaderCompilationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ShaderFoundryModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SketchUpModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SpriteMaskModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SpriteShapeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SubstanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TerrainModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TextCoreFontEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TextCoreTextEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TextRenderingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TilemapModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TreeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIAutomationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIBuilderModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIElementsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIElementsSamplesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIToolkitAuthoringModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UmbraModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UnityConnectModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.VFXModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.VectorGraphicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.VideoModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.XRModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEditor.Graphs.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\PlaybackEngines\WebGLSupport\UnityEditor.WebGL.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\PlaybackEngines\WindowsStandaloneSupport\UnityEditor.WindowsStandalone.Extensions.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.entities@637eb689ab47\Unity.Entities.CodeGen\Unity.Cecil.Awesome.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.collections@05d86f76b771\Unity.Collections.LowLevel.ILSupport\Unity.Collections.LowLevel.ILSupport.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.ext.nunit@d8c07649098d\net40\unity-custom\nunit.framework.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Collections.Immutable.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.IO.Pipelines.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.TimeProvider.dll + + + E:\Projects\Aetheria\Assets\Allegorithmic\Plugins\Substance\Substance Engine\Editor\Substance.Editor.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\R3.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Networking.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Argon2.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.SecureArray.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Runtime.CompilerServices.Unsafe.dll + + + E:\Projects\Aetheria\Assets\Allegorithmic\Plugins\Substance\Substance Source\Editor\SourceUnityLib.dll + + + E:\Projects\Aetheria\Assets\Allegorithmic\Plugins\Substance.Game.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\ConcurrentCollections.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Encodings.Web.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.NET.StringTools.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.collections@05d86f76b771\Unity.Collections.Tests\System.IO.Hashing\System.IO.Hashing.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.dll + + + E:\Projects\Aetheria\Assets\Plugins\QuickGraph.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.nuget.mono-cecil@ecb9724e46ff\Mono.Cecil.dll + + + E:\Projects\Aetheria\Assets\Plugins\Remotion.Linq.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.AsyncInterfaces.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Threading.Channels.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Logging.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Mesh.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Json.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Blake2b.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.MessagePack.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\LiteNetLib.dll + + + E:\Projects\Aetheria\Assets\Plugins\Microsoft.Extensions.Logging.Abstractions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\mscorlib.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Core.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Runtime.Serialization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.Vectors.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Net.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Microsoft.CSharp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.DataSetExtensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Drawing.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.ComponentModel.Composition.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Transactions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\Microsoft.Win32.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\netstandard.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.AppContext.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Buffers.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Concurrent.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.NonGeneric.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Specialized.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Annotations.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.EventBasedAsync.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.TypeConverter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Console.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Data.Common.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Contracts.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Debug.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.FileVersionInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Process.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.StackTrace.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TextWriterTraceListener.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Tools.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TraceSource.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Drawing.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Dynamic.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Calendars.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Compression.ZipFile.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.DriveInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Watcher.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.IsolatedStorage.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.MemoryMappedFiles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Pipes.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.UnmanagedMemoryStream.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Expressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Queryable.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Memory.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Http.Rtc.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NameResolution.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NetworkInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Ping.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Requests.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Sockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebHeaderCollection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.Client.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ObjectModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.DispatchProxy.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.ILGeneration.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.Lightweight.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Reader.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.ResourceManager.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Writer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.CompilerServices.VisualC.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Handles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.RuntimeInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.WindowsRuntime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Formatters.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Json.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Claims.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Algorithms.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Csp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.X509Certificates.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Principal.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.SecureString.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Duplex.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.NetTcp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.RegularExpressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Overlapped.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Thread.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.ThreadPool.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Timer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ValueTuple.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.ReaderWriter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlSerializer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.XDocument.dll + + + E:\Projects\Aetheria\Library\ScriptAssemblies\UnityEditor.UI.dll + + + E:\Projects\Aetheria\Library\ScriptAssemblies\UnityEngine.UI.dll + + + + + {d6094cfd-26d1-83d1-2366-5762cca2bc33} + GameCult.Aetheria.State.Unity + + + {037dacf0-10f4-20ed-7963-3a1e1ac15b64} + GameCult.Eve.Surface + + + {5cdb0413-71d7-c6b3-4ad9-d8e730b55ffb} + GameCult.Eve.UnityUIToolkit + + + + + diff --git a/GameCult.Aetheria.State.Unity.csproj b/GameCult.Aetheria.State.Unity.csproj new file mode 100644 index 000000000..fd0fff25d --- /dev/null +++ b/GameCult.Aetheria.State.Unity.csproj @@ -0,0 +1,562 @@ + + + + latest + <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package + <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package + true + false + + + Debug + AnyCPU + 10.0.20506 + 2.0 + GameCult.Aetheria.State.Unity + {d6094cfd-26d1-83d1-2366-5762cca2bc33} + {E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + GameCult.Aetheria.State.Unity + v4.7.1 + 512 + . + + + true + full + false + Temp\Bin\Debug\GameCult.Aetheria.State.Unity\ + UNITY_6000_4_2;UNITY_6000_4;UNITY_6000;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;UNITY_2019_4_OR_NEWER;UNITY_2020_1_OR_NEWER;UNITY_2020_2_OR_NEWER;UNITY_2020_3_OR_NEWER;UNITY_2021_1_OR_NEWER;UNITY_2021_2_OR_NEWER;UNITY_2021_3_OR_NEWER;UNITY_2022_1_OR_NEWER;UNITY_2022_2_OR_NEWER;UNITY_2022_3_OR_NEWER;UNITY_2023_1_OR_NEWER;UNITY_2023_2_OR_NEWER;UNITY_2023_3_OR_NEWER;UNITY_6000_0_OR_NEWER;UNITY_6000_1_OR_NEWER;UNITY_6000_2_OR_NEWER;UNITY_6000_3_OR_NEWER;UNITY_6000_4_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AR;ENABLE_AUDIO;ENABLE_AUDIO_SCRIPTABLE_PIPELINE;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_EVENT_QUEUE;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_VIRTUALTEXTURING;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_UNITY_CONSENT;ENABLE_UNITY_CLOUD_IDENTIFIERS;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_NATIVE_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_EDITOR_GAME_SERVICES;ENABLE_UNITY_GAME_SERVICES_ANALYTICS_SUPPORT;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;ENABLE_MANAGED_UNITYTLS;INCLUDE_DYNAMIC_GI;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_MARSHALLING_TESTS;ENABLE_VIDEO;ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK;ENABLE_ACCELERATOR_CLIENT_DEBUGGING;ENABLE_ACCESSIBILITY_SCREEN_READER;TEXTCORE_1_0_OR_NEWER;EDITOR_ONLY_NAVMESH_BUILDER_DEPRECATED;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_NVIDIA;ENABLE_AMD;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLOUD_SERVICES_ENGINE_DIAGNOSTICS;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;PLATFORM_UPDATES_TIME_OUTSIDE_OF_PLAYER_LOOP;GFXDEVICE_WAITFOREVENT_MESSAGEPUMP;PLATFORM_USES_EXPLICIT_MEMORY_MANAGER_INITIALIZER;PLATFORM_SUPPORTS_WAIT_FOR_PRESENTATION;PLATFORM_SUPPORTS_SPLIT_GRAPHICS_JOBS;ENABLE_MONO;NET_4_6;NET_UNITY_4_8;ENABLE_PROFILER;ENABLE_PROFILER_ASSISTANT_INTEGRATION;DEBUG;TRACE;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_INPUT_SYSTEM;TEXTCORE_FONT_ENGINE_1_5_OR_NEWER;TEXTCORE_TEXT_ENGINE_1_5_OR_NEWER;TEXTCORE_FONT_ENGINE_1_6_OR_NEWER;UNITY_POST_PROCESSING_STACK_V2;TEST_MSAGL;REPORTING;PERSISTENCE;SUBSTANCE_PLUGIN_ENABLED;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + prompt + 4 + 0169,0649 + False + False + + + true + true + false + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\ConcurrentCollections.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.MessagePack.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Logging.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Mesh.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Networking.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Argon2.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Blake2b.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.SecureArray.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\LiteNetLib.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.AsyncInterfaces.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.TimeProvider.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.NET.StringTools.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\MessagePack.Annotations.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\MessagePack.CultMesh.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\R3.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Collections.Immutable.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.IO.Pipelines.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Encodings.Web.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Json.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Threading.Channels.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\mscorlib.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Core.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Runtime.Serialization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.Vectors.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Net.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Microsoft.CSharp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.DataSetExtensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Drawing.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.ComponentModel.Composition.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Transactions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\Microsoft.Win32.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\netstandard.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.AppContext.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Buffers.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Concurrent.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.NonGeneric.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Specialized.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Annotations.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.EventBasedAsync.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.TypeConverter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Console.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Data.Common.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Contracts.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Debug.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.FileVersionInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Process.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.StackTrace.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TextWriterTraceListener.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Tools.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TraceSource.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Drawing.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Dynamic.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Calendars.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Compression.ZipFile.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.DriveInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Watcher.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.IsolatedStorage.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.MemoryMappedFiles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Pipes.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.UnmanagedMemoryStream.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Expressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Queryable.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Memory.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Http.Rtc.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NameResolution.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NetworkInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Ping.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Requests.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Sockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebHeaderCollection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.Client.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ObjectModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.DispatchProxy.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.ILGeneration.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.Lightweight.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Reader.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.ResourceManager.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Writer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.CompilerServices.VisualC.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Handles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.RuntimeInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.WindowsRuntime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Formatters.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Json.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Claims.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Algorithms.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Csp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.X509Certificates.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Principal.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.SecureString.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Duplex.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.NetTcp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.RegularExpressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Overlapped.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Thread.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.ThreadPool.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Timer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ValueTuple.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.ReaderWriter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlSerializer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.XDocument.dll + + + + + {037dacf0-10f4-20ed-7963-3a1e1ac15b64} + GameCult.Eve.Surface + + + + + diff --git a/GameCult.Brokkr.Editor.csproj b/GameCult.Brokkr.Editor.csproj new file mode 100644 index 000000000..c92145cc4 --- /dev/null +++ b/GameCult.Brokkr.Editor.csproj @@ -0,0 +1,954 @@ + + + + latest + <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package + <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package + true + false + + + Debug + AnyCPU + 10.0.20506 + 2.0 + GameCult.Brokkr.Editor + {f041e9dc-dc28-1d6c-75a3-824162b4a1ae} + {E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + GameCult.Brokkr.Editor + v4.7.1 + 512 + . + + + true + full + false + Temp\Bin\Debug\GameCult.Brokkr.Editor\ + UNITY_6000_4_2;UNITY_6000_4;UNITY_6000;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;UNITY_2019_4_OR_NEWER;UNITY_2020_1_OR_NEWER;UNITY_2020_2_OR_NEWER;UNITY_2020_3_OR_NEWER;UNITY_2021_1_OR_NEWER;UNITY_2021_2_OR_NEWER;UNITY_2021_3_OR_NEWER;UNITY_2022_1_OR_NEWER;UNITY_2022_2_OR_NEWER;UNITY_2022_3_OR_NEWER;UNITY_2023_1_OR_NEWER;UNITY_2023_2_OR_NEWER;UNITY_2023_3_OR_NEWER;UNITY_6000_0_OR_NEWER;UNITY_6000_1_OR_NEWER;UNITY_6000_2_OR_NEWER;UNITY_6000_3_OR_NEWER;UNITY_6000_4_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AR;ENABLE_AUDIO;ENABLE_AUDIO_SCRIPTABLE_PIPELINE;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_EVENT_QUEUE;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_VIRTUALTEXTURING;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_UNITY_CONSENT;ENABLE_UNITY_CLOUD_IDENTIFIERS;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_NATIVE_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_EDITOR_GAME_SERVICES;ENABLE_UNITY_GAME_SERVICES_ANALYTICS_SUPPORT;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;ENABLE_MANAGED_UNITYTLS;INCLUDE_DYNAMIC_GI;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_MARSHALLING_TESTS;ENABLE_VIDEO;ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK;ENABLE_ACCELERATOR_CLIENT_DEBUGGING;ENABLE_ACCESSIBILITY_SCREEN_READER;TEXTCORE_1_0_OR_NEWER;EDITOR_ONLY_NAVMESH_BUILDER_DEPRECATED;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_NVIDIA;ENABLE_AMD;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLOUD_SERVICES_ENGINE_DIAGNOSTICS;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;PLATFORM_UPDATES_TIME_OUTSIDE_OF_PLAYER_LOOP;GFXDEVICE_WAITFOREVENT_MESSAGEPUMP;PLATFORM_USES_EXPLICIT_MEMORY_MANAGER_INITIALIZER;PLATFORM_SUPPORTS_WAIT_FOR_PRESENTATION;PLATFORM_SUPPORTS_SPLIT_GRAPHICS_JOBS;ENABLE_MONO;NET_4_6;NET_UNITY_4_8;ENABLE_PROFILER;ENABLE_PROFILER_ASSISTANT_INTEGRATION;DEBUG;TRACE;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_INPUT_SYSTEM;TEXTCORE_FONT_ENGINE_1_5_OR_NEWER;TEXTCORE_TEXT_ENGINE_1_5_OR_NEWER;TEXTCORE_FONT_ENGINE_1_6_OR_NEWER;UNITY_POST_PROCESSING_STACK_V2;TEST_MSAGL;REPORTING;PERSISTENCE;SUBSTANCE_PLUGIN_ENABLED;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;UNITY_EDITOR_ONLY_COMPILATION + prompt + 4 + 0169,0649 + False + False + + + true + true + false + false + false + + + + + + + + + + + + + + + + + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\Unity.Scripting.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AMDModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ARModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AccessibilityModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AdaptivePerformanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AndroidJNIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AnimationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AssetBundleModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AudioModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ClothModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ClusterInputModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ClusterRendererModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ContentLoadModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.CoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.CrashReportingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.DSPGraphModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.DirectorModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GameCenterModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GraphicsStateCollectionSerializerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GridModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.HierarchyModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.HierarchyCoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.HotReloadModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.IMGUIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.IdentifiersModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ImageConversionModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InputModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InputForUIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InputLegacyModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InsightsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.JSONSerializeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.LocalizationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.MarshallingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.MultiplayerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.NVIDIAModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ParticleSystemModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PerformanceReportingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PhysicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.Physics2DModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PhysicsBackendPhysXModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PropertiesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.RenderAs2DModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ScreenCaptureModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ScriptingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ShaderVariantAnalyticsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SharedInternalsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SpriteMaskModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SpriteShapeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.StreamingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SubstanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SubsystemsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TLSModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TerrainModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TerrainPhysicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextCoreFontEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextCoreTextEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextRenderingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TilemapModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UIElementsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UmbraModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityAnalyticsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityAnalyticsCommonModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityConnectModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityConsentModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityCurlModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestAudioModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestTextureModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestWWWModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VFXModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VRModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VectorGraphicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VehiclesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VideoModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VirtualTexturingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.WindModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.XRModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.AccessibilityModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.AdaptivePerformanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.AssetComplianceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.BuildProfileModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ClothModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.CoreBusinessMetricsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.CoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.DeviceSimulatorModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.DiagnosticsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.EditorToolbarModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.EmbreeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GraphToolkitModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GraphViewModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GraphicsStateCollectionSerializerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GridAndSnapModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GridModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.HierarchyModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.MediaModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.MultiplayerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.Physics2DModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PhysicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PlayModeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PresetsUIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ProjectAuditorModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PropertiesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.QuickInstallModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.QuickSearchModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SafeModeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SceneTemplateModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SceneViewModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ShaderBuildSettingsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ShaderCompilationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ShaderFoundryModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SketchUpModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SpriteMaskModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SpriteShapeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SubstanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TerrainModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TextCoreFontEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TextCoreTextEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TextRenderingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TilemapModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TreeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIAutomationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIBuilderModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIElementsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIElementsSamplesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIToolkitAuthoringModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UmbraModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UnityConnectModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.VFXModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.VectorGraphicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.VideoModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.XRModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEditor.Graphs.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\PlaybackEngines\WebGLSupport\UnityEditor.WebGL.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\PlaybackEngines\WindowsStandaloneSupport\UnityEditor.WindowsStandalone.Extensions.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.ext.nunit@d8c07649098d\net40\unity-custom\nunit.framework.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\ConcurrentCollections.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.MessagePack.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Logging.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Mesh.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Networking.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Argon2.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Blake2b.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.SecureArray.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\LiteNetLib.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.AsyncInterfaces.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.TimeProvider.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.NET.StringTools.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\MessagePack.Annotations.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\MessagePack.CultMesh.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\R3.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Collections.Immutable.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.IO.Pipelines.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Encodings.Web.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Json.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Threading.Channels.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\mscorlib.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Core.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Runtime.Serialization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.Vectors.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Net.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Microsoft.CSharp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.DataSetExtensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Drawing.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.ComponentModel.Composition.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Transactions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\Microsoft.Win32.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\netstandard.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.AppContext.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Buffers.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Concurrent.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.NonGeneric.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Specialized.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Annotations.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.EventBasedAsync.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.TypeConverter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Console.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Data.Common.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Contracts.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Debug.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.FileVersionInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Process.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.StackTrace.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TextWriterTraceListener.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Tools.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TraceSource.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Drawing.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Dynamic.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Calendars.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Compression.ZipFile.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.DriveInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Watcher.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.IsolatedStorage.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.MemoryMappedFiles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Pipes.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.UnmanagedMemoryStream.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Expressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Queryable.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Memory.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Http.Rtc.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NameResolution.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NetworkInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Ping.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Requests.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Sockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebHeaderCollection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.Client.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ObjectModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.DispatchProxy.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.ILGeneration.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.Lightweight.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Reader.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.ResourceManager.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Writer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.CompilerServices.VisualC.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Handles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.RuntimeInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.WindowsRuntime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Formatters.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Json.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Claims.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Algorithms.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Csp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.X509Certificates.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Principal.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.SecureString.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Duplex.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.NetTcp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.RegularExpressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Overlapped.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Thread.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.ThreadPool.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Timer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ValueTuple.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.ReaderWriter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlSerializer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.XDocument.dll + + + E:\Projects\Aetheria\Library\ScriptAssemblies\UnityEditor.UI.dll + + + E:\Projects\Aetheria\Library\ScriptAssemblies\UnityEngine.UI.dll + + + E:\Projects\Aetheria\Library\ScriptAssemblies\UnityEngine.TestRunner.dll + + + E:\Projects\Aetheria\Library\ScriptAssemblies\UnityEditor.TestRunner.dll + + + + + {fc28bcbe-9ef4-444f-c1c9-dde8a3a864ad} + GameCult.Brokkr + + + + + diff --git a/GameCult.Brokkr.csproj b/GameCult.Brokkr.csproj new file mode 100644 index 000000000..a9e5a02e9 --- /dev/null +++ b/GameCult.Brokkr.csproj @@ -0,0 +1,926 @@ + + + + latest + <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package + <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package + true + false + + + Debug + AnyCPU + 10.0.20506 + 2.0 + GameCult.Brokkr + {fc28bcbe-9ef4-444f-c1c9-dde8a3a864ad} + {E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + GameCult.Brokkr + v4.7.1 + 512 + . + + + true + full + false + Temp\Bin\Debug\GameCult.Brokkr\ + UNITY_6000_4_2;UNITY_6000_4;UNITY_6000;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;UNITY_2019_4_OR_NEWER;UNITY_2020_1_OR_NEWER;UNITY_2020_2_OR_NEWER;UNITY_2020_3_OR_NEWER;UNITY_2021_1_OR_NEWER;UNITY_2021_2_OR_NEWER;UNITY_2021_3_OR_NEWER;UNITY_2022_1_OR_NEWER;UNITY_2022_2_OR_NEWER;UNITY_2022_3_OR_NEWER;UNITY_2023_1_OR_NEWER;UNITY_2023_2_OR_NEWER;UNITY_2023_3_OR_NEWER;UNITY_6000_0_OR_NEWER;UNITY_6000_1_OR_NEWER;UNITY_6000_2_OR_NEWER;UNITY_6000_3_OR_NEWER;UNITY_6000_4_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AR;ENABLE_AUDIO;ENABLE_AUDIO_SCRIPTABLE_PIPELINE;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_EVENT_QUEUE;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_VIRTUALTEXTURING;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_UNITY_CONSENT;ENABLE_UNITY_CLOUD_IDENTIFIERS;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_NATIVE_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_EDITOR_GAME_SERVICES;ENABLE_UNITY_GAME_SERVICES_ANALYTICS_SUPPORT;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;ENABLE_MANAGED_UNITYTLS;INCLUDE_DYNAMIC_GI;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_MARSHALLING_TESTS;ENABLE_VIDEO;ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK;ENABLE_ACCELERATOR_CLIENT_DEBUGGING;ENABLE_ACCESSIBILITY_SCREEN_READER;TEXTCORE_1_0_OR_NEWER;EDITOR_ONLY_NAVMESH_BUILDER_DEPRECATED;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_NVIDIA;ENABLE_AMD;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLOUD_SERVICES_ENGINE_DIAGNOSTICS;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;PLATFORM_UPDATES_TIME_OUTSIDE_OF_PLAYER_LOOP;GFXDEVICE_WAITFOREVENT_MESSAGEPUMP;PLATFORM_USES_EXPLICIT_MEMORY_MANAGER_INITIALIZER;PLATFORM_SUPPORTS_WAIT_FOR_PRESENTATION;PLATFORM_SUPPORTS_SPLIT_GRAPHICS_JOBS;ENABLE_MONO;NET_4_6;NET_UNITY_4_8;ENABLE_PROFILER;ENABLE_PROFILER_ASSISTANT_INTEGRATION;DEBUG;TRACE;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_INPUT_SYSTEM;TEXTCORE_FONT_ENGINE_1_5_OR_NEWER;TEXTCORE_TEXT_ENGINE_1_5_OR_NEWER;TEXTCORE_FONT_ENGINE_1_6_OR_NEWER;UNITY_POST_PROCESSING_STACK_V2;TEST_MSAGL;REPORTING;PERSISTENCE;SUBSTANCE_PLUGIN_ENABLED;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + prompt + 4 + 0169,0649 + False + False + + + true + true + false + false + false + + + + + + + + + + + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\Unity.Scripting.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ARModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AccessibilityModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AdaptivePerformanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AndroidJNIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AnimationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AssetBundleModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AudioModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ClothModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ClusterInputModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ClusterRendererModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ContentLoadModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.CoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.CrashReportingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.DSPGraphModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.DirectorModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GameCenterModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GraphicsStateCollectionSerializerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GridModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.HierarchyCoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.HotReloadModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.IMGUIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.IdentifiersModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ImageConversionModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InputModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InputForUIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InputLegacyModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InsightsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.JSONSerializeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.LocalizationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.MarshallingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.MultiplayerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ParticleSystemModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PerformanceReportingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PhysicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.Physics2DModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PhysicsBackendPhysXModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PropertiesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.RenderAs2DModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ScreenCaptureModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ScriptingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ShaderVariantAnalyticsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SharedInternalsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SpriteMaskModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SpriteShapeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.StreamingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SubstanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SubsystemsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TLSModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TerrainModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TerrainPhysicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextCoreFontEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextCoreTextEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextRenderingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TilemapModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UIElementsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UmbraModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityAnalyticsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityAnalyticsCommonModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityConnectModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityConsentModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityCurlModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestAudioModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestTextureModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestWWWModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VFXModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VRModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VectorGraphicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VehiclesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VideoModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VirtualTexturingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.WindModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.XRModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.AccessibilityModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.AdaptivePerformanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.AssetComplianceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.BuildProfileModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ClothModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.CoreBusinessMetricsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.CoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.DeviceSimulatorModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.DiagnosticsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.EditorToolbarModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.EmbreeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GraphToolkitModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GraphViewModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GraphicsStateCollectionSerializerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GridAndSnapModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GridModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.HierarchyModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.MediaModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.MultiplayerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.Physics2DModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PhysicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PlayModeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PresetsUIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ProjectAuditorModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PropertiesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.QuickInstallModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.QuickSearchModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SafeModeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SceneTemplateModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SceneViewModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ShaderBuildSettingsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ShaderCompilationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ShaderFoundryModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SketchUpModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SpriteMaskModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SpriteShapeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SubstanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TerrainModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TextCoreFontEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TextCoreTextEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TextRenderingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TilemapModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TreeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIAutomationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIBuilderModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIElementsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIElementsSamplesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIToolkitAuthoringModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UmbraModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UnityConnectModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.VFXModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.VectorGraphicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.VideoModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.XRModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEditor.Graphs.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\PlaybackEngines\WebGLSupport\UnityEditor.WebGL.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\PlaybackEngines\WindowsStandaloneSupport\UnityEditor.WindowsStandalone.Extensions.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\ConcurrentCollections.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.MessagePack.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Logging.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Mesh.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Networking.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Argon2.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Blake2b.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.SecureArray.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\LiteNetLib.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.AsyncInterfaces.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.TimeProvider.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.NET.StringTools.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\MessagePack.Annotations.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\MessagePack.CultMesh.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\R3.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Collections.Immutable.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.IO.Pipelines.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Encodings.Web.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Json.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Threading.Channels.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\mscorlib.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Core.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Runtime.Serialization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.Vectors.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Net.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Microsoft.CSharp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.DataSetExtensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Drawing.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.ComponentModel.Composition.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Transactions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\Microsoft.Win32.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\netstandard.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.AppContext.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Buffers.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Concurrent.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.NonGeneric.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Specialized.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Annotations.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.EventBasedAsync.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.TypeConverter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Console.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Data.Common.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Contracts.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Debug.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.FileVersionInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Process.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.StackTrace.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TextWriterTraceListener.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Tools.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TraceSource.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Drawing.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Dynamic.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Calendars.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Compression.ZipFile.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.DriveInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Watcher.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.IsolatedStorage.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.MemoryMappedFiles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Pipes.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.UnmanagedMemoryStream.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Expressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Queryable.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Memory.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Http.Rtc.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NameResolution.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NetworkInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Ping.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Requests.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Sockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebHeaderCollection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.Client.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ObjectModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.DispatchProxy.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.ILGeneration.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.Lightweight.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Reader.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.ResourceManager.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Writer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.CompilerServices.VisualC.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Handles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.RuntimeInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.WindowsRuntime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Formatters.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Json.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Claims.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Algorithms.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Csp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.X509Certificates.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Principal.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.SecureString.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Duplex.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.NetTcp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.RegularExpressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Overlapped.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Thread.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.ThreadPool.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Timer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ValueTuple.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.ReaderWriter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlSerializer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.XDocument.dll + + + E:\Projects\Aetheria\Library\ScriptAssemblies\UnityEditor.UI.dll + + + E:\Projects\Aetheria\Library\ScriptAssemblies\UnityEngine.UI.dll + + + + + + + diff --git a/GameCult.Eve.Surface.csproj b/GameCult.Eve.Surface.csproj new file mode 100644 index 000000000..405430e28 --- /dev/null +++ b/GameCult.Eve.Surface.csproj @@ -0,0 +1,529 @@ + + + + latest + <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package + <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package + true + false + + + Debug + AnyCPU + 10.0.20506 + 2.0 + GameCult.Eve.Surface + {037dacf0-10f4-20ed-7963-3a1e1ac15b64} + {E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + GameCult.Eve.Surface + v4.7.1 + 512 + . + + + true + full + false + Temp\Bin\Debug\GameCult.Eve.Surface\ + UNITY_6000_4_2;UNITY_6000_4;UNITY_6000;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;UNITY_2019_4_OR_NEWER;UNITY_2020_1_OR_NEWER;UNITY_2020_2_OR_NEWER;UNITY_2020_3_OR_NEWER;UNITY_2021_1_OR_NEWER;UNITY_2021_2_OR_NEWER;UNITY_2021_3_OR_NEWER;UNITY_2022_1_OR_NEWER;UNITY_2022_2_OR_NEWER;UNITY_2022_3_OR_NEWER;UNITY_2023_1_OR_NEWER;UNITY_2023_2_OR_NEWER;UNITY_2023_3_OR_NEWER;UNITY_6000_0_OR_NEWER;UNITY_6000_1_OR_NEWER;UNITY_6000_2_OR_NEWER;UNITY_6000_3_OR_NEWER;UNITY_6000_4_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AR;ENABLE_AUDIO;ENABLE_AUDIO_SCRIPTABLE_PIPELINE;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_EVENT_QUEUE;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_VIRTUALTEXTURING;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_UNITY_CONSENT;ENABLE_UNITY_CLOUD_IDENTIFIERS;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_NATIVE_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_EDITOR_GAME_SERVICES;ENABLE_UNITY_GAME_SERVICES_ANALYTICS_SUPPORT;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;ENABLE_MANAGED_UNITYTLS;INCLUDE_DYNAMIC_GI;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_MARSHALLING_TESTS;ENABLE_VIDEO;ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK;ENABLE_ACCELERATOR_CLIENT_DEBUGGING;ENABLE_ACCESSIBILITY_SCREEN_READER;TEXTCORE_1_0_OR_NEWER;EDITOR_ONLY_NAVMESH_BUILDER_DEPRECATED;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_NVIDIA;ENABLE_AMD;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLOUD_SERVICES_ENGINE_DIAGNOSTICS;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;PLATFORM_UPDATES_TIME_OUTSIDE_OF_PLAYER_LOOP;GFXDEVICE_WAITFOREVENT_MESSAGEPUMP;PLATFORM_USES_EXPLICIT_MEMORY_MANAGER_INITIALIZER;PLATFORM_SUPPORTS_WAIT_FOR_PRESENTATION;PLATFORM_SUPPORTS_SPLIT_GRAPHICS_JOBS;ENABLE_MONO;NET_4_6;NET_UNITY_4_8;ENABLE_PROFILER;ENABLE_PROFILER_ASSISTANT_INTEGRATION;DEBUG;TRACE;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_INPUT_SYSTEM;TEXTCORE_FONT_ENGINE_1_5_OR_NEWER;TEXTCORE_TEXT_ENGINE_1_5_OR_NEWER;TEXTCORE_FONT_ENGINE_1_6_OR_NEWER;UNITY_POST_PROCESSING_STACK_V2;TEST_MSAGL;REPORTING;PERSISTENCE;SUBSTANCE_PLUGIN_ENABLED;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + prompt + 4 + 0169,0649 + False + False + + + true + true + false + false + false + + + + + + + + + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.entities@637eb689ab47\Unity.Entities.CodeGen\Unity.Cecil.Awesome.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.collections@05d86f76b771\Unity.Collections.LowLevel.ILSupport\Unity.Collections.LowLevel.ILSupport.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.ext.nunit@d8c07649098d\net40\unity-custom\nunit.framework.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Collections.Immutable.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.IO.Pipelines.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.TimeProvider.dll + + + E:\Projects\Aetheria\Assets\Allegorithmic\Plugins\Substance\Substance Engine\Editor\Substance.Editor.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\R3.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Networking.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Argon2.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.SecureArray.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Runtime.CompilerServices.Unsafe.dll + + + E:\Projects\Aetheria\Assets\Allegorithmic\Plugins\Substance\Substance Source\Editor\SourceUnityLib.dll + + + E:\Projects\Aetheria\Assets\Allegorithmic\Plugins\Substance.Game.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\ConcurrentCollections.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Encodings.Web.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.NET.StringTools.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.collections@05d86f76b771\Unity.Collections.Tests\System.IO.Hashing\System.IO.Hashing.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.dll + + + E:\Projects\Aetheria\Assets\Plugins\QuickGraph.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.nuget.mono-cecil@ecb9724e46ff\Mono.Cecil.dll + + + E:\Projects\Aetheria\Assets\Plugins\Remotion.Linq.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.AsyncInterfaces.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Threading.Channels.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Logging.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Mesh.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Json.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Blake2b.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.MessagePack.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\LiteNetLib.dll + + + E:\Projects\Aetheria\Assets\Plugins\Microsoft.Extensions.Logging.Abstractions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\mscorlib.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Core.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Runtime.Serialization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.Vectors.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Net.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Microsoft.CSharp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.DataSetExtensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Drawing.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.ComponentModel.Composition.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Transactions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\Microsoft.Win32.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\netstandard.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.AppContext.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Buffers.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Concurrent.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.NonGeneric.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Specialized.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Annotations.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.EventBasedAsync.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.TypeConverter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Console.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Data.Common.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Contracts.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Debug.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.FileVersionInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Process.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.StackTrace.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TextWriterTraceListener.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Tools.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TraceSource.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Drawing.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Dynamic.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Calendars.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Compression.ZipFile.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.DriveInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Watcher.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.IsolatedStorage.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.MemoryMappedFiles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Pipes.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.UnmanagedMemoryStream.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Expressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Queryable.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Memory.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Http.Rtc.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NameResolution.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NetworkInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Ping.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Requests.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Sockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebHeaderCollection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.Client.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ObjectModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.DispatchProxy.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.ILGeneration.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.Lightweight.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Reader.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.ResourceManager.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Writer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.CompilerServices.VisualC.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Handles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.RuntimeInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.WindowsRuntime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Formatters.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Json.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Claims.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Algorithms.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Csp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.X509Certificates.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Principal.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.SecureString.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Duplex.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.NetTcp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.RegularExpressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Overlapped.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Thread.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.ThreadPool.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Timer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ValueTuple.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.ReaderWriter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlSerializer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.XDocument.dll + + + + + diff --git a/GameCult.Eve.UnityUIToolkit.csproj b/GameCult.Eve.UnityUIToolkit.csproj new file mode 100644 index 000000000..a8ec110b3 --- /dev/null +++ b/GameCult.Eve.UnityUIToolkit.csproj @@ -0,0 +1,959 @@ + + + + latest + <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package + <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package + true + false + + + Debug + AnyCPU + 10.0.20506 + 2.0 + GameCult.Eve.UnityUIToolkit + {5cdb0413-71d7-c6b3-4ad9-d8e730b55ffb} + {E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + GameCult.Eve.UnityUIToolkit + v4.7.1 + 512 + . + + + true + full + false + Temp\Bin\Debug\GameCult.Eve.UnityUIToolkit\ + UNITY_6000_4_2;UNITY_6000_4;UNITY_6000;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;UNITY_2019_4_OR_NEWER;UNITY_2020_1_OR_NEWER;UNITY_2020_2_OR_NEWER;UNITY_2020_3_OR_NEWER;UNITY_2021_1_OR_NEWER;UNITY_2021_2_OR_NEWER;UNITY_2021_3_OR_NEWER;UNITY_2022_1_OR_NEWER;UNITY_2022_2_OR_NEWER;UNITY_2022_3_OR_NEWER;UNITY_2023_1_OR_NEWER;UNITY_2023_2_OR_NEWER;UNITY_2023_3_OR_NEWER;UNITY_6000_0_OR_NEWER;UNITY_6000_1_OR_NEWER;UNITY_6000_2_OR_NEWER;UNITY_6000_3_OR_NEWER;UNITY_6000_4_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AR;ENABLE_AUDIO;ENABLE_AUDIO_SCRIPTABLE_PIPELINE;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_EVENT_QUEUE;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_VIRTUALTEXTURING;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_UNITY_CONSENT;ENABLE_UNITY_CLOUD_IDENTIFIERS;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_NATIVE_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_EDITOR_GAME_SERVICES;ENABLE_UNITY_GAME_SERVICES_ANALYTICS_SUPPORT;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;ENABLE_MANAGED_UNITYTLS;INCLUDE_DYNAMIC_GI;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_MARSHALLING_TESTS;ENABLE_VIDEO;ENABLE_NAVIGATION_OFFMESHLINK_TO_NAVMESHLINK;ENABLE_ACCELERATOR_CLIENT_DEBUGGING;ENABLE_ACCESSIBILITY_SCREEN_READER;TEXTCORE_1_0_OR_NEWER;EDITOR_ONLY_NAVMESH_BUILDER_DEPRECATED;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_NVIDIA;ENABLE_AMD;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLOUD_SERVICES_ENGINE_DIAGNOSTICS;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;PLATFORM_UPDATES_TIME_OUTSIDE_OF_PLAYER_LOOP;GFXDEVICE_WAITFOREVENT_MESSAGEPUMP;PLATFORM_USES_EXPLICIT_MEMORY_MANAGER_INITIALIZER;PLATFORM_SUPPORTS_WAIT_FOR_PRESENTATION;PLATFORM_SUPPORTS_SPLIT_GRAPHICS_JOBS;ENABLE_MONO;NET_4_6;NET_UNITY_4_8;ENABLE_PROFILER;ENABLE_PROFILER_ASSISTANT_INTEGRATION;DEBUG;TRACE;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_INPUT_SYSTEM;TEXTCORE_FONT_ENGINE_1_5_OR_NEWER;TEXTCORE_TEXT_ENGINE_1_5_OR_NEWER;TEXTCORE_FONT_ENGINE_1_6_OR_NEWER;UNITY_POST_PROCESSING_STACK_V2;TEST_MSAGL;REPORTING;PERSISTENCE;SUBSTANCE_PLUGIN_ENABLED;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + prompt + 4 + 0169,0649 + False + False + + + true + true + false + false + false + + + + + + + + + + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\Unity.Scripting.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ARModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AccessibilityModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AdaptivePerformanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AndroidJNIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AnimationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AssetBundleModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.AudioModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ClothModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ClusterInputModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ClusterRendererModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ContentLoadModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.CoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.CrashReportingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.DSPGraphModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.DirectorModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GameCenterModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GraphicsStateCollectionSerializerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.GridModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.HierarchyCoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.HotReloadModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.IMGUIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.IdentifiersModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ImageConversionModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InputModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InputForUIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InputLegacyModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.InsightsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.JSONSerializeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.LocalizationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.MarshallingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.MultiplayerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ParticleSystemModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PerformanceReportingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PhysicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.Physics2DModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PhysicsBackendPhysXModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.PropertiesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.RenderAs2DModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ScreenCaptureModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ScriptingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.ShaderVariantAnalyticsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SharedInternalsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SpriteMaskModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SpriteShapeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.StreamingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SubstanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.SubsystemsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TLSModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TerrainModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TerrainPhysicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextCoreFontEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextCoreTextEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextRenderingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.TilemapModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UIElementsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UmbraModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityAnalyticsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityAnalyticsCommonModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityConnectModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityConsentModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityCurlModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestAudioModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestTextureModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestWWWModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VFXModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VRModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VectorGraphicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VehiclesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VideoModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.VirtualTexturingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.WindModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.XRModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.AccessibilityModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.AdaptivePerformanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.AssetComplianceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.BuildProfileModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ClothModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.CoreBusinessMetricsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.CoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.DeviceSimulatorModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.DiagnosticsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.EditorToolbarModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.EmbreeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GraphToolkitModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GraphViewModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GraphicsStateCollectionSerializerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GridAndSnapModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.GridModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.HierarchyModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.MediaModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.MultiplayerModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.Physics2DModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PhysicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PlayModeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PresetsUIModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ProjectAuditorModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.PropertiesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.QuickInstallModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.QuickSearchModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SafeModeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SceneTemplateModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SceneViewModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ShaderBuildSettingsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ShaderCompilationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.ShaderFoundryModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SketchUpModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SpriteMaskModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SpriteShapeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.SubstanceModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TerrainModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TextCoreFontEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TextCoreTextEngineModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TextRenderingModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TilemapModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.TreeModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIAutomationModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIBuilderModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIElementsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIElementsSamplesModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UIToolkitAuthoringModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UmbraModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.UnityConnectModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.VFXModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.VectorGraphicsModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.VideoModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.XRModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEditor.Graphs.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\PlaybackEngines\WebGLSupport\UnityEditor.WebGL.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\PlaybackEngines\WindowsStandaloneSupport\UnityEditor.WindowsStandalone.Extensions.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.entities@637eb689ab47\Unity.Entities.CodeGen\Unity.Cecil.Awesome.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.collections@05d86f76b771\Unity.Collections.LowLevel.ILSupport\Unity.Collections.LowLevel.ILSupport.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.ext.nunit@d8c07649098d\net40\unity-custom\nunit.framework.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Collections.Immutable.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.IO.Pipelines.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.TimeProvider.dll + + + E:\Projects\Aetheria\Assets\Allegorithmic\Plugins\Substance\Substance Engine\Editor\Substance.Editor.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\R3.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Networking.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Argon2.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.SecureArray.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Runtime.CompilerServices.Unsafe.dll + + + E:\Projects\Aetheria\Assets\Allegorithmic\Plugins\Substance\Substance Source\Editor\SourceUnityLib.dll + + + E:\Projects\Aetheria\Assets\Allegorithmic\Plugins\Substance.Game.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\ConcurrentCollections.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Encodings.Web.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.NET.StringTools.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.collections@05d86f76b771\Unity.Collections.Tests\System.IO.Hashing\System.IO.Hashing.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.dll + + + E:\Projects\Aetheria\Assets\Plugins\QuickGraph.dll + + + E:\Projects\Aetheria\Library\PackageCache\com.unity.nuget.mono-cecil@ecb9724e46ff\Mono.Cecil.dll + + + E:\Projects\Aetheria\Assets\Plugins\Remotion.Linq.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Microsoft.Bcl.AsyncInterfaces.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Threading.Channels.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Logging.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Mesh.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\System.Text.Json.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\Isopoh.Cryptography.Blake2b.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\GameCult.Caching.MessagePack.dll + + + E:\Projects\Brokkr\surfaces\unity\Packages\com.gamecult.brokkr\Plugins\CultMesh\LiteNetLib.dll + + + E:\Projects\Aetheria\Assets\Plugins\Microsoft.Extensions.Logging.Abstractions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\mscorlib.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Core.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Runtime.Serialization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Xml.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Numerics.Vectors.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Net.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Microsoft.CSharp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Data.DataSetExtensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Drawing.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.IO.Compression.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.ComponentModel.Composition.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\System.Transactions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\Microsoft.Win32.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\netstandard.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.AppContext.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Buffers.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Concurrent.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.NonGeneric.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Collections.Specialized.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Annotations.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.EventBasedAsync.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ComponentModel.TypeConverter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Console.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Data.Common.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Contracts.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Debug.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.FileVersionInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Process.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.StackTrace.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TextWriterTraceListener.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.Tools.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Diagnostics.TraceSource.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Drawing.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Dynamic.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Calendars.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Globalization.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Compression.ZipFile.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.DriveInfo.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.FileSystem.Watcher.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.IsolatedStorage.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.MemoryMappedFiles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.Pipes.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.IO.UnmanagedMemoryStream.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Expressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Linq.Queryable.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Memory.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Http.Rtc.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NameResolution.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.NetworkInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Ping.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Requests.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.Sockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebHeaderCollection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.Client.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Net.WebSockets.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ObjectModel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.DispatchProxy.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.ILGeneration.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Emit.Lightweight.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Reflection.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Reader.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.ResourceManager.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Resources.Writer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.CompilerServices.VisualC.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Handles.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.RuntimeInformation.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.InteropServices.WindowsRuntime.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Numerics.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Formatters.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Json.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Runtime.Serialization.Xml.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Claims.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Algorithms.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Csp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Cryptography.X509Certificates.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.Principal.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Security.SecureString.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Duplex.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Http.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.NetTcp.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Primitives.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ServiceModel.Security.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.Encoding.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Text.RegularExpressions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Overlapped.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Extensions.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Tasks.Parallel.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Thread.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.ThreadPool.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Threading.Timer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.ValueTuple.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.ReaderWriter.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlDocument.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XmlSerializer.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Facades\System.Xml.XPath.XDocument.dll + + + E:\Projects\Aetheria\Library\ScriptAssemblies\UnityEditor.UI.dll + + + E:\Projects\Aetheria\Library\ScriptAssemblies\UnityEngine.UI.dll + + + + + {037dacf0-10f4-20ed-7963-3a1e1ac15b64} + GameCult.Eve.Surface + + + + + diff --git a/MessagePack.SourceDisabled.csproj b/MessagePack.SourceDisabled.csproj new file mode 100644 index 000000000..f59ab5d83 --- /dev/null +++ b/MessagePack.SourceDisabled.csproj @@ -0,0 +1,64 @@ + + + + 9.0 + <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package + <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package + true + false + + + Debug + AnyCPU + 10.0.20506 + 2.0 + + {b916e744-81e2-a0ba-3884-e845f1c5ef88} + {E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + MessagePack.SourceDisabled + v4.7.1 + 512 + . + + + true + full + false + Temp\Bin\Debug\Unity.Rider.Editor\ + UNITY_EDITOR + prompt + 4 + 0169,0649 + False + False + + + true + true + false + false + false + + + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.CoreModule.dll + + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEditor.dll + + + + + diff --git a/Packages/manifest.json b/Packages/manifest.json index 9f00b8637..09418fd95 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,26 +1,31 @@ { "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.gamecult.brokkr": "file:../../Brokkr/surfaces/unity/Packages/com.gamecult.brokkr/", + "com.unity.ide.rider": "3.0.39", + "com.unity.ide.visualstudio": "2.0.27", + "com.unity.inputsystem": "1.19.0", + "com.unity.entities.graphics": "1.4.20", + "org.gamecult.cultmath": "file:../../CultMath", + "com.unity.mathematics": "1.3.3", + "com.unity.multiplayer.center": "1.0.1", + "com.unity.postprocessing": "3.5.4", + "com.unity.recorder": "5.1.5", + "com.unity.render-pipelines.universal": "17.2.0", + "com.unity.test-framework": "1.6.0", + "com.unity.timeline": "1.8.12", + "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.eve.surface": "file:../../Eve/packages/org.gamecult.eve.surface", "org.gamecult.eve.unity-uitoolkit": "file:../../Eve/packages/org.gamecult.eve.unity-uitoolkit", - "com.unity.2d.sprite": "1.0.0", - "com.unity.2d.tilemap": "1.0.0", - "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.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": "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", @@ -47,6 +52,7 @@ "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", diff --git a/Packages/org.gamecult.aetheria.eve-runtime/README.md b/Packages/org.gamecult.aetheria.eve-runtime/README.md index da5a88d53..5f87aac37 100644 --- a/Packages/org.gamecult.aetheria.eve-runtime/README.md +++ b/Packages/org.gamecult.aetheria.eve-runtime/README.md @@ -9,13 +9,15 @@ CultCache state into Unity UI Toolkit. - 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 queues renderer-emitted commands as typed `.cc.eve.pending` command - envelopes for Aetheria's provider-owned command bridge. +- 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.operations`, so the -provider-owned operations surface is present at runtime without a hand-wired -scene object. Set `AETHERIA_EVE_SURFACE_ID` to mount a different surface, set +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 (`AETHERIA_EVE_STATE_PATH` still works as a legacy fallback), or disable the automatic mount with `AETHERIA_DISABLE_EVE_RUNTIME_BOOTSTRAP=true` or diff --git a/Packages/org.gamecult.aetheria.eve-runtime/README.md.meta b/Packages/org.gamecult.aetheria.eve-runtime/README.md.meta new file mode 100644 index 000000000..5b57d765b --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e1c93ed85db7ef24abd1a24ca16abe4e +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime.meta b/Packages/org.gamecult.aetheria.eve-runtime/Runtime.meta new file mode 100644 index 000000000..5e63775dd --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2860def02295c5045af9ba820ea8e696 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeBootstrap.cs b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeBootstrap.cs index 84c1843b6..ece2222f4 100644 --- a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeBootstrap.cs +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeBootstrap.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using GameCult.Aetheria.State.Unity; using UnityEngine; using UnityEngine.UIElements; @@ -9,7 +10,7 @@ namespace GameCult.Aetheria.EveRuntime { public static class AetheriaEveRuntimeBootstrap { - public const string DefaultSurfaceId = "aetheria.operations"; + 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 DisableCommandLineSwitch = "--aetheria-disable-eve-runtime-bootstrap"; diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeBootstrap.cs.meta b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeBootstrap.cs.meta new file mode 100644 index 000000000..015e7246b --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeBootstrap.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: dfb126623705ee04c9747f63b945c5f1 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs index a36cf7d58..824ddae0c 100644 --- a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs @@ -2,7 +2,6 @@ using System; using System.IO; using GameCult.Aetheria.State.Unity; using GameCult.Eve.Surface; -using GameCult.Eve.UnityUIToolkit; using UnityEngine; using UnityEngine.UIElements; @@ -15,7 +14,7 @@ namespace GameCult.Aetheria.EveRuntime public sealed class AetheriaEveSurfacePresenter : MonoBehaviour { [SerializeField] - private string surfaceId = "aetheria.catalog.operator"; + private string surfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId; [SerializeField] private string stateFilePathOverride = ""; @@ -23,7 +22,26 @@ namespace GameCult.Aetheria.EveRuntime [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 static readonly AetheriaEveUnitySurfaceChrome RootOnlyChrome = new AetheriaEveUnitySurfaceChrome + { + UseShell = false, + RootPaddingTop = 0f, + RootAlignItems = Align.Stretch, + RootJustifyContent = Justify.FlexStart, + RootPickingMode = PickingMode.Position + }; public string SurfaceId { @@ -37,6 +55,12 @@ namespace GameCult.Aetheria.EveRuntime set => stateFilePathOverride = value ?? ""; } + public bool RefreshInUpdate + { + get => refreshInUpdate; + set => refreshInUpdate = value; + } + public void Mount() { var document = ResolveDocument(); @@ -64,8 +88,7 @@ namespace GameCult.Aetheria.EveRuntime return; } - var lowerer = new EveUiToolkitSurfaceLowerer(); - root.Add(lowerer.Lower(surface, request => EmitCommand(statePath, request))); + MountSurface(statePath, surface); } private void OnEnable() @@ -74,6 +97,28 @@ namespace GameCult.Aetheria.EveRuntime Mount(); } + 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 = AetheriaRuntimeStateReader.ReadEveSurface(stateBoot.StateFilePath, surfaceId); + if (surface == null || !ShouldMountSurface(stateBoot.StateFilePath, surface)) + return; + + MountSurface(stateBoot.StateFilePath, surface); + } + private UIDocument ResolveDocument() { if (_document == null) @@ -97,11 +142,52 @@ namespace GameCult.Aetheria.EveRuntime 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 MountSurface(string statePath, EveSurfaceDocument surface) + { + AetheriaEveUnitySurfaceHost.Render( + transform, + ResolveDocument(), + "Aetheria Eve Surface", + surface, + request => EmitCommand(statePath, request), + RootOnlyChrome); + _mountedStatePath = statePath; + _mountedSurfaceId = surface.Surface.Id; + _mountedSurfaceVersion = surface.Version; + _mountedSurfaceUpdatedAtUtc = surface.UpdatedAtUtc ?? ""; + } + private static void EmitCommand(string statePath, EveSurfaceCommandRequest request) { - var envelope = AetheriaRuntimeEveCommandLog.QueueCommand(statePath, request); + if (AetheriaRuntimeDaemonSurfaceCommands.TrySubmit(statePath, request, out var daemonEnvelope)) + { + Debug.Log( + $"Submitted Aetheria daemon operation from Eve surface: {daemonEnvelope!.Kind} {daemonEnvelope.CommandId}"); + return; + } + + if (!AetheriaRuntimeEveCommands.TrySendKnownSurfaceCommand( + statePath, + request, + string.IsNullOrWhiteSpace(request.ClientId) ? "unity-uitoolkit" : request.ClientId, + out var envelope, + out var error)) + { + Debug.LogWarning( + $"Ignored or failed Aetheria Eve command: {request.ProviderId}/{request.SurfaceId}/{request.Command}: {error}"); + return; + } + Debug.Log( - $"Queued Eve command for CultMesh bridge: {envelope.ProviderId}/{envelope.SurfaceId}/{envelope.Command} {envelope.CommandId}"); + $"Submitted Eve command for CultMesh bridge: {envelope!.ProviderId}/{envelope.SurfaceId}/{envelope.Command} {envelope.CommandId}"); } } } diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs.meta b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs.meta new file mode 100644 index 000000000..576dc50fe --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f538568d3b6341c43adeb7e023573756 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySurfaceHost.cs b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySurfaceHost.cs new file mode 100644 index 000000000..28119b205 --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySurfaceHost.cs @@ -0,0 +1,187 @@ +using System; +using GameCult.Aetheria.State.Unity; +using GameCult.Eve.Surface; +using GameCult.Eve.UnityUIToolkit; +using UnityEngine; +using UnityEngine.UIElements; + +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 + { + public static UIDocument Render( + Transform owner, + UIDocument document, + string hostName, + EveSurfaceDocument surface, + Action commandHandler, + AetheriaEveUnitySurfaceChrome chrome, + 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 lowerer = new EveUiToolkitSurfaceLowerer(); + if (chrome.UseShell) + { + var shell = CreateShell(chrome); + root.Add(shell); + shell.Add(lowerer.Lower(surface, commandHandler)); + } + else + { + root.Add(lowerer.Lower(surface, commandHandler)); + } + + return document; + } + + public static UIDocument RenderRuntime( + Transform owner, + UIDocument document, + string hostName, + AetheriaRuntimeSurfaceDocument surface, + Action commandHandler, + AetheriaEveUnitySurfaceChrome chrome, + int sortingOrder = 1000) + { + if (surface == null) + throw new ArgumentNullException(nameof(surface)); + + return Render( + owner, + document, + hostName, + AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument(surface), + commandHandler, + chrome, + 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) + return document; + + var host = new GameObject(string.IsNullOrWhiteSpace(hostName) ? "Aetheria Eve Surface" : hostName); + host.transform.SetParent(owner, false); + host.layer = owner.gameObject.layer; + document = host.AddComponent(); + document.sortingOrder = sortingOrder; + host.SetActive(false); + return document; + } + + 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; + } + } +} diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySurfaceHost.cs.meta b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySurfaceHost.cs.meta new file mode 100644 index 000000000..4b8d1c5a3 --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySurfaceHost.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: eb0db70846a048e7b15232a70d9f5f8e diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/GameCult.Aetheria.EveRuntime.asmdef.meta b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/GameCult.Aetheria.EveRuntime.asmdef.meta new file mode 100644 index 000000000..eccca51e8 --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/GameCult.Aetheria.EveRuntime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 71b47dbc13067094fb870e349a6cbd14 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/org.gamecult.aetheria.eve-runtime/package.json.meta b/Packages/org.gamecult.aetheria.eve-runtime/package.json.meta new file mode 100644 index 000000000..964635e34 --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: bb8d7a317e284914da0dfaccc300c17c +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/org.gamecult.aetheria.state/Runtime.meta b/Packages/org.gamecult.aetheria.state/Runtime.meta new file mode 100644 index 000000000..da345cd92 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bd2e1c0bcf53796468d8246c0a4bdf72 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssemblyInfo.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssemblyInfo.cs new file mode 100644 index 000000000..f4032ec1e --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Aetheria.State.Unity.Smoke")] +[assembly: InternalsVisibleTo("Tests")] diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs new file mode 100644 index 000000000..0a91ed6d2 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GameCult.Eve.Surface; + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeCargoItemMetric + { + public AetheriaRuntimeCargoItemMetric(string id, string label, string value) + { + Id = id ?? ""; + Label = label ?? ""; + Value = value ?? ""; + } + + public string Id { get; } + public string Label { get; } + public string Value { get; } + } + + public sealed class AetheriaRuntimeCargoItemSection + { + public AetheriaRuntimeCargoItemSection( + string id, + string title, + IReadOnlyList metrics) + { + Id = id ?? ""; + Title = title ?? ""; + Metrics = metrics ?? Array.Empty(); + } + + public string Id { get; } + public string Title { get; } + public IReadOnlyList Metrics { get; } + } + + public sealed class AetheriaRuntimeCargoItemDetailsSurfaceState + { + public AetheriaRuntimeCargoItemDetailsSurfaceState( + string itemName, + string description, + string manufacturer, + string mass, + int price, + int quantity, + string tier, + string durability, + string thermalRange, + IReadOnlyList behaviorSections, + string updatedAtUtc) + { + ItemName = itemName ?? ""; + Description = description ?? ""; + Manufacturer = manufacturer ?? ""; + Mass = mass ?? ""; + Price = price; + Quantity = quantity; + Tier = tier ?? ""; + Durability = durability ?? ""; + ThermalRange = thermalRange ?? ""; + BehaviorSections = behaviorSections ?? Array.Empty(); + UpdatedAtUtc = updatedAtUtc ?? ""; + } + + public string ItemName { get; } + public string Description { get; } + public string Manufacturer { get; } + public string Mass { get; } + public int Price { get; } + public int Quantity { get; } + public string Tier { get; } + public string Durability { get; } + public string ThermalRange { get; } + public IReadOnlyList BehaviorSections { get; } + public string UpdatedAtUtc { get; } + public bool HasQuantity => Quantity > 0; + public bool HasEquipmentStatus => !string.IsNullOrWhiteSpace(Tier) || + !string.IsNullOrWhiteSpace(Durability) || + !string.IsNullOrWhiteSpace(ThermalRange); + } + + 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( + AetheriaRuntimeCargoItemDetailsSurfaceState state, + long version = 1) + { + state ??= new AetheriaRuntimeCargoItemDetailsSurfaceState( + "", + "", + "", + "", + 0, + 0, + "", + "", + "", + Array.Empty(), + ""); + + var children = new List + { + Card( + $"{SurfaceId}.summary", + state.ItemName, + Text( + $"{SurfaceId}.description", + string.IsNullOrWhiteSpace(state.Description) + ? "No typed item description is available." + : state.Description), + Text( + $"{SurfaceId}.note", + "Unity supplies the selected item; the shared runtime surface owns cargo-item inspection layout."), + Metric($"{SurfaceId}.manufacturer", "Manufacturer", state.Manufacturer), + Metric($"{SurfaceId}.mass", "Mass", state.Mass)) + }; + + if (state.Price > 0) + { + children.Add(Card( + $"{SurfaceId}.market.card", + "Market", + Metric($"{SurfaceId}.price", "Price", state.Price.ToString("N0")))); + } + + if (state.HasQuantity) + { + children.Add(Card( + $"{SurfaceId}.quantity.card", + "Quantity", + Metric($"{SurfaceId}.quantity", "Units", state.Quantity.ToString()))); + } + + if (state.HasEquipmentStatus) + { + children.Add(Card( + $"{SurfaceId}.status.card", + "Status", + Metric($"{SurfaceId}.tier", "Tier", state.Tier), + Metric($"{SurfaceId}.durability", "Durability", state.Durability), + Metric($"{SurfaceId}.temperature_range", "Thermal Range", state.ThermalRange))); + } + + foreach (var section in state.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)) + .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: state.UpdatedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + SurfaceId, + Node( + $"{SurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + children.ToArray()), + Array.Empty()), + commands: new[] + { + new AetheriaRuntimeSurfaceCommandTemplate(Close, "Close", "unity-uitoolkit") + }); + } + + 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) + { + return Node(id, "metric", new[] { ("label", label ?? ""), ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Text(string id, string value) + { + return Node(id, "text", new[] { ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) + { + return Node(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent ButtonRow( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent 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()); + } + } + + public enum AetheriaRuntimeCargoItemDetailsCommandKind + { + Unknown = 0, + Close = 1 + } + + 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.Command, AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Close, StringComparison.Ordinal)) + { + command = new AetheriaRuntimeCargoItemDetailsCommand(AetheriaRuntimeCargoItemDetailsCommandKind.Close); + return true; + } + + return false; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogCommands.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogCommands.cs new file mode 100644 index 000000000..14d8f5876 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogCommands.cs @@ -0,0 +1,13 @@ +namespace GameCult.Aetheria.State.Unity +{ + 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; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs index d411ca25f..abff9517d 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs @@ -260,13 +260,15 @@ namespace GameCult.Aetheria.State.Unity double max, double heatExponentMultiplier, double durabilityExponentMultiplier, - double qualityExponent) + double qualityExponent, + AetheriaRuntimeStatRecipe? recipe) { Min = min; Max = max; HeatExponentMultiplier = heatExponentMultiplier; DurabilityExponentMultiplier = durabilityExponentMultiplier; QualityExponent = qualityExponent; + Recipe = recipe; } public double Min { get; } @@ -274,6 +276,42 @@ namespace GameCult.Aetheria.State.Unity public double HeatExponentMultiplier { get; } public double DurabilityExponentMultiplier { get; } public double QualityExponent { get; } + public AetheriaRuntimeStatRecipe? Recipe { get; } + } + + public sealed class AetheriaRuntimeStatRecipe + { + public AetheriaRuntimeStatRecipe(double baseValue, IReadOnlyList modifiers) + { + BaseValue = baseValue; + Modifiers = modifiers ?? Array.Empty(); + } + + public double BaseValue { get; } + public IReadOnlyList Modifiers { get; } + } + + public sealed class AetheriaRuntimeStatRecipeModifier + { + public AetheriaRuntimeStatRecipeModifier( + string condition, + string operation, + double amount, + IReadOnlyList curveKeys, + bool enabled) + { + Condition = condition ?? ""; + Operation = operation ?? ""; + Amount = amount; + CurveKeys = curveKeys ?? Array.Empty(); + Enabled = enabled; + } + + public string Condition { get; } + public string Operation { get; } + public double Amount { get; } + public IReadOnlyList CurveKeys { get; } + public bool Enabled { get; } } public sealed class AetheriaRuntimeCurveKey @@ -871,7 +909,8 @@ namespace GameCult.Aetheria.State.Unity IReadOnlyList dockingBayAssignments, double visibility, int visibilitySourceCount, - IReadOnlyList contacts) + IReadOnlyList contacts, + double shutdownPerformance) { RecordKey = recordKey; Name = name; @@ -908,6 +947,7 @@ namespace GameCult.Aetheria.State.Unity Visibility = visibility; VisibilitySourceCount = visibilitySourceCount; Contacts = contacts; + ShutdownPerformance = shutdownPerformance; } public string RecordKey { get; } @@ -945,6 +985,7 @@ namespace GameCult.Aetheria.State.Unity public double Visibility { get; } public int VisibilitySourceCount { get; } public IReadOnlyList Contacts { get; } + public double ShutdownPerformance { get; } } public sealed class AetheriaRuntimeEntityContactSnapshot diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs.meta new file mode 100644 index 000000000..1be533ccd --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 521f121b64367a548837968dda720594 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs index 8525da039..1c7b40ab3 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs @@ -69,9 +69,195 @@ namespace GameCult.Aetheria.State.Unity surfaces.Add(ReadEveSurface(record.Payload)); } + surfaces.Add(ProjectStatRecipeSurface(stateFilePath)); return surfaces; } + private static EveSurfaceDocument ProjectStatRecipeSurface(string stateFilePath) + { + var state = ProjectStatRecipeSurfaceState(stateFilePath); + var document = AetheriaRuntimeStatRecipeSurfaceBuilder.Build( + state); + return AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument(document); + } + + private static AetheriaRuntimeStatRecipeSurfaceState ProjectStatRecipeSurfaceState(string stateFilePath) + { + var catalog = OpenReadOnly(stateFilePath); + var recipes = catalog.Items + .SelectMany(ProjectStatRecipeRows) + .OrderBy(recipe => recipe.StatName, StringComparer.Ordinal) + .ToArray(); + + var state = new AetheriaRuntimeStatRecipeSurfaceState( + recipes, + recipes.FirstOrDefault()?.StatName ?? "", + AetheriaRuntimeStatRecipePreviewState.Default, + DateTime.UtcNow.ToString("O")); + + return state; + } + + private static IEnumerable ProjectStatRecipeRows(AetheriaRuntimeCatalogItem item) + { + foreach (var behavior in item.BehaviorPayloads ?? Array.Empty()) + { + var metadata = AetheriaRuntimeBehaviorMetadataCatalog.Get(behavior.Kind); + foreach (var field in behavior.Fields ?? Array.Empty()) + { + var fieldMetadata = metadata?.DisplayFields.FirstOrDefault(candidate => candidate.Key == field.Key); + if (fieldMetadata?.ValueKind != AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat) + continue; + + yield return ProjectStatRecipeRow(item, behavior, field, fieldMetadata.Name); + } + } + } + + private static AetheriaRuntimeStatRecipeState ProjectStatRecipeRow( + AetheriaRuntimeCatalogItem item, + AetheriaRuntimeBehaviorPayload behavior, + AetheriaRuntimeBehaviorField field, + string fieldName) + { + var value = field.Value; + var recipe = ReadBehaviorStatRecipe(value); + var statName = string.Join( + " / ", + new[] { item.Name, behavior.Kind, fieldName } + .Where(part => !string.IsNullOrWhiteSpace(part))); + var recipeKey = $"{item.ItemKey}|{behavior.Kind}|{behavior.Group}|{field.Key}"; + var baseValue = recipe?.BaseValue ?? ReadBehaviorStatBaseValue(value); + return new AetheriaRuntimeStatRecipeState( + recipeKey, + statName, + baseValue, + (recipe?.Modifiers ?? Array.Empty()) + .Select(ProjectStatInfluence) + .ToArray()); + } + + private static double ReadBehaviorStatBaseValue(AetheriaRuntimeBehaviorValue value) + { + if (value?.Children == null || value.Children.Count < 2) + return 0; + + return value.Children[1].NumberValue; + } + + private static AetheriaRuntimeStatRecipe? ReadBehaviorStatRecipe(AetheriaRuntimeBehaviorValue? value) + { + if (value?.Children == null || value.Children.Count <= 5) + return null; + + var recipeValue = value.Children[5]; + if (recipeValue?.Children == null || recipeValue.Children.Count == 0) + return null; + + var baseValue = recipeValue.Children[0].NumberValue; + var modifierValues = recipeValue.Children.Count > 1 + ? recipeValue.Children[1].Children ?? Array.Empty() + : Array.Empty(); + var modifiers = modifierValues + .Select(ReadBehaviorStatRecipeModifier) + .OfType() + .ToArray(); + return new AetheriaRuntimeStatRecipe(baseValue, modifiers); + } + + private static AetheriaRuntimeStatRecipeModifier? ReadBehaviorStatRecipeModifier(AetheriaRuntimeBehaviorValue? value) + { + if (value?.Children == null) + return null; + + return new AetheriaRuntimeStatRecipeModifier( + ReadChildString(value, 0), + ReadChildString(value, 1), + ReadChildNumber(value, 2), + ReadBehaviorCurveKeys(ReadChildValue(value, 3)), + value.Children.Count <= 4 || ReadChildValue(value, 4)?.BoolValue != false); + } + + private static IReadOnlyList ReadBehaviorCurveKeys(AetheriaRuntimeBehaviorValue? value) + { + if (value?.Children == null || value.Children.Count == 0) + return Array.Empty(); + + return value.Children + .Where(key => key.Children != null && key.Children.Count >= 4) + .Select(key => new AetheriaRuntimeCurveKey( + ReadChildNumber(key, 0), + ReadChildNumber(key, 1), + ReadChildNumber(key, 2), + ReadChildNumber(key, 3))) + .ToArray(); + } + + private static AetheriaRuntimeStatInfluenceState ProjectStatInfluence(AetheriaRuntimeStatRecipeModifier modifier) + { + return new AetheriaRuntimeStatInfluenceState( + modifier.Condition, + string.IsNullOrWhiteSpace(modifier.Operation) ? AetheriaRuntimeStatRecipeOperations.Add : modifier.Operation, + modifier.Amount, + CurveLabel(modifier.CurveKeys), + SampleCurve(modifier.CurveKeys, AetheriaRuntimeStatRecipePreviewState.Default.GetConditionValue(modifier.Condition)), + modifier.Enabled); + } + + private static string CurveLabel(IReadOnlyList keys) + { + if (keys == null || keys.Count == 0) + return "linear"; + + var preset = CurvePresetLabel(keys); + return string.IsNullOrWhiteSpace(preset) ? $"{keys.Count} keys" : preset; + } + + private static double SampleCurve(IReadOnlyList 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); + return Clamp01(previous.Value + ((next.Value - previous.Value) * t)); + } + + return Clamp01(ordered[ordered.Length - 1].Value); + } + + private static double Clamp01(double value) + { + if (value < 0) + return 0; + return value > 1 ? 1 : value; + } + + private static string ReadChildString(AetheriaRuntimeBehaviorValue? value, int index) + { + return ReadChildValue(value, index)?.StringValue ?? ""; + } + + private static double ReadChildNumber(AetheriaRuntimeBehaviorValue? value, int index) + { + return ReadChildValue(value, index)?.NumberValue ?? 0; + } + + private static AetheriaRuntimeBehaviorValue? ReadChildValue(AetheriaRuntimeBehaviorValue? value, int index) + { + return value?.Children != null && value.Children.Count > index ? value.Children[index] : null; + } + public static AetheriaRuntimePlayerSettingsSnapshot? ReadPlayerSettings(string stateFilePath) { if (string.IsNullOrWhiteSpace(stateFilePath)) @@ -263,6 +449,422 @@ namespace GameCult.Aetheria.State.Unity return new PersistedRecord(key, schemaId, payload); } + private static int PatchEmbeddedItemRecords( + string stateFilePath, + IReadOnlyDictionary schemaCatalog, + IReadOnlyDictionary recipes) + { + var snapshotFields = ReadArrayFields(File.ReadAllBytes(stateFilePath)); + if (snapshotFields.Count <= 2) + return 0; + + var recordReader = new MessagePackReader(snapshotFields[2]); + var recordCount = recordReader.ReadArrayHeader(); + var changed = 0; + var patchedRecords = new byte[recordCount][]; + for (var index = 0; index < recordCount; index++) + { + var recordRaw = recordReader.ReadRaw().ToArray(); + var patched = PatchPersistedItemRecord(recordRaw, schemaCatalog, recipes); + if (!ReferenceEquals(patched, recordRaw)) + changed++; + patchedRecords[index] = patched; + } + + if (changed == 0) + return 0; + + snapshotFields[2] = WriteRawArray(patchedRecords); + WriteFileAtomically(stateFilePath, WriteRawArray(snapshotFields)); + return changed; + } + + private static int PatchSplitItemRecords( + string stateFilePath, + IReadOnlyDictionary schemaCatalog, + IReadOnlyDictionary recipes) + { + var recordDirectory = stateFilePath + ".records"; + if (!Directory.Exists(recordDirectory)) + return 0; + + var changed = 0; + foreach (var recordFile in Directory.EnumerateFiles(recordDirectory, "*.msgpack").OrderBy(path => path, StringComparer.Ordinal)) + { + var original = File.ReadAllBytes(recordFile); + var patched = PatchPersistedItemRecord(original, schemaCatalog, recipes); + if (ReferenceEquals(patched, original)) + continue; + + WriteFileAtomically(recordFile, patched); + changed++; + } + + return changed; + } + + private static byte[] PatchPersistedItemRecord( + byte[] recordRaw, + IReadOnlyDictionary schemaCatalog, + IReadOnlyDictionary recipes) + { + var record = ReadPersistedRecord(recordRaw); + if (!schemaCatalog.TryGetValue(record.SchemaId, out var schemaName) || + !string.Equals(schemaName, ItemDefinitionSchema, StringComparison.Ordinal)) + { + return recordRaw; + } + + var patchedPayload = PatchItemDefinitionPayload(record.Payload, recipes); + if (ReferenceEquals(patchedPayload, record.Payload)) + return recordRaw; + + var fields = ReadArrayFields(recordRaw); + if (fields.Count <= 3) + return recordRaw; + + fields[3] = WriteBinary(patchedPayload); + return WriteRawArray(fields); + } + + private static PersistedRecord ReadPersistedRecord(byte[] recordRaw) + { + var reader = new MessagePackReader(recordRaw); + return ReadPersistedRecord(ref reader); + } + + private static byte[] PatchItemDefinitionPayload( + byte[] payload, + IReadOnlyDictionary recipes) + { + var item = ReadItem(payload); + if (item?.BehaviorPayloads == null || + item.BehaviorPayloads.Count == 0 || + string.IsNullOrWhiteSpace(item.ItemKey) || + !recipes.Keys.Any(key => key.StartsWith(item.ItemKey + "|", StringComparison.Ordinal))) + { + return payload; + } + + var itemFields = ReadArrayFields(payload); + if (itemFields.Count <= 31) + return payload; + + var patchedBehaviorPayloads = WriteBehaviorPayloads(item, recipes); + if (RawEquals(itemFields[31], patchedBehaviorPayloads)) + return payload; + + itemFields[31] = patchedBehaviorPayloads; + return WriteRawArray(itemFields); + } + + private static byte[] WriteBehaviorPayloads( + AetheriaRuntimeCatalogItem item, + IReadOnlyDictionary recipes) + { + var buffer = new ArrayBufferWriter(); + var writer = new MessagePackWriter(buffer); + var payloads = item.BehaviorPayloads ?? Array.Empty(); + writer.WriteArrayHeader(payloads.Count); + foreach (var payload in payloads) + WriteBehaviorPayload(ref writer, item.ItemKey, payload, recipes); + writer.Flush(); + return buffer.WrittenSpan.ToArray(); + } + + private static void WriteBehaviorPayload( + ref MessagePackWriter writer, + string itemKey, + AetheriaRuntimeBehaviorPayload payload, + IReadOnlyDictionary recipes) + { + writer.WriteArrayHeader(4); + writer.Write(payload.UnionKey); + writer.Write(payload.Kind ?? ""); + writer.Write(payload.Group); + var fields = payload.Fields ?? Array.Empty(); + writer.WriteArrayHeader(fields.Count); + foreach (var field in fields) + { + writer.WriteArrayHeader(2); + writer.Write(field.Key); + var recipeKey = $"{itemKey}|{payload.Kind}|{payload.Group}|{field.Key}"; + WriteBehaviorValue( + ref writer, + recipes.TryGetValue(recipeKey, out var recipe) ? WithStatRecipe(field.Value, recipe) : field.Value); + } + } + + private static AetheriaRuntimeBehaviorValue WithStatRecipe( + AetheriaRuntimeBehaviorValue value, + AetheriaRuntimeStatRecipeState recipe) + { + var children = (value?.Children ?? Array.Empty()).ToList(); + while (children.Count <= 5) + children.Add(EmptyBehaviorValue()); + + children[5] = ToBehaviorValue(recipe, ReadBehaviorStatRecipe(value)); + return new AetheriaRuntimeBehaviorValue( + value?.Kind ?? "", + value?.StringValue ?? "", + value?.NumberValue ?? 0, + value?.BoolValue ?? false, + value?.LegacyIdValue ?? "", + value?.ItemKeyValue ?? "", + children, + value?.MapEntries ?? Array.Empty()); + } + + private static AetheriaRuntimeBehaviorValue ToBehaviorValue( + AetheriaRuntimeStatRecipeState recipe, + AetheriaRuntimeStatRecipe? originalRecipe) + { + return new AetheriaRuntimeBehaviorValue( + "statRecipe", + "", + 0, + false, + "", + "", + new[] + { + NumberBehaviorValue(recipe.BaseValue), + new AetheriaRuntimeBehaviorValue( + "array", + "", + 0, + false, + "", + "", + (recipe.Influences ?? Array.Empty()) + .Select(influence => ToBehaviorValue(influence, originalRecipe)) + .ToArray(), + Array.Empty()) + }, + Array.Empty()); + } + + private static AetheriaRuntimeBehaviorValue ToBehaviorValue( + AetheriaRuntimeStatInfluenceState influence, + AetheriaRuntimeStatRecipe? originalRecipe) + { + return new AetheriaRuntimeBehaviorValue( + "statRecipeModifier", + "", + 0, + false, + "", + "", + new[] + { + StringBehaviorValue(influence.Condition), + StringBehaviorValue(influence.Operation), + NumberBehaviorValue(influence.Amount), + ToCurveBehaviorValue(ResolveCurveKeys(influence, originalRecipe)), + BoolBehaviorValue(influence.Enabled) + }, + Array.Empty()); + } + + private static IReadOnlyList? ResolveCurveKeys( + AetheriaRuntimeStatInfluenceState influence, + AetheriaRuntimeStatRecipe? originalRecipe) + { + var originalKeys = originalRecipe?.Modifiers?.FirstOrDefault(modifier => + string.Equals(modifier.Condition, influence.Condition, StringComparison.Ordinal))?.CurveKeys; + return TryCreateCurvePreset(influence.CurveLabel, out var presetKeys) + ? presetKeys + : originalKeys; + } + + private static bool TryCreateCurvePreset(string label, out IReadOnlyList? keys) + { + switch (NormalizeCurveLabel(label)) + { + case "linear": + keys = Array.Empty(); + return true; + case "inverse": + keys = new[] + { + new AetheriaRuntimeCurveKey(0, 1, 0, -1), + new AetheriaRuntimeCurveKey(1, 0, -1, 0) + }; + return true; + case "easein": + keys = new[] + { + new AetheriaRuntimeCurveKey(0, 0, 0, 0), + new AetheriaRuntimeCurveKey(1, 1, 2, 0) + }; + return true; + case "easeout": + keys = new[] + { + new AetheriaRuntimeCurveKey(0, 0, 0, 2), + new AetheriaRuntimeCurveKey(1, 1, 0, 0) + }; + return true; + case "easeinout": + keys = new[] + { + new AetheriaRuntimeCurveKey(0, 0, 0, 0), + new AetheriaRuntimeCurveKey(1, 1, 0, 0) + }; + return true; + case "constant": + keys = new[] + { + new AetheriaRuntimeCurveKey(0, 1, 0, 0), + new AetheriaRuntimeCurveKey(1, 1, 0, 0) + }; + return true; + default: + keys = null; + return false; + } + } + + private static string CurvePresetLabel(IReadOnlyList keys) + { + if (keys == null || keys.Count == 0) + return "linear"; + + return MatchesCurvePreset(keys, "inverse") ? "inverse" : + MatchesCurvePreset(keys, "easein") ? "easeIn" : + MatchesCurvePreset(keys, "easeout") ? "easeOut" : + MatchesCurvePreset(keys, "easeinout") ? "easeInOut" : + MatchesCurvePreset(keys, "constant") ? "constant" : + ""; + } + + private static bool MatchesCurvePreset(IReadOnlyList keys, string preset) + { + if (!TryCreateCurvePreset(preset, out var presetKeys) || presetKeys == null || keys.Count != presetKeys.Count) + return false; + + for (var index = 0; index < keys.Count; index++) + { + if (!AlmostEqual(keys[index].Time, presetKeys[index].Time) || + !AlmostEqual(keys[index].Value, presetKeys[index].Value) || + !AlmostEqual(keys[index].InTangent, presetKeys[index].InTangent) || + !AlmostEqual(keys[index].OutTangent, presetKeys[index].OutTangent)) + { + return false; + } + } + + return true; + } + + private static string NormalizeCurveLabel(string label) + { + return new string((label ?? "") + .Where(char.IsLetterOrDigit) + .Select(char.ToLowerInvariant) + .ToArray()); + } + + private static bool AlmostEqual(double left, double right) + { + return Math.Abs(left - right) <= 0.00001; + } + + private static AetheriaRuntimeBehaviorValue ToCurveBehaviorValue(IReadOnlyList? keys) + { + return new AetheriaRuntimeBehaviorValue( + "curve", + "", + 0, + false, + "", + "", + (keys ?? Array.Empty()) + .Select(ToCurveKeyBehaviorValue) + .ToArray(), + Array.Empty()); + } + + private static AetheriaRuntimeBehaviorValue ToCurveKeyBehaviorValue(AetheriaRuntimeCurveKey key) + { + return new AetheriaRuntimeBehaviorValue( + "curveKey", + "", + 0, + false, + "", + "", + new[] + { + NumberBehaviorValue(key.Time), + NumberBehaviorValue(key.Value), + NumberBehaviorValue(key.InTangent), + NumberBehaviorValue(key.OutTangent) + }, + Array.Empty()); + } + + private static void WriteBehaviorValue(ref MessagePackWriter writer, AetheriaRuntimeBehaviorValue value) + { + value ??= EmptyBehaviorValue(); + writer.WriteArrayHeader(8); + writer.Write(value.Kind ?? ""); + writer.Write(value.StringValue ?? ""); + writer.Write(value.NumberValue); + writer.Write(value.BoolValue); + writer.Write(value.LegacyIdValue ?? ""); + writer.WriteArrayHeader((value.Children ?? Array.Empty()).Count); + foreach (var child in value.Children ?? Array.Empty()) + WriteBehaviorValue(ref writer, child); + writer.WriteArrayHeader((value.MapEntries ?? Array.Empty()).Count); + foreach (var entry in value.MapEntries ?? Array.Empty()) + { + writer.WriteArrayHeader(2); + writer.Write(entry.Key ?? ""); + WriteBehaviorValue(ref writer, entry.Value); + } + writer.Write(value.ItemKeyValue ?? ""); + } + + private static AetheriaRuntimeBehaviorValue StringBehaviorValue(string value) + { + return new AetheriaRuntimeBehaviorValue( + "string", + value ?? "", + 0, + false, + "", + "", + Array.Empty(), + Array.Empty()); + } + + private static AetheriaRuntimeBehaviorValue NumberBehaviorValue(double value) + { + return new AetheriaRuntimeBehaviorValue( + "number", + "", + value, + false, + "", + "", + Array.Empty(), + Array.Empty()); + } + + private static AetheriaRuntimeBehaviorValue BoolBehaviorValue(bool value) + { + return new AetheriaRuntimeBehaviorValue( + "bool", + "", + 0, + value, + "", + "", + Array.Empty(), + Array.Empty()); + } + private static AetheriaRuntimeCatalogItem ReadItem(byte[] payload) { var reader = new MessagePackReader(payload); @@ -623,7 +1225,8 @@ namespace GameCult.Aetheria.State.Unity var visibility = ReadFieldDouble(ref reader, fields, 27); var visibilitySourceCount = ReadFieldInt32(ref reader, fields, 28); var contacts = ReadFieldEntityContacts(ref reader, fields, 29); - SkipRemaining(ref reader, fields, 30); + var shutdownPerformance = ReadFieldDouble(ref reader, fields, 30); + SkipRemaining(ref reader, fields, 31); return new AetheriaRuntimeEntitySnapshot( recordKey, name, @@ -659,7 +1262,8 @@ namespace GameCult.Aetheria.State.Unity dockingBayAssignments, visibility, visibilitySourceCount, - contacts); + contacts, + shutdownPerformance); } private static IReadOnlyList ReadFieldEntityContacts(ref MessagePackReader reader, int fields, int index) @@ -1660,18 +2264,65 @@ namespace GameCult.Aetheria.State.Unity var heatExponentMultiplier = ReadFieldDouble(ref reader, statFields, 2); var durabilityExponentMultiplier = ReadFieldDouble(ref reader, statFields, 3); var qualityExponent = ReadFieldDouble(ref reader, statFields, 4); - SkipRemaining(ref reader, statFields, 5); + var recipe = ReadFieldStatRecipe(ref reader, statFields, 5); + SkipRemaining(ref reader, statFields, 6); return new AetheriaRuntimePerformanceStat( min, max, heatExponentMultiplier, durabilityExponentMultiplier, - qualityExponent); + qualityExponent, + recipe); } private static AetheriaRuntimePerformanceStat EmptyPerformanceStat() { - return new AetheriaRuntimePerformanceStat(0, 0, 0, 0, 0); + return new AetheriaRuntimePerformanceStat(0, 0, 0, 0, 0, null); + } + + private static AetheriaRuntimeStatRecipe? ReadFieldStatRecipe(ref MessagePackReader reader, int fields, int index) + { + if (index >= fields) + return null; + + var recipeFields = reader.ReadArrayHeader(); + if (recipeFields == 0) + return null; + + var baseValue = ReadFieldDouble(ref reader, recipeFields, 0); + var modifiers = ReadFieldStatRecipeModifiers(ref reader, recipeFields, 1); + SkipRemaining(ref reader, recipeFields, 2); + return new AetheriaRuntimeStatRecipe(baseValue, modifiers); + } + + private static IReadOnlyList ReadFieldStatRecipeModifiers( + ref MessagePackReader reader, + int fields, + int index) + { + if (index >= fields) + return Array.Empty(); + + var count = reader.ReadArrayHeader(); + var modifiers = new AetheriaRuntimeStatRecipeModifier[count]; + for (var modifier = 0; modifier < count; modifier++) + { + var modifierFields = reader.ReadArrayHeader(); + var condition = ReadFieldString(ref reader, modifierFields, 0); + var operation = ReadFieldString(ref reader, modifierFields, 1); + var amount = ReadFieldDouble(ref reader, modifierFields, 2); + var curveKeys = ReadFieldCurveKeys(ref reader, modifierFields, 3); + var enabled = modifierFields <= 4 || ReadFieldBool(ref reader, modifierFields, 4, true); + SkipRemaining(ref reader, modifierFields, 5); + modifiers[modifier] = new AetheriaRuntimeStatRecipeModifier( + condition, + operation, + amount, + curveKeys, + enabled); + } + + return modifiers; } private static IReadOnlyList ReadFieldBehaviorFields(ref MessagePackReader reader, int fields, int index) @@ -1814,6 +2465,65 @@ namespace GameCult.Aetheria.State.Unity reader.Skip(); } + private static List ReadArrayFields(byte[] rawArray) + { + var reader = new MessagePackReader(rawArray); + var count = reader.ReadArrayHeader(); + var fields = new List(count); + for (var index = 0; index < count; index++) + fields.Add(reader.ReadRaw().ToArray()); + return fields; + } + + private static byte[] WriteRawArray(IReadOnlyList fields) + { + var buffer = new ArrayBufferWriter(); + var writer = new MessagePackWriter(buffer); + writer.WriteArrayHeader(fields?.Count ?? 0); + foreach (var field in fields ?? Array.Empty()) + writer.WriteRaw(field); + writer.Flush(); + return buffer.WrittenSpan.ToArray(); + } + + private static byte[] WriteBinary(byte[] payload) + { + var buffer = new ArrayBufferWriter(); + var writer = new MessagePackWriter(buffer); + writer.Write(payload ?? Array.Empty()); + writer.Flush(); + return buffer.WrittenSpan.ToArray(); + } + + private static bool RawEquals(byte[] left, byte[] right) + { + if (ReferenceEquals(left, right)) + return true; + if (left == null || right == null || left.Length != right.Length) + return false; + + for (var index = 0; index < left.Length; index++) + { + if (left[index] != right[index]) + return false; + } + + return true; + } + + private static void WriteFileAtomically(string path, byte[] payload) + { + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(directory)) + Directory.CreateDirectory(directory); + + var tempPath = path + ".tmp"; + File.WriteAllBytes(tempPath, payload); + if (File.Exists(path)) + File.Delete(path); + File.Move(tempPath, path); + } + private readonly struct PersistedRecord { public PersistedRecord(string key, string schemaId, byte[] payload) diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs.meta new file mode 100644 index 000000000..f56d9deda --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4ae4a16968aa48e4a9244a3bfec4bd16 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetCommands.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetCommands.cs index bb20fd522..3661a4b1f 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetCommands.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetCommands.cs @@ -1,3 +1,8 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GameCult.Eve.Surface; + namespace GameCult.Aetheria.State.Unity { public static class AetheriaRuntimeClientTargetCommands @@ -28,4 +33,178 @@ namespace GameCult.Aetheria.State.Unity 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.Command ?? ""; + 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.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.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 != null && request.Payload.TryGetValue(key, out var value) + ? value ?? "" + : ""; + } + + 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, + SetCultMeshAddress = 5, + SetStateFilePath = 6, + SetDiscoveryEndpoints = 7, + DiscoverVerses = 8, + SelectDiscoveredVerse = 9, + SyncReplica = 10 + } + + private readonly struct AetheriaClientTargetOperation + { + public AetheriaClientTargetOperation( + AetheriaClientTargetOperationKind kind, + string value = "", + string verseId = "", + string title = "", + string cultMeshAddress = "", + IReadOnlyList? discoveryEndpoints = null) + { + Kind = kind; + Value = value ?? ""; + VerseId = verseId ?? ""; + Title = title ?? ""; + CultMeshAddress = cultMeshAddress ?? ""; + DiscoveryEndpoints = discoveryEndpoints?.ToArray() ?? Array.Empty(); + } + + public AetheriaClientTargetOperationKind Kind { get; } + public string Value { get; } + public string VerseId { get; } + public string Title { get; } + public string CultMeshAddress { get; } + public IReadOnlyList DiscoveryEndpoints { get; } + } + } +#endif } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetCommands.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetCommands.cs.meta new file mode 100644 index 000000000..2028e215d --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetCommands.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6bd3516801c897b40b02efef67e17fe9 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetStore.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetStore.cs.meta new file mode 100644 index 000000000..2ec1e3e14 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3aca91844413c7d46afaf6168a04ca8d \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs index c08fcb7ed..ff95eea0e 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs @@ -127,7 +127,7 @@ namespace GameCult.Aetheria.State.Unity ? state.BootFailureMessage : string.Equals(state.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 queue provider-owned Eve commands for the daemon bridge. Remote Verse targets hydrate a cache-only local replica before Unity reads them."; + : "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 Unity reads them."; var discoveryChildren = new List { @@ -277,7 +277,7 @@ namespace GameCult.Aetheria.State.Unity new[] { ("title", "Ownership") }, Text( "aetheria.clientTarget.host.note", - "Client target edits are local. Visibility changes queue provider-owned Eve commands against the selected local Verse state file."))), + "Client target edits are local. Visibility changes append provider-owned Eve requests against the selected local Verse state file."))), Array.Empty()), commands: new[] { diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs.meta new file mode 100644 index 000000000..a2e7698a8 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7c51ae1e0547a6c45b454932a285a555 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCommandPort.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCommandPort.cs new file mode 100644 index 000000000..fd185152c --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCommandPort.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using GameCult.Caching; +using GameCult.Caching.MessagePack; +using GameCult.Mesh; +using GameCult.Networking; +using GameCult.Aetheria.State.Unity; + +#nullable enable + +namespace Aetheria.State +{ + public sealed class AetheriaCommandPort : IDisposable + { + private readonly CultMeshNode _node; + + private AetheriaCommandPort(string stateFilePath, CultMeshNode node) + { + StateFilePath = stateFilePath; + _node = node; + } + + public string StateFilePath { get; } + + public static async Task OpenAsync( + string stateFilePath, + string runtimeId = "aetheria-command-client") + { + if (string.IsNullOrWhiteSpace(stateFilePath)) + throw new ArgumentException("State file path must be non-empty.", nameof(stateFilePath)); + + var fullPath = Path.GetFullPath(stateFilePath); + var registry = CreateCultCacheRegistry(); + var node = await CultMesh.CreateNodeAsync( + fullPath, + new CultMeshNodeOptions + { + StartServer = false, + EnableDurableShardLogs = true, + CacheOptions = new CultCacheOpenOptions + { + Registry = registry, + PullOnOpen = false, + StoreFlushOnDispose = true, + UseDirectoryStore = true + }, + DatabaseOptions = new CultNetDatabaseOptions + { + RuntimeId = string.IsNullOrWhiteSpace(runtimeId) ? "aetheria-command-client" : runtimeId, + DocumentRegistry = CreateCultNetRegistry(registry) + } + }) + .ConfigureAwait(false); + + return new AetheriaCommandPort(fullPath, node); + } + + public async Task SubmitDaemonCommandAsync( + AetheriaRuntimeDaemonCommandDocument command) + { + if (command == null) throw new ArgumentNullException(nameof(command)); + + command.Schema = AetheriaRuntimeDaemonSchemas.Command; + if (string.IsNullOrWhiteSpace(command.CommandId)) + command.CommandId = Guid.NewGuid().ToString("N"); + if (string.IsNullOrWhiteSpace(command.IssuedAtUtc)) + command.IssuedAtUtc = DateTime.UtcNow.ToString("O"); + + await _node.Database.PutAsync(DaemonCommandKey(command.CommandId), command).ConfigureAwait(false); + await _node.FlushAsync().ConfigureAwait(false); + return AetheriaRuntimeDaemonOperationClient.ToEnvelope(command); + } + + public async Task SubmitEveCommandAsync( + AetheriaRuntimeEveCommandDocument command) + { + if (command == null) throw new ArgumentNullException(nameof(command)); + + command.Schema = AetheriaRuntimeEveCommandDocument.SchemaId; + if (string.IsNullOrWhiteSpace(command.CommandId)) + command.CommandId = Guid.NewGuid().ToString("N"); + if (string.IsNullOrWhiteSpace(command.IssuedAtUtc)) + command.IssuedAtUtc = DateTime.UtcNow.ToString("O"); + + await _node.Database.PutAsync(EveCommandKey(command.CommandId), command).ConfigureAwait(false); + await _node.FlushAsync().ConfigureAwait(false); + return AetheriaRuntimeEveCommandClient.ToEnvelope(command); + } + + private static CultDocumentRegistry CreateCultCacheRegistry() + { + var registry = new CultDocumentRegistry(); + registry.GetRequired(typeof(AetheriaRuntimeDaemonCommandDocument)); + registry.GetRequired(typeof(AetheriaRuntimeEveCommandDocument)); + return registry; + } + + private static CultNetDocumentRegistry CreateCultNetRegistry(CultDocumentRegistry registry) + { + return new CultNetDocumentRegistry( + registry, + new[] + { + CultNetDocumentBinding.ForDocument(registry), + CultNetDocumentBinding.ForDocument(registry) + }); + } + + private static CultRecordKey DaemonCommandKey(string commandId) + { + return new CultRecordKey($"daemon:commands:{StableToken(commandId)}:gamecult.aetheria.daemon_command.v1"); + } + + private static CultRecordKey EveCommandKey(string commandId) + { + return new CultRecordKey($"eve:commands:{StableToken(commandId)}:gamecult.eve.command.v1"); + } + + private static string StableToken(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return "empty"; + + var chars = value + .Select(character => char.IsLetterOrDigit(character) ? character : '-') + .ToArray(); + var token = new string(chars).Trim('-').ToLowerInvariant(); + while (token.Contains("--", StringComparison.Ordinal)) + token = token.Replace("--", "-", StringComparison.Ordinal); + return string.IsNullOrWhiteSpace(token) ? "empty" : token; + } + + public void Dispose() + { + _node.Dispose(); + } + } +} + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeCommandPort : IDisposable + { + private readonly global::Aetheria.State.AetheriaCommandPort _port; + + private AetheriaRuntimeCommandPort(global::Aetheria.State.AetheriaCommandPort port) + { + _port = port ?? throw new ArgumentNullException(nameof(port)); + } + + public string StateFilePath => _port.StateFilePath; + + public static async Task OpenAsync( + string stateFilePath, + string runtimeId = "unity-input-provider") + { + var port = await global::Aetheria.State.AetheriaCommandPort.OpenAsync( + stateFilePath, + string.IsNullOrWhiteSpace(runtimeId) ? "unity-input-provider" : runtimeId) + .ConfigureAwait(false); + return new AetheriaRuntimeCommandPort(port); + } + + public Task SubmitDaemonCommandAsync( + AetheriaRuntimeDaemonCommandDocument command) + { + return _port.SubmitDaemonCommandAsync(command); + } + + public Task SubmitEveCommandAsync( + AetheriaRuntimeEveCommandDocument command) + { + return _port.SubmitEveCommandAsync(command); + } + + public void Dispose() + { + _port.Dispose(); + } + } + + public static class AetheriaRuntimeCommandSubmitter + { + private static readonly object Sync = new object(); + private static readonly Dictionary Ports = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + public static bool TrySubmitDaemonCommand( + string stateFilePath, + AetheriaRuntimeDaemonCommandDocument command, + string runtimeId, + out AetheriaRuntimeDaemonCommandEnvelope? envelope, + out string error) + { + envelope = null; + error = ""; + + try + { + envelope = GetOrOpen(stateFilePath, runtimeId) + .SubmitDaemonCommandAsync(command) + .GetAwaiter() + .GetResult(); + return true; + } + catch (Exception ex) + { + error = ex.ToString(); + return false; + } + } + + public static bool TrySubmitEveCommand( + string stateFilePath, + AetheriaRuntimeEveCommandDocument command, + string runtimeId, + out AetheriaRuntimeEveCommandEnvelope? envelope, + out string error) + { + envelope = null; + error = ""; + + try + { + envelope = GetOrOpen(stateFilePath, runtimeId) + .SubmitEveCommandAsync(command) + .GetAwaiter() + .GetResult(); + return true; + } + catch (Exception ex) + { + error = ex.ToString(); + return false; + } + } + + private static AetheriaRuntimeCommandPort GetOrOpen(string stateFilePath, string runtimeId) + { + var fullPath = Path.GetFullPath(stateFilePath ?? ""); + var key = $"{fullPath}|{(string.IsNullOrWhiteSpace(runtimeId) ? "unity-input-provider" : runtimeId)}"; + lock (Sync) + { + if (Ports.TryGetValue(key, out var existing)) + return existing; + } + + var created = AetheriaRuntimeCommandPort.OpenAsync(fullPath, runtimeId) + .GetAwaiter() + .GetResult(); + lock (Sync) + { + if (Ports.TryGetValue(key, out var existing)) + { + created.Dispose(); + return existing; + } + + Ports.Add(key, created); + return created; + } + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCommandPort.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCommandPort.cs.meta new file mode 100644 index 000000000..4ec57561b --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCommandPort.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0f4cc57dd7d14eddb7e3c3a4bde1a94e diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs new file mode 100644 index 000000000..e22b774ba --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs @@ -0,0 +1,661 @@ +using System; +using System.Buffers; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using MessagePack; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + internal static class AetheriaRuntimeCultCacheDocumentStore + { + private const string FormatVersion = "cultcache.store.v1"; + private const int StoreSnapshotFieldCount = 3; + private const int SchemaCatalogEntryFieldCount = 7; + private const int SchemaCatalogMemberFieldCount = 8; + private const int PersistedRecordFieldCount = 4; + + 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( + ReadDocumentPayload(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( + ReadDocumentPayload(path, AetheriaRuntimeDaemonSchemas.Command)); + } + + public static void WriteDaemonFrame(string path, AetheriaRuntimeDaemonFrameDocument document) + { + WriteDocument( + path, + $"latest:gamecult.aetheria.daemon_frame.{document.SessionId}.v1", + AetheriaRuntimeDaemonSchemas.Frame, + "gamecult.aetheria.daemon_frame", + "gamecult.aetheria.daemon_frame.v1", + document.PublishedAtUtc, + MessagePackSerializer.Serialize(document)); + } + + public static AetheriaRuntimeDaemonFrameDocument ReadDaemonFrame(string path) + { + return MessagePackSerializer.Deserialize( + ReadDocumentPayload(path, AetheriaRuntimeDaemonSchemas.Frame)); + } + + public static void WriteDaemonSoaView(string path, AetheriaRuntimeDaemonSoaViewDocument document) + { + WriteDocument( + path, + $"latest:gamecult.aetheria.daemon_soa_view.{document.SessionId}.v1", + AetheriaRuntimeDaemonSchemas.SoaView, + "gamecult.aetheria.daemon_soa_view", + "gamecult.aetheria.daemon_soa_view.v1", + document.PublishedAtUtc, + MessagePackSerializer.Serialize(document)); + } + + public static AetheriaRuntimeDaemonSoaViewDocument ReadDaemonSoaView(string path) + { + return MessagePackSerializer.Deserialize( + ReadDocumentPayload(path, AetheriaRuntimeDaemonSchemas.SoaView)); + } + + public static void WriteDaemonProviderAdvertisement( + string path, + AetheriaRuntimeDaemonProviderAdvertisementDocument document) + { + WriteDocument( + path, + $"latest:gamecult.aetheria.daemon_provider.{document.DaemonId}.v1", + AetheriaRuntimeDaemonSchemas.ProviderAdvertisement, + "gamecult.aetheria.daemon_provider_advertisement", + "gamecult.aetheria.daemon_provider_advertisement.v1", + document.PublishedAtUtc, + MessagePackSerializer.Serialize(document)); + } + + public static AetheriaRuntimeDaemonProviderAdvertisementDocument ReadDaemonProviderAdvertisement(string path) + { + return MessagePackSerializer.Deserialize( + ReadDocumentPayload(path, AetheriaRuntimeDaemonSchemas.ProviderAdvertisement)); + } + + public static void WriteDaemonHealth(string path, AetheriaRuntimeDaemonHealthDocument document) + { + WriteDocument( + path, + $"latest:gamecult.aetheria.daemon_health.{document.DaemonId}.v1", + AetheriaRuntimeDaemonSchemas.Health, + "gamecult.aetheria.daemon_health", + "gamecult.aetheria.daemon_health.v1", + document.PublishedAtUtc, + MessagePackSerializer.Serialize(document)); + } + + public static AetheriaRuntimeDaemonHealthDocument ReadDaemonHealth(string path) + { + return MessagePackSerializer.Deserialize( + ReadDocumentPayload(path, AetheriaRuntimeDaemonSchemas.Health)); + } + + public static void WriteDaemonCommandBoundary( + string path, + AetheriaRuntimeDaemonCommandBoundaryDocument document) + { + WriteDocument( + path, + $"latest:gamecult.aetheria.daemon_command_boundary.{document.BoundaryId}.v1", + AetheriaRuntimeDaemonSchemas.CommandBoundary, + "gamecult.aetheria.daemon_command_boundary", + "gamecult.aetheria.daemon_command_boundary.v1", + document.PublishedAtUtc, + MessagePackSerializer.Serialize(document)); + } + + public static AetheriaRuntimeDaemonCommandBoundaryDocument ReadDaemonCommandBoundary(string path) + { + return MessagePackSerializer.Deserialize( + ReadDocumentPayload(path, AetheriaRuntimeDaemonSchemas.CommandBoundary)); + } + + public static void WriteDaemonGameSurface(string path, AetheriaRuntimeSurfaceDocument document) + { + WriteDaemonSurface( + path, + document, + AetheriaRuntimeDaemonSchemas.GameSurface, + "gamecult.aetheria.daemon_game_surface", + "gamecult.aetheria.daemon_game_surface.v1"); + } + + public static AetheriaRuntimeSurfaceDocument ReadDaemonGameSurface(string path) + { + return ReadRuntimeSurfaceDocument(ReadDocumentPayload(path, AetheriaRuntimeDaemonSchemas.GameSurface)); + } + + public static void WriteDaemonEditorSurface(string path, AetheriaRuntimeSurfaceDocument document) + { + WriteDaemonSurface( + path, + document, + AetheriaRuntimeDaemonSchemas.EditorSurface, + "gamecult.aetheria.daemon_editor_surface", + "gamecult.aetheria.daemon_editor_surface.v1"); + } + + public static AetheriaRuntimeSurfaceDocument ReadDaemonEditorSurface(string path) + { + return ReadRuntimeSurfaceDocument(ReadDocumentPayload(path, AetheriaRuntimeDaemonSchemas.EditorSurface)); + } + + private static void WriteDaemonSurface( + string path, + AetheriaRuntimeSurfaceDocument document, + string schema, + string schemaName, + string schemaVersion) + { + WriteDocument( + path, + $"latest:{schemaName}.{document.Surface.Id}.v1", + schema, + schemaName, + schemaVersion, + document.UpdatedAtUtc, + WriteRuntimeSurfaceDocument(document)); + } + + private static void WriteDocument( + string path, + string key, + string schemaId, + string schemaName, + string schemaVersion, + string storedAt, + byte[] payload) + { + var buffer = new ArrayBufferWriter(); + var writer = new MessagePackWriter(buffer); + + writer.WriteArrayHeader(StoreSnapshotFieldCount); + writer.Write(FormatVersion); + writer.WriteArrayHeader(1); + WriteSchemaCatalogEntry(ref writer, schemaId, schemaName, schemaVersion, payload); + writer.WriteArrayHeader(1); + WritePersistedRecord(ref writer, key, schemaId, storedAt, payload); + writer.Flush(); + + WriteFileAtomically(path, buffer.WrittenSpan.ToArray()); + } + + private static byte[] ReadDocumentPayload(string path, string expectedSchemaId) + { + var reader = new MessagePackReader(File.ReadAllBytes(path)); + var fieldCount = reader.ReadArrayHeader(); + if (fieldCount < StoreSnapshotFieldCount) + { + throw new InvalidDataException($"CultCache document '{path}' is missing store fields."); + } + + var formatVersion = reader.ReadString() ?? ""; + if (!formatVersion.StartsWith("cultcache.store.v1", StringComparison.Ordinal)) + { + throw new InvalidDataException($"Document '{path}' is not a CultCache store."); + } + + var schemaIds = ReadSchemaCatalog(ref reader); + var recordCount = reader.ReadArrayHeader(); + if (recordCount != 1) + { + throw new InvalidDataException($"CultCache document '{path}' must contain exactly one record."); + } + + var recordFieldCount = reader.ReadArrayHeader(); + if (recordFieldCount < PersistedRecordFieldCount) + { + throw new InvalidDataException($"CultCache record '{path}' is missing record fields."); + } + + reader.Skip(); // key + var schemaId = reader.ReadString() ?? ""; + reader.Skip(); // storedAt + var payload = reader.ReadBytes()?.ToArray() ?? Array.Empty(); + for (var index = PersistedRecordFieldCount; index < recordFieldCount; index++) + { + reader.Skip(); + } + + for (var index = StoreSnapshotFieldCount; index < fieldCount; index++) + { + reader.Skip(); + } + + if (!string.Equals(schemaId, expectedSchemaId, StringComparison.Ordinal)) + { + throw new InvalidDataException($"Document '{path}' has schema '{schemaId}', expected '{expectedSchemaId}'."); + } + + if (!schemaIds.Contains(schemaId, StringComparer.Ordinal)) + { + throw new InvalidDataException($"Document '{path}' does not publish schema '{schemaId}' in its CultCache catalog."); + } + + return payload; + } + + private static string[] ReadSchemaCatalog(ref MessagePackReader reader) + { + var count = reader.ReadArrayHeader(); + var schemaIds = new string[count]; + for (var index = 0; index < count; index++) + { + var fieldCount = reader.ReadArrayHeader(); + schemaIds[index] = fieldCount > 0 ? reader.ReadString() ?? "" : ""; + for (var field = 1; field < fieldCount; field++) + { + reader.Skip(); + } + } + + return schemaIds; + } + + private static byte[] WriteRuntimeSurfaceDocument(AetheriaRuntimeSurfaceDocument document) + { + var buffer = new ArrayBufferWriter(); + 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(StringComparer.Ordinal), + Array.Empty()), + Array.Empty()); + + 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()) + { + 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(StringComparer.Ordinal), + Array.Empty()); + + writer.WriteArrayHeader(4); + 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(StringComparer.Ordinal)) + { + writer.Write(prop.Key ?? ""); + writer.Write(prop.Value ?? ""); + } + + writer.WriteArrayHeader(component.Children?.Count ?? 0); + foreach (var child in component.Children ?? Array.Empty()) + { + WriteSurfaceComponent(ref writer, child); + } + } + + private static void WriteSurfaceCommands( + ref MessagePackWriter writer, + System.Collections.Generic.IReadOnlyList commands) + { + writer.WriteArrayHeader(commands?.Count ?? 0); + foreach (var command in commands ?? Array.Empty()) + { + writer.WriteArrayHeader(3); + writer.Write(command.Command ?? ""); + writer.Write(command.Label ?? ""); + writer.Write(command.Transport ?? ""); + } + } + + private 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(StringComparer.Ordinal), + Array.Empty()), + Array.Empty()); + } + + 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(StringComparer.Ordinal), + Array.Empty()); + } + + 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); + for (var field = 4; field < componentFields; field++) + { + reader.Skip(); + } + + return new AetheriaRuntimeSurfaceComponent(id, kind, props, children); + } + + private static System.Collections.Generic.IReadOnlyList ReadFieldSurfaceComponents( + ref MessagePackReader reader, + int fields, + int index) + { + if (index >= fields || reader.NextMessagePackType == MessagePackType.Nil) + { + if (index < fields) + reader.ReadNil(); + + return Array.Empty(); + } + + 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 ReadFieldStringMap( + ref MessagePackReader reader, + int fields, + int index) + { + var values = new System.Collections.Generic.Dictionary(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 ReadFieldSurfaceStyles( + ref MessagePackReader reader, + int fields, + int index) + { + if (index >= fields || reader.NextMessagePackType == MessagePackType.Nil) + { + if (index < fields) + reader.ReadNil(); + + return Array.Empty(); + } + + 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 ReadFieldSurfaceCommands( + ref MessagePackReader reader, + int fields, + int index) + { + if (index >= fields || reader.NextMessagePackType == MessagePackType.Nil) + { + if (index < fields) + reader.ReadNil(); + + return Array.Empty(); + } + + 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 transport = ReadFieldString(ref reader, commandFields, 2, ""); + for (var field = 3; field < commandFields; field++) + { + reader.Skip(); + } + + commands[command] = new AetheriaRuntimeSurfaceCommandTemplate(name, label, transport); + } + + return commands; + } + + private static void WriteSchemaCatalogEntry( + ref MessagePackWriter writer, + string schemaId, + string schemaName, + string schemaVersion, + byte[] payload) + { + writer.WriteArrayHeader(SchemaCatalogEntryFieldCount); + writer.Write(schemaId); + writer.Write(schemaName); + writer.Write(schemaVersion); + writer.Write(ContentHash(payload)); + writer.Write(""); + writer.WriteArrayHeader(0); + writer.WriteArrayHeader(0); + } + + private static void WritePersistedRecord( + ref MessagePackWriter writer, + string key, + string schemaId, + string storedAt, + byte[] payload) + { + writer.WriteArrayHeader(PersistedRecordFieldCount); + writer.Write(key); + writer.Write(schemaId); + writer.Write(storedAt ?? ""); + writer.Write(payload); + } + + 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(); + } + + private static string ContentHash(byte[] payload) + { + using var sha256 = SHA256.Create(); + return BitConverter.ToString(sha256.ComputeHash(payload)).Replace("-", "").ToLowerInvariant(); + } + + private static void WriteFileAtomically(string path, byte[] payload) + { + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(directory)) + { + Directory.CreateDirectory(directory); + } + + var tempPath = path + ".tmp"; + File.WriteAllBytes(tempPath, payload); + if (File.Exists(path)) + { + File.Delete(path); + } + + File.Move(tempPath, path); + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs.meta new file mode 100644 index 000000000..26e9fce21 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 730416bfa990947479bc2eb704361bd8 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonCommandClient.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonCommandClient.cs.meta new file mode 100644 index 000000000..69966e283 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonCommandClient.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 09d3397c0783416489eb3c74c6e958c7 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonCommandLog.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonCommandLog.cs.meta new file mode 100644 index 000000000..5f26e9bf7 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonCommandLog.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 09ab260807a4ebb41b0f4e28be870b2d \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs index 7fb568f1f..040aac8d8 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs @@ -227,14 +227,7 @@ namespace GameCult.Aetheria.State.Unity public long FrameId { get; set; } [Key(6)] - public int PendingBeforeApply { get; set; } - - [IgnoreMember] - public int ObservedCommandCount - { - get => PendingBeforeApply; - set => PendingBeforeApply = value; - } + public int ObservedCommandCount { get; set; } [Key(7)] public int AppliedCommandCount { get; set; } @@ -388,6 +381,9 @@ namespace GameCult.Aetheria.State.Unity [Key(12)] public IReadOnlyList RejectedCommandIds { get; set; } = Array.Empty(); + [Key(13)] + public IReadOnlyList AccountedCommandIds { get; set; } = Array.Empty(); + public static AetheriaRuntimeDaemonFrameDocument Create( AetheriaRuntimeRunCheckpointCommit run, string daemonId, diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs.meta new file mode 100644 index 000000000..0d67939e6 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 89b7a1cbc494a264ba4130e65485ee5b \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonEditorSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonEditorSurfaceBuilder.cs new file mode 100644 index 000000000..93c157cf7 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonEditorSurfaceBuilder.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + 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) + { + provider ??= new AetheriaRuntimeDaemonProviderAdvertisementDocument(); + health ??= new AetheriaRuntimeDaemonHealthDocument(); + commandBoundary ??= AetheriaRuntimeDaemonCommandBoundaryDocument.Create(provider.DaemonId); + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria.daemon", + 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 Witness", provider.StateWitnessPath)), + Node( + "aetheria.daemon.editor.witnesses", + "card", + new[] { ("title", "Witnesses") }, + Metric("aetheria.daemon.editor.witnesses.frame", "Frame", provider.FrameWitnessPath), + Metric("aetheria.daemon.editor.witnesses.soa", "SoA", provider.SoaWitnessPath), + Metric("aetheria.daemon.editor.witnesses.health", "Health", provider.HealthWitnessPath), + Metric("aetheria.daemon.editor.witnesses.commands", "Command Boundary", provider.CommandBoundaryWitnessPath), + Metric("aetheria.daemon.editor.witnesses.game", "Game Surface", provider.EveGuiSurfaceWitnessPath), + Metric("aetheria.daemon.editor.witnesses.editor", "Editor Surface", provider.EditorGuiSurfaceWitnessPath)), + 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()))), + Array.Empty()), + commands: commandBoundary.Commands + .Select(entry => new AetheriaRuntimeSurfaceCommandTemplate( + CommandName(entry.Kind), + entry.Kind.ToString(), + "cultmesh")) + .ToArray()); + } + + 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 "aetheria.daemon.commands." + 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()); + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonFrameStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonFrameStore.cs new file mode 100644 index 000000000..2b48770ff --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonFrameStore.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeDaemonFrameStore + { + public static string GetFramePath(string stateFilePath) + { + if (string.IsNullOrWhiteSpace(stateFilePath)) + throw new ArgumentException("State file path must be non-empty.", nameof(stateFilePath)); + + return AetheriaRuntimeStateBoundary.GetDaemonFramePath(stateFilePath); + } + + public static string PublishFrame(string stateFilePath, AetheriaRuntimeDaemonFrameDocument frame) + { + if (frame == null) throw new ArgumentNullException(nameof(frame)); + + frame.Schema = AetheriaRuntimeDaemonSchemas.Frame; + if (string.IsNullOrWhiteSpace(frame.PublishedAtUtc)) + frame.PublishedAtUtc = DateTime.UtcNow.ToString("O"); + + var framePath = GetFramePath(stateFilePath); + Directory.CreateDirectory(Path.GetDirectoryName(framePath) ?? "."); + AetheriaRuntimeCultCacheDocumentStore.WriteDaemonFrame(framePath, frame); + return framePath; + } + + public static bool TryReadFrame(string stateFilePath, out AetheriaRuntimeDaemonFrameDocument frame) + { + var framePath = GetFramePath(stateFilePath); + if (!File.Exists(framePath)) + { + frame = new AetheriaRuntimeDaemonFrameDocument(); + return false; + } + + frame = AetheriaRuntimeCultCacheDocumentStore.ReadDaemonFrame(framePath); + return true; + } + + public static AetheriaRuntimeDaemonFrameDocument ReadFrame(string stateFilePath) + { + return AetheriaRuntimeCultCacheDocumentStore.ReadDaemonFrame(GetFramePath(stateFilePath)); + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonFrameStore.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonFrameStore.cs.meta new file mode 100644 index 000000000..dafaa0eee --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonFrameStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2da25246f5a801f4db2d26f4100b70ad \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonGameSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonGameSurfaceBuilder.cs new file mode 100644 index 000000000..27c7f7d2b --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonGameSurfaceBuilder.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeDaemonGameSurfaceBuilder + { + public const string SurfaceId = "aetheria.game"; + public const string TuiSurfaceId = "aetheria.game.tui"; + + public static AetheriaRuntimeSurfaceDocument Build( + AetheriaRuntimeDaemonFrameDocument frame, + AetheriaRuntimeDaemonHealthDocument health, + AetheriaRuntimeDaemonCommandBoundaryDocument commandBoundary) + { + frame ??= new AetheriaRuntimeDaemonFrameDocument(); + health ??= new AetheriaRuntimeDaemonHealthDocument(); + commandBoundary ??= AetheriaRuntimeDaemonCommandBoundaryDocument.Create(frame.DaemonId); + + var run = frame.Run ?? new AetheriaRuntimeRunCheckpointCommit(); + var zone = FindCurrentZone(run); + var entity = FindCurrentEntity(run, zone); + var target = FindTargetEntity(zone, entity); + var entityName = string.IsNullOrWhiteSpace(entity?.Name) ? "(no current entity)" : entity!.Name; + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria.daemon", + providerKind: "game.daemon", + title: "Aetheria Daemon", + version: frame.FrameId, + updatedAtUtc: frame.PublishedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + SurfaceId, + Node( + "aetheria.daemon.game.root", + "surface", + Array.Empty<(string Key, string Value)>(), + Node( + "aetheria.daemon.game.frame", + "card", + new[] { ("title", "Daemon Frame") }, + Metric("aetheria.daemon.game.frame.daemon", "Daemon", frame.DaemonId), + Metric("aetheria.daemon.game.frame.verse", "Verse", health.VerseId), + Metric("aetheria.daemon.game.frame.frameId", "Frame", frame.FrameId.ToString(CultureInfo.InvariantCulture)), + Metric("aetheria.daemon.game.frame.time", "Time", frame.SimulationTimeSeconds.ToString("0.###", CultureInfo.InvariantCulture)), + Metric("aetheria.daemon.game.frame.status", "Status", health.Status), + Metric("aetheria.daemon.game.frame.observed_commands", "Observed Commands", health.ObservedCommandCount.ToString(CultureInfo.InvariantCulture)), + Metric("aetheria.daemon.game.frame.applied", "Applied", health.AppliedCommandCount.ToString(CultureInfo.InvariantCulture)), + Metric("aetheria.daemon.game.frame.rejected", "Rejected", health.RejectedCommandCount.ToString(CultureInfo.InvariantCulture))), + Node( + "aetheria.daemon.game.player", + "card", + new[] { ("title", "Current Entity") }, + Metric("aetheria.daemon.game.player.run", "Run", run.RunId), + Metric("aetheria.daemon.game.player.zone", "Zone", run.CurrentZoneIndex.ToString(CultureInfo.InvariantCulture)), + Metric("aetheria.daemon.game.player.key", "Entity Key", run.CurrentEntityKey), + Metric("aetheria.daemon.game.player.name", "Name", entityName), + Metric("aetheria.daemon.game.player.position", "Position", FormatPosition(entity)), + Metric("aetheria.daemon.game.player.target", "Target", string.IsNullOrWhiteSpace(target?.Name) ? "(none)" : target!.Name), + Metric("aetheria.daemon.game.player.equipment", "Equipment", Count(entity?.Equipment).ToString(CultureInfo.InvariantCulture)), + Metric("aetheria.daemon.game.player.cargo", "Cargo Bays", Count(entity?.CargoContents).ToString(CultureInfo.InvariantCulture)), + Metric("aetheria.daemon.game.player.weaponGroups", "Weapon Groups", Count(entity?.WeaponGroups).ToString(CultureInfo.InvariantCulture))), + Node( + "aetheria.daemon.game.commands", + "card", + new[] { ("title", "Typed Command Boundary") }, + Metric( + "aetheria.daemon.game.commands.boundary", + "Boundary", + commandBoundary.BoundaryId), + Metric( + "aetheria.daemon.game.commands.count", + "Commands", + Count(commandBoundary.Commands).ToString(CultureInfo.InvariantCulture)), + Row( + "aetheria.daemon.game.commands.primary", + CommandButton("aetheria.daemon.game.commands.move", "Move", AetheriaRuntimeDaemonCommandKinds.SetMoveVector), + CommandButton("aetheria.daemon.game.commands.target", "Set Target", AetheriaRuntimeDaemonCommandKinds.SetTarget), + CommandButton("aetheria.daemon.game.commands.fire", "Fire", AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup), + CommandButton("aetheria.daemon.game.commands.ping", "Sensor Ping", AetheriaRuntimeDaemonCommandKinds.SensorPing)))), + Array.Empty()), + commands: commandBoundary.Commands + .Select(entry => new AetheriaRuntimeSurfaceCommandTemplate( + CommandName(entry.Kind), + Label(entry.Kind), + "cultmesh")) + .ToArray()); + } + + private static AetheriaRuntimeZoneSnapshotCommit FindCurrentZone(AetheriaRuntimeRunCheckpointCommit run) + { + return run.Zones.FirstOrDefault(zone => zone.ZoneIndex == run.CurrentZoneIndex) + ?? run.Zones.FirstOrDefault() + ?? new AetheriaRuntimeZoneSnapshotCommit(); + } + + private static AetheriaRuntimeEntitySnapshotCommit? FindCurrentEntity( + AetheriaRuntimeRunCheckpointCommit run, + AetheriaRuntimeZoneSnapshotCommit zone) + { + var entityIndex = TryParseEntityIndex(run.CurrentEntityKey); + if (entityIndex >= 0) + return zone.Entities.FirstOrDefault(entity => entity.EntityIndex == entityIndex); + + return zone.Entities.FirstOrDefault(); + } + + private static AetheriaRuntimeEntitySnapshotCommit? FindTargetEntity( + AetheriaRuntimeZoneSnapshotCommit zone, + AetheriaRuntimeEntitySnapshotCommit? entity) + { + if (entity == null || entity.TargetEntityIndex < 0) + return null; + + return zone.Entities.FirstOrDefault(candidate => candidate.EntityIndex == entity.TargetEntityIndex); + } + + private static int TryParseEntityIndex(string key) + { + if (string.IsNullOrWhiteSpace(key)) + return -1; + + var marker = ".entity."; + var markerIndex = key.LastIndexOf(marker, StringComparison.Ordinal); + if (markerIndex < 0) + return -1; + + var start = markerIndex + marker.Length; + var end = start; + while (end < key.Length && char.IsDigit(key[end])) + end++; + + return int.TryParse(key.Substring(start, end - start), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) + ? value + : -1; + } + + private static string FormatPosition(AetheriaRuntimeEntitySnapshotCommit? entity) + { + if (entity == null) + return "(none)"; + + return string.Join(", ", new[] + { + entity.PositionX.ToString("0.###", CultureInfo.InvariantCulture), + entity.PositionY.ToString("0.###", CultureInfo.InvariantCulture), + entity.PositionZ.ToString("0.###", CultureInfo.InvariantCulture) + }); + } + + private static int Count(IReadOnlyCollection? values) + { + return values?.Count ?? 0; + } + + private static string CommandName(AetheriaRuntimeDaemonCommandKinds kind) + { + return "aetheria.daemon.commands." + kind; + } + + private static string Label(AetheriaRuntimeDaemonCommandKinds kind) + { + return kind.ToString(); + } + + private static AetheriaRuntimeSurfaceComponent Metric(string id, string label, string value) + { + return Node(id, "metric", new[] { ("label", label), ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent CommandButton( + string id, + string label, + AetheriaRuntimeDaemonCommandKinds kind) + { + return Node( + id, + "control.button", + new[] + { + ("label", label), + ("command", CommandName(kind)), + ("commandBody", nameof(AetheriaRuntimeDaemonCommandDocument)) + }); + } + + 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()); + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonIntentState.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonIntentState.cs new file mode 100644 index 000000000..36556d5e7 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonIntentState.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeDaemonIntentState + { + public AetheriaRuntimeDaemonMovementIntent? Movement { get; set; } + public List WeaponGroups { get; } = + new List(); + public List Behaviors { get; } = + new List(); + public List Consumables { get; } = + new List(); + public List Docking { get; } = + new List(); + public List Wormholes { get; } = + new List(); + public List Towing { get; } = + new List(); + public bool SensorPingRequested { get; set; } + + public bool HasAny => + Movement != null || + SensorPingRequested || + WeaponGroups.Count > 0 || + Behaviors.Count > 0 || + Consumables.Count > 0 || + Docking.Count > 0 || + Wormholes.Count > 0 || + Towing.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 AetheriaRuntimeDaemonBehaviorIntent + { + public string ActorEntityKey { get; set; } = ""; + public int EquipmentIndex { get; set; } = -1; + public int BehaviorIndex { get; set; } = -1; + public bool Active { get; set; } + } + + 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 TargetZoneIndex { get; set; } = -1; + public double PositionX { get; set; } + public double PositionY { get; set; } + } + + public sealed class AetheriaRuntimeDaemonTowIntent + { + public string ActorEntityKey { get; set; } = ""; + public string StationEntityKey { get; set; } = ""; + public int TargetZoneIndex { get; set; } = -1; + public double PositionX { get; set; } + public double PositionY { get; set; } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonObservationCursor.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonObservationCursor.cs new file mode 100644 index 000000000..1f9d9a419 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonObservationCursor.cs @@ -0,0 +1,89 @@ +using System; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + 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 string LastFramePath { get; private set; } = ""; + public string LastSoaViewPath { get; private set; } = ""; + + public AetheriaRuntimeDaemonObservationResult Observe(AetheriaRuntimeObservedDaemonState? 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 ?? ""; + LastFramePath = observed.FramePath ?? ""; + LastSoaViewPath = observed.SoaViewPath ?? ""; + + return new AetheriaRuntimeDaemonObservationResult( + true, + frameChanged, + soaViewChanged, + frame.FrameId, + soaGeneration, + frame.IsAuthoritative); + } + + public void Reset() + { + LastFrameId = -1; + LastSoaGeneration = -1; + LastDaemonId = ""; + LastSessionId = ""; + LastFramePath = ""; + LastSoaViewPath = ""; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonObservationCursor.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonObservationCursor.cs.meta new file mode 100644 index 000000000..c2f0eef89 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonObservationCursor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bd46bb16aafbc684fa18d590767c9f9f \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonPublicationStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonPublicationStore.cs new file mode 100644 index 000000000..2d3a19299 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonPublicationStore.cs @@ -0,0 +1,282 @@ +using System; +using System.IO; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeDaemonPublicationStore + { + public static string GetProviderAdvertisementPath(string stateFilePath) + { + return AetheriaRuntimeStateBoundary.GetDaemonProviderPath(stateFilePath); + } + + public static string GetHealthPath(string stateFilePath) + { + return AetheriaRuntimeStateBoundary.GetDaemonHealthPath(stateFilePath); + } + + public static string GetCommandBoundaryPath(string stateFilePath) + { + return AetheriaRuntimeStateBoundary.GetDaemonCommandBoundaryPath(stateFilePath); + } + + public static string GetGameSurfacePath(string stateFilePath) + { + return AetheriaRuntimeStateBoundary.GetDaemonGameSurfacePath(stateFilePath); + } + + public static string GetGameTuiSurfacePath(string stateFilePath) + { + return AetheriaRuntimeStateBoundary.GetDaemonGameTuiSurfacePath(stateFilePath); + } + + public static string GetEditorSurfacePath(string stateFilePath) + { + return AetheriaRuntimeStateBoundary.GetDaemonEditorSurfacePath(stateFilePath); + } + + public static string GetEditorTuiSurfacePath(string stateFilePath) + { + return AetheriaRuntimeStateBoundary.GetDaemonEditorTuiSurfacePath(stateFilePath); + } + + public static string PublishProviderAdvertisement( + string stateFilePath, + AetheriaRuntimeDaemonProviderAdvertisementDocument document) + { + if (document == null) throw new ArgumentNullException(nameof(document)); + + document.Schema = AetheriaRuntimeDaemonSchemas.ProviderAdvertisement; + if (string.IsNullOrWhiteSpace(document.PublishedAtUtc)) + document.PublishedAtUtc = DateTime.UtcNow.ToString("O"); + + var path = GetProviderAdvertisementPath(stateFilePath); + Directory.CreateDirectory(Path.GetDirectoryName(path) ?? "."); + AetheriaRuntimeCultCacheDocumentStore.WriteDaemonProviderAdvertisement(path, document); + return path; + } + + public static string PublishHealth(string stateFilePath, AetheriaRuntimeDaemonHealthDocument document) + { + if (document == null) throw new ArgumentNullException(nameof(document)); + + document.Schema = AetheriaRuntimeDaemonSchemas.Health; + if (string.IsNullOrWhiteSpace(document.PublishedAtUtc)) + document.PublishedAtUtc = DateTime.UtcNow.ToString("O"); + + var path = GetHealthPath(stateFilePath); + Directory.CreateDirectory(Path.GetDirectoryName(path) ?? "."); + AetheriaRuntimeCultCacheDocumentStore.WriteDaemonHealth(path, document); + return path; + } + + public static string PublishCommandBoundary( + string stateFilePath, + AetheriaRuntimeDaemonCommandBoundaryDocument document) + { + if (document == null) throw new ArgumentNullException(nameof(document)); + + document.Schema = AetheriaRuntimeDaemonSchemas.CommandBoundary; + if (string.IsNullOrWhiteSpace(document.PublishedAtUtc)) + document.PublishedAtUtc = DateTime.UtcNow.ToString("O"); + + var path = GetCommandBoundaryPath(stateFilePath); + Directory.CreateDirectory(Path.GetDirectoryName(path) ?? "."); + AetheriaRuntimeCultCacheDocumentStore.WriteDaemonCommandBoundary(path, document); + return path; + } + + public static string PublishGameSurface(string stateFilePath, AetheriaRuntimeSurfaceDocument document) + { + if (document == null) throw new ArgumentNullException(nameof(document)); + + var path = GetGameSurfacePath(stateFilePath); + Directory.CreateDirectory(Path.GetDirectoryName(path) ?? "."); + AetheriaRuntimeCultCacheDocumentStore.WriteDaemonGameSurface(path, document); + return path; + } + + public static string PublishGameTuiSurface(string stateFilePath, AetheriaRuntimeSurfaceDocument document) + { + if (document == null) throw new ArgumentNullException(nameof(document)); + + var path = GetGameTuiSurfacePath(stateFilePath); + Directory.CreateDirectory(Path.GetDirectoryName(path) ?? "."); + AetheriaRuntimeCultCacheDocumentStore.WriteDaemonGameSurface(path, document); + return path; + } + + public static string PublishEditorSurface(string stateFilePath, AetheriaRuntimeSurfaceDocument document) + { + if (document == null) throw new ArgumentNullException(nameof(document)); + + var path = GetEditorSurfacePath(stateFilePath); + Directory.CreateDirectory(Path.GetDirectoryName(path) ?? "."); + AetheriaRuntimeCultCacheDocumentStore.WriteDaemonEditorSurface(path, document); + return path; + } + + public static string PublishEditorTuiSurface(string stateFilePath, AetheriaRuntimeSurfaceDocument document) + { + if (document == null) throw new ArgumentNullException(nameof(document)); + + var path = GetEditorTuiSurfacePath(stateFilePath); + Directory.CreateDirectory(Path.GetDirectoryName(path) ?? "."); + AetheriaRuntimeCultCacheDocumentStore.WriteDaemonEditorSurface(path, document); + return path; + } + + public static bool TryReadProviderAdvertisement( + string stateFilePath, + out AetheriaRuntimeDaemonProviderAdvertisementDocument document) + { + var path = GetProviderAdvertisementPath(stateFilePath); + if (!File.Exists(path)) + { + document = new AetheriaRuntimeDaemonProviderAdvertisementDocument(); + return false; + } + + document = AetheriaRuntimeCultCacheDocumentStore.ReadDaemonProviderAdvertisement(path); + return true; + } + + public static bool TryReadHealth(string stateFilePath, out AetheriaRuntimeDaemonHealthDocument document) + { + var path = GetHealthPath(stateFilePath); + if (!File.Exists(path)) + { + document = new AetheriaRuntimeDaemonHealthDocument(); + return false; + } + + document = AetheriaRuntimeCultCacheDocumentStore.ReadDaemonHealth(path); + return true; + } + + public static bool TryReadCommandBoundary( + string stateFilePath, + out AetheriaRuntimeDaemonCommandBoundaryDocument document) + { + var path = GetCommandBoundaryPath(stateFilePath); + if (!File.Exists(path)) + { + document = new AetheriaRuntimeDaemonCommandBoundaryDocument(); + return false; + } + + document = AetheriaRuntimeCultCacheDocumentStore.ReadDaemonCommandBoundary(path); + return true; + } + + public static bool TryReadGameSurface(string stateFilePath, out AetheriaRuntimeSurfaceDocument document) + { + var path = GetGameSurfacePath(stateFilePath); + if (!File.Exists(path)) + { + document = new AetheriaRuntimeSurfaceDocument( + "aetheria.daemon", + "game.daemon", + "", + 0, + "", + new AetheriaRuntimeSurfaceTree( + AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId, + new AetheriaRuntimeSurfaceComponent( + "aetheria.daemon.game.empty", + "surface", + new System.Collections.Generic.Dictionary(StringComparer.Ordinal), + Array.Empty()), + Array.Empty()), + Array.Empty()); + return false; + } + + document = AetheriaRuntimeCultCacheDocumentStore.ReadDaemonGameSurface(path); + return true; + } + + public static bool TryReadGameTuiSurface(string stateFilePath, out AetheriaRuntimeSurfaceDocument document) + { + var path = GetGameTuiSurfacePath(stateFilePath); + if (!File.Exists(path)) + { + document = new AetheriaRuntimeSurfaceDocument( + "aetheria.daemon", + "game.daemon", + "Aetheria Daemon", + 0, + "", + new AetheriaRuntimeSurfaceTree( + AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId, + new AetheriaRuntimeSurfaceComponent( + "aetheria.daemon.game.tui.missing", + "surface", + new System.Collections.Generic.Dictionary(StringComparer.Ordinal), + Array.Empty()), + Array.Empty()), + Array.Empty()); + return false; + } + + document = AetheriaRuntimeCultCacheDocumentStore.ReadDaemonGameSurface(path); + return true; + } + + public static bool TryReadEditorSurface(string stateFilePath, out AetheriaRuntimeSurfaceDocument document) + { + var path = GetEditorSurfacePath(stateFilePath); + if (!File.Exists(path)) + { + document = new AetheriaRuntimeSurfaceDocument( + "aetheria.daemon", + "editor.daemon", + "", + 0, + "", + new AetheriaRuntimeSurfaceTree( + AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId, + new AetheriaRuntimeSurfaceComponent( + "aetheria.daemon.editor.empty", + "surface", + new System.Collections.Generic.Dictionary(StringComparer.Ordinal), + Array.Empty()), + Array.Empty()), + Array.Empty()); + return false; + } + + document = AetheriaRuntimeCultCacheDocumentStore.ReadDaemonEditorSurface(path); + return true; + } + + public static bool TryReadEditorTuiSurface(string stateFilePath, out AetheriaRuntimeSurfaceDocument document) + { + var path = GetEditorTuiSurfacePath(stateFilePath); + if (!File.Exists(path)) + { + document = new AetheriaRuntimeSurfaceDocument( + "aetheria.daemon", + "editor.daemon", + "Aetheria Daemon Editor", + 0, + "", + new AetheriaRuntimeSurfaceTree( + AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId, + new AetheriaRuntimeSurfaceComponent( + "aetheria.daemon.editor.tui.missing", + "surface", + new System.Collections.Generic.Dictionary(StringComparer.Ordinal), + Array.Empty()), + Array.Empty()), + Array.Empty()); + return false; + } + + document = AetheriaRuntimeCultCacheDocumentStore.ReadDaemonEditorSurface(path); + return true; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaDocuments.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaDocuments.cs new file mode 100644 index 000000000..16a7f586f --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaDocuments.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections.Generic; +using GameCult.Caching; +using MessagePack; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeDaemonSoaBackends + { + public const string CultCache = "cultcache"; + 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 EntityKey = "entity.key"; + public const string ZoneIndex = "entity.zone_index"; + public const string PositionX = "transform.position.x"; + public const string PositionY = "transform.position.y"; + public const string PositionZ = "transform.position.z"; + public const string RotationRadians = "transform.rotation.radians"; + public const string VelocityX = "motion.velocity.x"; + public const string VelocityY = "motion.velocity.y"; + public const string VelocityZ = "motion.velocity.z"; + 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 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 Buffers { get; set; } = + Array.Empty(); + + [Key(10)] + public IReadOnlyList Columns { get; set; } = + Array.Empty(); + + [Key(11)] + public IReadOnlyList DirtyRanges { get; set; } = + Array.Empty(); + + [Key(12)] + public IReadOnlyList RenderGroups { get; set; } = + Array.Empty(); + + public static AetheriaRuntimeDaemonSoaViewDocument Create( + string daemonId, + string sessionId, + long frameId, + long generation, + IReadOnlyList? buffers, + IReadOnlyList? columns, + IReadOnlyList? dirtyRanges = null, + string backend = AetheriaRuntimeDaemonSoaBackends.CultCache, + string synchronizationMode = AetheriaRuntimeDaemonSoaSynchronizationModes.ImmutableFrame, + bool isAuthoritative = true, + IReadOnlyList? renderGroups = 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(), + Columns = columns ?? Array.Empty(), + DirtyRanges = dirtyRanges ?? Array.Empty(), + RenderGroups = renderGroups ?? Array.Empty() + }; + } + } + + [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; + } + + [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; } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaDocuments.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaDocuments.cs.meta new file mode 100644 index 000000000..0ea32ffd1 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaDocuments.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: aaba35482c606b241adcf479cbfa4298 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewIndex.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewIndex.cs new file mode 100644 index 000000000..b70934d05 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewIndex.cs @@ -0,0 +1,451 @@ +using System; +using System.Collections.Generic; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeDaemonSoaColumnBinding + { + public AetheriaRuntimeDaemonSoaColumnBinding( + AetheriaRuntimeDaemonSoaColumnDocument column, + AetheriaRuntimeDaemonSoaBufferDocument buffer, + long absoluteByteOffset, + long byteLength, + bool directMemoryCompatible) + { + Column = column ?? throw new ArgumentNullException(nameof(column)); + Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); + AbsoluteByteOffset = absoluteByteOffset; + ByteLength = byteLength; + DirectMemoryCompatible = directMemoryCompatible; + } + + public AetheriaRuntimeDaemonSoaColumnDocument Column { get; } + public AetheriaRuntimeDaemonSoaBufferDocument Buffer { get; } + public long AbsoluteByteOffset { get; } + public long ByteLength { get; } + public bool DirectMemoryCompatible { get; } + } + + public sealed class AetheriaRuntimeDaemonSoaViewIndex + { + private readonly Dictionary _buffersById; + private readonly Dictionary _columnsById; + private readonly Dictionary> _columnsByKind; + private readonly Dictionary> _dirtyRangesByColumnId; + private readonly Dictionary _renderGroupsById; + + private AetheriaRuntimeDaemonSoaViewIndex( + AetheriaRuntimeDaemonSoaViewDocument view, + Dictionary buffersById, + Dictionary columnsById, + Dictionary> columnsByKind, + Dictionary> dirtyRangesByColumnId, + Dictionary renderGroupsById, + string[] validationErrors) + { + View = view ?? throw new ArgumentNullException(nameof(view)); + _buffersById = buffersById; + _columnsById = columnsById; + _columnsByKind = columnsByKind; + _dirtyRangesByColumnId = dirtyRangesByColumnId; + _renderGroupsById = renderGroupsById; + ValidationErrors = validationErrors; + } + + public AetheriaRuntimeDaemonSoaViewDocument View { get; } + public IReadOnlyList ValidationErrors { get; } + public bool IsValid => ValidationErrors.Count == 0; + + public static AetheriaRuntimeDaemonSoaViewIndex Empty { get; } = + Build(new AetheriaRuntimeDaemonSoaViewDocument { IsAuthoritative = false }); + + public static AetheriaRuntimeDaemonSoaViewIndex Build( + AetheriaRuntimeDaemonSoaViewDocument? view, + bool requireObserverReadOnly = true) + { + var effectiveView = view ?? new AetheriaRuntimeDaemonSoaViewDocument { IsAuthoritative = false }; + var errors = new List(); + var buffersById = new Dictionary(StringComparer.Ordinal); + var columnsById = new Dictionary(StringComparer.Ordinal); + var columnsByKind = new Dictionary>(StringComparer.Ordinal); + var dirtyRangesByColumnId = new Dictionary>(StringComparer.Ordinal); + var renderGroupsById = new Dictionary(); + + if (!string.Equals(effectiveView.Schema, AetheriaRuntimeDaemonSchemas.SoaView, StringComparison.Ordinal)) + { + errors.Add($"Unexpected SoA schema '{effectiveView.Schema}'."); + } + + foreach (var buffer in effectiveView.Buffers ?? Array.Empty()) + { + if (string.IsNullOrWhiteSpace(buffer.BufferId)) + { + errors.Add("SoA buffer is missing a buffer id."); + continue; + } + + if (buffersById.ContainsKey(buffer.BufferId)) + { + errors.Add($"Duplicate SoA buffer id '{buffer.BufferId}'."); + continue; + } + + if (buffer.ByteOffset < 0) + { + errors.Add($"SoA buffer '{buffer.BufferId}' has a negative byte offset."); + } + + if (buffer.ByteLength < 0) + { + errors.Add($"SoA buffer '{buffer.BufferId}' has a negative byte length."); + } + + if (requireObserverReadOnly && buffer.ObserverWritable) + { + errors.Add($"SoA buffer '{buffer.BufferId}' is observer-writable; Unity must observe daemon state read-only."); + } + + buffersById.Add(buffer.BufferId, buffer); + } + + foreach (var column in effectiveView.Columns ?? Array.Empty()) + { + if (string.IsNullOrWhiteSpace(column.ColumnId)) + { + errors.Add("SoA column is missing a column id."); + continue; + } + + if (columnsById.ContainsKey(column.ColumnId)) + { + errors.Add($"Duplicate SoA column id '{column.ColumnId}'."); + continue; + } + + if (!buffersById.TryGetValue(column.BufferId ?? "", out var buffer)) + { + errors.Add($"SoA column '{column.ColumnId}' references missing buffer '{column.BufferId}'."); + continue; + } + + if (string.IsNullOrWhiteSpace(column.Kind)) + { + errors.Add($"SoA column '{column.ColumnId}' is missing a semantic kind."); + } + + if (column.ByteOffset < 0) + { + errors.Add($"SoA column '{column.ColumnId}' has a negative byte offset."); + } + + if (column.ElementStride <= 0) + { + errors.Add($"SoA column '{column.ColumnId}' has a non-positive element stride."); + } + + if (column.ElementCount < 0) + { + errors.Add($"SoA column '{column.ColumnId}' has a negative element count."); + } + + var scalarByteLength = GetScalarByteLength(column.ScalarType); + if (scalarByteLength <= 0) + { + errors.Add($"SoA column '{column.ColumnId}' has unsupported scalar type '{column.ScalarType}'."); + scalarByteLength = Math.Max(1, column.ElementStride); + } + + var columnByteLength = GetColumnByteLength(column.ElementCount, column.ElementStride, scalarByteLength); + var absoluteByteOffset = buffer.ByteOffset + column.ByteOffset; + if (column.ByteOffset >= 0 && columnByteLength >= 0 && buffer.ByteLength >= 0) + { + var columnEnd = column.ByteOffset + columnByteLength; + if (columnEnd > buffer.ByteLength) + { + errors.Add($"SoA column '{column.ColumnId}' exceeds buffer '{buffer.BufferId}'."); + } + } + + var binding = new AetheriaRuntimeDaemonSoaColumnBinding( + column, + buffer, + absoluteByteOffset, + columnByteLength, + IsDirectMemoryCompatible(buffer)); + + ValidateSemanticColumn(column, errors); + + columnsById.Add(column.ColumnId, binding); + if (!columnsByKind.TryGetValue(column.Kind ?? "", out var kindBindings)) + { + kindBindings = new List(); + columnsByKind[column.Kind ?? ""] = kindBindings; + } + + kindBindings.Add(binding); + } + + foreach (var dirtyRange in effectiveView.DirtyRanges ?? Array.Empty()) + { + if (!columnsById.TryGetValue(dirtyRange.ColumnId ?? "", out var binding)) + { + errors.Add($"SoA dirty range references missing column '{dirtyRange.ColumnId}'."); + continue; + } + + if (dirtyRange.StartIndex < 0) + { + errors.Add($"SoA dirty range for column '{dirtyRange.ColumnId}' has a negative start index."); + } + + if (dirtyRange.Count < 0) + { + errors.Add($"SoA dirty range for column '{dirtyRange.ColumnId}' has a negative count."); + } + + if (dirtyRange.StartIndex >= 0 && dirtyRange.Count >= 0 && + dirtyRange.StartIndex + dirtyRange.Count > binding.Column.ElementCount) + { + errors.Add($"SoA dirty range for column '{dirtyRange.ColumnId}' exceeds the column element count."); + } + + if (!dirtyRangesByColumnId.TryGetValue(dirtyRange.ColumnId ?? "", out var ranges)) + { + ranges = new List(); + dirtyRangesByColumnId[dirtyRange.ColumnId ?? ""] = ranges; + } + + ranges.Add(dirtyRange); + } + + foreach (var renderGroup in effectiveView.RenderGroups ?? Array.Empty()) + { + if (renderGroup.GroupId < 0) + { + errors.Add($"Render group '{renderGroup.GroupId}' has a negative group id."); + continue; + } + + if (renderGroupsById.ContainsKey(renderGroup.GroupId)) + { + errors.Add($"Duplicate render group id '{renderGroup.GroupId}'."); + continue; + } + + if (string.IsNullOrWhiteSpace(renderGroup.MeshKey)) + { + errors.Add($"Render group '{renderGroup.GroupId}' is missing a mesh key."); + } + + if (string.IsNullOrWhiteSpace(renderGroup.MaterialKey)) + { + errors.Add($"Render group '{renderGroup.GroupId}' is missing a material key."); + } + + if (renderGroup.SubMeshIndex < 0) + { + errors.Add($"Render group '{renderGroup.GroupId}' has a negative submesh index."); + } + + if (renderGroup.InstanceCount < -1) + { + errors.Add($"Render group '{renderGroup.GroupId}' has an invalid instance count."); + } + + if (renderGroup.BoundsSizeX <= 0 || + renderGroup.BoundsSizeY <= 0 || + renderGroup.BoundsSizeZ <= 0) + { + errors.Add($"Render group '{renderGroup.GroupId}' must publish positive render bounds."); + } + + if (!IsValidShadowMode(renderGroup.ShadowMode)) + { + errors.Add($"Render group '{renderGroup.GroupId}' has invalid shadow mode '{renderGroup.ShadowMode}'."); + } + + if (renderGroup.DefaultScale <= 0) + { + errors.Add($"Render group '{renderGroup.GroupId}' has a non-positive default scale."); + } + + if (renderGroup.Lod < -1) + { + errors.Add($"Render group '{renderGroup.GroupId}' has an invalid render lod."); + } + + renderGroupsById.Add(renderGroup.GroupId, renderGroup); + } + + if (renderGroupsById.Count > 1 && + !columnsByKind.ContainsKey(AetheriaRuntimeDaemonSoaColumnKinds.RenderGroupId)) + { + errors.Add("Multiple render groups require a render group id column."); + } + + return new AetheriaRuntimeDaemonSoaViewIndex( + effectiveView, + buffersById, + columnsById, + columnsByKind, + dirtyRangesByColumnId, + renderGroupsById, + errors.ToArray()); + } + + public bool TryGetBuffer(string bufferId, out AetheriaRuntimeDaemonSoaBufferDocument buffer) + { + return _buffersById.TryGetValue(bufferId ?? "", out buffer); + } + + public bool TryGetColumn(string columnId, out AetheriaRuntimeDaemonSoaColumnBinding binding) + { + return _columnsById.TryGetValue(columnId ?? "", out binding); + } + + public bool TryGetFirstColumnOfKind(string kind, out AetheriaRuntimeDaemonSoaColumnBinding binding) + { + if (_columnsByKind.TryGetValue(kind ?? "", out var bindings) && bindings.Count > 0) + { + binding = bindings[0]; + return true; + } + + binding = null!; + return false; + } + + public IReadOnlyList GetColumnsOfKind(string kind) + { + return _columnsByKind.TryGetValue(kind ?? "", out var bindings) + ? bindings + : Array.Empty(); + } + + public IReadOnlyList GetDirtyRanges(string columnId) + { + return _dirtyRangesByColumnId.TryGetValue(columnId ?? "", out var ranges) + ? ranges + : Array.Empty(); + } + + public IReadOnlyList RenderGroups => + View.RenderGroups ?? Array.Empty(); + + public bool TryGetRenderGroup(int groupId, out AetheriaRuntimeDaemonRenderGroupDocument renderGroup) + { + return _renderGroupsById.TryGetValue(groupId, out renderGroup); + } + + private static bool IsDirectMemoryCompatible(AetheriaRuntimeDaemonSoaBufferDocument buffer) + { + return !buffer.ObserverWritable && + (string.Equals(buffer.Backend, AetheriaRuntimeDaemonSoaBackends.MemoryMappedFile, StringComparison.Ordinal) || + string.Equals(buffer.Backend, AetheriaRuntimeDaemonSoaBackends.SharedNativeMemory, StringComparison.Ordinal)); + } + + private static void ValidateSemanticColumn( + AetheriaRuntimeDaemonSoaColumnDocument column, + List errors) + { + switch (column.Kind) + { + case AetheriaRuntimeDaemonSoaColumnKinds.PositionX: + case AetheriaRuntimeDaemonSoaColumnKinds.PositionY: + case AetheriaRuntimeDaemonSoaColumnKinds.PositionZ: + case AetheriaRuntimeDaemonSoaColumnKinds.RotationRadians: + case AetheriaRuntimeDaemonSoaColumnKinds.VelocityX: + case AetheriaRuntimeDaemonSoaColumnKinds.VelocityY: + case AetheriaRuntimeDaemonSoaColumnKinds.VelocityZ: + case AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyRadius: + case AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyMass: + case AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyInverseMass: + if (!string.Equals(column.ScalarType, "float32", StringComparison.Ordinal)) + { + errors.Add($"SoA column '{column.ColumnId}' {column.Kind} must use float32."); + } + + break; + case AetheriaRuntimeDaemonSoaColumnKinds.RenderScale: + if (!string.Equals(column.ScalarType, "float32", StringComparison.Ordinal)) + { + errors.Add($"SoA column '{column.ColumnId}' render scale must use float32."); + } + + break; + case AetheriaRuntimeDaemonSoaColumnKinds.RenderVisibility: + if (!string.Equals(column.ScalarType, "bool", StringComparison.Ordinal) && + !string.Equals(column.ScalarType, "byte", StringComparison.Ordinal) && + !string.Equals(column.ScalarType, "uint8", StringComparison.Ordinal)) + { + errors.Add($"SoA column '{column.ColumnId}' render visibility must use bool, byte, or uint8."); + } + + break; + case AetheriaRuntimeDaemonSoaColumnKinds.RenderLod: + if (!string.Equals(column.ScalarType, "int32", StringComparison.Ordinal)) + { + errors.Add($"SoA column '{column.ColumnId}' render lod must use int32."); + } + + break; + } + } + + private static bool IsValidShadowMode(string? shadowMode) + { + switch (shadowMode) + { + case AetheriaRuntimeDaemonRenderShadowModes.Off: + case AetheriaRuntimeDaemonRenderShadowModes.On: + case AetheriaRuntimeDaemonRenderShadowModes.TwoSided: + case AetheriaRuntimeDaemonRenderShadowModes.ShadowsOnly: + return true; + default: + return false; + } + } + + private static int GetScalarByteLength(string? scalarType) + { + switch (scalarType) + { + case "float32": + case "int32": + case "uint32": + return 4; + case "float64": + case "int64": + case "uint64": + return 8; + case "int16": + case "uint16": + return 2; + case "byte": + case "uint8": + case "int8": + case "bool": + return 1; + default: + return 0; + } + } + + private static long GetColumnByteLength(int elementCount, int elementStride, int scalarByteLength) + { + if (elementCount <= 0) + { + return 0; + } + + if (elementStride <= 0 || scalarByteLength <= 0) + { + return -1; + } + + return ((long)elementCount - 1) * elementStride + scalarByteLength; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewIndex.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewIndex.cs.meta new file mode 100644 index 000000000..e0114364c --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewIndex.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0e36baec351d02f4f8de31d3a9ad74dd \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewStore.cs new file mode 100644 index 000000000..dade3fe38 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewStore.cs @@ -0,0 +1,39 @@ +using System.IO; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeDaemonSoaViewStore + { + public static string GetViewPath(string stateFilePath) + { + return AetheriaRuntimeStateBoundary.GetDaemonSoaViewPath(stateFilePath); + } + + public static string PublishView(string stateFilePath, AetheriaRuntimeDaemonSoaViewDocument view) + { + var path = GetViewPath(stateFilePath); + AetheriaRuntimeCultCacheDocumentStore.WriteDaemonSoaView(path, view); + return path; + } + + public static bool TryReadView(string stateFilePath, out AetheriaRuntimeDaemonSoaViewDocument view) + { + var path = GetViewPath(stateFilePath); + if (!File.Exists(path)) + { + view = new AetheriaRuntimeDaemonSoaViewDocument(); + return false; + } + + view = AetheriaRuntimeCultCacheDocumentStore.ReadDaemonSoaView(path); + return true; + } + + public static AetheriaRuntimeDaemonSoaViewDocument ReadView(string stateFilePath) + { + return AetheriaRuntimeCultCacheDocumentStore.ReadDaemonSoaView(GetViewPath(stateFilePath)); + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewStore.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewStore.cs.meta new file mode 100644 index 000000000..4ac81fa8c --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e7b9b2f5fbcc6fd44b0a7984041a6330 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSurfaceCommands.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSurfaceCommands.cs new file mode 100644 index 000000000..5b56f7340 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSurfaceCommands.cs @@ -0,0 +1,51 @@ +using System; +using GameCult.Eve.Surface; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeDaemonSurfaceCommands + { + private const string CommandPrefix = "aetheria.daemon.commands."; + + public static bool TrySubmit( + string stateFilePath, + EveSurfaceCommandRequest request, + out AetheriaRuntimeDaemonCommandEnvelope? envelope) + { + envelope = null; + if (request == null || + !string.Equals(request.ProviderId, "aetheria.daemon", StringComparison.Ordinal) || + !TryResolveKind(request, out var kind)) + { + return false; + } + + AetheriaRuntimeStateReader.TryReadObservedDaemonState(stateFilePath, out var observed); + var client = new AetheriaRuntimeDaemonOperationClient( + stateFilePath, + string.IsNullOrWhiteSpace(request.ClientId) ? "unity-uitoolkit" : request.ClientId, + observed?.Frame.SessionId ?? "local"); + var command = client.Create(kind, observed); + if (client.TrySend(command, out envelope, out _)) + { + return true; + } + + envelope = AetheriaRuntimeDaemonOperationClient.ToEnvelope(command); + return true; + } + + private static bool TryResolveKind(EveSurfaceCommandRequest request, out AetheriaRuntimeDaemonCommandKinds kind) + { + kind = AetheriaRuntimeDaemonCommandKinds.None; + var command = request.Command ?? ""; + if (command.StartsWith(CommandPrefix, StringComparison.Ordinal)) + command = command.Substring(CommandPrefix.Length); + + return Enum.TryParse(command, ignoreCase: false, out kind) && + kind != AetheriaRuntimeDaemonCommandKinds.None; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonTickRunner.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonTickRunner.cs new file mode 100644 index 000000000..3538ea52a --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonTickRunner.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeDaemonTickOptions + { + public string DaemonId { get; set; } = "aetheria-daemon"; + public string SessionId { get; set; } = "local"; + public string VerseId { get; set; } = "aetheria.local"; + public string CultMeshAddress { get; set; } = "cultmesh://aetheria.local/eve/providers/aetheria.daemon"; + public long FrameId { get; set; } + public double SimulationTimeSeconds { get; set; } + public double FixedDeltaSeconds { get; set; } + public IReadOnlyList ObservedCommands { get; set; } = + Array.Empty(); + public IReadOnlyList AccountedCommandIds { get; set; } = + Array.Empty(); + public AetheriaRuntimeDaemonOperationContext OperationContext { get; set; } = + new AetheriaRuntimeDaemonOperationContext(); + } + + public sealed class AetheriaRuntimeDaemonTickResult + { + public AetheriaRuntimeDaemonTickResult( + AetheriaRuntimeRunCheckpointCommit run, + AetheriaRuntimeDaemonOperationResult operationResult, + AetheriaRuntimeDaemonFrameDocument frame, + string framePath, + string providerAdvertisementPath, + string healthPath, + string commandBoundaryPath, + string gameSurfacePath, + string gameTuiSurfacePath, + string editorSurfacePath, + string editorTuiSurfacePath) + { + Run = run ?? new AetheriaRuntimeRunCheckpointCommit(); + OperationResult = operationResult ?? new AetheriaRuntimeDaemonOperationResult( + Run, + Array.Empty(), + Array.Empty()); + Frame = frame ?? new AetheriaRuntimeDaemonFrameDocument(); + FramePath = framePath ?? ""; + ProviderAdvertisementPath = providerAdvertisementPath ?? ""; + HealthPath = healthPath ?? ""; + CommandBoundaryPath = commandBoundaryPath ?? ""; + GameSurfacePath = gameSurfacePath ?? ""; + GameTuiSurfacePath = gameTuiSurfacePath ?? ""; + EditorSurfacePath = editorSurfacePath ?? ""; + EditorTuiSurfacePath = editorTuiSurfacePath ?? ""; + } + + public AetheriaRuntimeRunCheckpointCommit Run { get; } + public AetheriaRuntimeDaemonOperationResult OperationResult { get; } + public AetheriaRuntimeDaemonFrameDocument Frame { get; } + public string FramePath { get; } + public string ProviderAdvertisementPath { get; } + public string HealthPath { get; } + public string CommandBoundaryPath { get; } + public string GameSurfacePath { get; } + public string GameTuiSurfacePath { get; } + public string EditorSurfacePath { get; } + public string EditorTuiSurfacePath { get; } + public AetheriaRuntimeDaemonIntentState Intents => OperationResult.Intents; + } + + public static class AetheriaRuntimeDaemonTickRunner + { + public static AetheriaRuntimeDaemonTickResult Tick( + string stateFilePath, + AetheriaRuntimeRunCheckpointCommit run, + AetheriaRuntimeDaemonTickOptions options) + { + if (string.IsNullOrWhiteSpace(stateFilePath)) + throw new ArgumentException("State file path must be non-empty.", nameof(stateFilePath)); + + run ??= new AetheriaRuntimeRunCheckpointCommit(); + options ??= new AetheriaRuntimeDaemonTickOptions(); + options.OperationContext ??= new AetheriaRuntimeDaemonOperationContext(); + + var observedCommands = (options.ObservedCommands ?? Array.Empty()) + .Where(command => command != null) + .ToArray(); + var accountedBeforeTick = new HashSet( + options.AccountedCommandIds ?? Array.Empty(), + StringComparer.Ordinal); + var commands = observedCommands + .Where(command => !string.IsNullOrWhiteSpace(command.CommandId)) + .Where(command => !accountedBeforeTick.Contains(command.CommandId)) + .ToArray(); + var operationResult = AetheriaRuntimeDaemonOperations.Execute( + run, + commands, + options.OperationContext); + + var frame = AetheriaRuntimeDaemonFrameDocument.Create( + operationResult.Run, + options.DaemonId, + options.SessionId, + options.FrameId, + options.SimulationTimeSeconds, + options.FixedDeltaSeconds); + frame.AppliedCommandIds = operationResult.AppliedCommandIds; + frame.RejectedCommandIds = operationResult.RejectedCommandIds; + frame.AccountedCommandIds = accountedBeforeTick + .Concat(operationResult.AppliedCommandIds) + .Concat(operationResult.RejectedCommandIds) + .Where(commandId => !string.IsNullOrWhiteSpace(commandId)) + .Distinct(StringComparer.Ordinal) + .ToArray(); + frame.Capabilities = new[] + { + "aetheria.daemon.operation_execute.v1", + "aetheria.daemon.intent_state.v1", + "aetheria.daemon.authoritative_frame.v1", + AetheriaRuntimeDaemonSchemas.ProviderAdvertisement, + AetheriaRuntimeDaemonSchemas.Health, + AetheriaRuntimeDaemonSchemas.CommandBoundary, + AetheriaRuntimeDaemonSchemas.GameSurface, + AetheriaRuntimeDaemonSchemas.EditorSurface + }; + + var framePath = AetheriaRuntimeDaemonFrameStore.PublishFrame(stateFilePath, frame); + var commandBoundary = AetheriaRuntimeDaemonCommandBoundaryDocument.Create(options.DaemonId); + var commandBoundaryPath = AetheriaRuntimeDaemonPublicationStore.PublishCommandBoundary( + stateFilePath, + commandBoundary); + var providerAdvertisement = AetheriaRuntimeDaemonProviderAdvertisementDocument.Create( + stateFilePath, + options.DaemonId, + options.VerseId, + options.CultMeshAddress); + var providerAdvertisementPath = AetheriaRuntimeDaemonPublicationStore.PublishProviderAdvertisement( + stateFilePath, + providerAdvertisement); + var healthPath = AetheriaRuntimeDaemonPublicationStore.PublishHealth( + stateFilePath, + new AetheriaRuntimeDaemonHealthDocument + { + DaemonId = string.IsNullOrWhiteSpace(options.DaemonId) ? "aetheria-daemon" : options.DaemonId, + VerseId = string.IsNullOrWhiteSpace(options.VerseId) ? "aetheria.local" : options.VerseId, + PublishedAtUtc = DateTime.UtcNow.ToString("O"), + StatePath = stateFilePath, + FrameId = frame.FrameId, + ObservedCommandCount = observedCommands.Length, + AppliedCommandCount = operationResult.AppliedCommandIds.Count, + RejectedCommandCount = operationResult.RejectedCommandIds.Count, + Status = operationResult.RejectedCommandIds.Count == 0 ? "healthy" : "commands-rejected", + PublicationSource = "daemon-published", + Transport = "cultcache-witness", + CommandBoundaryPath = commandBoundaryPath + }); + AetheriaRuntimeDaemonPublicationStore.TryReadHealth(stateFilePath, out var health); + var gameSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( + frame, + health, + commandBoundary); + var gameSurfacePath = AetheriaRuntimeDaemonPublicationStore.PublishGameSurface( + stateFilePath, + gameSurface); + var gameTuiSurfacePath = AetheriaRuntimeDaemonPublicationStore.PublishGameTuiSurface( + stateFilePath, + gameSurface); + var editorSurface = AetheriaRuntimeDaemonEditorSurfaceBuilder.Build( + providerAdvertisement, + health, + commandBoundary); + var editorSurfacePath = AetheriaRuntimeDaemonPublicationStore.PublishEditorSurface( + stateFilePath, + editorSurface); + var editorTuiSurfacePath = AetheriaRuntimeDaemonPublicationStore.PublishEditorTuiSurface( + stateFilePath, + editorSurface); + return new AetheriaRuntimeDaemonTickResult( + operationResult.Run, + operationResult, + frame, + framePath, + providerAdvertisementPath, + healthPath, + commandBoundaryPath, + gameSurfacePath, + gameTuiSurfacePath, + editorSurfacePath, + editorTuiSurfacePath); + } + + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs new file mode 100644 index 000000000..044c6db94 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs @@ -0,0 +1,522 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using GameCult.Eve.Surface; + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeEquippedItemMetric + { + public AetheriaRuntimeEquippedItemMetric(string id, string label, string value) + { + Id = id ?? ""; + Label = label ?? ""; + Value = value ?? ""; + } + + public string Id { get; } + public string Label { get; } + public string Value { get; } + } + + public sealed class AetheriaRuntimeEquippedItemSection + { + public AetheriaRuntimeEquippedItemSection( + string id, + string title, + IReadOnlyList metrics) + { + Id = id ?? ""; + Title = title ?? ""; + Metrics = metrics ?? Array.Empty(); + } + + public string Id { get; } + public string Title { get; } + public IReadOnlyList Metrics { get; } + } + + public sealed class AetheriaRuntimeEquippedItemControl + { + public AetheriaRuntimeEquippedItemControl( + string id, + string label, + string command, + IReadOnlyDictionary payload) + { + Id = id ?? ""; + Label = label ?? ""; + Command = command ?? ""; + Payload = payload ?? EmptyPayload; + } + + public string Id { get; } + public string Label { get; } + public string Command { get; } + public IReadOnlyDictionary Payload { get; } + + private static readonly IReadOnlyDictionary EmptyPayload = + new Dictionary(StringComparer.Ordinal); + } + + public sealed class AetheriaRuntimeEquippedItemTemperatureControl + { + public AetheriaRuntimeEquippedItemTemperatureControl( + string id, + string label, + string value, + IReadOnlyDictionary payload) + { + Id = id ?? ""; + Label = label ?? ""; + Value = value ?? ""; + Payload = payload ?? EmptyPayload; + } + + public string Id { get; } + public string Label { get; } + public string Value { get; } + public IReadOnlyDictionary Payload { get; } + + private static readonly IReadOnlyDictionary EmptyPayload = + new Dictionary(StringComparer.Ordinal); + } + + public sealed class AetheriaRuntimeEquippedItemActionBarSlot + { + public AetheriaRuntimeEquippedItemActionBarSlot( + string id, + string title, + string currentBinding, + IReadOnlyList controls) + { + Id = id ?? ""; + Title = title ?? ""; + CurrentBinding = currentBinding ?? ""; + Controls = controls ?? Array.Empty(); + } + + public string Id { get; } + public string Title { get; } + public string CurrentBinding { get; } + public IReadOnlyList Controls { get; } + } + + public sealed class AetheriaRuntimeEquippedItemDetailsSurfaceState + { + public AetheriaRuntimeEquippedItemDetailsSurfaceState( + string itemName, + string description, + string manufacturer, + string mass, + string durability, + string temperature, + string thermalRange, + string overrideShutdown, + string overrideShutdownLabel, + IReadOnlyList temperatureControls, + IReadOnlyList behaviorSections, + IReadOnlyList weaponGroupControls, + IReadOnlyList actionBarSlots, + string updatedAtUtc) + { + ItemName = itemName ?? ""; + Description = description ?? ""; + Manufacturer = manufacturer ?? ""; + Mass = mass ?? ""; + Durability = durability ?? ""; + Temperature = temperature ?? ""; + ThermalRange = thermalRange ?? ""; + OverrideShutdown = overrideShutdown ?? ""; + OverrideShutdownLabel = overrideShutdownLabel ?? ""; + TemperatureControls = temperatureControls ?? Array.Empty(); + BehaviorSections = behaviorSections ?? Array.Empty(); + WeaponGroupControls = weaponGroupControls ?? Array.Empty(); + ActionBarSlots = actionBarSlots ?? Array.Empty(); + UpdatedAtUtc = updatedAtUtc ?? ""; + } + + public string ItemName { get; } + public string Description { get; } + public string Manufacturer { get; } + public string Mass { get; } + public string Durability { get; } + public string Temperature { get; } + public string ThermalRange { get; } + public string OverrideShutdown { get; } + public string OverrideShutdownLabel { get; } + public IReadOnlyList TemperatureControls { get; } + public IReadOnlyList BehaviorSections { get; } + public IReadOnlyList WeaponGroupControls { get; } + public IReadOnlyList ActionBarSlots { get; } + public string UpdatedAtUtc { get; } + public bool HasWeaponControls => WeaponGroupControls.Count > 0; + } + + public static class AetheriaRuntimeEquippedItemDetailsSurfaceBuilder + { + public const string SurfaceId = "aetheria.inventory.equipped_item_details"; + public const string Close = "aetheria.inventory.equipped_item_details.close"; + public const string ToggleOverrideShutdown = "aetheria.inventory.equipped_item_details.override_shutdown.toggle"; + public const string SetTargetTemperature = "aetheria.inventory.equipped_item_details.target_temperature.set"; + public const string ToggleWeaponGroup = "aetheria.inventory.equipped_item_details.weapon_group.toggle"; + public const string BindWeaponGroup = "aetheria.inventory.equipped_item_details.weapon_group.bind"; + public const string ClearActionBarBinding = "aetheria.inventory.equipped_item_details.action_bar.clear"; + + public static AetheriaRuntimeSurfaceDocument Build( + AetheriaRuntimeEquippedItemDetailsSurfaceState state, + long version = 1) + { + state ??= new AetheriaRuntimeEquippedItemDetailsSurfaceState( + "", + "", + "", + "", + "", + "", + "", + "", + "", + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + ""); + + var children = new List + { + Card( + $"{SurfaceId}.summary", + state.ItemName, + Text( + $"{SurfaceId}.description", + string.IsNullOrWhiteSpace(state.Description) + ? "No typed item description is available." + : state.Description), + Text( + $"{SurfaceId}.note", + "Unity projects the selected equipment; the shared runtime surface owns equipped-item inspection layout and commands."), + Metric($"{SurfaceId}.manufacturer", "Manufacturer", state.Manufacturer), + Metric($"{SurfaceId}.mass", "Mass", state.Mass)), + Card( + $"{SurfaceId}.status.card", + "Status", + Metric($"{SurfaceId}.durability", "Durability", state.Durability), + Metric($"{SurfaceId}.temperature", "Temperature", state.Temperature), + Metric($"{SurfaceId}.thermal_range", "Thermal Range", state.ThermalRange)) + }; + + foreach (var section in state.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)) + .ToArray())); + } + + children.Add(BuildControlsCard(state)); + + if (state.HasWeaponControls) + { + children.Add(Card( + $"{SurfaceId}.weapon_groups.card", + "Weapon Groups", + Text( + $"{SurfaceId}.weapon_groups.note", + "Toggle membership directly; action-bar group binding is handled below."), + ButtonRow( + $"{SurfaceId}.weapon_groups.actions", + state.WeaponGroupControls.Select(Button).ToArray()))); + + children.AddRange(state.ActionBarSlots.Select(BuildActionBarCard)); + } + + children.Add(ButtonRow( + $"{SurfaceId}.actions", + Button($"{SurfaceId}.close", "Close", Close))); + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "inventory.menu", + title: "Inventory Equipped Item Details", + version: version, + updatedAtUtc: state.UpdatedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + SurfaceId, + Node( + $"{SurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + children.ToArray()), + Array.Empty()), + commands: new[] + { + new AetheriaRuntimeSurfaceCommandTemplate(Close, "Close", "unity-uitoolkit"), + new AetheriaRuntimeSurfaceCommandTemplate(ToggleOverrideShutdown, "Toggle Override Shutdown", "unity-uitoolkit"), + new AetheriaRuntimeSurfaceCommandTemplate(SetTargetTemperature, "Set Target Temperature", "unity-uitoolkit"), + new AetheriaRuntimeSurfaceCommandTemplate(ToggleWeaponGroup, "Toggle Weapon Group", "unity-uitoolkit"), + new AetheriaRuntimeSurfaceCommandTemplate(BindWeaponGroup, "Bind Weapon Group", "unity-uitoolkit"), + new AetheriaRuntimeSurfaceCommandTemplate(ClearActionBarBinding, "Clear Action Bar Binding", "unity-uitoolkit") + }); + } + + public static IReadOnlyDictionary Payload(params (string Key, string Value)[] values) + { + return (values ?? Array.Empty<(string Key, string Value)>()) + .ToDictionary(value => value.Key ?? "", value => value.Value ?? "", StringComparer.Ordinal); + } + + private static AetheriaRuntimeSurfaceComponent BuildControlsCard( + AetheriaRuntimeEquippedItemDetailsSurfaceState state) + { + var children = new List + { + Metric( + $"{SurfaceId}.controls.override_shutdown.metric", + "Override Shutdown", + state.OverrideShutdown), + ButtonRow( + $"{SurfaceId}.controls.override_shutdown.actions", + Button( + $"{SurfaceId}.controls.override_shutdown.toggle", + string.IsNullOrWhiteSpace(state.OverrideShutdownLabel) + ? "Toggle Override" + : state.OverrideShutdownLabel, + ToggleOverrideShutdown)) + }; + + children.AddRange(state.TemperatureControls.Select(control => Metric( + $"{control.Id}.metric", + control.Label, + control.Value))); + children.AddRange(state.TemperatureControls.Select(control => TextField( + control.Id, + control.Label, + SetTargetTemperature, + control.Value, + control.Payload))); + + return Card($"{SurfaceId}.controls.card", "Controls", children.ToArray()); + } + + private static AetheriaRuntimeSurfaceComponent BuildActionBarCard( + AetheriaRuntimeEquippedItemActionBarSlot slot) + { + return Card( + slot.Id, + slot.Title, + Metric($"{slot.Id}.binding", "Current", slot.CurrentBinding), + ButtonRow($"{slot.Id}.actions", slot.Controls.Select(Button).ToArray())); + } + + 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) + { + 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(AetheriaRuntimeEquippedItemControl control) + { + return CommandButton(control.Id, control.Label, control.Command, control.Payload); + } + + private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) + { + return Node(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent CommandButton( + string id, + string label, + string command, + IReadOnlyDictionary payload) + { + var props = new List<(string Key, string Value)> + { + ("label", label ?? ""), + ("command", command ?? "") + }; + + if (payload != null) + { + props.AddRange(payload.Select(entry => (entry.Key, entry.Value))); + } + + return Node(id, "control.button", props); + } + + private static AetheriaRuntimeSurfaceComponent TextField( + string id, + string label, + string command, + string value, + IReadOnlyDictionary payload) + { + var props = new List<(string Key, string Value)> + { + ("label", label ?? ""), + ("command", command ?? ""), + ("value", value ?? "") + }; + + if (payload != null) + { + props.AddRange(payload.Select(entry => (entry.Key, entry.Value))); + } + + return Node(id, "control.text", props); + } + + 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 ?? Array.Empty<(string Key, string Value)>()) + .ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal), + children ?? Array.Empty()); + } + } + + public enum AetheriaRuntimeEquippedItemDetailsCommandKind + { + Unknown = 0, + Close = 1, + ToggleOverrideShutdown = 2, + SetTargetTemperature = 3, + ToggleWeaponGroup = 4, + BindWeaponGroup = 5, + ClearActionBarBinding = 6 + } + + public readonly struct AetheriaRuntimeEquippedItemDetailsCommand + { + public AetheriaRuntimeEquippedItemDetailsCommand( + AetheriaRuntimeEquippedItemDetailsCommandKind kind, + int behaviorIndex = -1, + float targetTemperature = 0f, + int groupIndex = -1, + int slotIndex = -1) + { + Kind = kind; + BehaviorIndex = behaviorIndex; + TargetTemperature = targetTemperature; + GroupIndex = groupIndex; + SlotIndex = slotIndex; + } + + public AetheriaRuntimeEquippedItemDetailsCommandKind Kind { get; } + public int BehaviorIndex { get; } + public float TargetTemperature { get; } + public int GroupIndex { get; } + public int SlotIndex { get; } + } + + public static class AetheriaRuntimeEquippedItemDetailsSurfaceCommands + { + public static bool TryRead( + EveSurfaceCommandRequest request, + out AetheriaRuntimeEquippedItemDetailsCommand command) + { + command = default; + if (request == null || + !string.Equals(request.SurfaceId, AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SurfaceId, StringComparison.Ordinal)) + return false; + + switch (request.Command ?? "") + { + case AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.Close: + command = new AetheriaRuntimeEquippedItemDetailsCommand( + AetheriaRuntimeEquippedItemDetailsCommandKind.Close); + return true; + case AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.ToggleOverrideShutdown: + command = new AetheriaRuntimeEquippedItemDetailsCommand( + AetheriaRuntimeEquippedItemDetailsCommandKind.ToggleOverrideShutdown); + return true; + case AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SetTargetTemperature: + command = new AetheriaRuntimeEquippedItemDetailsCommand( + AetheriaRuntimeEquippedItemDetailsCommandKind.SetTargetTemperature, + behaviorIndex: ReadInt(request, "behaviorIndex", -1), + targetTemperature: ReadFloat(request, "value", 0f)); + return true; + case AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.ToggleWeaponGroup: + command = new AetheriaRuntimeEquippedItemDetailsCommand( + AetheriaRuntimeEquippedItemDetailsCommandKind.ToggleWeaponGroup, + groupIndex: ReadInt(request, "group", -1)); + return true; + case AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.BindWeaponGroup: + command = new AetheriaRuntimeEquippedItemDetailsCommand( + AetheriaRuntimeEquippedItemDetailsCommandKind.BindWeaponGroup, + groupIndex: ReadInt(request, "group", -1), + slotIndex: ReadInt(request, "slot", -1)); + return true; + case AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.ClearActionBarBinding: + command = new AetheriaRuntimeEquippedItemDetailsCommand( + AetheriaRuntimeEquippedItemDetailsCommandKind.ClearActionBarBinding, + slotIndex: ReadInt(request, "slot", -1)); + return true; + default: + return false; + } + } + + private static int ReadInt(EveSurfaceCommandRequest request, string key, int defaultValue) + { + return request.Payload != null && + request.Payload.TryGetValue(key, out var raw) && + int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) + ? value + : defaultValue; + } + + private static float ReadFloat(EveSurfaceCommandRequest request, string key, float defaultValue) + { + return request.Payload != null && + request.Payload.TryGetValue(key, out var raw) && + float.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var value) + ? value + : defaultValue; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandClient.cs new file mode 100644 index 000000000..f0a628cd6 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandClient.cs @@ -0,0 +1,433 @@ +using System; +using GameCult.Eve.Surface; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeEveCommands + { + public static bool TryCreateKnownSurfaceCommand( + string stateFilePath, + EveSurfaceCommandRequest request, + out AetheriaRuntimeEveCommandEnvelope? envelope) + { + return AetheriaRuntimeEveCommandClient.TryCreateKnownSurfaceCommand(request, out envelope); + } + + public static AetheriaRuntimeEveCommandEnvelope SubmitPlayerSettingsCommand( + string stateFilePath, + string command, + AetheriaRuntimePlayerSettingsCommandBody body, + string clientId) + { + return AetheriaRuntimeEveCommandClient.CreatePlayerSettingsCommand(command, body, clientId); + } + + public static bool TrySendPlayerSettingsCommand( + string stateFilePath, + EveSurfaceCommandRequest request, + string clientId, + out AetheriaRuntimeEveCommandEnvelope? envelope, + out string error) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + return TrySend( + stateFilePath, + AetheriaRuntimeEveCommandClient.CreatePlayerSettingsCommand(request, clientId), + clientId, + out envelope, + out error); + } + + public static AetheriaRuntimeEveCommandEnvelope SubmitPlayerSettingsCommand( + string stateFilePath, + EveSurfaceCommandRequest request) + { + return AetheriaRuntimeEveCommandClient.CreatePlayerSettingsCommand(request, request?.ClientId ?? ""); + } + + public static AetheriaRuntimeEveCommandEnvelope SubmitPlayerSettingsCommand( + string stateFilePath, + EveSurfaceCommandRequest request, + string clientId) + { + return AetheriaRuntimeEveCommandClient.CreatePlayerSettingsCommand(request, clientId); + } + + public static AetheriaRuntimeEveCommandEnvelope SubmitInputSettingsCommand( + string stateFilePath, + string command, + AetheriaRuntimeInputSettingsCommandBody body, + string clientId) + { + return AetheriaRuntimeEveCommandClient.CreateInputSettingsCommand(command, body, clientId); + } + + public static bool TrySendInputSettingsCommand( + string stateFilePath, + string command, + AetheriaRuntimeInputSettingsCommandBody body, + string clientId, + out AetheriaRuntimeEveCommandEnvelope? envelope, + out string error) + { + return TrySend( + stateFilePath, + AetheriaRuntimeEveCommandClient.CreateInputSettingsCommand(command, body, clientId), + clientId, + out envelope, + out error); + } + + public static AetheriaRuntimeEveCommandEnvelope SubmitCatalogCommand( + string stateFilePath, + string command, + string clientId) + { + return AetheriaRuntimeEveCommandClient.CreateCatalogCommand(command, clientId); + } + + public static AetheriaRuntimeEveCommandEnvelope SubmitOperationsCommand( + string stateFilePath, + string command, + string clientId) + { + return AetheriaRuntimeEveCommandClient.CreateOperationsCommand(command, clientId); + } + + public static AetheriaRuntimeEveCommandEnvelope SubmitVerseHostCommand( + string stateFilePath, + string command, + string clientId) + { + return AetheriaRuntimeEveCommandClient.CreateVerseHostCommand(command, clientId); + } + + public static bool TrySendVerseHostCommand( + string stateFilePath, + string command, + string clientId, + out AetheriaRuntimeEveCommandEnvelope? envelope, + out string error) + { + return TrySend( + stateFilePath, + AetheriaRuntimeEveCommandClient.CreateVerseHostCommand(command, clientId), + clientId, + out envelope, + out error); + } + + public static AetheriaRuntimeEveCommandEnvelope SubmitLoadoutTemplateCommand( + string stateFilePath, + AetheriaRuntimeLoadoutTemplateCommit loadoutTemplate, + string clientId) + { + return AetheriaRuntimeEveCommandClient.CreateLoadoutTemplateCommand(loadoutTemplate, clientId); + } + + public static bool TrySendLoadoutTemplateCommand( + string stateFilePath, + AetheriaRuntimeLoadoutTemplateCommit loadoutTemplate, + string clientId, + out AetheriaRuntimeEveCommandEnvelope? envelope, + out string error) + { + return TrySend( + stateFilePath, + AetheriaRuntimeEveCommandClient.CreateLoadoutTemplateCommand(loadoutTemplate, clientId), + clientId, + out envelope, + out error); + } + + public static bool TrySendKnownSurfaceCommand( + string stateFilePath, + EveSurfaceCommandRequest request, + string clientId, + out AetheriaRuntimeEveCommandEnvelope? envelope, + out string error) + { + if (!AetheriaRuntimeEveCommandClient.TryCreateKnownSurfaceCommand(request, out var commandEnvelope)) + { + envelope = null; + error = $"Unknown Aetheria Eve surface command: {request?.ProviderId}/{request?.SurfaceId}/{request?.Command}"; + return false; + } + + return TrySend( + stateFilePath, + commandEnvelope!, + clientId, + out envelope, + out error); + } + + private static bool TrySend( + string stateFilePath, + AetheriaRuntimeEveCommandEnvelope commandEnvelope, + string clientId, + out AetheriaRuntimeEveCommandEnvelope? envelope, + out string error) + { + return AetheriaRuntimeCommandSubmitter.TrySubmitEveCommand( + stateFilePath, + AetheriaRuntimeEveCommandClient.ToDocument(commandEnvelope), + string.IsNullOrWhiteSpace(clientId) ? "aetheria-eve-client" : clientId, + out envelope, + out error); + } + } + + public static class AetheriaRuntimeEveCommandClient + { + public const string CommandSchema = AetheriaRuntimeEveCommandDocument.SchemaId; + + public static bool TryCreateKnownSurfaceCommand( + EveSurfaceCommandRequest request, + out AetheriaRuntimeEveCommandEnvelope? envelope) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + + var command = request.Command ?? ""; + var clientId = request.ClientId ?? ""; + switch (request.SurfaceId ?? "") + { + case AetheriaRuntimePlayerSettingsCommands.SurfaceId + when AetheriaRuntimePlayerSettingsCommands.IsKnown(command): + envelope = CreatePlayerSettingsCommand(request, clientId); + return true; + case AetheriaRuntimeInputSettingsCommands.SurfaceId + when AetheriaRuntimeInputSettingsCommands.IsKnown(command): + envelope = CreateInputSettingsCommand(request, clientId); + return true; + case AetheriaRuntimeCatalogCommands.SurfaceId + when AetheriaRuntimeCatalogCommands.IsKnown(command): + envelope = CreateCatalogCommand(command, clientId); + return true; + case AetheriaRuntimeOperationsCommands.SurfaceId + when AetheriaRuntimeOperationsCommands.IsKnown(command): + envelope = CreateOperationsCommand(command, clientId); + return true; + case AetheriaRuntimeVerseHostCommands.SurfaceId + when AetheriaRuntimeVerseHostCommands.IsKnown(command): + envelope = CreateVerseHostCommand(command, clientId); + return true; + } + + envelope = null; + return false; + } + + public static AetheriaRuntimeEveCommandEnvelope CreatePlayerSettingsCommand( + string command, + AetheriaRuntimePlayerSettingsCommandBody body, + string clientId) + { + return CreateTypedCommand( + AetheriaRuntimePlayerSettingsCommands.SurfaceId, + command, + clientId, + playerSettings: body ?? new AetheriaRuntimePlayerSettingsCommandBody(), + inputSettings: new AetheriaRuntimeInputSettingsCommandBody(), + loadoutTemplate: null); + } + + public static AetheriaRuntimeEveCommandEnvelope CreatePlayerSettingsCommand( + EveSurfaceCommandRequest request) + { + return CreatePlayerSettingsCommand(request, request?.ClientId ?? ""); + } + + public static AetheriaRuntimeEveCommandEnvelope CreatePlayerSettingsCommand( + EveSurfaceCommandRequest request, + string clientId) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + return CreatePlayerSettingsCommand( + request.Command ?? "", + ReadPlayerSettingsBody(request), + string.IsNullOrWhiteSpace(clientId) ? request.ClientId ?? "" : clientId); + } + + public static AetheriaRuntimeEveCommandEnvelope CreateInputSettingsCommand( + string command, + AetheriaRuntimeInputSettingsCommandBody body, + string clientId) + { + return CreateTypedCommand( + AetheriaRuntimeInputSettingsCommands.SurfaceId, + command, + clientId, + playerSettings: new AetheriaRuntimePlayerSettingsCommandBody(), + inputSettings: body ?? new AetheriaRuntimeInputSettingsCommandBody(), + loadoutTemplate: null); + } + + public static AetheriaRuntimeEveCommandEnvelope CreateInputSettingsCommand( + EveSurfaceCommandRequest request, + string clientId) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + return CreateInputSettingsCommand( + request.Command ?? "", + ReadInputSettingsBody(request), + string.IsNullOrWhiteSpace(clientId) ? request.ClientId ?? "" : clientId); + } + + public static AetheriaRuntimeEveCommandEnvelope CreateCatalogCommand( + string command, + string clientId) + { + return CreateTypedCommand( + AetheriaRuntimeCatalogCommands.SurfaceId, + command, + clientId, + playerSettings: new AetheriaRuntimePlayerSettingsCommandBody(), + inputSettings: new AetheriaRuntimeInputSettingsCommandBody(), + loadoutTemplate: null); + } + + public static AetheriaRuntimeEveCommandEnvelope CreateOperationsCommand( + string command, + string clientId) + { + return CreateTypedCommand( + AetheriaRuntimeOperationsCommands.SurfaceId, + command, + clientId, + playerSettings: new AetheriaRuntimePlayerSettingsCommandBody(), + inputSettings: new AetheriaRuntimeInputSettingsCommandBody(), + loadoutTemplate: null); + } + + public static AetheriaRuntimeEveCommandEnvelope CreateVerseHostCommand( + string command, + string clientId) + { + return CreateTypedCommand( + AetheriaRuntimeVerseHostCommands.SurfaceId, + command, + clientId, + playerSettings: new AetheriaRuntimePlayerSettingsCommandBody(), + inputSettings: new AetheriaRuntimeInputSettingsCommandBody(), + loadoutTemplate: null); + } + + public static AetheriaRuntimeEveCommandEnvelope CreateLoadoutTemplateCommand( + AetheriaRuntimeLoadoutTemplateCommit loadoutTemplate, + string clientId) + { + return CreateTypedCommand( + AetheriaRuntimeLoadoutTemplateCommands.SurfaceId, + AetheriaRuntimeLoadoutTemplateCommands.Save, + clientId, + playerSettings: new AetheriaRuntimePlayerSettingsCommandBody(), + inputSettings: new AetheriaRuntimeInputSettingsCommandBody(), + loadoutTemplate: loadoutTemplate); + } + + public static AetheriaRuntimeEveCommandDocument ToDocument(AetheriaRuntimeEveCommandEnvelope envelope) + { + if (envelope == null) throw new ArgumentNullException(nameof(envelope)); + + return new AetheriaRuntimeEveCommandDocument + { + Schema = string.IsNullOrWhiteSpace(envelope.Schema) ? CommandSchema : envelope.Schema, + CommandId = envelope.CommandId ?? "", + ProviderId = envelope.ProviderId ?? "", + SurfaceId = envelope.SurfaceId ?? "", + Command = envelope.Command ?? "", + IssuedAtUtc = envelope.IssuedAtUtc ?? "", + ClientId = envelope.ClientId ?? "", + PlayerSettings = envelope.PlayerSettings ?? new AetheriaRuntimePlayerSettingsCommandBody(), + InputSettings = envelope.InputSettings ?? new AetheriaRuntimeInputSettingsCommandBody(), + LoadoutTemplate = envelope.LoadoutTemplate + }; + } + + private static AetheriaRuntimeEveCommandEnvelope CreateTypedCommand( + string surfaceId, + string command, + string clientId, + AetheriaRuntimePlayerSettingsCommandBody playerSettings, + AetheriaRuntimeInputSettingsCommandBody inputSettings, + AetheriaRuntimeLoadoutTemplateCommit? loadoutTemplate) + { + var commandId = Guid.NewGuid().ToString("N"); + var issuedAtUtc = DateTime.UtcNow.ToString("O"); + var document = new AetheriaRuntimeEveCommandDocument + { + Schema = CommandSchema, + CommandId = commandId, + ProviderId = "aetheria", + SurfaceId = surfaceId ?? "", + Command = command ?? "", + IssuedAtUtc = issuedAtUtc, + ClientId = clientId ?? "", + PlayerSettings = playerSettings, + InputSettings = inputSettings, + LoadoutTemplate = loadoutTemplate + }; + + return ToEnvelope(document); + } + + public static AetheriaRuntimeEveCommandEnvelope ToEnvelope(AetheriaRuntimeEveCommandDocument document) + { + return new AetheriaRuntimeEveCommandEnvelope( + document.Schema ?? "", + document.CommandId ?? "", + document.ProviderId ?? "", + document.SurfaceId ?? "", + document.Command ?? "", + document.IssuedAtUtc ?? "", + document.ClientId ?? "", + document.PlayerSettings ?? new AetheriaRuntimePlayerSettingsCommandBody(), + document.InputSettings ?? new AetheriaRuntimeInputSettingsCommandBody(), + "", + document.LoadoutTemplate); + } + + private static AetheriaRuntimePlayerSettingsCommandBody ReadPlayerSettingsBody(EveSurfaceCommandRequest request) + { + if (!string.Equals(request.SurfaceId, AetheriaRuntimePlayerSettingsCommands.SurfaceId, StringComparison.Ordinal)) + return new AetheriaRuntimePlayerSettingsCommandBody(); + + return new AetheriaRuntimePlayerSettingsCommandBody + { + PlayerName = ReadPayload(request, "value") + }; + } + + private static AetheriaRuntimeInputSettingsCommandBody ReadInputSettingsBody(EveSurfaceCommandRequest request) + { + if (!string.Equals(request.SurfaceId, AetheriaRuntimeInputSettingsCommands.SurfaceId, StringComparison.Ordinal)) + return new AetheriaRuntimeInputSettingsCommandBody(); + + return new AetheriaRuntimeInputSettingsCommandBody + { + ActionName = ReadPayload(request, "actionName"), + BindingIndex = ReadPayloadInt(request, "bindingIndex", -1), + InputSystemPath = ReadPayload(request, "inputSystemPath"), + Enabled = string.Equals(ReadPayload(request, "enabled"), "true", StringComparison.OrdinalIgnoreCase) + }; + } + + private static string ReadPayload(EveSurfaceCommandRequest request, string key) + { + return request.Payload != null && request.Payload.TryGetValue(key, out var value) + ? value ?? "" + : ""; + } + + private static int ReadPayloadInt(EveSurfaceCommandRequest request, string key, int defaultValue) + { + return int.TryParse(ReadPayload(request, key), out var value) + ? value + : defaultValue; + } + + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandClient.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandClient.cs.meta new file mode 100644 index 000000000..cbcb61867 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandClient.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4b9f4bb18b4c4f348b3ed44f0b797275 diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandDocument.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandDocument.cs index 752487968..2ff6798bb 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandDocument.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandDocument.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using GameCult.Caching; using MessagePack; #nullable enable @@ -16,8 +16,10 @@ namespace GameCult.Aetheria.State.Unity string command, string issuedAtUtc, string clientId, - IReadOnlyDictionary payload, - string path) + AetheriaRuntimePlayerSettingsCommandBody playerSettings, + AetheriaRuntimeInputSettingsCommandBody inputSettings, + string path, + AetheriaRuntimeLoadoutTemplateCommit? loadoutTemplate = null) { Schema = schema; CommandId = commandId; @@ -26,8 +28,10 @@ namespace GameCult.Aetheria.State.Unity Command = command; IssuedAtUtc = issuedAtUtc; ClientId = clientId; - Payload = payload; + PlayerSettings = playerSettings ?? new AetheriaRuntimePlayerSettingsCommandBody(); + InputSettings = inputSettings ?? new AetheriaRuntimeInputSettingsCommandBody(); Path = path; + LoadoutTemplate = loadoutTemplate; } public string Schema { get; } @@ -37,10 +41,13 @@ namespace GameCult.Aetheria.State.Unity public string Command { get; } public string IssuedAtUtc { get; } public string ClientId { get; } - public IReadOnlyDictionary Payload { get; } + public AetheriaRuntimePlayerSettingsCommandBody PlayerSettings { get; } + public AetheriaRuntimeInputSettingsCommandBody InputSettings { get; } + public AetheriaRuntimeLoadoutTemplateCommit? LoadoutTemplate { get; } public string Path { get; } } + [CultDocument("gamecult.eve.command", "gamecult.eve.command.v1")] [MessagePackObject] public sealed class AetheriaRuntimeEveCommandDocument { @@ -68,6 +75,35 @@ namespace GameCult.Aetheria.State.Unity public string ClientId { get; set; } = ""; [Key(7)] - public Dictionary Payload { get; set; } = new Dictionary(StringComparer.Ordinal); + public AetheriaRuntimePlayerSettingsCommandBody PlayerSettings { get; set; } = new AetheriaRuntimePlayerSettingsCommandBody(); + + [Key(8)] + public AetheriaRuntimeLoadoutTemplateCommit? LoadoutTemplate { get; set; } + + [Key(9)] + public AetheriaRuntimeInputSettingsCommandBody InputSettings { get; set; } = new AetheriaRuntimeInputSettingsCommandBody(); + } + + [MessagePackObject] + public sealed class AetheriaRuntimePlayerSettingsCommandBody + { + [Key(0)] + public string PlayerName { get; set; } = ""; + } + + [MessagePackObject] + public sealed class AetheriaRuntimeInputSettingsCommandBody + { + [Key(0)] + public string ActionName { get; set; } = ""; + + [Key(1)] + public int BindingIndex { get; set; } = -1; + + [Key(2)] + public string InputSystemPath { get; set; } = ""; + + [Key(3)] + public bool Enabled { get; set; } } } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandDocument.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandDocument.cs.meta new file mode 100644 index 000000000..4d1f2ec8d --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandDocument.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1a2cc3083bb1d0b4299e0175dd9e4b8e \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandLog.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandLog.cs deleted file mode 100644 index 1c67697e7..000000000 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandLog.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using GameCult.Eve.Surface; - -#nullable enable - -namespace GameCult.Aetheria.State.Unity -{ - public static class AetheriaRuntimeEveCommandLog - { - public const string CommandSchema = AetheriaRuntimeEveCommandDocument.SchemaId; - - public static string GetPendingDirectory(string stateFilePath) - { - if (string.IsNullOrWhiteSpace(stateFilePath)) - throw new ArgumentException("State file path must be non-empty.", nameof(stateFilePath)); - - return stateFilePath + ".eve.pending"; - } - - public static AetheriaRuntimeEveCommandEnvelope QueueCommand( - string stateFilePath, - EveSurfaceCommandRequest request) - { - if (request == null) throw new ArgumentNullException(nameof(request)); - - var commandId = Guid.NewGuid().ToString("N"); - var issuedAtUtc = request.IssuedAt.UtcDateTime.ToString("O"); - var pendingDirectory = GetPendingDirectory(stateFilePath); - Directory.CreateDirectory(pendingDirectory); - - var finalPath = Path.Combine( - pendingDirectory, - $"{issuedAtUtc.Replace(':', '-')}.{StableToken(request.SurfaceId)}.{StableToken(request.Command)}.{commandId}.cc"); - var document = new AetheriaRuntimeEveCommandDocument - { - Schema = CommandSchema, - CommandId = commandId, - ProviderId = request.ProviderId ?? "", - SurfaceId = request.SurfaceId ?? "", - Command = request.Command ?? "", - IssuedAtUtc = issuedAtUtc, - ClientId = request.ClientId ?? "", - Payload = CopyPayload(request.Payload) - }; - - AetheriaRuntimePendingCultCacheStore.WriteEveCommand(finalPath, document); - - return new AetheriaRuntimeEveCommandEnvelope( - document.Schema, - document.CommandId, - document.ProviderId, - document.SurfaceId, - document.Command, - document.IssuedAtUtc, - document.ClientId, - document.Payload, - finalPath); - } - - public static IReadOnlyList ReadPending(string stateFilePath) - { - var pendingDirectory = GetPendingDirectory(stateFilePath); - if (!Directory.Exists(pendingDirectory)) - return Array.Empty(); - - return Directory - .EnumerateFiles(pendingDirectory, "*.cc") - .OrderBy(path => path, StringComparer.Ordinal) - .Select(ReadEnvelope) - .ToArray(); - } - - public static AetheriaRuntimeEveCommandDocument ReadDocument(string path) - { - return AetheriaRuntimePendingCultCacheStore.ReadEveCommand(path); - } - - private static AetheriaRuntimeEveCommandEnvelope ReadEnvelope(string path) - { - var document = ReadDocument(path); - - return new AetheriaRuntimeEveCommandEnvelope( - document.Schema ?? "", - document.CommandId ?? "", - document.ProviderId ?? "", - document.SurfaceId ?? "", - document.Command ?? "", - document.IssuedAtUtc ?? "", - document.ClientId ?? "", - document.Payload ?? EmptyPayload(), - path); - } - - private static Dictionary CopyPayload(IReadOnlyDictionary? payload) - { - var copy = new Dictionary(StringComparer.Ordinal); - foreach (var entry in (payload ?? EmptyPayload()).OrderBy(entry => entry.Key, StringComparer.Ordinal)) - { - if (!string.IsNullOrWhiteSpace(entry.Key)) - copy[entry.Key] = entry.Value ?? ""; - } - - return copy; - } - - private static Dictionary EmptyPayload() - { - return new Dictionary(0, StringComparer.Ordinal); - } - - private static string StableToken(string value) - { - if (string.IsNullOrWhiteSpace(value)) - return "empty"; - - var chars = value - .Select(character => char.IsLetterOrDigit(character) ? character : '-') - .ToArray(); - var token = new string(chars).Trim('-').ToLowerInvariant(); - while (token.Contains("--", StringComparison.Ordinal)) - token = token.Replace("--", "-", StringComparison.Ordinal); - return string.IsNullOrWhiteSpace(token) ? "empty" : token; - } - - } -} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandLog.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandLog.cs.meta new file mode 100644 index 000000000..c3b108b40 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandLog.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9e066eaa98317c243847ce778a1a5305 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceAdapter.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceAdapter.cs new file mode 100644 index 000000000..f90c8a4fc --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceAdapter.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GameCult.Eve.Surface; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeEveSurfaceAdapter + { + public static EveSurfaceDocument ToEveSurfaceDocument(AetheriaRuntimeSurfaceDocument document) + { + if (document == null) throw new ArgumentNullException(nameof(document)); + + return new EveSurfaceDocument( + "surface-state", + "gamecult.eve.surface.v1", + document.ProviderId, + document.ProviderKind, + document.Title, + document.Version, + document.UpdatedAtUtc, + new EveSurfaceTree( + document.Surface.Id, + ToEveSurfaceComponent(document.Surface.Root), + document.Surface.Styles + .Select(style => new EveStyleToken(style.Name, style.Value)) + .ToArray()), + document.Commands + .Select(command => new EveCommandTemplate(command.Command, command.Label, command.Transport)) + .ToArray()); + } + + public static EveSurfaceDocument EmptySurface(string surfaceId) + { + var id = string.IsNullOrWhiteSpace(surfaceId) ? "aetheria.surface.missing" : surfaceId; + return new EveSurfaceDocument( + "surface-state", + "gamecult.eve.surface.v1", + "aetheria.daemon", + "daemon", + "", + 0, + "", + new EveSurfaceTree( + id, + new EveSurfaceComponent( + id + ".missing", + "surface", + new Dictionary(StringComparer.Ordinal), + Array.Empty()), + Array.Empty()), + Array.Empty()); + } + + private static EveSurfaceComponent ToEveSurfaceComponent(AetheriaRuntimeSurfaceComponent component) + { + return new EveSurfaceComponent( + component.Id, + component.Kind, + new Dictionary(component.Props, StringComparer.Ordinal), + component.Children.Select(ToEveSurfaceComponent).ToArray()); + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsCommands.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsCommands.cs index 69f006b85..cec15feb7 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsCommands.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsCommands.cs @@ -6,14 +6,18 @@ namespace GameCult.Aetheria.State.Unity public const string Refresh = "aetheria.input_settings.refresh"; public const string BeginCapture = "aetheria.input_settings.binding.capture"; public const string CancelCapture = "aetheria.input_settings.binding.cancel"; + public const string SetBindingOverride = "aetheria.input_settings.binding.override"; public const string ToggleActionBar = "aetheria.input_settings.action_bar.toggle"; + public const string SetActionBarEnabled = "aetheria.input_settings.action_bar.set_enabled"; public static bool IsKnown(string command) { return command == Refresh || command == BeginCapture || command == CancelCapture || - command == ToggleActionBar; + command == SetBindingOverride || + command == ToggleActionBar || + command == SetActionBarEnabled; } } } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsCommands.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsCommands.cs.meta new file mode 100644 index 000000000..8f657b465 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsCommands.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f3ca8e46e25c53b44a8309d50f1eb429 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsSurfaceBuilder.cs index e2aaa9458..61ba9b77d 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsSurfaceBuilder.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using GameCult.Eve.Surface; namespace GameCult.Aetheria.State.Unity { @@ -109,7 +111,7 @@ namespace GameCult.Aetheria.State.Unity settings.ActionBarInputs.Count(entry => entry.Enabled).ToString()), Text( "aetheria.inputSettings.summary.note", - "Low-level InputSystem edits flow through this Eve surface and queue typed player-settings commits.")), + "Low-level InputSystem edits flow through this Eve surface as typed input-setting requests.")), Node( "aetheria.inputSettings.capture", "card", @@ -147,7 +149,7 @@ namespace GameCult.Aetheria.State.Unity "aetheria.inputSettings.actionBarInputs.grid", "grid", Array.Empty<(string Key, string Value)>(), - settings.ActionBarInputs.Select(BuildActionBarCard).ToArray())), + settings.ActionBarInputs.Select(BuildActionBarCard).ToArray()))), Array.Empty()), commands: new[] { @@ -259,4 +261,106 @@ namespace GameCult.Aetheria.State.Unity children ?? Array.Empty()); } } + + public enum AetheriaRuntimeInputSettingsCommandKind + { + Unknown = 0, + Refresh = 1, + BeginCapture = 2, + CancelCapture = 3, + ToggleActionBar = 4 + } + + public readonly struct AetheriaRuntimeInputSettingsSurfaceCommand + { + public AetheriaRuntimeInputSettingsSurfaceCommand( + AetheriaRuntimeInputSettingsCommandKind kind, + string actionName = "", + int bindingIndex = -1, + string bindingLabel = "", + string inputPath = "", + bool enabled = false) + { + Kind = kind; + ActionName = actionName ?? ""; + BindingIndex = bindingIndex; + BindingLabel = bindingLabel ?? ""; + InputPath = inputPath ?? ""; + Enabled = enabled; + } + + public AetheriaRuntimeInputSettingsCommandKind Kind { get; } + public string ActionName { get; } + public int BindingIndex { get; } + public string BindingLabel { get; } + public string InputPath { get; } + public bool Enabled { get; } + } + + public static class AetheriaRuntimeInputSettingsSurfaceCommands + { + public static bool TryRead( + EveSurfaceCommandRequest request, + out AetheriaRuntimeInputSettingsSurfaceCommand command) + { + command = default; + if (request == null || + !string.Equals(request.SurfaceId, AetheriaRuntimeInputSettingsCommands.SurfaceId, StringComparison.Ordinal)) + return false; + + switch (request.Command ?? "") + { + case AetheriaRuntimeInputSettingsCommands.Refresh: + command = new AetheriaRuntimeInputSettingsSurfaceCommand( + AetheriaRuntimeInputSettingsCommandKind.Refresh); + return true; + case AetheriaRuntimeInputSettingsCommands.CancelCapture: + command = new AetheriaRuntimeInputSettingsSurfaceCommand( + AetheriaRuntimeInputSettingsCommandKind.CancelCapture); + return true; + case AetheriaRuntimeInputSettingsCommands.BeginCapture: + var actionName = ReadString(request, "actionName"); + command = new AetheriaRuntimeInputSettingsSurfaceCommand( + AetheriaRuntimeInputSettingsCommandKind.BeginCapture, + actionName: actionName, + bindingIndex: ReadInt(request, "bindingIndex", -1), + bindingLabel: ReadString(request, "bindingLabel", actionName)); + return true; + case AetheriaRuntimeInputSettingsCommands.ToggleActionBar: + command = new AetheriaRuntimeInputSettingsSurfaceCommand( + AetheriaRuntimeInputSettingsCommandKind.ToggleActionBar, + inputPath: ReadString(request, "inputPath"), + enabled: ReadBool(request, "enabled", false)); + return true; + default: + return false; + } + } + + private static string ReadString(EveSurfaceCommandRequest request, string key, string defaultValue = "") + { + return request.Payload != null && + request.Payload.TryGetValue(key, out var raw) + ? raw ?? defaultValue + : defaultValue; + } + + private static int ReadInt(EveSurfaceCommandRequest request, string key, int defaultValue) + { + return request.Payload != null && + request.Payload.TryGetValue(key, out var raw) && + int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) + ? value + : defaultValue; + } + + private static bool ReadBool(EveSurfaceCommandRequest request, string key, bool defaultValue) + { + return request.Payload != null && + request.Payload.TryGetValue(key, out var raw) && + bool.TryParse(raw, out var value) + ? value + : defaultValue; + } + } } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsSurfaceBuilder.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsSurfaceBuilder.cs.meta new file mode 100644 index 000000000..e3eb4d75d --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsSurfaceBuilder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5f5930ec908217844a62e1c49ecb72e2 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceBuilder.cs new file mode 100644 index 000000000..63237f6cf --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceBuilder.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GameCult.Eve.Surface; + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeInventoryDropdownOption + { + public AetheriaRuntimeInventoryDropdownOption(string id, string label, string command) + { + Id = id ?? ""; + Label = label ?? ""; + Command = command ?? ""; + } + + public string Id { get; } + public string Label { get; } + public string Command { get; } + public bool IsCommand => !string.IsNullOrWhiteSpace(Command); + } + + public sealed class AetheriaRuntimeInventoryDropdownGroup + { + public AetheriaRuntimeInventoryDropdownGroup( + string id, + string title, + IReadOnlyList options) + { + Id = id ?? ""; + Title = title ?? ""; + Options = options ?? Array.Empty(); + } + + public string Id { get; } + public string Title { get; } + public IReadOnlyList Options { get; } + } + + public sealed class AetheriaRuntimeInventoryDropdownSurfaceState + { + public AetheriaRuntimeInventoryDropdownSurfaceState( + string currentView, + IReadOnlyList groups, + string updatedAtUtc) + { + CurrentView = currentView ?? ""; + Groups = groups ?? Array.Empty(); + UpdatedAtUtc = updatedAtUtc ?? ""; + } + + public string CurrentView { get; } + public IReadOnlyList Groups { get; } + public string UpdatedAtUtc { get; } + } + + public static class AetheriaRuntimeInventoryDropdownSurfaceBuilder + { + public const string SurfaceId = "aetheria.inventory.panel.dropdown"; + public const string Close = "aetheria.inventory.panel.dropdown.close"; + public const string SaveLoadout = "aetheria.inventory.panel.dropdown.save_loadout"; + public const string DockingBay = "aetheria.inventory.panel.dropdown.docking_bay"; + + public static string EntityEquipmentCommand(int entityIndex) + { + return $"{SurfaceId}.entity_{entityIndex}.equipment"; + } + + public static string EntityBayCommand(int entityIndex, int bayIndex) + { + return $"{SurfaceId}.entity_{entityIndex}.bay_{bayIndex}"; + } + + public static string EntityCommand(int entityIndex) + { + return $"{SurfaceId}.entity_{entityIndex}"; + } + + public static string LoadoutCommand(int templateIndex) + { + return $"{SurfaceId}.loadout_{templateIndex}"; + } + + public static AetheriaRuntimeSurfaceDocument Build( + AetheriaRuntimeInventoryDropdownSurfaceState state, + long version = 1) + { + state ??= new AetheriaRuntimeInventoryDropdownSurfaceState( + "", + Array.Empty(), + ""); + + var children = new List + { + Card( + $"{SurfaceId}.summary", + "Inventory Actions", + Metric( + $"{SurfaceId}.current", + "Current View", + string.IsNullOrWhiteSpace(state.CurrentView) ? "None" : state.CurrentView), + Text( + $"{SurfaceId}.note", + "Unity projects available inventory navigation; the shared runtime surface owns the dropdown contract.")) + }; + + foreach (var group in state.Groups) + { + if (group?.Options == null || group.Options.Count == 0) + continue; + + children.Add(Card( + string.IsNullOrWhiteSpace(group.Id) ? $"{SurfaceId}.group.{SafeId(group.Title)}" : group.Id, + group.Title, + ButtonColumn( + $"{(string.IsNullOrWhiteSpace(group.Id) ? $"{SurfaceId}.group.{SafeId(group.Title)}" : group.Id)}.options", + group.Options.Select(ToOptionComponent).ToArray()))); + } + + children.Add(ButtonRow( + $"{SurfaceId}.actions", + Button($"{SurfaceId}.close", "Close", Close))); + + var commands = state.Groups + .Where(group => group?.Options != null) + .SelectMany(group => group.Options) + .Where(option => option?.IsCommand == true) + .Select(option => new AetheriaRuntimeSurfaceCommandTemplate(option.Command, option.Label, "unity-uitoolkit")) + .Append(new AetheriaRuntimeSurfaceCommandTemplate(Close, "Close", "unity-uitoolkit")) + .ToArray(); + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "inventory.panel", + title: "Inventory Dropdown", + version: version, + updatedAtUtc: state.UpdatedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + SurfaceId, + Node( + $"{SurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + children.ToArray()), + Array.Empty()), + commands: commands); + } + + private static AetheriaRuntimeSurfaceComponent ToOptionComponent(AetheriaRuntimeInventoryDropdownOption option) + { + if (option.IsCommand) + return Button(option.Id, option.Label, option.Command); + + return Text(option.Id, option.Label); + } + + 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) + { + return Node(id, "metric", new[] { ("label", label ?? ""), ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Text(string id, string value) + { + return Node(id, "text", new[] { ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) + { + return Node(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent ButtonRow( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent ButtonColumn( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "column", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent Node( + string id, + string kind, + IEnumerable<(string Key, string Value)> props, + params AetheriaRuntimeSurfaceComponent[] children) + { + return new AetheriaRuntimeSurfaceComponent( + id ?? "", + kind ?? "", + (props ?? Array.Empty<(string Key, string Value)>()) + .ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal), + children ?? Array.Empty()); + } + } + + public enum AetheriaRuntimeInventoryDropdownCommandKind + { + Unknown = 0, + Close = 1, + Select = 2 + } + + public readonly struct AetheriaRuntimeInventoryDropdownCommand + { + public AetheriaRuntimeInventoryDropdownCommand( + AetheriaRuntimeInventoryDropdownCommandKind kind, + string command) + { + Kind = kind; + Command = command ?? ""; + } + + public AetheriaRuntimeInventoryDropdownCommandKind Kind { get; } + public string Command { get; } + } + + public static class AetheriaRuntimeInventoryDropdownSurfaceCommands + { + public static bool TryRead( + EveSurfaceCommandRequest request, + out AetheriaRuntimeInventoryDropdownCommand command) + { + command = default; + if (request == null || + !string.Equals(request.SurfaceId, AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId, StringComparison.Ordinal)) + return false; + + var commandText = request.Command ?? ""; + if (string.Equals(commandText, AetheriaRuntimeInventoryDropdownSurfaceBuilder.Close, StringComparison.Ordinal)) + { + command = new AetheriaRuntimeInventoryDropdownCommand( + AetheriaRuntimeInventoryDropdownCommandKind.Close, + commandText); + return true; + } + + if (string.IsNullOrWhiteSpace(commandText)) + return false; + + command = new AetheriaRuntimeInventoryDropdownCommand( + AetheriaRuntimeInventoryDropdownCommandKind.Select, + commandText); + return true; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeItemCategories.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeItemCategories.cs.meta new file mode 100644 index 000000000..02bb0b096 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeItemCategories.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d28ccdf9a2f86a94e943981f0b35b432 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeLoadoutSnapshotProjector.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeLoadoutSnapshotProjector.cs new file mode 100644 index 000000000..82c91f747 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeLoadoutSnapshotProjector.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeLoadoutSnapshotProjector + { + public static string AppendToZone( + AetheriaRuntimeRunCheckpointCommit run, + int zoneIndex, + string parentEntityKey, + AetheriaRuntimeLoadoutTemplateCommit template) + { + if (run == null || template?.RootEntity == null) + return ""; + + var zones = (run.Zones ?? Array.Empty()).ToArray(); + var zone = zones.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex); + if (zone == null) + return ""; + + var entities = (zone.Entities ?? Array.Empty()).ToList(); + var parentIndex = TryParseEntityKey(parentEntityKey, out var parentZoneIndex, out var parsedParentIndex) && + parentZoneIndex == zoneIndex + ? parsedParentIndex + : -1; + var rootIndex = AppendEntity(entities, template.RootEntity, template.Name); + + if (parentIndex >= 0 && parentIndex < entities.Count) + { + var children = (entities[parentIndex].ChildEntityIndices ?? Array.Empty()).ToList(); + if (!children.Contains(rootIndex)) + children.Add(rootIndex); + entities[parentIndex].ChildEntityIndices = children.ToArray(); + } + + zone.Entities = entities.ToArray(); + run.Zones = zones; + return EntityKey(run.RunId, zoneIndex, rootIndex); + } + + private static int AppendEntity( + List entities, + AetheriaRuntimeEntityLoadoutCommit loadout, + string templateName) + { + var entityIndex = entities.Count; + var entity = new AetheriaRuntimeEntitySnapshotCommit + { + EntityIndex = entityIndex, + Name = string.IsNullOrWhiteSpace(loadout.Name) ? templateName ?? "" : loadout.Name, + Kind = string.IsNullOrWhiteSpace(loadout.Kind) ? "ship" : loadout.Kind, + DirectionX = 0, + DirectionY = 1, + IsActive = true, + HullItemKey = loadout.Hull?.ItemKey ?? "", + FactionKey = loadout.FactionKey ?? "", + Equipment = CloneSlots(loadout.Equipment), + CargoBays = CloneSlots(loadout.CargoBays), + DockingBays = CloneSlots(loadout.DockingBays), + CargoContents = CloneCargo(loadout.CargoContents), + DockingBayContents = CloneCargo(loadout.DockingBayContents), + DockingBayAssignments = (loadout.DockingBayAssignments ?? Array.Empty()).ToArray(), + WeaponGroups = (loadout.WeaponGroups ?? Array.Empty>()) + .Select(group => (IReadOnlyList)(group ?? Array.Empty()).ToArray()) + .ToArray(), + TargetEntityIndex = -1, + ShutdownPerformance = 0.25 + }; + entities.Add(entity); + + var childIndices = new List(); + foreach (var child in loadout.Children ?? Array.Empty()) + { + if (child == null) + continue; + childIndices.Add(AppendEntity(entities, child, "")); + } + + entity.ChildEntityIndices = childIndices.ToArray(); + return entityIndex; + } + + private static AetheriaRuntimeLoadoutItemSlotCommit[] CloneSlots( + IReadOnlyList? slots) + { + return (slots ?? Array.Empty()) + .Where(slot => slot != null) + .Select(CloneSlot) + .ToArray(); + } + + private static AetheriaRuntimeCargoBayLoadoutCommit[] CloneCargo( + IReadOnlyList? cargo) + { + return (cargo ?? Array.Empty()) + .Select(bay => new AetheriaRuntimeCargoBayLoadoutCommit + { + Items = CloneSlots(bay?.Items) + }) + .ToArray(); + } + + private static AetheriaRuntimeLoadoutItemSlotCommit CloneSlot(AetheriaRuntimeLoadoutItemSlotCommit slot) + { + return new AetheriaRuntimeLoadoutItemSlotCommit + { + X = slot.X, + Y = slot.Y, + Item = CloneItem(slot.Item) + }; + } + + private static AetheriaRuntimeLoadoutItemCommit CloneItem(AetheriaRuntimeLoadoutItemCommit? item) + { + return new AetheriaRuntimeLoadoutItemCommit + { + ItemKey = item?.ItemKey ?? "", + Quality = item?.Quality ?? 1.0, + Durability = item?.Durability ?? 1.0, + Quantity = item?.Quantity ?? 1, + Enabled = item?.Enabled ?? true, + OverrideShutdown = item?.OverrideShutdown ?? false + }; + } + + private static string EntityKey(string runId, int zoneIndex, int entityIndex) + { + return $"global:aetheria.run_state.{(string.IsNullOrWhiteSpace(runId) ? "local" : runId)}.zone.{zoneIndex}.entity.{entityIndex}.v1"; + } + + private static bool TryParseEntityKey(string entityKey, out int zoneIndex, out int entityIndex) + { + zoneIndex = -1; + entityIndex = -1; + if (string.IsNullOrWhiteSpace(entityKey)) + return false; + + var parts = entityKey.Split('.'); + for (var i = 0; i < parts.Length - 1; i++) + { + if (string.Equals(parts[i], "zone", StringComparison.Ordinal) && + int.TryParse(parts[i + 1], out zoneIndex)) + { + continue; + } + + if (string.Equals(parts[i], "entity", StringComparison.Ordinal) && + int.TryParse(parts[i + 1], out entityIndex)) + { + continue; + } + } + + return zoneIndex >= 0 && entityIndex >= 0; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeLoadoutTemplateCommands.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeLoadoutTemplateCommands.cs new file mode 100644 index 000000000..47d10e583 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeLoadoutTemplateCommands.cs @@ -0,0 +1,13 @@ +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeLoadoutTemplateCommands + { + public const string SurfaceId = "aetheria.loadout_templates"; + public const string Save = "aetheria.loadout_templates.save"; + + public static bool IsKnown(string command) + { + return command == Save; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs new file mode 100644 index 000000000..d3318d3f2 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs @@ -0,0 +1,568 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GameCult.Eve.Surface; + +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeMainMenuCommands + { + public const string RootSurfaceId = "aetheria.main_menu.root"; + public const string SettingsSurfaceId = "aetheria.main_menu.settings"; + public const string InputSettingsSurfaceId = "aetheria.main_menu.input_settings"; + public const string PlayerSettingsShellSurfaceId = "aetheria.main_menu.player_settings"; + public const string VerseSettingsShellSurfaceId = "aetheria.main_menu.verse_settings"; + + public const string ContinueRun = "aetheria.main_menu.root.continue"; + public const string NewGame = "aetheria.main_menu.root.new_game"; + public const string ShowSettings = "aetheria.main_menu.root.show_settings"; + public const string Quit = "aetheria.main_menu.root.quit"; + public const string OpenRuntimeInputScreen = "aetheria.main_menu.input_settings.open_runtime_screen"; + public const string ShowPlayerSettings = "aetheria.main_menu.settings.show_player_settings"; + public const string ShowVerseSettings = "aetheria.main_menu.settings.show_verse_settings"; + public const string ShowInputSettings = "aetheria.main_menu.settings.show_input_settings"; + public const string BackToMain = "aetheria.main_menu.settings.back_to_main"; + public const string BackToSettings = "aetheria.main_menu.settings.back_to_settings"; + } + + public sealed class AetheriaRuntimeMainMenuSurfaceState + { + public AetheriaRuntimeMainMenuSurfaceState( + string targetLabel, + string targetKind, + string targetSource, + string verseTitle, + string verseId, + string verseVisibility, + string verseCultMeshAddress, + bool inGame, + bool hasAuthoritativeDaemonFrame, + string daemonRunId, + long daemonFrameId, + int bindingOverrideCount, + int actionBarInputCount, + bool canOpenRuntimeInputScreen, + string updatedAtUtc) + { + TargetLabel = targetLabel ?? ""; + TargetKind = targetKind ?? ""; + TargetSource = targetSource ?? ""; + VerseTitle = verseTitle ?? ""; + VerseId = verseId ?? ""; + VerseVisibility = verseVisibility ?? ""; + VerseCultMeshAddress = verseCultMeshAddress ?? ""; + InGame = inGame; + HasAuthoritativeDaemonFrame = hasAuthoritativeDaemonFrame; + DaemonRunId = daemonRunId ?? ""; + DaemonFrameId = daemonFrameId; + BindingOverrideCount = bindingOverrideCount; + ActionBarInputCount = actionBarInputCount; + CanOpenRuntimeInputScreen = canOpenRuntimeInputScreen; + UpdatedAtUtc = updatedAtUtc ?? ""; + } + + public string TargetLabel { get; } + public string TargetKind { get; } + public string TargetSource { get; } + public string VerseTitle { get; } + public string VerseId { get; } + public string VerseVisibility { get; } + public string VerseCultMeshAddress { get; } + public bool InGame { get; } + public bool HasAuthoritativeDaemonFrame { get; } + public string DaemonRunId { get; } + public long DaemonFrameId { get; } + public int BindingOverrideCount { get; } + public int ActionBarInputCount { get; } + public bool CanOpenRuntimeInputScreen { get; } + public string UpdatedAtUtc { get; } + + public string VerseLabel + { + get + { + var title = string.IsNullOrWhiteSpace(VerseTitle) ? "Unknown Verse" : VerseTitle; + var verseId = string.IsNullOrWhiteSpace(VerseId) ? "unknown" : VerseId; + return string.Equals(title, verseId, StringComparison.Ordinal) ? title : $"{title} ({verseId})"; + } + } + } + + public static class AetheriaRuntimeMainMenuSurfaceBuilder + { + public static AetheriaRuntimeSurfaceDocument BuildRoot( + AetheriaRuntimeMainMenuSurfaceState state, + long version = 1) + { + state ??= EmptyState(); + + var commands = new List(); + var actionButtons = new List(); + var cardChildren = new List(); + var targetKind = string.IsNullOrWhiteSpace(state.TargetKind) ? "unknown" : state.TargetKind; + var targetSource = string.IsNullOrWhiteSpace(state.TargetSource) ? "unknown" : state.TargetSource; + var verseVisibility = string.IsNullOrWhiteSpace(state.VerseVisibility) ? "unknown" : state.VerseVisibility; + var verseMeshAddress = string.IsNullOrWhiteSpace(state.VerseCultMeshAddress) ? "unpublished" : state.VerseCultMeshAddress; + + if (!state.InGame) + { + if (!state.HasAuthoritativeDaemonFrame) + { + cardChildren.Add(Text( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.continue.note", + $"No authoritative daemon frame is available in {state.VerseLabel} yet. Start or connect a daemon before opening the observer scene.")); + } + else + { + commands.Add(Command(AetheriaRuntimeMainMenuCommands.ContinueRun, "Continue")); + actionButtons.Add(Button( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.continue", + "Continue", + AetheriaRuntimeMainMenuCommands.ContinueRun)); + cardChildren.Add(Metric( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.continue.run", + "Daemon Run", + state.DaemonRunId)); + cardChildren.Add(Metric( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.continue.frame", + "Daemon Frame", + state.DaemonFrameId.ToString())); + } + } + + cardChildren.Add(Metric( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.target.title", + "Client Target", + state.TargetLabel)); + cardChildren.Add(Metric( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.target.transport", + "Transport", + targetKind)); + cardChildren.Add(Metric( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.target.source", + "Target Source", + targetSource)); + cardChildren.Add(Metric( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.verse.title", + "Verse", + state.VerseLabel)); + cardChildren.Add(Metric( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.verse.visibility", + "Visibility", + verseVisibility)); + cardChildren.Add(Metric( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.verse.mesh", + "CultMesh", + verseMeshAddress)); + + commands.Add(Command(AetheriaRuntimeMainMenuCommands.NewGame, "New Game")); + commands.Add(Command(AetheriaRuntimeMainMenuCommands.ShowSettings, "Settings")); + commands.Add(Command(AetheriaRuntimeMainMenuCommands.Quit, "Quit")); + + actionButtons.Add(Button($"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.newGame", "New Game", AetheriaRuntimeMainMenuCommands.NewGame)); + actionButtons.Add(Button($"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.settings", "Settings", AetheriaRuntimeMainMenuCommands.ShowSettings)); + actionButtons.Add(Button($"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.quit", "Quit", AetheriaRuntimeMainMenuCommands.Quit)); + + cardChildren.Add(Text( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.note", + $"The client lowers this shell through Eve. The client target chooses which Verse it follows; game truth belongs to the daemon serving {state.VerseLabel}.")); + cardChildren.Add(ButtonColumn($"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.actions", actionButtons.ToArray())); + + return BuildSurfaceDocument( + AetheriaRuntimeMainMenuCommands.RootSurfaceId, + "Aetheria Terminus", + state.UpdatedAtUtc, + version, + commands, + Card( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.card", + "Aetheria Terminus", + cardChildren.ToArray())); + } + + public static AetheriaRuntimeSurfaceDocument BuildSettings( + string updatedAtUtc, + long version = 1) + { + return BuildSurfaceDocument( + AetheriaRuntimeMainMenuCommands.SettingsSurfaceId, + "Aetheria Settings", + updatedAtUtc, + version, + new[] + { + Command(AetheriaRuntimeMainMenuCommands.ShowPlayerSettings, "Player Settings"), + Command(AetheriaRuntimeMainMenuCommands.ShowVerseSettings, "Verse"), + Command(AetheriaRuntimeMainMenuCommands.ShowInputSettings, "Input"), + Command(AetheriaRuntimeMainMenuCommands.BackToMain, "Back") + }, + Card( + "aetheria.mainMenu.settings.card", + "Settings", + Text( + "aetheria.mainMenu.settings.note", + "Player settings, client target selection, and input rebinding all lower through typed Eve surfaces. Audio still has no typed owner."), + ButtonRow( + "aetheria.mainMenu.settings.actions", + Button("aetheria.mainMenu.settings.playerSettings", "Player Settings", AetheriaRuntimeMainMenuCommands.ShowPlayerSettings), + Button("aetheria.mainMenu.settings.verse", "Verse", AetheriaRuntimeMainMenuCommands.ShowVerseSettings), + Button("aetheria.mainMenu.settings.input", "Input", AetheriaRuntimeMainMenuCommands.ShowInputSettings), + Button("aetheria.mainMenu.settings.back", "Back", AetheriaRuntimeMainMenuCommands.BackToMain)))); + } + + public static AetheriaRuntimeSurfaceDocument BuildInputSettings( + AetheriaRuntimeMainMenuSurfaceState state, + long version = 1) + { + state ??= EmptyState(); + + var commands = new List + { + Command(AetheriaRuntimeMainMenuCommands.BackToSettings, "Back") + }; + + var cardChildren = new List + { + Metric( + "aetheria.mainMenu.input.bindingOverrides", + "Binding Overrides", + state.BindingOverrideCount.ToString()), + Metric( + "aetheria.mainMenu.input.actionBarInputs", + "Action-Bar Inputs", + state.ActionBarInputCount.ToString()) + }; + + if (state.CanOpenRuntimeInputScreen) + { + commands.Insert(0, Command(AetheriaRuntimeMainMenuCommands.OpenRuntimeInputScreen, "Open Remap Screen")); + cardChildren.Add(Text( + "aetheria.mainMenu.input.note", + "The runtime Eve input screen owns low-level InputSystem rebinding and action-bar input edits. This title shell reports typed player-settings state and hands off to that owner.")); + } + else if (state.InGame) + { + cardChildren.Add(Text( + "aetheria.mainMenu.input.note", + "The runtime Eve input screen should own rebinding here, but this scene has no active input surface to hand off to.")); + } + else + { + cardChildren.Add(Text( + "aetheria.mainMenu.input.note", + "This title shell reports the typed player-settings state. Launch a run to open the runtime Eve input screen that owns low-level InputSystem rebinding.")); + } + + var buttons = new List(); + if (state.CanOpenRuntimeInputScreen) + { + buttons.Add(Button( + "aetheria.mainMenu.input.openRuntimeScreen", + "Open Remap Screen", + AetheriaRuntimeMainMenuCommands.OpenRuntimeInputScreen)); + } + + buttons.Add(Button("aetheria.mainMenu.input.back", "Back", AetheriaRuntimeMainMenuCommands.BackToSettings)); + cardChildren.Add(ButtonRow("aetheria.mainMenu.input.actions", buttons.ToArray())); + + return BuildSurfaceDocument( + AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId, + "Aetheria Input Settings", + state.UpdatedAtUtc, + version, + commands, + Card( + "aetheria.mainMenu.input.card", + "Input Settings", + cardChildren.ToArray())); + } + + public static AetheriaRuntimeSurfaceDocument WithBackAction( + AetheriaRuntimeSurfaceDocument document, + string surfaceId, + string backCommand, + string backLabel) + { + if (document == null) throw new ArgumentNullException(nameof(document)); + + return new AetheriaRuntimeSurfaceDocument( + document.ProviderId, + document.ProviderKind, + document.Title, + document.Version, + document.UpdatedAtUtc, + new AetheriaRuntimeSurfaceTree( + surfaceId, + Node( + $"{surfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + document.Surface.Root, + ButtonRow( + $"{surfaceId}.actions", + Button($"{surfaceId}.back", backLabel, backCommand))), + document.Surface.Styles), + document.Commands + .Concat(new[] { Command(backCommand, backLabel) }) + .ToArray()); + } + + private static AetheriaRuntimeMainMenuSurfaceState EmptyState() + { + return new AetheriaRuntimeMainMenuSurfaceState( + "", + "", + "", + "", + "", + "", + "", + inGame: false, + hasAuthoritativeDaemonFrame: false, + daemonRunId: "", + daemonFrameId: -1, + bindingOverrideCount: 0, + actionBarInputCount: 0, + canOpenRuntimeInputScreen: false, + updatedAtUtc: ""); + } + + private static AetheriaRuntimeSurfaceDocument BuildSurfaceDocument( + string surfaceId, + string title, + string updatedAtUtc, + long version, + IReadOnlyList commands, + params AetheriaRuntimeSurfaceComponent[] children) + { + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "game.menu", + title: title, + version: version, + updatedAtUtc: updatedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + surfaceId, + Node($"{surfaceId}.root", "surface", Array.Empty<(string Key, string Value)>(), children), + Array.Empty()), + commands: commands); + } + + private static AetheriaRuntimeSurfaceCommandTemplate Command(string command, string label) + { + return new AetheriaRuntimeSurfaceCommandTemplate(command, label, "unity-uitoolkit"); + } + + private static AetheriaRuntimeSurfaceComponent Card( + string id, + string title, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "card", new[] { ("title", title) }, children); + } + + private static AetheriaRuntimeSurfaceComponent Metric(string id, string label, string value) + { + return Node(id, "metric", new[] { ("label", label), ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Text(string id, string value) + { + return Node(id, "text", new[] { ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) + { + return Node(id, "control.button", new[] { ("label", label), ("command", command) }); + } + + private static AetheriaRuntimeSurfaceComponent ButtonRow( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent ButtonColumn( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "column", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent Node( + string id, + string kind, + IEnumerable<(string Key, string Value)> props, + params AetheriaRuntimeSurfaceComponent[] children) + { + return new AetheriaRuntimeSurfaceComponent( + id, + kind, + props.ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal), + children ?? Array.Empty()); + } + } + + public enum AetheriaRuntimeMainMenuCommandKind + { + Unknown = 0, + ContinueRun = 1, + NewGame = 2, + ShowSettings = 3, + Quit = 4, + ShowPlayerSettings = 5, + ShowVerseSettings = 6, + ShowInputSettings = 7, + BackToMain = 8, + BackToSettings = 9, + OpenRuntimeInputScreen = 10, + PlayerSettingsCommand = 11, + ClientTargetCommand = 12, + VerseHostCommand = 13 + } + + public readonly struct AetheriaRuntimeMainMenuCommand + { + public AetheriaRuntimeMainMenuCommand( + AetheriaRuntimeMainMenuCommandKind kind, + string command = "") + { + Kind = kind; + Command = command ?? ""; + } + + public AetheriaRuntimeMainMenuCommandKind Kind { get; } + public string Command { get; } + } + + public static class AetheriaRuntimeMainMenuSurfaceCommands + { + public static bool TryRead( + EveSurfaceCommandRequest request, + out AetheriaRuntimeMainMenuCommand command) + { + command = default; + if (request == null) + return false; + + switch (request.SurfaceId ?? "") + { + case AetheriaRuntimeMainMenuCommands.RootSurfaceId: + return TryReadRoot(request.Command ?? "", out command); + case AetheriaRuntimeMainMenuCommands.SettingsSurfaceId: + return TryReadSettings(request.Command ?? "", out command); + case AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId: + return TryReadInputSettings(request.Command ?? "", out command); + case AetheriaRuntimeMainMenuCommands.PlayerSettingsShellSurfaceId: + return TryReadPlayerSettingsShell(request.Command ?? "", out command); + case AetheriaRuntimeMainMenuCommands.VerseSettingsShellSurfaceId: + return TryReadVerseSettingsShell(request.Command ?? "", out command); + default: + return false; + } + } + + private static bool TryReadRoot(string commandText, out AetheriaRuntimeMainMenuCommand command) + { + switch (commandText) + { + case AetheriaRuntimeMainMenuCommands.ContinueRun: + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.ContinueRun, commandText); + return true; + case AetheriaRuntimeMainMenuCommands.NewGame: + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.NewGame, commandText); + return true; + case AetheriaRuntimeMainMenuCommands.ShowSettings: + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.ShowSettings, commandText); + return true; + case AetheriaRuntimeMainMenuCommands.Quit: + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.Quit, commandText); + return true; + default: + command = default; + return false; + } + } + + private static bool TryReadSettings(string commandText, out AetheriaRuntimeMainMenuCommand command) + { + switch (commandText) + { + case AetheriaRuntimeMainMenuCommands.ShowPlayerSettings: + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.ShowPlayerSettings, commandText); + return true; + case AetheriaRuntimeMainMenuCommands.ShowVerseSettings: + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.ShowVerseSettings, commandText); + return true; + case AetheriaRuntimeMainMenuCommands.ShowInputSettings: + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.ShowInputSettings, commandText); + return true; + case AetheriaRuntimeMainMenuCommands.BackToMain: + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.BackToMain, commandText); + return true; + default: + command = default; + return false; + } + } + + private static bool TryReadInputSettings(string commandText, out AetheriaRuntimeMainMenuCommand command) + { + switch (commandText) + { + case AetheriaRuntimeMainMenuCommands.OpenRuntimeInputScreen: + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.OpenRuntimeInputScreen, commandText); + return true; + case AetheriaRuntimeMainMenuCommands.BackToSettings: + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.BackToSettings, commandText); + return true; + default: + command = default; + return false; + } + } + + private static bool TryReadPlayerSettingsShell(string commandText, out AetheriaRuntimeMainMenuCommand command) + { + if (string.Equals(commandText, AetheriaRuntimeMainMenuCommands.BackToSettings, StringComparison.Ordinal)) + { + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.BackToSettings, commandText); + return true; + } + + if (AetheriaRuntimePlayerSettingsCommands.IsKnown(commandText)) + { + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.PlayerSettingsCommand, commandText); + return true; + } + + command = default; + return false; + } + + private static bool TryReadVerseSettingsShell(string commandText, out AetheriaRuntimeMainMenuCommand command) + { + if (string.Equals(commandText, AetheriaRuntimeMainMenuCommands.BackToSettings, StringComparison.Ordinal)) + { + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.BackToSettings, commandText); + return true; + } + + if (AetheriaRuntimeClientTargetCommands.IsKnown(commandText)) + { + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.ClientTargetCommand, commandText); + return true; + } + + if (AetheriaRuntimeVerseHostCommands.IsKnown(commandText)) + { + command = new AetheriaRuntimeMainMenuCommand(AetheriaRuntimeMainMenuCommandKind.VerseHostCommand, commandText); + return true; + } + + command = default; + return false; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMenuTabsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMenuTabsSurfaceBuilder.cs new file mode 100644 index 000000000..b4b871449 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMenuTabsSurfaceBuilder.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GameCult.Eve.Surface; + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeMenuTabSurfaceEntry + { + public AetheriaRuntimeMenuTabSurfaceEntry(string key, string label, bool selected) + { + Key = key ?? ""; + Label = label ?? ""; + Selected = selected; + } + + public string Key { get; } + public string Label { get; } + public bool Selected { get; } + } + + public sealed class AetheriaRuntimeMenuTabsSurfaceState + { + public AetheriaRuntimeMenuTabsSurfaceState( + string currentTabKey, + IReadOnlyList visibleTabs, + string updatedAtUtc) + { + CurrentTabKey = currentTabKey ?? ""; + VisibleTabs = visibleTabs ?? Array.Empty(); + UpdatedAtUtc = updatedAtUtc ?? ""; + } + + public string CurrentTabKey { get; } + public IReadOnlyList VisibleTabs { get; } + public string UpdatedAtUtc { get; } + } + + public static class AetheriaRuntimeMenuTabsSurfaceBuilder + { + public const string SurfaceId = "aetheria.runtime_menu.tabs"; + + public static string CommandFor(string tabKey) + { + var normalized = string.IsNullOrWhiteSpace(tabKey) + ? "unknown" + : tabKey.Trim().ToLowerInvariant(); + return $"aetheria.runtime_menu.tab.{normalized}"; + } + + public static AetheriaRuntimeSurfaceDocument Build( + AetheriaRuntimeMenuTabsSurfaceState state, + long version = 1) + { + state ??= new AetheriaRuntimeMenuTabsSurfaceState( + "", + Array.Empty(), + ""); + + var commands = state.VisibleTabs + .Select(tab => new AetheriaRuntimeSurfaceCommandTemplate( + CommandFor(tab.Key), + LabelFor(tab), + "unity-uitoolkit")) + .ToArray(); + var buttons = state.VisibleTabs + .Select(tab => Button( + $"{SurfaceId}.{SafeId(tab.Key)}", + tab.Selected ? $"{LabelFor(tab)} *" : LabelFor(tab), + CommandFor(tab.Key))) + .ToArray(); + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "runtime.menu", + title: "Runtime Menu Tabs", + version: version, + updatedAtUtc: state.UpdatedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + SurfaceId, + Node( + $"{SurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + Text( + $"{SurfaceId}.current", + $"Current: {state.CurrentTabKey}"), + ButtonRow($"{SurfaceId}.tabs", buttons)), + Array.Empty()), + commands: commands); + } + + private static string LabelFor(AetheriaRuntimeMenuTabSurfaceEntry tab) + { + return string.IsNullOrWhiteSpace(tab.Label) ? tab.Key : tab.Label; + } + + 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 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()); + } + } + + public enum AetheriaRuntimeMenuTabCommandKind + { + Unknown = 0, + SelectTab = 1 + } + + public readonly struct AetheriaRuntimeMenuTabCommand + { + public AetheriaRuntimeMenuTabCommand( + AetheriaRuntimeMenuTabCommandKind kind, + string tabKey) + { + Kind = kind; + TabKey = tabKey ?? ""; + } + + public AetheriaRuntimeMenuTabCommandKind Kind { get; } + public string TabKey { get; } + } + + public static class AetheriaRuntimeMenuTabsSurfaceCommands + { + private const string CommandPrefix = "aetheria.runtime_menu.tab."; + + public static bool TryRead( + EveSurfaceCommandRequest request, + out AetheriaRuntimeMenuTabCommand command) + { + command = default; + if (request == null || + !string.Equals(request.SurfaceId, AetheriaRuntimeMenuTabsSurfaceBuilder.SurfaceId, StringComparison.Ordinal)) + return false; + + var commandText = request.Command ?? ""; + if (!commandText.StartsWith(CommandPrefix, StringComparison.Ordinal)) + return false; + + var tabKey = commandText.Substring(CommandPrefix.Length); + if (string.IsNullOrWhiteSpace(tabKey)) + return false; + + command = new AetheriaRuntimeMenuTabCommand( + AetheriaRuntimeMenuTabCommandKind.SelectTab, + tabKey); + return true; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeObservedDaemonState.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeObservedDaemonState.cs new file mode 100644 index 000000000..05b159fda --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeObservedDaemonState.cs @@ -0,0 +1,31 @@ +using System; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeObservedDaemonState + { + public AetheriaRuntimeObservedDaemonState( + AetheriaRuntimeDaemonFrameDocument frame, + AetheriaRuntimeDaemonSoaViewDocument? soaView, + string framePath, + string soaViewPath) + { + Frame = frame ?? throw new ArgumentNullException(nameof(frame)); + SoaView = soaView; + SoaIndex = AetheriaRuntimeDaemonSoaViewIndex.Build(soaView); + FramePath = framePath ?? ""; + SoaViewPath = soaViewPath ?? ""; + } + + public AetheriaRuntimeDaemonFrameDocument Frame { get; } + public AetheriaRuntimeDaemonSoaViewDocument? SoaView { get; } + public AetheriaRuntimeDaemonSoaViewIndex SoaIndex { get; } + public string FramePath { get; } + public string SoaViewPath { get; } + public bool HasSoaView => SoaView != null && SoaIndex.IsValid; + public bool IsAuthoritative => Frame.IsAuthoritative; + public AetheriaRuntimeRunCheckpointCommit Run => Frame.Run; + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeObservedDaemonState.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeObservedDaemonState.cs.meta new file mode 100644 index 000000000..7fc9dd922 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeObservedDaemonState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: faf4bdd4c3ff79548932e1e85d115eed \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeOperationsCommands.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeOperationsCommands.cs new file mode 100644 index 000000000..913054451 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeOperationsCommands.cs @@ -0,0 +1,13 @@ +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeOperationsCommands + { + public const string SurfaceId = "aetheria.operations"; + public const string Refresh = "aetheria.operations.refresh"; + + public static bool IsKnown(string command) + { + return command == Refresh; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePendingCultCacheStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePendingCultCacheStore.cs deleted file mode 100644 index 759a90bbd..000000000 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePendingCultCacheStore.cs +++ /dev/null @@ -1,403 +0,0 @@ -using System; -using System.Buffers; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using MessagePack; - -#nullable enable - -namespace GameCult.Aetheria.State.Unity -{ - internal static class AetheriaRuntimePendingCultCacheStore - { - private const string FormatVersion = "cultcache.store.v1"; - private const int StoreSnapshotFieldCount = 3; - private const int SchemaCatalogEntryFieldCount = 7; - private const int SchemaCatalogMemberFieldCount = 8; - private const int PersistedRecordFieldCount = 4; - - public static void WriteStateCommit(string path, AetheriaRuntimeStateCommitDocument document) - { - WriteDocument( - path, - $"pending:aetheria.runtime_commit.{document.CommandId}.v1", - AetheriaRuntimeStateCommitDocument.SchemaId, - "aetheria.runtime_commit", - "aetheria.runtime_commit.v1", - document.CreatedAtUtc, - MessagePackSerializer.Serialize(document)); - } - - public static AetheriaRuntimeStateCommitDocument ReadStateCommit(string path) - { - return ReadStateCommitDocument(ReadDocumentPayload(path, AetheriaRuntimeStateCommitDocument.SchemaId)); - } - - public static void WriteEveCommand(string path, AetheriaRuntimeEveCommandDocument document) - { - WriteDocument( - path, - $"pending: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( - ReadDocumentPayload(path, AetheriaRuntimeEveCommandDocument.SchemaId)); - } - - private static void WriteDocument( - string path, - string key, - string schemaId, - string schemaName, - string schemaVersion, - string storedAt, - byte[] payload) - { - var buffer = new ArrayBufferWriter(); - var writer = new MessagePackWriter(buffer); - - writer.WriteArrayHeader(StoreSnapshotFieldCount); - writer.Write(FormatVersion); - writer.WriteArrayHeader(1); - WriteSchemaCatalogEntry(ref writer, schemaId, schemaName, schemaVersion, payload); - writer.WriteArrayHeader(1); - WritePersistedRecord(ref writer, key, schemaId, storedAt, payload); - writer.Flush(); - - WriteFileAtomically(path, buffer.WrittenSpan.ToArray()); - } - - private static byte[] ReadDocumentPayload(string path, string expectedSchemaId) - { - var reader = new MessagePackReader(File.ReadAllBytes(path)); - var fieldCount = reader.ReadArrayHeader(); - if (fieldCount < StoreSnapshotFieldCount) - { - throw new InvalidDataException($"Pending CultCache document '{path}' is missing store fields."); - } - - var formatVersion = reader.ReadString() ?? ""; - if (!formatVersion.StartsWith("cultcache.store.v1", StringComparison.Ordinal)) - { - throw new InvalidDataException($"Pending document '{path}' is not a CultCache store."); - } - - var schemaIds = ReadSchemaCatalog(ref reader); - var recordCount = reader.ReadArrayHeader(); - if (recordCount != 1) - { - throw new InvalidDataException($"Pending CultCache document '{path}' must contain exactly one record."); - } - - var recordFieldCount = reader.ReadArrayHeader(); - if (recordFieldCount < PersistedRecordFieldCount) - { - throw new InvalidDataException($"Pending CultCache record '{path}' is missing record fields."); - } - - reader.Skip(); // key - var schemaId = reader.ReadString() ?? ""; - reader.Skip(); // storedAt - var payload = reader.ReadBytes()?.ToArray() ?? Array.Empty(); - for (var index = PersistedRecordFieldCount; index < recordFieldCount; index++) - { - reader.Skip(); - } - - for (var index = StoreSnapshotFieldCount; index < fieldCount; index++) - { - reader.Skip(); - } - - if (!string.Equals(schemaId, expectedSchemaId, StringComparison.Ordinal)) - { - throw new InvalidDataException($"Pending document '{path}' has schema '{schemaId}', expected '{expectedSchemaId}'."); - } - - if (!schemaIds.Contains(schemaId, StringComparer.Ordinal)) - { - throw new InvalidDataException($"Pending document '{path}' does not publish schema '{schemaId}' in its CultCache catalog."); - } - - return payload; - } - - private static AetheriaRuntimeStateCommitDocument ReadStateCommitDocument(byte[] payload) - { - var reader = new MessagePackReader(payload); - var fieldCount = reader.ReadArrayHeader(); - var document = new AetheriaRuntimeStateCommitDocument - { - Schema = ReadFieldString(ref reader, fieldCount, 0, AetheriaRuntimeStateCommitDocument.SchemaId), - Kind = ReadFieldString(ref reader, fieldCount, 1, ""), - CommandId = ReadFieldString(ref reader, fieldCount, 2, ""), - CreatedAtUtc = ReadFieldString(ref reader, fieldCount, 3, ""), - PlayerSettings = ReadFieldDocument(ref reader, fieldCount, 4), - LoadoutTemplate = ReadFieldDocument(ref reader, fieldCount, 5), - RunCheckpoint = ReadFieldRunCheckpoint(ref reader, fieldCount, 6) - }; - - for (var index = 7; index < fieldCount; index++) - { - reader.Skip(); - } - - return document; - } - - private static T? ReadFieldDocument(ref MessagePackReader reader, int fields, int index) - where T : class - { - if (index >= fields) - { - return null; - } - - if (reader.NextMessagePackType == MessagePackType.Nil) - { - reader.ReadNil(); - return null; - } - - return MessagePackSerializer.Deserialize(ReadRawBytes(ref reader)); - } - - private static AetheriaRuntimeRunCheckpointCommit? ReadFieldRunCheckpoint(ref MessagePackReader reader, int fields, int index) - { - if (index >= fields) - { - return null; - } - - if (reader.NextMessagePackType == MessagePackType.Nil) - { - reader.ReadNil(); - return null; - } - - return ReadRunCheckpoint(ReadRawBytes(ref reader)); - } - - private static AetheriaRuntimeRunCheckpointCommit ReadRunCheckpoint(byte[] payload) - { - var reader = new MessagePackReader(payload); - var fields = reader.ReadArrayHeader(); - - var runId = ReadFieldString(ref reader, fields, 0, "local"); - var isTutorial = ReadFieldBool(ref reader, fields, 1, false); - var entranceZoneIndex = ReadFieldInt32(ref reader, fields, 2, -1); - var exitZoneIndex = ReadFieldInt32(ref reader, fields, 3, -1); - var currentZoneIndex = ReadFieldInt32(ref reader, fields, 4, -1); - - string currentEntityKey; - int[] discoveredZoneIndices; - AetheriaRuntimeZoneSnapshotCommit[] zones; - AetheriaRuntimeActionBarBindingCommit[] actionBarBindings; - AetheriaRuntimeFactionRelationshipCommit[] factionRelationships; - uint generationSeed; - - if (fields >= 12) - { - var legacyCurrentZoneEntityIndex = ReadFieldInt32(ref reader, fields, 5, -1); - discoveredZoneIndices = ReadFieldArray(ref reader, fields, 6); - zones = ReadFieldArray(ref reader, fields, 7); - actionBarBindings = ReadFieldArray(ref reader, fields, 8); - factionRelationships = ReadFieldArray(ref reader, fields, 9); - generationSeed = ReadFieldUInt32(ref reader, fields, 10, 0); - currentEntityKey = ReadFieldString(ref reader, fields, 11, ""); - if (string.IsNullOrWhiteSpace(currentEntityKey)) - { - currentEntityKey = LegacyCurrentEntityKey(runId, currentZoneIndex, legacyCurrentZoneEntityIndex); - } - } - else - { - discoveredZoneIndices = ReadFieldArray(ref reader, fields, 5); - zones = ReadFieldArray(ref reader, fields, 6); - actionBarBindings = ReadFieldArray(ref reader, fields, 7); - factionRelationships = ReadFieldArray(ref reader, fields, 8); - generationSeed = ReadFieldUInt32(ref reader, fields, 9, 0); - currentEntityKey = ReadFieldString(ref reader, fields, 10, ""); - } - - return new AetheriaRuntimeRunCheckpointCommit - { - RunId = runId, - IsTutorial = isTutorial, - EntranceZoneIndex = entranceZoneIndex, - ExitZoneIndex = exitZoneIndex, - CurrentZoneIndex = currentZoneIndex, - DiscoveredZoneIndices = discoveredZoneIndices, - Zones = zones, - ActionBarBindings = actionBarBindings, - FactionRelationships = factionRelationships, - GenerationSeed = generationSeed, - CurrentEntityKey = currentEntityKey - }; - } - - private static string[] ReadSchemaCatalog(ref MessagePackReader reader) - { - var count = reader.ReadArrayHeader(); - var schemaIds = new string[count]; - for (var index = 0; index < count; index++) - { - var fieldCount = reader.ReadArrayHeader(); - schemaIds[index] = fieldCount > 0 ? reader.ReadString() ?? "" : ""; - for (var field = 1; field < fieldCount; field++) - { - reader.Skip(); - } - } - - return schemaIds; - } - - private static void WriteSchemaCatalogEntry( - ref MessagePackWriter writer, - string schemaId, - string schemaName, - string schemaVersion, - byte[] payload) - { - writer.WriteArrayHeader(SchemaCatalogEntryFieldCount); - writer.Write(schemaId); - writer.Write(schemaName); - writer.Write(schemaVersion); - writer.Write(ContentHash(payload)); - writer.Write(""); - writer.WriteArrayHeader(0); - writer.WriteArrayHeader(0); - } - - private static void WritePersistedRecord( - ref MessagePackWriter writer, - string key, - string schemaId, - string storedAt, - byte[] payload) - { - writer.WriteArrayHeader(PersistedRecordFieldCount); - writer.Write(key); - writer.Write(schemaId); - writer.Write(storedAt ?? ""); - writer.Write(payload); - } - - 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 uint ReadFieldUInt32(ref MessagePackReader reader, int fields, int index, uint fallback) - { - if (index >= fields) - { - return fallback; - } - - if (reader.NextMessagePackType == MessagePackType.Nil) - { - reader.ReadNil(); - return fallback; - } - - return reader.ReadUInt32(); - } - - private static bool ReadFieldBool(ref MessagePackReader reader, int fields, int index, bool fallback) - { - if (index >= fields) - { - return fallback; - } - - if (reader.NextMessagePackType == MessagePackType.Nil) - { - reader.ReadNil(); - return fallback; - } - - return reader.ReadBoolean(); - } - - private static T[] ReadFieldArray(ref MessagePackReader reader, int fields, int index) - { - if (index >= fields) - { - return Array.Empty(); - } - - if (reader.NextMessagePackType == MessagePackType.Nil) - { - reader.ReadNil(); - return Array.Empty(); - } - - return MessagePackSerializer.Deserialize(ReadRawBytes(ref reader)) ?? Array.Empty(); - } - - private static byte[] ReadRawBytes(ref MessagePackReader reader) - { - return reader.ReadRaw().ToArray(); - } - - private static string LegacyCurrentEntityKey(string runId, int zoneIndex, int entityIndex) - { - if (string.IsNullOrWhiteSpace(runId) || zoneIndex < 0 || entityIndex < 0) - { - return ""; - } - - return $"global:aetheria.run_state.{runId}.zone.{zoneIndex}.entity.{entityIndex}.v1"; - } - - private static string ContentHash(byte[] payload) - { - using var sha256 = SHA256.Create(); - return BitConverter.ToString(sha256.ComputeHash(payload)).Replace("-", "").ToLowerInvariant(); - } - - private static void WriteFileAtomically(string path, byte[] payload) - { - var directory = Path.GetDirectoryName(path); - if (!string.IsNullOrWhiteSpace(directory)) - { - Directory.CreateDirectory(directory); - } - - var tempPath = path + ".tmp"; - File.WriteAllBytes(tempPath, payload); - if (File.Exists(path)) - { - File.Delete(path); - } - - File.Move(tempPath, path); - } - } -} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsCommands.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsCommands.cs.meta new file mode 100644 index 000000000..dd0569004 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsCommands.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cb5e0eb28b9776c4aad9f237dce09945 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs index cceef91ca..51101e284 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs @@ -192,7 +192,7 @@ namespace GameCult.Aetheria.State.Unity ("activeRun", settings.ActiveRunKey)), Text( "playerSettings.summary.note", - "Input remapping lowers through the runtime Eve input screen and queues typed player-settings commits.")), + "Input remapping lowers through the runtime Eve input screen and sends typed input-setting requests.")), Node( "aetheria.playerSettings.gameplay", "card", diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs.meta new file mode 100644 index 000000000..01a2e8412 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7c8e6757ad932d94aa990bebad419106 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeShipSettingsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeShipSettingsSurfaceBuilder.cs new file mode 100644 index 000000000..562fa7eb6 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeShipSettingsSurfaceBuilder.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GameCult.Eve.Surface; + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeShipSettingsSurfaceState + { + public AetheriaRuntimeShipSettingsSurfaceState( + string shipName, + string shutdownPerformance, + string updatedAtUtc) + { + ShipName = shipName ?? ""; + ShutdownPerformance = shutdownPerformance ?? ""; + UpdatedAtUtc = updatedAtUtc ?? ""; + } + + public string ShipName { get; } + public string ShutdownPerformance { get; } + public string UpdatedAtUtc { get; } + } + + public static class AetheriaRuntimeShipSettingsSurfaceBuilder + { + public const string SurfaceId = "aetheria.inventory.current_ship_settings"; + public const string DecrementShutdownThreshold = "aetheria.inventory.current_ship_settings.shutdown.decrement"; + public const string IncrementShutdownThreshold = "aetheria.inventory.current_ship_settings.shutdown.increment"; + public const string ResetShutdownThreshold = "aetheria.inventory.current_ship_settings.shutdown.reset"; + public const string Close = "aetheria.inventory.current_ship_settings.close"; + + public static AetheriaRuntimeSurfaceDocument Build( + AetheriaRuntimeShipSettingsSurfaceState state, + long version = 1) + { + state ??= new AetheriaRuntimeShipSettingsSurfaceState("", "", ""); + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "inventory.menu", + title: "Current Ship Settings", + version: version, + updatedAtUtc: state.UpdatedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + SurfaceId, + Node( + $"{SurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + Card( + $"{SurfaceId}.card", + state.ShipName, + Metric( + $"{SurfaceId}.shutdown.metric", + "Shutdown Threshold", + state.ShutdownPerformance), + Text( + $"{SurfaceId}.note", + "Unity projects selected ship state; shutdown changes are sent as daemon operations."), + ButtonRow( + $"{SurfaceId}.shutdown.buttons", + Button($"{SurfaceId}.shutdown.decrement", "Threshold -", DecrementShutdownThreshold), + Button($"{SurfaceId}.shutdown.increment", "Threshold +", IncrementShutdownThreshold), + Button($"{SurfaceId}.shutdown.reset", "Default", ResetShutdownThreshold), + Button($"{SurfaceId}.close", "Close", Close)))), + Array.Empty()), + commands: new[] + { + new AetheriaRuntimeSurfaceCommandTemplate(DecrementShutdownThreshold, "Threshold -", "unity-uitoolkit"), + new AetheriaRuntimeSurfaceCommandTemplate(IncrementShutdownThreshold, "Threshold +", "unity-uitoolkit"), + new AetheriaRuntimeSurfaceCommandTemplate(ResetShutdownThreshold, "Default", "unity-uitoolkit"), + new AetheriaRuntimeSurfaceCommandTemplate(Close, "Close", "unity-uitoolkit") + }); + } + + private static AetheriaRuntimeSurfaceComponent Card( + string id, + string title, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "card", new[] { ("title", title ?? "") }, children); + } + + private static AetheriaRuntimeSurfaceComponent Metric(string id, string label, string value) + { + return Node(id, "metric", new[] { ("label", label ?? ""), ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Text(string id, string value) + { + return Node(id, "text", new[] { ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) + { + return Node(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent ButtonRow( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "control.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 ?? Array.Empty<(string Key, string Value)>()) + .ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal), + children ?? Array.Empty()); + } + } + + public enum AetheriaRuntimeShipSettingsCommandKind + { + Unknown = 0, + DecrementShutdownThreshold = 1, + IncrementShutdownThreshold = 2, + ResetShutdownThreshold = 3, + Close = 4 + } + + public readonly struct AetheriaRuntimeShipSettingsCommand + { + public AetheriaRuntimeShipSettingsCommand(AetheriaRuntimeShipSettingsCommandKind kind) + { + Kind = kind; + } + + public AetheriaRuntimeShipSettingsCommandKind Kind { get; } + } + + public static class AetheriaRuntimeShipSettingsSurfaceCommands + { + public static bool TryRead( + EveSurfaceCommandRequest request, + out AetheriaRuntimeShipSettingsCommand command) + { + command = default; + if (request == null || + !string.Equals(request.SurfaceId, AetheriaRuntimeShipSettingsSurfaceBuilder.SurfaceId, StringComparison.Ordinal)) + return false; + + switch (request.Command ?? "") + { + case AetheriaRuntimeShipSettingsSurfaceBuilder.DecrementShutdownThreshold: + command = new AetheriaRuntimeShipSettingsCommand( + AetheriaRuntimeShipSettingsCommandKind.DecrementShutdownThreshold); + return true; + case AetheriaRuntimeShipSettingsSurfaceBuilder.IncrementShutdownThreshold: + command = new AetheriaRuntimeShipSettingsCommand( + AetheriaRuntimeShipSettingsCommandKind.IncrementShutdownThreshold); + return true; + case AetheriaRuntimeShipSettingsSurfaceBuilder.ResetShutdownThreshold: + command = new AetheriaRuntimeShipSettingsCommand( + AetheriaRuntimeShipSettingsCommandKind.ResetShutdownThreshold); + return true; + case AetheriaRuntimeShipSettingsSurfaceBuilder.Close: + command = new AetheriaRuntimeShipSettingsCommand( + AetheriaRuntimeShipSettingsCommandKind.Close); + return true; + default: + return false; + } + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs similarity index 93% rename from Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs rename to Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs index fa4c9e135..d521fd8be 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs @@ -5,65 +5,7 @@ using MessagePack; #nullable enable namespace GameCult.Aetheria.State.Unity -{ - public enum AetheriaRuntimeCommitKind - { - PlayerSettings, - LoadoutTemplate, - RunCheckpoint - } - - public sealed class AetheriaRuntimeCommitEnvelope - { - public AetheriaRuntimeCommitEnvelope( - string schema, - AetheriaRuntimeCommitKind kind, - string commandId, - string createdAtUtc, - string path) - { - Schema = schema; - Kind = kind; - CommandId = commandId; - CreatedAtUtc = createdAtUtc; - Path = path; - } - - public string Schema { get; } - public AetheriaRuntimeCommitKind Kind { get; } - public string CommandId { get; } - public string CreatedAtUtc { get; } - public string Path { get; } - } - - [MessagePackObject] - public sealed class AetheriaRuntimeStateCommitDocument - { - public const string SchemaId = "gamecult.aetheria.runtime_commit.v1"; - - [Key(0)] - public string Schema { get; set; } = SchemaId; - - [Key(1)] - public string Kind { get; set; } = ""; - - [Key(2)] - public string CommandId { get; set; } = ""; - - [Key(3)] - public string CreatedAtUtc { get; set; } = ""; - - [Key(4)] - public AetheriaRuntimePlayerSettingsCommit? PlayerSettings { get; set; } - - [Key(5)] - public AetheriaRuntimeLoadoutTemplateCommit? LoadoutTemplate { get; set; } - - [Key(6)] - public AetheriaRuntimeRunCheckpointCommit? RunCheckpoint { get; set; } - } - - [MessagePackObject] +{ [MessagePackObject] public sealed class AetheriaRuntimePlayerSettingsCommit { [Key(0)] @@ -247,6 +189,9 @@ namespace GameCult.Aetheria.State.Unity [Key(10)] public string CurrentEntityKey { get; set; } = ""; + + [Key(11)] + public int Credits { get; set; } } [MessagePackObject] @@ -644,6 +589,9 @@ namespace GameCult.Aetheria.State.Unity [Key(36)] public string FactionKey { get; set; } = ""; + + [Key(37)] + public double ShutdownPerformance { get; set; } } [MessagePackObject] diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeCommandApplier.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeCommandApplier.cs.meta new file mode 100644 index 000000000..6503393c0 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeCommandApplier.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4f6e72cdd4fa40c498f05485e125efa2 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeCommands.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeCommands.cs new file mode 100644 index 000000000..2ac2db5f3 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeCommands.cs @@ -0,0 +1,40 @@ +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeStatRecipeCommands + { + public const string SurfaceId = "aetheria.statRecipes"; + + public const string Refresh = "aetheria.stat_recipes.refresh"; + public const string SelectStat = "aetheria.stat_recipes.select_stat"; + public const string AddStat = "aetheria.stat_recipes.add_stat"; + public const string RemoveStat = "aetheria.stat_recipes.remove_stat"; + public const string SetStatName = "aetheria.stat_recipes.set_stat_name"; + public const string SetBaseValue = "aetheria.stat_recipes.set_base_value"; + public const string ToggleCondition = "aetheria.stat_recipes.toggle_condition"; + public const string CycleInfluenceOperation = "aetheria.stat_recipes.cycle_influence_operation"; + public const string SetInfluenceAmount = "aetheria.stat_recipes.set_influence_amount"; + public const string SetInfluenceCurve = "aetheria.stat_recipes.set_influence_curve"; + public const string SetPreviewCondition = "aetheria.stat_recipes.set_preview_condition"; + + public static bool IsKnown(string command) + { + switch (command ?? "") + { + case Refresh: + case SelectStat: + case AddStat: + case RemoveStat: + case SetStatName: + case SetBaseValue: + case ToggleCondition: + case CycleInfluenceOperation: + case SetInfluenceAmount: + case SetInfluenceCurve: + case SetPreviewCondition: + return true; + default: + return false; + } + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeCommands.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeCommands.cs.meta new file mode 100644 index 000000000..8b1849c82 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeCommands.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8acd5987bf00dc44abaa44e90634c4d4 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeSurfaceBuilder.cs new file mode 100644 index 000000000..96fe3454c --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeSurfaceBuilder.cs @@ -0,0 +1,713 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeStatRecipeSurfaceState + { + public AetheriaRuntimeStatRecipeSurfaceState( + IReadOnlyList recipes, + string selectedStatName, + AetheriaRuntimeStatRecipePreviewState preview, + string updatedAtUtc) + { + Recipes = recipes ?? Array.Empty(); + SelectedStatName = selectedStatName ?? ""; + Preview = preview ?? AetheriaRuntimeStatRecipePreviewState.Default; + UpdatedAtUtc = updatedAtUtc ?? ""; + } + + public IReadOnlyList Recipes { get; } + + public string SelectedStatName { get; } + + public AetheriaRuntimeStatRecipePreviewState Preview { get; } + + public string UpdatedAtUtc { get; } + + public AetheriaRuntimeStatRecipeState SelectedRecipe => + Recipes.FirstOrDefault(recipe => string.Equals(recipe.StatName, SelectedStatName, StringComparison.Ordinal)) ?? + Recipes.FirstOrDefault(); + } + + public sealed class AetheriaRuntimeStatRecipeState + { + public AetheriaRuntimeStatRecipeState( + string recipeKey, + string statName, + double baseValue, + IReadOnlyList influences) + { + RecipeKey = recipeKey ?? ""; + StatName = statName ?? ""; + BaseValue = baseValue; + Influences = influences ?? Array.Empty(); + } + + public string RecipeKey { get; } + + public string StatName { get; } + + public double BaseValue { get; } + + public IReadOnlyList Influences { get; } + } + + public sealed class AetheriaRuntimeStatInfluenceState + { + public AetheriaRuntimeStatInfluenceState( + string condition, + string operation, + double amount, + string curveLabel, + double previewSample, + bool enabled) + { + Condition = condition ?? ""; + Operation = operation ?? AetheriaRuntimeStatRecipeOperations.Add; + Amount = amount; + CurveLabel = curveLabel ?? ""; + PreviewSample = previewSample; + Enabled = enabled; + } + + public string Condition { get; } + + public string Operation { get; } + + public double Amount { get; } + + public string CurveLabel { get; } + + public double PreviewSample { get; } + + public bool Enabled { get; } + } + + public sealed class AetheriaRuntimeStatRecipePreviewState + { + public static readonly AetheriaRuntimeStatRecipePreviewState Default = + new AetheriaRuntimeStatRecipePreviewState( + quality: 1, + durability: 1, + heat: 0, + charge: 1, + ammo: 1, + range: 1, + integrity: 1, + pilotSkill: 0, + environment: 0); + + public AetheriaRuntimeStatRecipePreviewState( + double quality, + double durability, + double heat, + double charge, + double ammo, + double range, + double integrity, + double pilotSkill, + double environment) + { + Quality = quality; + Durability = durability; + Heat = heat; + Charge = charge; + Ammo = ammo; + Range = range; + Integrity = integrity; + PilotSkill = pilotSkill; + Environment = environment; + } + + public double Quality { get; } + + public double Durability { get; } + + public double Heat { get; } + + public double Charge { get; } + + public double Ammo { get; } + + public double Range { get; } + + public double Integrity { get; } + + public double PilotSkill { get; } + + public double Environment { get; } + + public double GetConditionValue(string condition) + { + switch (condition ?? "") + { + case AetheriaRuntimeStatRecipeConditions.Quality: + return Quality; + case AetheriaRuntimeStatRecipeConditions.Durability: + return Durability; + case AetheriaRuntimeStatRecipeConditions.Heat: + return Heat; + case AetheriaRuntimeStatRecipeConditions.Charge: + return Charge; + case AetheriaRuntimeStatRecipeConditions.Ammo: + return Ammo; + case AetheriaRuntimeStatRecipeConditions.Range: + return Range; + case AetheriaRuntimeStatRecipeConditions.Integrity: + return Integrity; + case AetheriaRuntimeStatRecipeConditions.PilotSkill: + return PilotSkill; + case AetheriaRuntimeStatRecipeConditions.Environment: + return Environment; + default: + return 0; + } + } + } + + public static class AetheriaRuntimeStatRecipeConditions + { + public const string Quality = "quality"; + public const string Durability = "durability"; + public const string Heat = "heat"; + public const string Charge = "charge"; + public const string Ammo = "ammo"; + public const string Range = "range"; + public const string Integrity = "integrity"; + public const string PilotSkill = "pilotSkill"; + public const string Environment = "environment"; + + public static readonly IReadOnlyList All = new[] + { + Quality, + Durability, + Heat, + Charge, + Ammo, + Range, + Integrity, + PilotSkill, + Environment + }; + } + + public static class AetheriaRuntimeStatRecipeOperations + { + public const string Add = "add"; + public const string Multiply = "multiply"; + public const string Override = "override"; + } + + public static class AetheriaRuntimeStatRecipeSurfaceBuilder + { + public static AetheriaRuntimeSurfaceDocument Build( + AetheriaRuntimeStatRecipeSurfaceState state, + long version = 1) + { + state ??= new AetheriaRuntimeStatRecipeSurfaceState( + Array.Empty(), + "", + AetheriaRuntimeStatRecipePreviewState.Default, + ""); + + var selected = state.SelectedRecipe; + var previewValue = selected == null ? 0 : EvaluatePreview(selected, state.Preview); + var enabledInfluenceCount = selected?.Influences.Count(influence => influence.Enabled) ?? 0; + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "game.design", + title: "Aetheria Stat Recipes", + version: version, + updatedAtUtc: state.UpdatedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + AetheriaRuntimeStatRecipeCommands.SurfaceId, + Node( + "aetheria.statRecipes.root", + "surface", + Array.Empty<(string Key, string Value)>(), + BuildSummaryCard(selected, previewValue, enabledInfluenceCount), + BuildRecipeList(state, selected), + BuildSelectedRecipeCard(selected), + BuildConditionPalette(selected), + BuildInfluenceList(selected, state.Preview), + BuildPreviewCard(state.Preview)), + Array.Empty()), + commands: BuildCommandTemplates()); + } + + private static AetheriaRuntimeSurfaceComponent BuildSummaryCard( + AetheriaRuntimeStatRecipeState selected, + double previewValue, + int enabledInfluenceCount) + { + var statName = string.IsNullOrWhiteSpace(selected?.StatName) ? "No stat selected" : selected.StatName; + + return Node( + "aetheria.statRecipes.summary", + "card", + new[] { ("title", "Stat Recipe") }, + Metric("aetheria.statRecipes.summary.stat", "Selected Stat", statName), + Metric("aetheria.statRecipes.summary.base", "Base", Format(selected?.BaseValue ?? 0)), + Metric("aetheria.statRecipes.summary.preview", "Preview", Format(previewValue)), + Metric("aetheria.statRecipes.summary.influences", "Active Conditions", enabledInfluenceCount.ToString()), + Text( + "aetheria.statRecipes.summary.note", + "Each recipe owns only the conditions it actually cares about; runtime evaluation can sample those modifiers instead of walking every global performance axis.")); + } + + private static AetheriaRuntimeSurfaceComponent BuildRecipeList( + AetheriaRuntimeStatRecipeSurfaceState state, + AetheriaRuntimeStatRecipeState selected) + { + var children = state.Recipes + .Select((recipe, index) => Button( + $"aetheria.statRecipes.recipe.{index}.select", + string.Equals(recipe.StatName, selected?.StatName, StringComparison.Ordinal) ? $"{recipe.StatName} (Selected)" : recipe.StatName, + AetheriaRuntimeStatRecipeCommands.SelectStat, + ("recipeKey", recipe.RecipeKey), + ("statName", recipe.StatName))) + .Concat(new[] + { + Button( + "aetheria.statRecipes.recipe.add", + "Add Stat", + AetheriaRuntimeStatRecipeCommands.AddStat) + }) + .ToArray(); + + return Node( + "aetheria.statRecipes.recipes", + "card", + new[] { ("title", "Recipes") }, + Node("aetheria.statRecipes.recipes.list", "grid", Array.Empty<(string Key, string Value)>(), children)); + } + + private static AetheriaRuntimeSurfaceComponent BuildSelectedRecipeCard(AetheriaRuntimeStatRecipeState selected) + { + if (selected == null) + { + return Node( + "aetheria.statRecipes.selected", + "card", + new[] { ("title", "Selected Recipe") }, + Text("aetheria.statRecipes.selected.empty", "Add or select a stat recipe to start authoring modifiers.")); + } + + return Node( + "aetheria.statRecipes.selected", + "card", + new[] { ("title", "Selected Recipe") }, + TextInput( + "aetheria.statRecipes.selected.name", + "Stat Name", + selected.StatName, + AetheriaRuntimeStatRecipeCommands.SetStatName, + ("recipeKey", selected.RecipeKey), + ("statName", selected.StatName)), + NumberInput( + "aetheria.statRecipes.selected.base", + "Base Value", + selected.BaseValue, + AetheriaRuntimeStatRecipeCommands.SetBaseValue, + ("recipeKey", selected.RecipeKey), + ("statName", selected.StatName)), + ButtonRow( + "aetheria.statRecipes.selected.actions", + Button( + "aetheria.statRecipes.selected.remove", + "Remove Stat", + AetheriaRuntimeStatRecipeCommands.RemoveStat, + ("recipeKey", selected.RecipeKey), + ("statName", selected.StatName)), + Button( + "aetheria.statRecipes.selected.refresh", + "Refresh", + AetheriaRuntimeStatRecipeCommands.Refresh))); + } + + private static AetheriaRuntimeSurfaceComponent BuildConditionPalette(AetheriaRuntimeStatRecipeState selected) + { + var children = AetheriaRuntimeStatRecipeConditions.All + .Select(condition => + { + var influence = selected?.Influences.FirstOrDefault(candidate => string.Equals(candidate.Condition, condition, StringComparison.Ordinal)); + var enabled = influence?.Enabled ?? false; + + return Toggle( + $"aetheria.statRecipes.conditions.{condition}", + ConditionLabel(condition), + enabled, + AetheriaRuntimeStatRecipeCommands.ToggleCondition, + ("recipeKey", selected?.RecipeKey ?? ""), + ("statName", selected?.StatName ?? ""), + ("condition", condition), + ("enabled", enabled ? "false" : "true")); + }) + .ToArray(); + + return Node( + "aetheria.statRecipes.conditions", + "card", + new[] { ("title", "Affected By") }, + Node("aetheria.statRecipes.conditions.grid", "grid", Array.Empty<(string Key, string Value)>(), children)); + } + + private static AetheriaRuntimeSurfaceComponent BuildInfluenceList( + AetheriaRuntimeStatRecipeState selected, + AetheriaRuntimeStatRecipePreviewState preview) + { + if (selected == null || selected.Influences.Count == 0) + { + return Node( + "aetheria.statRecipes.influences", + "card", + new[] { ("title", "Modifiers") }, + Text("aetheria.statRecipes.influences.empty", "Choose a condition above to create the first modifier for this stat.")); + } + + return Node( + "aetheria.statRecipes.influences", + "card", + new[] { ("title", "Modifiers") }, + Node( + "aetheria.statRecipes.influences.grid", + "grid", + Array.Empty<(string Key, string Value)>(), + selected.Influences.Select(influence => BuildInfluenceCard(selected, influence, preview)).ToArray())); + } + + private static AetheriaRuntimeSurfaceComponent BuildInfluenceCard( + AetheriaRuntimeStatRecipeState recipe, + AetheriaRuntimeStatInfluenceState influence, + AetheriaRuntimeStatRecipePreviewState preview) + { + var conditionValue = preview.GetConditionValue(influence.Condition); + var sample = ResolvePreviewSample(influence, conditionValue); + + return Node( + $"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}", + "card", + new[] { ("title", ConditionLabel(influence.Condition)) }, + Metric( + $"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}.operation", + "Operation", + OperationLabel(influence.Operation)), + NumberInput( + $"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}.amount", + "Amount", + influence.Amount, + AetheriaRuntimeStatRecipeCommands.SetInfluenceAmount, + ("recipeKey", recipe.RecipeKey), + ("statName", recipe.StatName), + ("condition", influence.Condition)), + TextInput( + $"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}.curve", + "Curve", + influence.CurveLabel, + AetheriaRuntimeStatRecipeCommands.SetInfluenceCurve, + ("recipeKey", recipe.RecipeKey), + ("statName", recipe.StatName), + ("condition", influence.Condition)), + Metric( + $"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}.sample", + "Preview Sample", + Format(sample)), + ButtonRow( + $"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}.actions", + Button( + $"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}.operation.cycle", + "Cycle Operation", + AetheriaRuntimeStatRecipeCommands.CycleInfluenceOperation, + ("recipeKey", recipe.RecipeKey), + ("statName", recipe.StatName), + ("condition", influence.Condition)))); + } + + private static AetheriaRuntimeSurfaceComponent BuildPreviewCard(AetheriaRuntimeStatRecipePreviewState preview) + { + return Node( + "aetheria.statRecipes.preview", + "card", + new[] { ("title", "Preview Context") }, + Node( + "aetheria.statRecipes.preview.grid", + "grid", + Array.Empty<(string Key, string Value)>(), + AetheriaRuntimeStatRecipeConditions.All + .Select(condition => Slider( + $"aetheria.statRecipes.preview.{condition}", + ConditionLabel(condition), + preview.GetConditionValue(condition), + 0, + 1, + AetheriaRuntimeStatRecipeCommands.SetPreviewCondition, + ("condition", condition))) + .ToArray())); + } + + private static IReadOnlyList BuildCommandTemplates() + { + return new[] + { + new AetheriaRuntimeSurfaceCommandTemplate(AetheriaRuntimeStatRecipeCommands.Refresh, "Refresh", "cultmesh"), + new AetheriaRuntimeSurfaceCommandTemplate(AetheriaRuntimeStatRecipeCommands.SelectStat, "Select Stat", "cultmesh"), + new AetheriaRuntimeSurfaceCommandTemplate(AetheriaRuntimeStatRecipeCommands.AddStat, "Add Stat", "cultmesh"), + new AetheriaRuntimeSurfaceCommandTemplate(AetheriaRuntimeStatRecipeCommands.RemoveStat, "Remove Stat", "cultmesh"), + new AetheriaRuntimeSurfaceCommandTemplate(AetheriaRuntimeStatRecipeCommands.SetStatName, "Set Stat Name", "cultmesh"), + new AetheriaRuntimeSurfaceCommandTemplate(AetheriaRuntimeStatRecipeCommands.SetBaseValue, "Set Base Value", "cultmesh"), + new AetheriaRuntimeSurfaceCommandTemplate(AetheriaRuntimeStatRecipeCommands.ToggleCondition, "Toggle Condition", "cultmesh"), + new AetheriaRuntimeSurfaceCommandTemplate(AetheriaRuntimeStatRecipeCommands.CycleInfluenceOperation, "Cycle Operation", "cultmesh"), + new AetheriaRuntimeSurfaceCommandTemplate(AetheriaRuntimeStatRecipeCommands.SetInfluenceAmount, "Set Influence Amount", "cultmesh"), + new AetheriaRuntimeSurfaceCommandTemplate(AetheriaRuntimeStatRecipeCommands.SetInfluenceCurve, "Set Influence Curve", "cultmesh"), + new AetheriaRuntimeSurfaceCommandTemplate(AetheriaRuntimeStatRecipeCommands.SetPreviewCondition, "Set Preview Condition", "cultmesh") + }; + } + + private static double EvaluatePreview( + AetheriaRuntimeStatRecipeState recipe, + AetheriaRuntimeStatRecipePreviewState preview) + { + var value = recipe.BaseValue; + + foreach (var influence in recipe.Influences.Where(influence => influence.Enabled)) + { + var sample = ResolvePreviewSample(influence, preview.GetConditionValue(influence.Condition)); + switch (influence.Operation) + { + case AetheriaRuntimeStatRecipeOperations.Multiply: + value *= 1 + ((influence.Amount - 1) * sample); + break; + case AetheriaRuntimeStatRecipeOperations.Override: + value = Lerp(value, influence.Amount, sample); + break; + default: + value += influence.Amount * sample; + break; + } + } + + return value; + } + + private static double ResolvePreviewSample( + AetheriaRuntimeStatInfluenceState influence, + double conditionValue) + { + if (influence.PreviewSample > 0) + return Clamp01(influence.PreviewSample); + + return Clamp01(conditionValue); + } + + private static double Clamp01(double value) + { + if (value < 0) + return 0; + if (value > 1) + return 1; + return value; + } + + private static double Lerp(double from, double to, double sample) + { + return from + ((to - from) * Clamp01(sample)); + } + + private static string Format(double value) + { + return value.ToString("0.###", CultureInfo.InvariantCulture); + } + + private static string ConditionLabel(string condition) + { + switch (condition ?? "") + { + case AetheriaRuntimeStatRecipeConditions.Quality: + return "Quality"; + case AetheriaRuntimeStatRecipeConditions.Durability: + return "Durability"; + case AetheriaRuntimeStatRecipeConditions.Heat: + return "Heat"; + case AetheriaRuntimeStatRecipeConditions.Charge: + return "Charge"; + case AetheriaRuntimeStatRecipeConditions.Ammo: + return "Ammo"; + case AetheriaRuntimeStatRecipeConditions.Range: + return "Range"; + case AetheriaRuntimeStatRecipeConditions.Integrity: + return "Integrity"; + case AetheriaRuntimeStatRecipeConditions.PilotSkill: + return "Pilot Skill"; + case AetheriaRuntimeStatRecipeConditions.Environment: + return "Environment"; + default: + return condition ?? ""; + } + } + + private static string OperationLabel(string operation) + { + switch (operation ?? "") + { + case AetheriaRuntimeStatRecipeOperations.Multiply: + return "Multiply"; + case AetheriaRuntimeStatRecipeOperations.Override: + return "Override"; + default: + return "Add"; + } + } + + private static string StableToken(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return "none"; + + return new string(value + .Select(character => char.IsLetterOrDigit(character) ? character : '_') + .ToArray()); + } + + 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, + params (string Key, string Value)[] extraProps) + { + var props = new List<(string Key, string Value)> + { + ("label", label ?? ""), + ("command", command ?? "") + }; + if (extraProps != null) + { + props.AddRange(extraProps); + } + + return Node(id, "control.button", props); + } + + private static AetheriaRuntimeSurfaceComponent Toggle( + string id, + string label, + bool value, + string command, + params (string Key, string Value)[] extraProps) + { + var props = new List<(string Key, string Value)> + { + ("label", label ?? ""), + ("value", value ? "true" : "false"), + ("command", command ?? "") + }; + if (extraProps != null) + { + props.AddRange(extraProps); + } + + return Node(id, "control.toggle", props); + } + + private static AetheriaRuntimeSurfaceComponent Slider( + string id, + string label, + double value, + double min, + double max, + string command, + params (string Key, string Value)[] extraProps) + { + var props = new List<(string Key, string Value)> + { + ("label", label ?? ""), + ("value", Format(value)), + ("min", Format(min)), + ("max", Format(max)), + ("command", command ?? "") + }; + if (extraProps != null) + { + props.AddRange(extraProps); + } + + return Node(id, "control.slider", props); + } + + private static AetheriaRuntimeSurfaceComponent NumberInput( + string id, + string label, + double value, + string command, + params (string Key, string Value)[] extraProps) + { + var props = new List<(string Key, string Value)> + { + ("label", label ?? ""), + ("value", Format(value)), + ("command", command ?? ""), + ("valueKind", "number") + }; + if (extraProps != null) + { + props.AddRange(extraProps); + } + + return Node(id, "control.text", props); + } + + private static AetheriaRuntimeSurfaceComponent TextInput( + string id, + string label, + string value, + string command, + params (string Key, string Value)[] extraProps) + { + var props = new List<(string Key, string Value)> + { + ("label", label ?? ""), + ("value", value ?? ""), + ("command", command ?? "") + }; + if (extraProps != null) + { + props.AddRange(extraProps); + } + + return Node(id, "control.text", props); + } + + 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()); + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeSurfaceBuilder.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeSurfaceBuilder.cs.meta new file mode 100644 index 000000000..9d6f78d5c --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeSurfaceBuilder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c630337961a252149a6e9db3d7513ee0 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipes.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipes.cs new file mode 100644 index 000000000..a04cda3bd --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipes.cs @@ -0,0 +1,363 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public static class AetheriaRuntimeStatRecipes + { + public static AetheriaRuntimeStatRecipeSurfaceState Refresh( + AetheriaRuntimeStatRecipeSurfaceState? state, + string? updatedAtUtc = null) + { + state ??= EmptyState(); + return With(state, state.Recipes, state.SelectedStatName, state.Preview, Stamp(updatedAtUtc)); + } + + public static AetheriaRuntimeStatRecipeSurfaceState SelectStat( + AetheriaRuntimeStatRecipeSurfaceState? state, + string recipeKey, + string statName = "", + string? updatedAtUtc = null) + { + state ??= EmptyState(); + var recipe = FindRecipe(state, recipeKey, statName); + return recipe == null + ? state + : With(state, state.Recipes, recipe.StatName, state.Preview, Stamp(updatedAtUtc)); + } + + public static AetheriaRuntimeStatRecipeSurfaceState AddStat( + AetheriaRuntimeStatRecipeSurfaceState? state, + string statName, + string recipeKey = "", + double baseValue = 0, + string? updatedAtUtc = null) + { + state ??= EmptyState(); + recipeKey = string.IsNullOrWhiteSpace(recipeKey) ? $"design:{Guid.NewGuid():N}" : recipeKey; + var recipe = new AetheriaRuntimeStatRecipeState( + recipeKey, + string.IsNullOrWhiteSpace(statName) ? "New Stat Recipe" : statName, + baseValue, + Array.Empty()); + + return With( + state, + state.Recipes.Concat(new[] { recipe }).ToArray(), + recipe.StatName, + state.Preview, + Stamp(updatedAtUtc)); + } + + public static AetheriaRuntimeStatRecipeSurfaceState RemoveStat( + AetheriaRuntimeStatRecipeSurfaceState? state, + string recipeKey, + string statName = "", + string? updatedAtUtc = null) + { + state ??= EmptyState(); + var recipe = FindRecipe(state, recipeKey, statName); + if (recipe == null) + return state; + + var remaining = state.Recipes + .Where(candidate => !SameRecipe(candidate, recipe)) + .ToArray(); + return With( + state, + remaining, + remaining.FirstOrDefault()?.StatName ?? "", + state.Preview, + Stamp(updatedAtUtc)); + } + + public static AetheriaRuntimeStatRecipeSurfaceState SetStatName( + AetheriaRuntimeStatRecipeSurfaceState? state, + string recipeKey, + string statName, + string? currentStatName = null, + string? updatedAtUtc = null) + { + return UpdateRecipe(state, recipeKey, currentStatName ?? "", Stamp(updatedAtUtc), recipe => + new AetheriaRuntimeStatRecipeState( + recipe.RecipeKey, + statName ?? "", + recipe.BaseValue, + recipe.Influences)); + } + + public static AetheriaRuntimeStatRecipeSurfaceState SetBaseValue( + AetheriaRuntimeStatRecipeSurfaceState? state, + string recipeKey, + double baseValue, + string statName = "", + string? updatedAtUtc = null) + { + return UpdateRecipe(state, recipeKey, statName, Stamp(updatedAtUtc), recipe => + new AetheriaRuntimeStatRecipeState( + recipe.RecipeKey, + recipe.StatName, + baseValue, + recipe.Influences)); + } + + public static AetheriaRuntimeStatRecipeSurfaceState SetConditionEnabled( + AetheriaRuntimeStatRecipeSurfaceState? state, + string recipeKey, + string condition, + bool enabled, + string statName = "", + string? updatedAtUtc = null) + { + return UpsertInfluence( + state, + recipeKey, + statName, + condition, + Stamp(updatedAtUtc), + influence => new AetheriaRuntimeStatInfluenceState( + influence.Condition, + influence.Operation, + influence.Amount, + influence.CurveLabel, + influence.PreviewSample, + enabled), + enabled); + } + + public static AetheriaRuntimeStatRecipeSurfaceState ToggleCondition( + AetheriaRuntimeStatRecipeSurfaceState? state, + string recipeKey, + string condition, + string statName = "", + string? updatedAtUtc = null) + { + state ??= EmptyState(); + var recipe = FindRecipe(state, recipeKey, statName); + var current = recipe?.Influences.FirstOrDefault(influence => + string.Equals(influence.Condition, NormalizeCondition(condition), StringComparison.Ordinal)); + return SetConditionEnabled( + state, + recipeKey, + condition, + current == null || !current.Enabled, + statName, + updatedAtUtc); + } + + public static AetheriaRuntimeStatRecipeSurfaceState CycleInfluenceOperation( + AetheriaRuntimeStatRecipeSurfaceState? state, + string recipeKey, + string condition, + string statName = "", + string? updatedAtUtc = null) + { + return UpsertInfluence(state, recipeKey, statName, condition, Stamp(updatedAtUtc), influence => + new AetheriaRuntimeStatInfluenceState( + influence.Condition, + NextOperation(influence.Operation), + influence.Amount, + influence.CurveLabel, + influence.PreviewSample, + influence.Enabled)); + } + + public static AetheriaRuntimeStatRecipeSurfaceState SetInfluenceAmount( + AetheriaRuntimeStatRecipeSurfaceState? state, + string recipeKey, + string condition, + double amount, + string statName = "", + string? updatedAtUtc = null) + { + return UpsertInfluence(state, recipeKey, statName, condition, Stamp(updatedAtUtc), influence => + new AetheriaRuntimeStatInfluenceState( + influence.Condition, + influence.Operation, + amount, + influence.CurveLabel, + influence.PreviewSample, + influence.Enabled)); + } + + public static AetheriaRuntimeStatRecipeSurfaceState SetInfluenceCurve( + AetheriaRuntimeStatRecipeSurfaceState? state, + string recipeKey, + string condition, + string curveLabel, + string statName = "", + string? updatedAtUtc = null) + { + return UpsertInfluence(state, recipeKey, statName, condition, Stamp(updatedAtUtc), influence => + new AetheriaRuntimeStatInfluenceState( + influence.Condition, + influence.Operation, + influence.Amount, + curveLabel ?? "", + influence.PreviewSample, + influence.Enabled)); + } + + public static AetheriaRuntimeStatRecipeSurfaceState SetPreviewCondition( + AetheriaRuntimeStatRecipeSurfaceState? state, + string condition, + double value, + string? updatedAtUtc = null) + { + state ??= EmptyState(); + condition = NormalizeCondition(condition); + if (string.IsNullOrWhiteSpace(condition)) + return state; + + var preview = state.Preview; + var next = condition switch + { + AetheriaRuntimeStatRecipeConditions.Quality => new AetheriaRuntimeStatRecipePreviewState(value, preview.Durability, preview.Heat, preview.Charge, preview.Ammo, preview.Range, preview.Integrity, preview.PilotSkill, preview.Environment), + AetheriaRuntimeStatRecipeConditions.Durability => new AetheriaRuntimeStatRecipePreviewState(preview.Quality, value, preview.Heat, preview.Charge, preview.Ammo, preview.Range, preview.Integrity, preview.PilotSkill, preview.Environment), + AetheriaRuntimeStatRecipeConditions.Heat => new AetheriaRuntimeStatRecipePreviewState(preview.Quality, preview.Durability, value, preview.Charge, preview.Ammo, preview.Range, preview.Integrity, preview.PilotSkill, preview.Environment), + AetheriaRuntimeStatRecipeConditions.Charge => new AetheriaRuntimeStatRecipePreviewState(preview.Quality, preview.Durability, preview.Heat, value, preview.Ammo, preview.Range, preview.Integrity, preview.PilotSkill, preview.Environment), + AetheriaRuntimeStatRecipeConditions.Ammo => new AetheriaRuntimeStatRecipePreviewState(preview.Quality, preview.Durability, preview.Heat, preview.Charge, value, preview.Range, preview.Integrity, preview.PilotSkill, preview.Environment), + AetheriaRuntimeStatRecipeConditions.Range => new AetheriaRuntimeStatRecipePreviewState(preview.Quality, preview.Durability, preview.Heat, preview.Charge, preview.Ammo, value, preview.Integrity, preview.PilotSkill, preview.Environment), + AetheriaRuntimeStatRecipeConditions.Integrity => new AetheriaRuntimeStatRecipePreviewState(preview.Quality, preview.Durability, preview.Heat, preview.Charge, preview.Ammo, preview.Range, value, preview.PilotSkill, preview.Environment), + AetheriaRuntimeStatRecipeConditions.PilotSkill => new AetheriaRuntimeStatRecipePreviewState(preview.Quality, preview.Durability, preview.Heat, preview.Charge, preview.Ammo, preview.Range, preview.Integrity, value, preview.Environment), + AetheriaRuntimeStatRecipeConditions.Environment => new AetheriaRuntimeStatRecipePreviewState(preview.Quality, preview.Durability, preview.Heat, preview.Charge, preview.Ammo, preview.Range, preview.Integrity, preview.PilotSkill, value), + _ => preview + }; + + return With(state, state.Recipes, state.SelectedStatName, next, Stamp(updatedAtUtc)); + } + + private static AetheriaRuntimeStatRecipeSurfaceState UpsertInfluence( + AetheriaRuntimeStatRecipeSurfaceState? state, + string recipeKey, + string statName, + string condition, + string updatedAtUtc, + Func update, + bool defaultEnabled = true) + { + condition = NormalizeCondition(condition); + if (string.IsNullOrWhiteSpace(condition)) + return state ?? EmptyState(); + + return UpdateRecipe(state, recipeKey, statName, updatedAtUtc, recipe => + { + var influences = recipe.Influences.ToList(); + var index = influences.FindIndex(influence => string.Equals(influence.Condition, condition, StringComparison.Ordinal)); + var current = index >= 0 + ? influences[index] + : new AetheriaRuntimeStatInfluenceState( + condition, + AetheriaRuntimeStatRecipeOperations.Add, + 0, + "linear", + 0, + defaultEnabled); + var next = update(current); + + if (index >= 0) + influences[index] = next; + else + influences.Add(next); + + return new AetheriaRuntimeStatRecipeState(recipe.RecipeKey, recipe.StatName, recipe.BaseValue, influences); + }); + } + + private static AetheriaRuntimeStatRecipeSurfaceState UpdateRecipe( + AetheriaRuntimeStatRecipeSurfaceState? state, + string recipeKey, + string statName, + string updatedAtUtc, + Func update) + { + state ??= EmptyState(); + var target = FindRecipe(state, recipeKey, statName); + if (target == null) + return state; + + var recipes = state.Recipes + .Select(recipe => SameRecipe(recipe, target) ? update(recipe) : recipe) + .ToArray(); + var selected = recipes.FirstOrDefault(recipe => string.Equals(recipe.RecipeKey, target.RecipeKey, StringComparison.Ordinal)) + ?? recipes.FirstOrDefault(recipe => string.Equals(recipe.StatName, target.StatName, StringComparison.Ordinal)); + + return With(state, recipes, selected?.StatName ?? state.SelectedStatName, state.Preview, updatedAtUtc); + } + + private static AetheriaRuntimeStatRecipeSurfaceState With( + AetheriaRuntimeStatRecipeSurfaceState state, + IReadOnlyList recipes, + string selectedStatName, + AetheriaRuntimeStatRecipePreviewState preview, + string updatedAtUtc) + { + return new AetheriaRuntimeStatRecipeSurfaceState( + recipes, + selectedStatName, + preview ?? state.Preview, + updatedAtUtc); + } + + private static AetheriaRuntimeStatRecipeState? FindRecipe( + AetheriaRuntimeStatRecipeSurfaceState state, + string recipeKey, + string statName) + { + if (!string.IsNullOrWhiteSpace(recipeKey)) + { + var byKey = state.Recipes.FirstOrDefault(recipe => string.Equals(recipe.RecipeKey, recipeKey, StringComparison.Ordinal)); + if (byKey != null) + return byKey; + } + + statName = string.IsNullOrWhiteSpace(statName) ? state.SelectedStatName : statName; + return state.Recipes.FirstOrDefault(recipe => string.Equals(recipe.StatName, statName, StringComparison.Ordinal)) + ?? state.SelectedRecipe; + } + + private static bool SameRecipe(AetheriaRuntimeStatRecipeState left, AetheriaRuntimeStatRecipeState right) + { + if (!string.IsNullOrWhiteSpace(left.RecipeKey) || !string.IsNullOrWhiteSpace(right.RecipeKey)) + return string.Equals(left.RecipeKey, right.RecipeKey, StringComparison.Ordinal); + + return string.Equals(left.StatName, right.StatName, StringComparison.Ordinal); + } + + private static string NormalizeCondition(string condition) + { + return AetheriaRuntimeStatRecipeConditions.All.FirstOrDefault(candidate => + string.Equals(candidate, condition, StringComparison.OrdinalIgnoreCase)) ?? ""; + } + + private static string NextOperation(string operation) + { + switch (operation ?? "") + { + case AetheriaRuntimeStatRecipeOperations.Add: + return AetheriaRuntimeStatRecipeOperations.Multiply; + case AetheriaRuntimeStatRecipeOperations.Multiply: + return AetheriaRuntimeStatRecipeOperations.Override; + default: + return AetheriaRuntimeStatRecipeOperations.Add; + } + } + + private static AetheriaRuntimeStatRecipeSurfaceState EmptyState() + { + return new AetheriaRuntimeStatRecipeSurfaceState( + Array.Empty(), + "", + AetheriaRuntimeStatRecipePreviewState.Default, + ""); + } + + private static string Stamp(string? updatedAtUtc) + { + return string.IsNullOrWhiteSpace(updatedAtUtc) ? DateTime.UtcNow.ToString("O") : updatedAtUtc; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoot.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoot.cs.meta new file mode 100644 index 000000000..213de1d5f --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoot.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2c0bbe678cc85f44abfef1c7c2ab4cfb \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs index f9afb240d..f33424508 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs @@ -8,6 +8,15 @@ namespace GameCult.Aetheria.State.Unity public const string RuntimeStateFileName = "aetheria-world.cc"; public const string RuntimeClientTargetFileName = "aetheria-client.cc"; public const string RuntimeReplicaDirectoryName = "Verses"; + public const string RuntimeDaemonFrameFileSuffix = ".daemon.frame.cc"; + public const string RuntimeDaemonSoaViewFileSuffix = ".daemon.soa.cc"; + public const string RuntimeDaemonProviderFileSuffix = ".daemon.provider.cc"; + public const string RuntimeDaemonHealthFileSuffix = ".daemon.health.cc"; + public const string RuntimeDaemonCommandBoundaryFileSuffix = ".daemon.commands.cc"; + public const string RuntimeDaemonGameSurfaceFileSuffix = ".daemon.game.eve.cc"; + public const string RuntimeDaemonGameTuiSurfaceFileSuffix = ".daemon.game.tui.cc"; + public const string RuntimeDaemonEditorSurfaceFileSuffix = ".daemon.editor.eve.cc"; + public const string RuntimeDaemonEditorTuiSurfaceFileSuffix = ".daemon.editor.tui.cc"; public const string RuntimeStatePathOverrideEnvironmentVariable = "AETHERIA_STATE_PATH"; public const string LegacyRuntimeStatePathOverrideEnvironmentVariable = "AETHERIA_EVE_STATE_PATH"; @@ -21,6 +30,51 @@ namespace GameCult.Aetheria.State.Unity return Path.Combine(gameDataDirectory.FullName, RuntimeClientTargetFileName); } + public static string GetDaemonFramePath(string stateFilePath) + { + return stateFilePath + RuntimeDaemonFrameFileSuffix; + } + + public static string GetDaemonSoaViewPath(string stateFilePath) + { + return stateFilePath + RuntimeDaemonSoaViewFileSuffix; + } + + public static string GetDaemonProviderPath(string stateFilePath) + { + return stateFilePath + RuntimeDaemonProviderFileSuffix; + } + + public static string GetDaemonHealthPath(string stateFilePath) + { + return stateFilePath + RuntimeDaemonHealthFileSuffix; + } + + public static string GetDaemonCommandBoundaryPath(string stateFilePath) + { + return stateFilePath + RuntimeDaemonCommandBoundaryFileSuffix; + } + + public static string GetDaemonGameSurfacePath(string stateFilePath) + { + return stateFilePath + RuntimeDaemonGameSurfaceFileSuffix; + } + + public static string GetDaemonGameTuiSurfacePath(string stateFilePath) + { + return stateFilePath + RuntimeDaemonGameTuiSurfaceFileSuffix; + } + + public static string GetDaemonEditorSurfacePath(string stateFilePath) + { + return stateFilePath + RuntimeDaemonEditorSurfaceFileSuffix; + } + + public static string GetDaemonEditorTuiSurfacePath(string stateFilePath) + { + return stateFilePath + RuntimeDaemonEditorTuiSurfaceFileSuffix; + } + public static string GetReplicaStateFilePath(DirectoryInfo gameDataDirectory, string verseId) { var safeVerseId = string.IsNullOrWhiteSpace(verseId) diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs.meta new file mode 100644 index 000000000..e24242a45 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7efc611cb12d9944592855511d71ea9a \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs.meta new file mode 100644 index 000000000..78102b75a --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitDocument.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3a889a3c5631c5f41ad856f8563395f7 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitLog.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitLog.cs deleted file mode 100644 index 2204d4f85..000000000 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitLog.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -#nullable enable - -namespace GameCult.Aetheria.State.Unity -{ - public static class AetheriaRuntimeStateCommitLog - { - public const string CommitSchema = AetheriaRuntimeStateCommitDocument.SchemaId; - - public static string GetPendingDirectory(string stateFilePath) - { - if (string.IsNullOrWhiteSpace(stateFilePath)) - throw new ArgumentException("State file path must be non-empty.", nameof(stateFilePath)); - - return stateFilePath + ".pending"; - } - - public static AetheriaRuntimeCommitEnvelope QueuePlayerSettings( - string stateFilePath, - AetheriaRuntimePlayerSettingsCommit settings) - { - if (settings == null) throw new ArgumentNullException(nameof(settings)); - return WriteCommit(stateFilePath, AetheriaRuntimeCommitKind.PlayerSettings, document => document.PlayerSettings = settings); - } - - public static AetheriaRuntimeCommitEnvelope QueueLoadoutTemplate( - string stateFilePath, - AetheriaRuntimeLoadoutTemplateCommit loadout) - { - if (loadout == null) throw new ArgumentNullException(nameof(loadout)); - return WriteCommit(stateFilePath, AetheriaRuntimeCommitKind.LoadoutTemplate, document => document.LoadoutTemplate = loadout); - } - - public static AetheriaRuntimeCommitEnvelope QueueRunCheckpoint( - string stateFilePath, - AetheriaRuntimeRunCheckpointCommit run) - { - if (run == null) throw new ArgumentNullException(nameof(run)); - return WriteCommit(stateFilePath, AetheriaRuntimeCommitKind.RunCheckpoint, document => document.RunCheckpoint = run); - } - - public static IReadOnlyList ReadPending(string stateFilePath) - { - var pendingDirectory = GetPendingDirectory(stateFilePath); - if (!Directory.Exists(pendingDirectory)) - return Array.Empty(); - - return Directory - .EnumerateFiles(pendingDirectory, "*.cc") - .OrderBy(path => path, StringComparer.Ordinal) - .Select(ReadEnvelope) - .ToArray(); - } - - public static AetheriaRuntimeStateCommitDocument ReadDocument(string path) - { - return AetheriaRuntimePendingCultCacheStore.ReadStateCommit(path); - } - - private static AetheriaRuntimeCommitEnvelope WriteCommit( - string stateFilePath, - AetheriaRuntimeCommitKind kind, - Action attachPayload) - { - var commandId = Guid.NewGuid().ToString("N"); - var createdAtUtc = DateTime.UtcNow.ToString("O"); - var pendingDirectory = GetPendingDirectory(stateFilePath); - Directory.CreateDirectory(pendingDirectory); - - var finalPath = Path.Combine( - pendingDirectory, - $"{createdAtUtc.Replace(':', '-')}.{KindToken(kind)}.{commandId}.cc"); - var document = new AetheriaRuntimeStateCommitDocument - { - Schema = CommitSchema, - Kind = KindToken(kind), - CommandId = commandId, - CreatedAtUtc = createdAtUtc - }; - attachPayload(document); - - AetheriaRuntimePendingCultCacheStore.WriteStateCommit(finalPath, document); - - return new AetheriaRuntimeCommitEnvelope(CommitSchema, kind, commandId, createdAtUtc, finalPath); - } - - private static AetheriaRuntimeCommitEnvelope ReadEnvelope(string path) - { - var document = ReadDocument(path); - return new AetheriaRuntimeCommitEnvelope( - document.Schema ?? "", - ParseKind(document.Kind ?? ""), - document.CommandId ?? "", - document.CreatedAtUtc ?? "", - path); - } - - private static string KindToken(AetheriaRuntimeCommitKind kind) - { - return kind switch - { - AetheriaRuntimeCommitKind.PlayerSettings => "player_settings", - AetheriaRuntimeCommitKind.LoadoutTemplate => "loadout_template", - AetheriaRuntimeCommitKind.RunCheckpoint => "run_checkpoint", - _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null) - }; - } - - private static AetheriaRuntimeCommitKind ParseKind(string token) - { - return token switch - { - "player_settings" => AetheriaRuntimeCommitKind.PlayerSettings, - "loadout_template" => AetheriaRuntimeCommitKind.LoadoutTemplate, - "run_checkpoint" => AetheriaRuntimeCommitKind.RunCheckpoint, - _ => throw new InvalidDataException($"Unknown Aetheria runtime commit kind '{token}'.") - }; - } - - } -} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitLog.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitLog.cs.meta new file mode 100644 index 000000000..8aeca1a51 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateCommitLog.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f3d6eef59b606fb4abb54b258a977a76 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateReader.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateReader.cs index 1ca7672df..94b610adc 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateReader.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateReader.cs @@ -45,10 +45,128 @@ namespace GameCult.Aetheria.State.Unity return AetheriaRuntimeCatalogStore.ReadEntitySnapshots(stateFilePath); } + public static bool TryReadDaemonFrame(string stateFilePath, out AetheriaRuntimeDaemonFrameDocument frame) + { + return AetheriaRuntimeDaemonFrameStore.TryReadFrame(stateFilePath, out frame); + } + + public static bool TryReadDaemonSoaView(string stateFilePath, out AetheriaRuntimeDaemonSoaViewDocument view) + { + return AetheriaRuntimeDaemonSoaViewStore.TryReadView(stateFilePath, out view); + } + + public static bool TryReadObservedDaemonState( + string stateFilePath, + out AetheriaRuntimeObservedDaemonState observed) + { + if (!AetheriaRuntimeDaemonFrameStore.TryReadFrame(stateFilePath, out var frame)) + { + observed = new AetheriaRuntimeObservedDaemonState( + new AetheriaRuntimeDaemonFrameDocument { IsAuthoritative = false, StateSource = "missing" }, + null, + AetheriaRuntimeDaemonFrameStore.GetFramePath(stateFilePath), + AetheriaRuntimeDaemonSoaViewStore.GetViewPath(stateFilePath)); + return false; + } + + AetheriaRuntimeDaemonSoaViewStore.TryReadView(stateFilePath, out var soaView); + observed = new AetheriaRuntimeObservedDaemonState( + frame, + string.Equals(soaView.Schema, AetheriaRuntimeDaemonSchemas.SoaView, StringComparison.Ordinal) ? soaView : null, + AetheriaRuntimeDaemonFrameStore.GetFramePath(stateFilePath), + AetheriaRuntimeDaemonSoaViewStore.GetViewPath(stateFilePath)); + return true; + } + public static EveSurfaceDocument? ReadEveSurface(string stateFilePath, string surfaceId) { + if (string.Equals(surfaceId, AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId, StringComparison.Ordinal) && + TryReadDaemonGameSurface(stateFilePath, out var gameSurface)) + { + return gameSurface; + } + + if (string.Equals(surfaceId, AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId, StringComparison.Ordinal) && + TryReadDaemonGameTuiSurface(stateFilePath, out var gameTuiSurface)) + { + return gameTuiSurface; + } + + if (string.Equals(surfaceId, AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId, StringComparison.Ordinal) && + TryReadDaemonEditorSurface(stateFilePath, out var editorSurface)) + { + return editorSurface; + } + + if (string.Equals(surfaceId, AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId, StringComparison.Ordinal) && + TryReadDaemonEditorTuiSurface(stateFilePath, out var editorTuiSurface)) + { + return editorTuiSurface; + } + return AetheriaRuntimeCatalogStore.ReadEveSurfaces(stateFilePath) .FirstOrDefault(candidate => string.Equals(candidate.Surface.Id, surfaceId, StringComparison.Ordinal)); } + + public static bool TryReadDaemonGameSurface( + string stateFilePath, + out EveSurfaceDocument document) + { + if (!AetheriaRuntimeDaemonPublicationStore.TryReadGameSurface(stateFilePath, out var surface)) + { + document = EmptySurface(AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId); + return false; + } + + document = AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument(surface); + return true; + } + + public static bool TryReadDaemonGameTuiSurface( + string stateFilePath, + out EveSurfaceDocument document) + { + if (!AetheriaRuntimeDaemonPublicationStore.TryReadGameTuiSurface(stateFilePath, out var surface)) + { + document = EmptySurface(AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId); + return false; + } + + document = AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument(surface); + return true; + } + + public static bool TryReadDaemonEditorSurface( + string stateFilePath, + out EveSurfaceDocument document) + { + if (!AetheriaRuntimeDaemonPublicationStore.TryReadEditorSurface(stateFilePath, out var surface)) + { + document = EmptySurface(AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId); + return false; + } + + document = AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument(surface); + return true; + } + + public static bool TryReadDaemonEditorTuiSurface( + string stateFilePath, + out EveSurfaceDocument document) + { + if (!AetheriaRuntimeDaemonPublicationStore.TryReadEditorTuiSurface(stateFilePath, out var surface)) + { + document = EmptySurface(AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId); + return false; + } + + document = AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument(surface); + return true; + } + + private static EveSurfaceDocument EmptySurface(string surfaceId) + { + return AetheriaRuntimeEveSurfaceAdapter.EmptySurface(surfaceId); + } } } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateReader.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateReader.cs.meta new file mode 100644 index 000000000..f625bc42b --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateReader.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f7c024a72ddd924419a46ca748527ab2 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.cs new file mode 100644 index 000000000..87d1a4d45 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GameCult.Eve.Surface; + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeTradeCargoTargetOption + { + public AetheriaRuntimeTradeCargoTargetOption(string id, string label, string command) + { + Id = id ?? ""; + Label = label ?? ""; + Command = command ?? ""; + } + + public string Id { get; } + public string Label { get; } + public string Command { get; } + } + + public sealed class AetheriaRuntimeTradeCargoSelectorSurfaceState + { + public AetheriaRuntimeTradeCargoSelectorSurfaceState( + string currentTarget, + IReadOnlyList targets, + string updatedAtUtc) + { + CurrentTarget = currentTarget ?? ""; + Targets = targets ?? Array.Empty(); + UpdatedAtUtc = updatedAtUtc ?? ""; + } + + public string CurrentTarget { get; } + public IReadOnlyList Targets { get; } + public string UpdatedAtUtc { get; } + } + + public static class AetheriaRuntimeTradeCargoSelectorSurfaceBuilder + { + public const string SurfaceId = "aetheria.trade.target_cargo_selector"; + public const string Close = "aetheria.trade.target_cargo_selector.close"; + public const string DockingBay = "aetheria.trade.target_cargo_selector.docking_bay"; + + public static string ShipBayCommand(int shipIndex, int bayIndex) + { + return $"{SurfaceId}.ship_{shipIndex}_bay_{bayIndex}"; + } + + public static AetheriaRuntimeSurfaceDocument Build( + AetheriaRuntimeTradeCargoSelectorSurfaceState state, + long version = 1) + { + state ??= new AetheriaRuntimeTradeCargoSelectorSurfaceState( + "", + Array.Empty(), + ""); + + var targets = state.Targets + .OrderBy(target => target.Label, StringComparer.Ordinal) + .ToArray(); + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "trade.menu", + title: "Trade Target Cargo Selector", + version: version, + updatedAtUtc: state.UpdatedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + SurfaceId, + Node( + $"{SurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + Card( + $"{SurfaceId}.card", + "Target Cargo", + Metric($"{SurfaceId}.current", "Current", state.CurrentTarget), + Text( + $"{SurfaceId}.note", + "Unity projects available cargo targets; the shared runtime surface owns the cargo selector contract."), + ButtonColumn( + $"{SurfaceId}.options", + targets + .Select(target => Button(target.Id, target.Label, target.Command)) + .ToArray()), + ButtonRow( + $"{SurfaceId}.actions", + Button($"{SurfaceId}.close", "Close", Close)))), + Array.Empty()), + commands: targets + .Select(target => new AetheriaRuntimeSurfaceCommandTemplate(target.Command, target.Label, "unity-uitoolkit")) + .Append(new AetheriaRuntimeSurfaceCommandTemplate(Close, "Close", "unity-uitoolkit")) + .ToArray()); + } + + private static AetheriaRuntimeSurfaceComponent Card( + string id, + string title, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "card", new[] { ("title", title ?? "") }, children); + } + + private static AetheriaRuntimeSurfaceComponent Metric(string id, string label, string value) + { + return Node(id, "metric", new[] { ("label", label ?? ""), ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Text(string id, string value) + { + return Node(id, "text", new[] { ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) + { + return Node(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent ButtonRow( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent ButtonColumn( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "column", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent Node( + string id, + string kind, + IEnumerable<(string Key, string Value)> props, + params AetheriaRuntimeSurfaceComponent[] children) + { + return new AetheriaRuntimeSurfaceComponent( + id ?? "", + kind ?? "", + (props ?? Array.Empty<(string Key, string Value)>()) + .ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal), + children ?? Array.Empty()); + } + } + + public enum AetheriaRuntimeTradeCargoSelectorCommandKind + { + Unknown = 0, + Close = 1, + Select = 2 + } + + public readonly struct AetheriaRuntimeTradeCargoSelectorCommand + { + public AetheriaRuntimeTradeCargoSelectorCommand( + AetheriaRuntimeTradeCargoSelectorCommandKind kind, + string command) + { + Kind = kind; + Command = command ?? ""; + } + + public AetheriaRuntimeTradeCargoSelectorCommandKind Kind { get; } + public string Command { get; } + } + + public static class AetheriaRuntimeTradeCargoSelectorSurfaceCommands + { + public static bool TryRead( + EveSurfaceCommandRequest request, + out AetheriaRuntimeTradeCargoSelectorCommand command) + { + command = default; + if (request == null || + !string.Equals(request.SurfaceId, AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.SurfaceId, StringComparison.Ordinal)) + return false; + + var commandText = request.Command ?? ""; + if (string.Equals(commandText, AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.Close, StringComparison.Ordinal)) + { + command = new AetheriaRuntimeTradeCargoSelectorCommand( + AetheriaRuntimeTradeCargoSelectorCommandKind.Close, + commandText); + return true; + } + + if (string.IsNullOrWhiteSpace(commandText)) + return false; + + command = new AetheriaRuntimeTradeCargoSelectorCommand( + AetheriaRuntimeTradeCargoSelectorCommandKind.Select, + commandText); + return true; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeInteractionSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeInteractionSurfaceBuilder.cs new file mode 100644 index 000000000..65cfae327 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeInteractionSurfaceBuilder.cs @@ -0,0 +1,340 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GameCult.Eve.Surface; + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeTradeSurfaceOption + { + public AetheriaRuntimeTradeSurfaceOption(string id, string label, string command) + { + Id = id ?? ""; + Label = label ?? ""; + Command = command ?? ""; + } + + public string Id { get; } + public string Label { get; } + public string Command { get; } + } + + public sealed class AetheriaRuntimeTradeSurfaceGroup + { + public AetheriaRuntimeTradeSurfaceGroup( + string id, + string title, + IReadOnlyList options) + { + Id = id ?? ""; + Title = title ?? ""; + Options = options ?? Array.Empty(); + } + + public string Id { get; } + public string Title { get; } + public IReadOnlyList Options { get; } + } + + public sealed class AetheriaRuntimeTradeFilterSurfaceState + { + public AetheriaRuntimeTradeFilterSurfaceState( + string filterSummary, + IReadOnlyList groups, + string updatedAtUtc) + { + FilterSummary = filterSummary ?? ""; + Groups = groups ?? Array.Empty(); + UpdatedAtUtc = updatedAtUtc ?? ""; + } + + public string FilterSummary { get; } + public IReadOnlyList Groups { get; } + public string UpdatedAtUtc { get; } + } + + public sealed class AetheriaRuntimeTradeRowActionSurfaceState + { + public AetheriaRuntimeTradeRowActionSurfaceState( + string title, + IReadOnlyList actions, + string updatedAtUtc) + { + Title = title ?? ""; + Actions = actions ?? Array.Empty(); + UpdatedAtUtc = updatedAtUtc ?? ""; + } + + public string Title { get; } + public IReadOnlyList Actions { get; } + public string UpdatedAtUtc { get; } + } + + public static class AetheriaRuntimeTradeInteractionSurfaceBuilder + { + public const string FilterSurfaceId = "aetheria.trade.filter_selector"; + public const string CloseFilter = "aetheria.trade.filter_selector.close"; + public const string RowActionSurfaceId = "aetheria.trade.row_actions"; + public const string CloseRowAction = "aetheria.trade.row_actions.close"; + + public static string HardpointFilterCommand(string hardpointType) + { + return $"{FilterSurfaceId}.hardpoint.{hardpointType ?? ""}"; + } + + public static string SimpleCommodityFilterCommand(string commodityType) + { + return $"{FilterSurfaceId}.simple.{commodityType ?? ""}"; + } + + public static string CompoundCommodityFilterCommand(string commodityType) + { + return $"{FilterSurfaceId}.compound.{commodityType ?? ""}"; + } + + public static string BehaviorFilterCommand(string behaviorKind) + { + return $"{FilterSurfaceId}.behavior.{behaviorKind ?? ""}"; + } + + public static string MinimumSizeFilterCommand() + { + return $"{FilterSurfaceId}.size.minimum"; + } + + public static string MaximumSizeFilterCommand() + { + return $"{FilterSurfaceId}.size.maximum"; + } + + public static string RowActionCommand(int index) + { + return $"{RowActionSurfaceId}.action_{index}"; + } + + public static AetheriaRuntimeSurfaceDocument BuildFilter( + AetheriaRuntimeTradeFilterSurfaceState state, + long version = 1) + { + state ??= new AetheriaRuntimeTradeFilterSurfaceState( + "", + Array.Empty(), + ""); + + var children = new List + { + Card( + $"{FilterSurfaceId}.summary", + "Trade Filters", + Text( + $"{FilterSurfaceId}.note", + "Unity projects available trade filters; the shared runtime surface owns the filter selector contract."), + Text($"{FilterSurfaceId}.active", state.FilterSummary)) + }; + + foreach (var group in state.Groups) + { + if (group?.Options == null || group.Options.Count == 0) + continue; + + children.Add(Card( + group.Id, + group.Title, + ButtonColumn( + $"{group.Id}.options", + group.Options + .Select(option => Button(option.Id, option.Label, option.Command)) + .ToArray()))); + } + + children.Add(ButtonRow( + $"{FilterSurfaceId}.actions", + Button($"{FilterSurfaceId}.close", "Close", CloseFilter))); + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "trade.menu", + title: "Trade Filter Selector", + version: version, + updatedAtUtc: state.UpdatedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + FilterSurfaceId, + Node( + $"{FilterSurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + children.ToArray()), + Array.Empty()), + commands: state.Groups + .Where(group => group?.Options != null) + .SelectMany(group => group.Options) + .Select(option => new AetheriaRuntimeSurfaceCommandTemplate(option.Command, option.Label, "unity-uitoolkit")) + .Append(new AetheriaRuntimeSurfaceCommandTemplate(CloseFilter, "Close", "unity-uitoolkit")) + .ToArray()); + } + + public static AetheriaRuntimeSurfaceDocument BuildRowActions( + AetheriaRuntimeTradeRowActionSurfaceState state, + long version = 1) + { + state ??= new AetheriaRuntimeTradeRowActionSurfaceState( + "", + Array.Empty(), + ""); + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "trade.menu", + title: "Trade Row Actions", + version: version, + updatedAtUtc: state.UpdatedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + RowActionSurfaceId, + Node( + $"{RowActionSurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + Card( + $"{RowActionSurfaceId}.card", + "Trade Action", + Text($"{RowActionSurfaceId}.title", state.Title), + Text( + $"{RowActionSurfaceId}.note", + "Unity projects available row actions; the shared runtime surface owns the row action contract."), + ButtonColumn( + $"{RowActionSurfaceId}.options", + state.Actions + .Select(action => Button(action.Id, action.Label, action.Command)) + .ToArray()), + ButtonRow( + $"{RowActionSurfaceId}.actions", + Button($"{RowActionSurfaceId}.close", "Close", CloseRowAction)))), + Array.Empty()), + commands: state.Actions + .Select(action => new AetheriaRuntimeSurfaceCommandTemplate(action.Command, action.Label, "unity-uitoolkit")) + .Append(new AetheriaRuntimeSurfaceCommandTemplate(CloseRowAction, "Close", "unity-uitoolkit")) + .ToArray()); + } + + private static AetheriaRuntimeSurfaceComponent Card( + string id, + string title, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "card", new[] { ("title", title ?? "") }, children); + } + + private static AetheriaRuntimeSurfaceComponent Text(string id, string value) + { + return Node(id, "text", new[] { ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) + { + return Node(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent ButtonRow( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent ButtonColumn( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "column", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent Node( + string id, + string kind, + IEnumerable<(string Key, string Value)> props, + params AetheriaRuntimeSurfaceComponent[] children) + { + return new AetheriaRuntimeSurfaceComponent( + id ?? "", + kind ?? "", + (props ?? Array.Empty<(string Key, string Value)>()) + .ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal), + children ?? Array.Empty()); + } + } + + public enum AetheriaRuntimeTradeInteractionCommandKind + { + Unknown = 0, + Close = 1, + Select = 2 + } + + public readonly struct AetheriaRuntimeTradeInteractionCommand + { + public AetheriaRuntimeTradeInteractionCommand( + AetheriaRuntimeTradeInteractionCommandKind kind, + string command) + { + Kind = kind; + Command = command ?? ""; + } + + public AetheriaRuntimeTradeInteractionCommandKind Kind { get; } + public string Command { get; } + } + + public static class AetheriaRuntimeTradeInteractionSurfaceCommands + { + public static bool TryReadFilter( + EveSurfaceCommandRequest request, + out AetheriaRuntimeTradeInteractionCommand command) + { + return TryRead( + request, + AetheriaRuntimeTradeInteractionSurfaceBuilder.FilterSurfaceId, + AetheriaRuntimeTradeInteractionSurfaceBuilder.CloseFilter, + out command); + } + + public static bool TryReadRowAction( + EveSurfaceCommandRequest request, + out AetheriaRuntimeTradeInteractionCommand command) + { + return TryRead( + request, + AetheriaRuntimeTradeInteractionSurfaceBuilder.RowActionSurfaceId, + AetheriaRuntimeTradeInteractionSurfaceBuilder.CloseRowAction, + out command); + } + + private static bool TryRead( + EveSurfaceCommandRequest request, + string surfaceId, + string closeCommand, + out AetheriaRuntimeTradeInteractionCommand command) + { + command = default; + if (request == null || + !string.Equals(request.SurfaceId, surfaceId, StringComparison.Ordinal)) + return false; + + var commandText = request.Command ?? ""; + if (string.Equals(commandText, closeCommand, StringComparison.Ordinal)) + { + command = new AetheriaRuntimeTradeInteractionCommand( + AetheriaRuntimeTradeInteractionCommandKind.Close, + commandText); + return true; + } + + if (string.IsNullOrWhiteSpace(commandText)) + return false; + + command = new AetheriaRuntimeTradeInteractionCommand( + AetheriaRuntimeTradeInteractionCommandKind.Select, + commandText); + return true; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeItemDetailsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeItemDetailsSurfaceBuilder.cs new file mode 100644 index 000000000..7517f626f --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeItemDetailsSurfaceBuilder.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GameCult.Eve.Surface; + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeTradeItemMetric + { + public AetheriaRuntimeTradeItemMetric(string id, string label, string value) + { + Id = id ?? ""; + Label = label ?? ""; + Value = value ?? ""; + } + + public string Id { get; } + public string Label { get; } + public string Value { get; } + } + + public sealed class AetheriaRuntimeTradeItemSection + { + public AetheriaRuntimeTradeItemSection( + string id, + string title, + IReadOnlyList metrics) + { + Id = id ?? ""; + Title = title ?? ""; + Metrics = metrics ?? Array.Empty(); + } + + public string Id { get; } + public string Title { get; } + public IReadOnlyList Metrics { get; } + } + + public sealed class AetheriaRuntimeTradeItemDetailsSurfaceState + { + public AetheriaRuntimeTradeItemDetailsSurfaceState( + string itemName, + string description, + string manufacturer, + string mass, + int price, + string durability, + string thermalRange, + IReadOnlyList behaviorSections, + string updatedAtUtc) + { + ItemName = itemName ?? ""; + Description = description ?? ""; + Manufacturer = manufacturer ?? ""; + Mass = mass ?? ""; + Price = price; + Durability = durability ?? ""; + ThermalRange = thermalRange ?? ""; + BehaviorSections = behaviorSections ?? Array.Empty(); + UpdatedAtUtc = updatedAtUtc ?? ""; + } + + public string ItemName { get; } + public string Description { get; } + public string Manufacturer { get; } + public string Mass { get; } + public int Price { get; } + public string Durability { get; } + public string ThermalRange { get; } + public IReadOnlyList BehaviorSections { get; } + public string UpdatedAtUtc { get; } + public bool HasEquipmentStatus => !string.IsNullOrWhiteSpace(Durability) || + !string.IsNullOrWhiteSpace(ThermalRange) || + BehaviorSections.Count > 0; + } + + public static class AetheriaRuntimeTradeItemDetailsSurfaceBuilder + { + public const string SurfaceId = "aetheria.trade.item_details"; + public const string Close = "aetheria.trade.item_details.close"; + + public static AetheriaRuntimeSurfaceDocument Build( + AetheriaRuntimeTradeItemDetailsSurfaceState state, + long version = 1) + { + state ??= new AetheriaRuntimeTradeItemDetailsSurfaceState( + "", + "", + "", + "", + 0, + "", + "", + Array.Empty(), + ""); + + var children = new List + { + Card( + $"{SurfaceId}.summary", + state.ItemName, + Text( + $"{SurfaceId}.description", + string.IsNullOrWhiteSpace(state.Description) + ? "No typed item description is available." + : state.Description), + Text( + $"{SurfaceId}.note", + "Unity supplies the selected market row; the shared runtime surface owns trade item inspection layout."), + Metric($"{SurfaceId}.manufacturer", "Manufacturer", state.Manufacturer), + Metric($"{SurfaceId}.mass", "Mass", state.Mass), + Metric($"{SurfaceId}.price", "Price", state.Price.ToString("N0"))) + }; + + if (state.HasEquipmentStatus) + { + children.Add(Card( + $"{SurfaceId}.durability.card", + "Durability", + Metric($"{SurfaceId}.durability", "Max Durability", state.Durability), + Metric($"{SurfaceId}.temperature_range", "Thermal Range", state.ThermalRange))); + } + + foreach (var section in state.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)) + .ToArray())); + } + + children.Add(ButtonRow( + $"{SurfaceId}.actions", + Button($"{SurfaceId}.close", "Close", Close))); + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "trade.menu", + title: "Trade Item Details", + version: version, + updatedAtUtc: state.UpdatedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + SurfaceId, + Node( + $"{SurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + children.ToArray()), + Array.Empty()), + commands: new[] + { + new AetheriaRuntimeSurfaceCommandTemplate(Close, "Close", "unity-uitoolkit") + }); + } + + 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) + { + return Node(id, "metric", new[] { ("label", label ?? ""), ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Text(string id, string value) + { + return Node(id, "text", new[] { ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) + { + return Node(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent ButtonRow( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent Node( + string id, + string kind, + IEnumerable<(string Key, string Value)> props, + params AetheriaRuntimeSurfaceComponent[] children) + { + return new AetheriaRuntimeSurfaceComponent( + id ?? "", + kind ?? "", + (props ?? Array.Empty<(string Key, string Value)>()) + .ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal), + children ?? Array.Empty()); + } + } + + public enum AetheriaRuntimeTradeItemDetailsCommandKind + { + Unknown = 0, + Close = 1 + } + + public readonly struct AetheriaRuntimeTradeItemDetailsCommand + { + public AetheriaRuntimeTradeItemDetailsCommand(AetheriaRuntimeTradeItemDetailsCommandKind kind) + { + Kind = kind; + } + + public AetheriaRuntimeTradeItemDetailsCommandKind Kind { get; } + } + + public static class AetheriaRuntimeTradeItemDetailsSurfaceCommands + { + public static bool TryRead( + EveSurfaceCommandRequest request, + out AetheriaRuntimeTradeItemDetailsCommand command) + { + command = default; + if (request == null || + !string.Equals(request.SurfaceId, AetheriaRuntimeTradeItemDetailsSurfaceBuilder.SurfaceId, StringComparison.Ordinal)) + return false; + + if (string.Equals(request.Command, AetheriaRuntimeTradeItemDetailsSurfaceBuilder.Close, StringComparison.Ordinal)) + { + command = new AetheriaRuntimeTradeItemDetailsCommand(AetheriaRuntimeTradeItemDetailsCommandKind.Close); + return true; + } + + return false; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseDiscovery.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseDiscovery.cs.meta new file mode 100644 index 000000000..a4e9d7e2c --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseDiscovery.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7ed50a9bfc7a42d409cf7f7df931cf55 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseHostCommands.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseHostCommands.cs.meta new file mode 100644 index 000000000..0f154b799 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseHostCommands.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b7f9743297bcb0248a79b8d6c8b776e9 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseReplicaBridge.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseReplicaBridge.cs index cbbe1847a..4abfd56a6 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseReplicaBridge.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseReplicaBridge.cs @@ -1,5 +1,7 @@ +using System; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text; namespace GameCult.Aetheria.State.Unity diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseReplicaBridge.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseReplicaBridge.cs.meta new file mode 100644 index 000000000..92236177e --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseReplicaBridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7fd2760f2901e4e4a8e5b86f2c6d9e3b \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeZoneDetailsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeZoneDetailsSurfaceBuilder.cs new file mode 100644 index 000000000..05448fa17 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeZoneDetailsSurfaceBuilder.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GameCult.Eve.Surface; + +namespace GameCult.Aetheria.State.Unity +{ + public sealed class AetheriaRuntimeZoneDetailsSurfaceState + { + public AetheriaRuntimeZoneDetailsSurfaceState( + string zoneName, + string ownerName, + string mass, + string radius, + IReadOnlyList otherFactions, + bool hasContents, + string planets, + string asteroidBelts, + string gasGiants, + string stars, + string stations, + string turrets, + string ships, + string updatedAtUtc) + { + ZoneName = zoneName ?? ""; + OwnerName = ownerName ?? ""; + Mass = mass ?? ""; + Radius = radius ?? ""; + OtherFactions = otherFactions ?? Array.Empty(); + HasContents = hasContents; + Planets = planets ?? ""; + AsteroidBelts = asteroidBelts ?? ""; + GasGiants = gasGiants ?? ""; + Stars = stars ?? ""; + Stations = stations ?? ""; + Turrets = turrets ?? ""; + Ships = ships ?? ""; + UpdatedAtUtc = updatedAtUtc ?? ""; + } + + public string ZoneName { get; } + public string OwnerName { get; } + public string Mass { get; } + public string Radius { get; } + public IReadOnlyList OtherFactions { get; } + public bool HasContents { get; } + public string Planets { get; } + public string AsteroidBelts { get; } + public string GasGiants { get; } + public string Stars { get; } + public string Stations { get; } + public string Turrets { get; } + public string Ships { get; } + public string UpdatedAtUtc { get; } + } + + public static class AetheriaRuntimeZoneDetailsSurfaceBuilder + { + public const string SurfaceId = "aetheria.sector_map.zone_details"; + public const string Close = "aetheria.sector_map.zone_details.close"; + + public static AetheriaRuntimeSurfaceDocument Build( + AetheriaRuntimeZoneDetailsSurfaceState state, + long version = 1) + { + state ??= new AetheriaRuntimeZoneDetailsSurfaceState( + "", + "", + "", + "", + Array.Empty(), + false, + "", + "", + "", + "", + "", + "", + "", + ""); + + var children = new List + { + Card( + $"{SurfaceId}.card", + state.ZoneName, + Metric($"{SurfaceId}.owner", "Owner", string.IsNullOrWhiteSpace(state.OwnerName) ? "None" : state.OwnerName), + Metric($"{SurfaceId}.mass", "Mass", state.Mass), + Metric($"{SurfaceId}.radius", "Radius", state.Radius)) + }; + + if (state.OtherFactions.Count > 0) + { + children.Add(Text( + $"{SurfaceId}.factions", + $"Factions Present: {string.Join(", ", state.OtherFactions)}")); + } + + if (!state.HasContents) + { + children.Add(Text( + $"{SurfaceId}.unvisited", + "Has not been visited.")); + } + else + { + children.Add(Card( + $"{SurfaceId}.contents", + "Contents", + Metric($"{SurfaceId}.planets", "Planets", state.Planets), + Metric($"{SurfaceId}.belts", "Asteroid Belts", state.AsteroidBelts), + Metric($"{SurfaceId}.giants", "Gas Giants", state.GasGiants), + Metric($"{SurfaceId}.stars", "Stars", state.Stars), + Metric($"{SurfaceId}.stations", "Stations", state.Stations), + Metric($"{SurfaceId}.turrets", "Turrets", state.Turrets), + Metric($"{SurfaceId}.ships", "Ships", state.Ships))); + } + + children.Add(ButtonRow( + $"{SurfaceId}.actions", + Button($"{SurfaceId}.close", "Close", Close))); + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "sector.map", + title: state.ZoneName, + version: version, + updatedAtUtc: state.UpdatedAtUtc, + surface: new AetheriaRuntimeSurfaceTree( + SurfaceId, + Node( + $"{SurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + children.ToArray()), + Array.Empty()), + commands: new[] + { + new AetheriaRuntimeSurfaceCommandTemplate(Close, "Close", "unity-uitoolkit") + }); + } + + private static AetheriaRuntimeSurfaceComponent Card( + string id, + string title, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "card", new[] { ("title", title ?? "") }, children); + } + + private static AetheriaRuntimeSurfaceComponent Metric(string id, string label, string value) + { + return Node(id, "metric", new[] { ("label", label ?? ""), ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Text(string id, string value) + { + return Node(id, "text", new[] { ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) + { + return Node(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent ButtonRow( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent Node( + string id, + string kind, + IEnumerable<(string Key, string Value)> props, + params AetheriaRuntimeSurfaceComponent[] children) + { + return new AetheriaRuntimeSurfaceComponent( + id ?? "", + kind ?? "", + (props ?? Array.Empty<(string Key, string Value)>()) + .ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal), + children ?? Array.Empty()); + } + } + + public enum AetheriaRuntimeZoneDetailsCommandKind + { + Unknown = 0, + Close = 1 + } + + public readonly struct AetheriaRuntimeZoneDetailsCommand + { + public AetheriaRuntimeZoneDetailsCommand(AetheriaRuntimeZoneDetailsCommandKind kind) + { + Kind = kind; + } + + public AetheriaRuntimeZoneDetailsCommandKind Kind { get; } + } + + public static class AetheriaRuntimeZoneDetailsSurfaceCommands + { + public static bool TryRead( + EveSurfaceCommandRequest request, + out AetheriaRuntimeZoneDetailsCommand command) + { + command = default; + if (request == null || + !string.Equals(request.SurfaceId, AetheriaRuntimeZoneDetailsSurfaceBuilder.SurfaceId, StringComparison.Ordinal)) + return false; + + if (string.Equals(request.Command, AetheriaRuntimeZoneDetailsSurfaceBuilder.Close, StringComparison.Ordinal)) + { + command = new AetheriaRuntimeZoneDetailsCommand(AetheriaRuntimeZoneDetailsCommandKind.Close); + return true; + } + + return false; + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaState.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaState.cs new file mode 100644 index 000000000..7f9d8fcba --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaState.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +#nullable enable + +namespace GameCult.Aetheria.State.Unity +{ + public readonly struct AetheriaState + { + private readonly DirectoryInfo _gameDataDirectory; + + private AetheriaState(DirectoryInfo gameDataDirectory) + { + _gameDataDirectory = gameDataDirectory ?? throw new ArgumentNullException(nameof(gameDataDirectory)); + } + + public static AetheriaState At(DirectoryInfo gameDataDirectory) + { + return new AetheriaState(gameDataDirectory); + } + + public AetheriaRuntimeStateBootReport Boot => + AetheriaRuntimeStateBoot.Inspect(_gameDataDirectory); + + public AetheriaClientTarget ClientTarget => + new AetheriaClientTarget(_gameDataDirectory); + + public bool TryReadDaemonFrame(out AetheriaRuntimeDaemonFrameDocument? frame) + { + var boot = Boot; + return AetheriaRuntimeStateReader.TryReadDaemonFrame(boot.StateFilePath, out frame); + } + + public AetheriaRuntimeVerseHostSettingsSnapshot ReadVerseHostSettings() + { + var boot = Boot; + return AetheriaRuntimeStateReader.ReadVerseHostSettings(boot.StateFilePath); + } + } + + public readonly struct AetheriaClientTarget + { + private readonly DirectoryInfo _gameDataDirectory; + + internal AetheriaClientTarget(DirectoryInfo gameDataDirectory) + { + _gameDataDirectory = gameDataDirectory ?? throw new ArgumentNullException(nameof(gameDataDirectory)); + } + + public AetheriaRuntimeClientTargetDocument Refresh() + { + var paths = Paths(); + return AetheriaRuntimeClientTargetStore.ReadOrInitialize(paths.ClientTargetPath, paths.DefaultStateFilePath); + } + + public AetheriaRuntimeClientTargetDocument DiscoverVerses() + { + var paths = Paths(); + return AetheriaRuntimeVerseDiscovery.RefreshClientTarget(paths.ClientTargetPath, paths.DefaultStateFilePath); + } + + public AetheriaRuntimeClientTargetDocument SyncReplica() + { + var paths = Paths(); + return SyncReplica(paths.ClientTargetPath, paths.DefaultStateFilePath); + } + + public AetheriaRuntimeClientTargetDocument CycleTransport() + { + return Edit(document => + { + document.TargetKind = string.Equals(document.TargetKind, AetheriaRuntimeClientTargetKinds.CultMeshVerse, StringComparison.Ordinal) + ? AetheriaRuntimeClientTargetKinds.StateFile + : AetheriaRuntimeClientTargetKinds.CultMeshVerse; + document.LastReplicaSyncError = ""; + }); + } + + public AetheriaRuntimeClientTargetDocument RequestTitle(string title) + { + return Edit(document => document.Title = title ?? ""); + } + + public AetheriaRuntimeClientTargetDocument RequestVerseId(string verseId) + { + var gameDataDirectory = _gameDataDirectory; + return Edit(document => + { + document.VerseId = verseId ?? ""; + document.ReplicaStateFilePath = AetheriaRuntimeStateBoundary.GetReplicaStateFilePath(gameDataDirectory, document.VerseId); + document.LastReplicaSyncError = ""; + }); + } + + public AetheriaRuntimeClientTargetDocument RequestCultMeshAddress(string cultMeshAddress) + { + return Edit(document => + { + document.CultMeshAddress = cultMeshAddress ?? ""; + document.LastReplicaSyncError = ""; + }); + } + + public AetheriaRuntimeClientTargetDocument RequestStateFilePath(string stateFilePath) + { + return Edit(document => document.StateFilePath = stateFilePath ?? ""); + } + + public AetheriaRuntimeClientTargetDocument RequestDiscoveryEndpoints(IEnumerable? discoveryEndpoints) + { + return Edit(document => + { + document.DiscoveryEndpoints = NormalizeDiscoveryEndpoints(discoveryEndpoints); + document.LastDiscoveryError = ""; + }); + } + + public AetheriaRuntimeClientTargetDocument SelectDiscoveredVerse( + string verseId, + string title, + string cultMeshAddress, + IEnumerable? discoveryEndpoints) + { + var gameDataDirectory = _gameDataDirectory; + return Edit(document => + { + document.TargetKind = AetheriaRuntimeClientTargetKinds.CultMeshVerse; + document.Title = title ?? ""; + document.VerseId = verseId ?? ""; + document.CultMeshAddress = cultMeshAddress ?? ""; + document.ReplicaStateFilePath = AetheriaRuntimeStateBoundary.GetReplicaStateFilePath(gameDataDirectory, document.VerseId); + document.DiscoveryEndpoints = NormalizeDiscoveryEndpoints(discoveryEndpoints); + document.LastDiscoveryError = ""; + document.LastReplicaSyncError = ""; + }); + } + + private AetheriaRuntimeClientTargetDocument Edit(Action mutate) + { + if (mutate == null) throw new ArgumentNullException(nameof(mutate)); + + var paths = Paths(); + return AetheriaRuntimeClientTargetStore.Update( + paths.ClientTargetPath, + paths.DefaultStateFilePath, + document => + { + mutate(document); + if (string.IsNullOrWhiteSpace(document.TargetKind)) + document.TargetKind = AetheriaRuntimeClientTargetKinds.StateFile; + if (string.IsNullOrWhiteSpace(document.StateFilePath)) + document.StateFilePath = paths.DefaultStateFilePath; + document.UpdatedAtUtc = DateTime.UtcNow.ToString("O"); + }); + } + + private AetheriaRuntimeClientTargetDocument SyncReplica( + string clientTargetPath, + string defaultStateFilePath) + { + var target = AetheriaRuntimeClientTargetStore.ReadOrInitialize(clientTargetPath, defaultStateFilePath); + var syncedAtUtc = DateTime.UtcNow.ToString("O"); + try + { + var result = AetheriaRuntimeVerseReplicaBridge.Sync(_gameDataDirectory, target); + return AetheriaRuntimeClientTargetStore.Update( + clientTargetPath, + defaultStateFilePath, + document => + { + document.ReplicaStateFilePath = result.ReplicaStateFilePath; + document.LastReplicaSyncAtUtc = syncedAtUtc; + document.LastReplicaSyncError = ""; + document.UpdatedAtUtc = syncedAtUtc; + }); + } + catch (Exception ex) + { + AetheriaRuntimeClientTargetStore.Update( + clientTargetPath, + defaultStateFilePath, + document => + { + document.LastReplicaSyncAtUtc = syncedAtUtc; + document.LastReplicaSyncError = ex.Message ?? ex.GetType().Name; + document.UpdatedAtUtc = syncedAtUtc; + }); + throw; + } + } + + private (string ClientTargetPath, string DefaultStateFilePath) Paths() + { + return ( + AetheriaRuntimeStateBoundary.GetClientTargetPath(_gameDataDirectory), + AetheriaRuntimeStateBoundary.GetStateFilePath(_gameDataDirectory)); + } + + private static string[] NormalizeDiscoveryEndpoints(IEnumerable? discoveryEndpoints) + { + return discoveryEndpoints? + .Select(endpoint => endpoint?.Trim() ?? "") + .Where(endpoint => !string.IsNullOrWhiteSpace(endpoint)) + .Distinct(StringComparer.Ordinal) + .ToArray() + ?? Array.Empty(); + } + + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef b/Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef index 607c4733f..9a7571753 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef +++ b/Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef @@ -3,13 +3,36 @@ "rootNamespace": "GameCult.Aetheria.State.Unity", "references": [ "GameCult.Eve.Surface", + "GameCult.Mesh", "MessagePack" ], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], + "overrideReferences": true, + "precompiledReferences": [ + "ConcurrentCollections.dll", + "GameCult.Caching.MessagePack.dll", + "GameCult.Caching.dll", + "GameCult.Logging.dll", + "GameCult.Mesh.dll", + "GameCult.Networking.dll", + "Isopoh.Cryptography.Argon2.dll", + "Isopoh.Cryptography.Blake2b.dll", + "Isopoh.Cryptography.SecureArray.dll", + "LiteNetLib.dll", + "Microsoft.Bcl.AsyncInterfaces.dll", + "Microsoft.Bcl.TimeProvider.dll", + "Microsoft.NET.StringTools.dll", + "MessagePack.Annotations.dll", + "MessagePack.CultMesh.dll", + "R3.dll", + "System.Collections.Immutable.dll", + "System.IO.Pipelines.dll", + "System.Text.Encodings.Web.dll", + "System.Text.Json.dll", + "System.Threading.Channels.dll" + ], "autoReferenced": true, "defineConstraints": [], "versionDefines": [], diff --git a/Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef.meta b/Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef.meta new file mode 100644 index 000000000..811736d41 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 401dd961a671a704ab2228a298a97c06 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/org.gamecult.aetheria.state/package.json b/Packages/org.gamecult.aetheria.state/package.json index 1b7ea5f6c..af08b88f7 100644 --- a/Packages/org.gamecult.aetheria.state/package.json +++ b/Packages/org.gamecult.aetheria.state/package.json @@ -5,6 +5,7 @@ "unity": "2021.3", "description": "Unity runtime boundary for Aetheria typed CultCache state and future CultMesh catalog boot.", "dependencies": { + "com.gamecult.brokkr": "0.1.0", "org.gamecult.eve.surface": "0.1.0" }, "keywords": [ diff --git a/Packages/org.gamecult.aetheria.state/package.json.meta b/Packages/org.gamecult.aetheria.state/package.json.meta new file mode 100644 index 000000000..290628216 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cacdce838f3fff04892be6653af81fe3 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index f48eb46f8..2304b0e0b 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -1,36 +1,11 @@ { "dependencies": { - "org.gamecult.aetheria.eve-runtime": { - "version": "file:org.gamecult.aetheria.eve-runtime", - "depth": 0, - "source": "embedded", - "dependencies": { - "org.gamecult.aetheria.state": "0.1.0", - "org.gamecult.eve.unity-uitoolkit": "0.1.0" - } - }, - "org.gamecult.aetheria.state": { - "version": "file:org.gamecult.aetheria.state", - "depth": 0, - "source": "embedded", - "dependencies": { - "org.gamecult.eve.surface": "0.1.0" - } - }, - "org.gamecult.eve.surface": { - "version": "file:../../Eve/packages/org.gamecult.eve.surface", + "com.gamecult.brokkr": { + "version": "file:../../Brokkr/surfaces/unity/Packages/com.gamecult.brokkr/", "depth": 0, "source": "local", "dependencies": {} }, - "org.gamecult.eve.unity-uitoolkit": { - "version": "file:../../Eve/packages/org.gamecult.eve.unity-uitoolkit", - "depth": 0, - "source": "local", - "dependencies": { - "org.gamecult.eve.surface": "0.1.0" - } - }, "com.unity.2d.sprite": { "version": "1.0.0", "depth": 0, @@ -47,7 +22,7 @@ } }, "com.unity.ai.navigation": { - "version": "2.0.8", + "version": "2.0.12", "depth": 0, "source": "registry", "dependencies": { @@ -56,16 +31,16 @@ "url": "https://packages.unity.com" }, "com.unity.bindings.openimageio": { - "version": "1.0.0", + "version": "1.0.2", "depth": 1, "source": "registry", "dependencies": { - "com.unity.collections": "1.0.0" + "com.unity.collections": "1.2.4" }, "url": "https://packages.unity.com" }, "com.unity.burst": { - "version": "1.8.23", + "version": "1.8.29", "depth": 0, "source": "registry", "dependencies": { @@ -75,7 +50,7 @@ "url": "https://packages.unity.com" }, "com.unity.cinemachine": { - "version": "2.10.4", + "version": "2.10.7", "depth": 0, "source": "registry", "dependencies": { @@ -84,16 +59,47 @@ "url": "https://packages.unity.com" }, "com.unity.collections": { - "version": "2.5.1", + "version": "6.4.0", "depth": 1, - "source": "registry", + "source": "builtin", "dependencies": { - "com.unity.burst": "1.8.17", - "com.unity.test-framework": "1.4.5", - "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.burst": "1.8.23", + "com.unity.mathematics": "1.3.2", + "com.unity.nuget.mono-cecil": "1.11.5", + "com.unity.test-framework": "1.4.6", "com.unity.test-framework.performance": "3.0.3" - }, - "url": "https://packages.unity.com" + } + }, + "com.unity.entities": { + "version": "6.4.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.burst": "1.8.23", + "com.unity.collections": "6.4.0", + "com.unity.mathematics": "1.3.2", + "com.unity.nuget.mono-cecil": "1.11.5", + "com.unity.profiling.core": "1.0.2", + "com.unity.scriptablebuildpipeline": "2.4.3", + "com.unity.serialization": "3.1.2", + "com.unity.test-framework.performance": "3.0.3", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.entities.graphics": { + "version": "6.4.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.entities": "6.4.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.render-pipelines.core": "14.0.10" + } }, "com.unity.ext.nunit": { "version": "2.0.5", @@ -102,7 +108,7 @@ "dependencies": {} }, "com.unity.ide.rider": { - "version": "3.0.36", + "version": "3.0.39", "depth": 0, "source": "registry", "dependencies": { @@ -111,16 +117,16 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.23", + "version": "2.0.27", "depth": 0, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.9" + "com.unity.test-framework": "1.1.33" }, "url": "https://packages.unity.com" }, "com.unity.inputsystem": { - "version": "1.14.0", + "version": "1.19.0", "depth": 0, "source": "registry", "dependencies": { @@ -129,14 +135,14 @@ "url": "https://packages.unity.com" }, "com.unity.mathematics": { - "version": "1.3.2", + "version": "1.3.3", "depth": 0, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.multiplayer.center": { - "version": "1.0.0", + "version": "1.0.1", "depth": 0, "source": "builtin", "dependencies": { @@ -144,34 +150,109 @@ } }, "com.unity.nuget.mono-cecil": { - "version": "1.11.4", + "version": "1.11.6", "depth": 2, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.postprocessing": { - "version": "3.5.0", + "version": "3.5.4", "depth": 0, "source": "registry", "dependencies": { + "com.unity.modules.imgui": "1.0.0", "com.unity.modules.physics": "1.0.0" }, "url": "https://packages.unity.com" }, + "com.unity.profiling.core": { + "version": "1.0.3", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, "com.unity.recorder": { - "version": "5.1.2", + "version": "5.1.5", "depth": 0, "source": "registry", "dependencies": { "com.unity.timeline": "1.8.7", "com.unity.collections": "1.2.4", - "com.unity.bindings.openimageio": "1.0.0" + "com.unity.bindings.openimageio": "1.0.2" }, "url": "https://packages.unity.com" }, + "com.unity.render-pipelines.core": { + "version": "17.4.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.burst": "1.8.14", + "com.unity.mathematics": "1.3.2", + "com.unity.ugui": "2.0.0", + "com.unity.collections": "2.4.3", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.render-pipelines.universal": { + "version": "17.4.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.4.0", + "com.unity.shadergraph": "17.4.0", + "com.unity.render-pipelines.universal-config": "17.4.0" + } + }, + "com.unity.render-pipelines.universal-config": { + "version": "17.4.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.4.0" + } + }, + "com.unity.scriptablebuildpipeline": { + "version": "2.6.1", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.4.5", + "com.unity.modules.assetbundle": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.searcher": { + "version": "4.9.4", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.serialization": { + "version": "3.1.5", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.burst": "1.8.13", + "com.unity.collections": "2.4.2" + }, + "url": "https://packages.unity.com" + }, + "com.unity.shadergraph": { + "version": "17.4.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.4.0", + "com.unity.searcher": "4.9.3" + } + }, "com.unity.test-framework": { - "version": "1.5.1", + "version": "1.6.0", "depth": 0, "source": "builtin", "dependencies": { @@ -181,7 +262,7 @@ } }, "com.unity.test-framework.performance": { - "version": "3.1.0", + "version": "3.4.0", "depth": 2, "source": "registry", "dependencies": { @@ -191,7 +272,7 @@ "url": "https://packages.unity.com" }, "com.unity.timeline": { - "version": "1.8.7", + "version": "1.8.12", "depth": 0, "source": "registry", "dependencies": { @@ -212,21 +293,66 @@ } }, "com.unity.xr.legacyinputhelpers": { - "version": "2.1.12", + "version": "3.0.1", "depth": 0, "source": "registry", "dependencies": { - "com.unity.modules.vr": "1.0.0", "com.unity.modules.xr": "1.0.0" }, "url": "https://packages.unity.com" }, + "org.gamecult.aetheria.eve-runtime": { + "version": "file:org.gamecult.aetheria.eve-runtime", + "depth": 0, + "source": "embedded", + "dependencies": { + "org.gamecult.aetheria.state": "0.1.0", + "org.gamecult.eve.unity-uitoolkit": "0.1.0" + } + }, + "org.gamecult.aetheria.state": { + "version": "file:org.gamecult.aetheria.state", + "depth": 0, + "source": "embedded", + "dependencies": { + "com.gamecult.brokkr": "0.1.0", + "org.gamecult.eve.surface": "0.1.0" + } + }, + "org.gamecult.cultmath": { + "version": "file:../../CultMath", + "depth": 0, + "source": "local", + "dependencies": {} + }, + "org.gamecult.eve.surface": { + "version": "file:../../Eve/packages/org.gamecult.eve.surface", + "depth": 0, + "source": "local", + "dependencies": {} + }, + "org.gamecult.eve.unity-uitoolkit": { + "version": "file:../../Eve/packages/org.gamecult.eve.unity-uitoolkit", + "depth": 0, + "source": "local", + "dependencies": { + "org.gamecult.eve.surface": "0.1.0" + } + }, "com.unity.modules.accessibility": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, + "com.unity.modules.adaptiveperformance": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.subsystems": "1.0.0" + } + }, "com.unity.modules.ai": { "version": "1.0.0", "depth": 0, @@ -369,7 +495,8 @@ "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.hierarchycore": "1.0.0" + "com.unity.modules.hierarchycore": "1.0.0", + "com.unity.modules.physics": "1.0.0" } }, "com.unity.modules.umbra": { @@ -433,6 +560,16 @@ "com.unity.modules.imageconversion": "1.0.0" } }, + "com.unity.modules.vectorgraphics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0" + } + }, "com.unity.modules.vehicles": { "version": "1.0.0", "depth": 0, diff --git a/ProjectSettings/GraphicsSettings.asset b/ProjectSettings/GraphicsSettings.asset index d7bcbe91e..0874f9a26 100644 --- a/ProjectSettings/GraphicsSettings.asset +++ b/ProjectSettings/GraphicsSettings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e51023be8350fba51546a33ef20515bb5ecb714bfa51f1aea5254db518d7aa67 -size 2622 +oid sha256:f49b079fb613c3ae8807d0966f63cd88cf47a7d39e346bc3516df5a5a849963b +size 2935 diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index aa8918bb4..374598b4a 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17a83fb9ceca0b2e409b1468a0113b49c4cac28b9e71b616d779f14f23050598 -size 23565 +oid sha256:7480eea7bba457b4a3f4136783c22746bd5124863d989118cd4a5254fe60a939 +size 23861 diff --git a/ProjectSettings/QualitySettings.asset b/ProjectSettings/QualitySettings.asset index a6d501c74..13960cf79 100644 --- a/ProjectSettings/QualitySettings.asset +++ b/ProjectSettings/QualitySettings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b9a9991d751d8db1a528e0547a648abdf2a18cb812f96944136086b3a224cf09 -size 1468 +oid sha256:9d8ae52829a778e8155042832641c4c1d5137ec2ab03eb4c2c73819b5b510750 +size 2085 diff --git a/ProjectSettings/ShaderGraphSettings.asset b/ProjectSettings/ShaderGraphSettings.asset new file mode 100644 index 000000000..5040df567 --- /dev/null +++ b/ProjectSettings/ShaderGraphSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fead5a816c4eed70f224d5472340ecf1ea0b4de4f87d5926bb17fa10ffb3dc9 +size 633 diff --git a/csc.rsp b/csc.rsp new file mode 100644 index 000000000..aafdd9933 --- /dev/null +++ b/csc.rsp @@ -0,0 +1 @@ +-langversion:latest diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 75db9e6ba..d7441c915 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -17,6 +17,15 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. ## Current Mechanism +- `Aetheria.State.Daemon` is the first-class local Verse daemon host. It opens + `AetheriaStateNode` with the CultMesh server enabled, serves + `AetheriaVerseDiscoveryHost`, accepts typed Eve command documents, advances + authoritative daemon frames through `AetheriaRuntimeDaemonTickRunner`, and + publishes provider advertisements, daemon health, command boundaries, and + Eve GUI/TUI witness surfaces. This is the Odin/VoidBot-shaped daemon + contract: the daemon owns the provider state and surfaces; Unity, Codex MCP + tools, and future clients lower or command that provider through CultMesh + rather than becoming state owners. - Unity client/runtime lives under `Assets/`. Gameplay now opens the typed runtime catalog from `GameData/aetheria-world.cc` and projects current `ItemData` DTOs through `ItemManager` over the typed runtime catalog snapshot. `ActionGameManager` no @@ -30,11 +39,11 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. boot. When the typed state file exists, `ActionGameManager` opens a package-owned read-only runtime catalog snapshot from the `.cc` directory store before constructing the temporary legacy - `ItemManager`. Player settings saves, loadout saves, shutdown checkpoints, - and wormhole transition checkpoints now queue typed `.cc.pending` Verse - commit documents through the embedded runtime state package. Run checkpoint - documents include the current zone plus its runtime entity snapshots. - `Aetheria.State` owns applying those command documents into canonical + `ItemManager`. Player settings, loadout edits, shutdown checkpoints, and + wormhole transitions now flow as typed Eve or daemon command records observed + by the Aetheria Verse daemon. Run checkpoint documents include the current + zone plus its runtime entity snapshots. `Aetheria.State` owns publishing + canonical `AetheriaPlayerSettings`, `AetheriaLoadoutTemplate`, `AetheriaRunState`, `AetheriaZoneState`, and `AetheriaEntitySnapshot` documents through `AetheriaStateNode`. The embedded Unity state package can also read typed @@ -46,12 +55,11 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. files, and generated keyboard layout caches no longer write bespoke durable files. The legacy `RuntimePlayerSettings` runtime object is no longer named or decorated as a - MessagePack persistence shape. `Economy.Server` now drains `aetheria-world.cc.pending` - CultCache-record queue files into canonical typed state and drains typed - `aetheria-world.cc.eve.pending` CultCache-record command documents through - the provider-owned Eve command bridge on startup and on a daemon polling loop - while hosting the CultMesh state node. `Aetheria.State.ApplyPending` remains a - bounded local operator applicator for both pending lanes. + MessagePack persistence shape. `Aetheria.State.Daemon` now observes typed + daemon and Eve command records from the Aetheria state graph, accepts them + through the provider-owned command bridge, and publishes canonical typed state + while hosting the CultMesh state node. The standalone `Aetheria.State.DrainCommands` + applicator is deleted so command acceptance remains daemon behavior. - Shared item domain state is now built around typed item-key references, typed runtime catalog rows, direct `RuntimeBehaviorDefinition` behavior construction, and runtime geometry/stat primitives. The old `ItemData` DTO hierarchy is deleted @@ -63,9 +71,8 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. `StrategyGameManager.csbak` backup file and unused Unity asset MessagePack formatter classes have also been deleted, so the remaining direct `MessagePackSerializer` calls are no longer present in live gameplay source; - the package-owned pending lanes now write CultCache-shaped `.cc` queue records. - Their current typed record payload codec still uses MessagePack until Unity - imports/generated real CultCache serializers from CultLib. + renderer/client code now observes typed `.cc` state records and sends typed + command records through CultNet/CultMesh boundaries. - Local legacy catalog data remains in `GameData/AetherDB.msgpack` and `GameData/NameFile/*.msgpack` as migration inputs only. Unity gameplay no longer opens those MessagePack files at runtime. The old @@ -86,9 +93,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. pointer into the typed Verse replacement for `PlayerSettings.msgpack`: player name, tutorial flag, story-file hash cursors, gameplay formatting, graphics preferences, input binding overrides, and action-bar inputs. Unity's menu and - input screens route edits through named player-settings commit primitives that - mutate the session `RuntimePlayerSettings` projection and queue typed Verse - commits. The in-memory projection is not portable state authority. Aetheria has its own + input screens route edits through named typed Eve commands accepted by the + daemon, which updates the session `RuntimePlayerSettings` projection as a + read model. The in-memory projection is not portable state authority. + Aetheria has its own remapping UI that calls Unity's InputSystem at the binding/action layer; Unity's generated `AetheriaInput` class is the edge consumer of typed binding overrides, not @@ -99,8 +107,7 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. non-consumable inventory drops and creates consumable bindings around typed catalog rows. Missing typed rows are rejected for consumable binding instead of falling through to legacy DTO classification. Slot-binding drag/drop now - routes through `ActionGameManager.CommitActionBarBinding`, queues a typed run - checkpoint immediately, and rebuilt current-entity binds come from typed + routes through typed daemon operations, and rebuilt current-entity binds come from typed action-bar descriptor rows instead of stale slot-local `Entity` references. Consumable activation, active-duration fill, runtime duration, and effectiveness curves now use typed item rows with neutral defaults for missing optional facets. Gear action-bar bindings read custom icon resource @@ -441,9 +448,9 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. from `aetheria-world.cc`. Runtime item references no longer carry hydrated DTO projection caches or global link-resolution authority. MessagePack catalog construction no longer grabs global link-resolution authority, and the old generic `RuntimeCatalogLink` abstraction is gone. -- `Economy.Server` now starts the modern `Aetheria.State` CultMesh node, drains - pending Unity runtime commits through the typed state applicator, and no - longer owns RethinkDB/LiteNetLib state. +- `Aetheria.State.Daemon` starts the modern `Aetheria.State` CultMesh node as + the first-class Verse daemon and no longer shares authority with the old + RethinkDB/LiteNetLib server. - `Aetheria.State.Import` writes a typed quarantine manifest and migration ledger for the legacy catalog files. It also raw-decodes stable old MessagePack union fields into typed item/faction/name-file documents without @@ -492,30 +499,23 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. `Aetheria.Shared.Unity` references this package directly so `Galaxy` can consume typed corporation and name-file keys without loading legacy `Faction` or `NameFile` documents. The - package also owns Unity's typed runtime commit log writer for settings, - loadout-template, and run-checkpoint command documents under - `aetheria-world.cc.pending`. This log is command-only: it cannot decide - canonical state, and the `Aetheria.State` node applicator deletes applied - commands after writing typed documents. Run checkpoint documents now carry - current-zone and entity snapshots so the old `.zone` file path has a live - typed runtime projection path. -- `Aetheria.State.ApplyPending` opens the typed state node and applies queued - Unity runtime command documents from `aetheria-world.cc.pending`, deleting - successfully applied command files by default. It is an operational bridge, - not a second state owner; the applicator delegates all writes to - `AetheriaStateNode`. -- `AetheriaRuntimeEveCommandLog` emits typed - `AetheriaRuntimeEveCommandDocument` files under - `aetheria-world.cc.eve.pending`; `AetheriaEveCommandBridge` consumes the - same shared document type, validates provider/surface/command authority, and - deletes accounted command files. The Eve pending lane is command transport, - not renderer-owned state. -- `Economy.Server` hosts the CultMesh state node and now owns the long-running - pending runtime commit drain loop. `--apply-pending-once` runs the same drain - path once for smoke/operator use without keeping the process alive. -- `AetheriaRuntimeCommitDrainStatus` and the `aetheria.operations` Eve surface - publish pending-drain health, pending depth, applied counts, failures, and - timestamps as typed state. Console logs are notification-only. + package also owns Unity's typed daemon/Eve command helpers for settings, + loadout-template, and runtime requests. These helpers are transport-only: + they cannot decide canonical state, and the Verse daemon accounts accepted + command records after publishing typed documents. Run checkpoint documents now + carry current-zone and entity snapshots so the old `.zone` file path has a + live typed runtime projection path. +- `AetheriaRuntimeEveCommands` is the renderer-facing typed command port. The + internal Eve command path persists typed `AetheriaRuntimeEveCommandDocument` + records; `AetheriaEveCommandBridge` consumes the same shared document type, + validates provider/surface/command authority, and deletes accounted command + records. The Eve command lane is transport, not renderer-owned state. +- `Aetheria.State.Daemon` hosts the CultMesh Verse member and owns long-running + Eve command acceptance for provider-owned surfaces. `--once` runs one daemon + tick for smoke/operator use without keeping the process alive. +- `AetheriaEveCommandAcceptanceStatus` and the `aetheria.operations` Eve surface + publish Eve request acceptance health, observed depth, applied counts, + failures, and timestamps as typed state. Console logs are notification-only. - Daemon Verse/service identity now lives in typed `aetheria.verse_host_settings.v1`, owned by the Aetheria daemon. Provider advertisement and operations telemetry project from that document, so public @@ -600,7 +600,7 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. consumables. The older `ItemDefinitionLegacyId` and `HullItemDefinitionLegacyId` fields have been deleted from the pending commit contract; the applier no longer reconstructs item keys from legacy item GUIDs. - Corporation legacy ID fallback fields are also deleted from pending runtime + Corporation legacy ID fallback fields are also deleted from runtime commit DTOs; current commands must publish typed faction keys directly. - Entity restore and loadout manufacturer-distance weighting no longer read legacy `Faction` entries through `ItemManager`; they use `Galaxy.ResolveFaction` @@ -678,13 +678,13 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. `org.gamecult.aetheria.eve-runtime` now adds the Aetheria-specific `UIDocument` presenter that reads provider-owned Eve surfaces from `GameData/aetheria-world.cc` and mounts them through the lowerer. Its command - path queues `gamecult.eve.command.v1` envelopes under - `aetheria-world.cc.eve.pending` for the provider-owned command bridge instead - of accepting renderer-local command effects. `AetheriaEveCommandBridge` + path sends typed `gamecult.eve.command.v1` records to the provider-owned + command bridge instead of accepting renderer-local command effects. + `AetheriaEveCommandBridge` currently accepts the advertised catalog/operations refresh commands plus the first provider-owned player-settings mutation commands, republishes provider-owned surfaces, rejects unknown/unadvertised commands, and records - `AetheriaEveCommandDrainStatus` as typed state. + `AetheriaEveCommandAcceptanceStatus` as typed state. ## Invariants @@ -924,11 +924,10 @@ First Aetheria surfaces to publish: - Done: add the Unity runtime read projection for typed loadout templates, so the restore menu can rehydrate saved loadouts from CultCache instead of relying on the in-memory session list after restart. - - Done: route runtime loadout restore through - `ActionGameManager.CommitRuntimeLoadoutRestore`. `InventoryPanel` may list - typed loadout templates and display the restored entity, but gameplay owns - blueprint instantiation, credit spend, docked-ship assignment, current - entity update, and typed run checkpoint. + - Done: route runtime loadout restore through typed daemon operations. + `InventoryPanel` may list typed loadout templates and display the restored + entity, but the daemon owns blueprint instantiation, credit spend, + docked-ship assignment, current entity update, and typed run checkpoint. - Done: add typed node ports and smoke coverage for run -> zone -> entity snapshots so `.zone` no longer lacks a durable Verse replacement graph. - Done: extend run checkpoint commits and smoke coverage to carry typed @@ -1117,12 +1116,12 @@ First Aetheria surfaces to publish: dynamic column metadata. - Done: move trade menu buy price, ship-hull classification, and simple commodity stack-size decisions onto typed catalog rows; inventory transfer - and ship construction remain the runtime mutation owners. - - Done: route trade purchase mutation through gameplay-owned checkpoint - commits. `TradeMenu` still presents typed trade rows and calculates display - prices, but no longer subtracts credits, transfers purchased cargo, or - constructs purchased ships directly. `ActionGameManager.CommitTradePurchase` - owns the purchase mutation and queues a typed run checkpoint. + and ship construction are daemon-owned runtime mutations. + - Done: route trade purchase mutation through typed daemon operations. + `TradeMenu` still presents typed trade rows and calculates display prices, + but no longer subtracts credits, transfers purchased cargo, or constructs + purchased ships directly. The daemon owns the purchase mutation and typed + run checkpoint. - Done: move TradeMenu dynamic behavior columns onto typed behavior payload fields; the menu no longer hydrates `ItemData` or `BehaviorData` for row display, filtering, sorting, or buy decisions. @@ -1452,14 +1451,14 @@ First Aetheria surfaces to publish: typed item-key strings; runtime body resource commit projection no longer calls `FromLegacyId`. - Done: move resource scanner targets and mining tool asteroid belts to - typed body-key runtime commit fields; raw body ID pending-command fields + typed body-key runtime commit fields; raw body ID command-document fields are deleted. - Done: delete the old `ItemData`/`EquippableItemData`/`HullData`/ `WeaponItemData` DTO hierarchy from live Unity source; shared runtime geometry/stat primitives now live in `RuntimeGeometry.cs`. 5. Mesh host - - Replace `Economy.Server` RethinkDB and LiteNetLib database authority with a + - Replace the old RethinkDB/LiteNetLib database authority with a CultMesh node. - Keep any transport bridge only if it delegates to CultMesh and protects a named external compatibility contract. @@ -1476,23 +1475,21 @@ First Aetheria surfaces to publish: - Done: add `org.gamecult.aetheria.eve-runtime` with a `UIDocument` presenter that mounts typed Eve surfaces from `GameData/aetheria-world.cc` through UI Toolkit without giving the renderer state authority. - - Done: queue renderer-emitted Eve commands as typed - `gamecult.eve.command.v1` documents under `.eve.pending`, separate from - runtime state commits so the existing state applicator cannot accidentally - accept commands it does not own. - - Done: add the provider-owned Eve command bridge that drains `.eve.pending`, + - Done: publish renderer-emitted Eve commands as typed + `gamecult.eve.command.v1` records, separate from runtime state commits so + only the daemon-owned provider bridge accepts commands it owns. + - Done: add the provider-owned Eve command bridge that accepts observed Eve commands, validates provider/surface/command templates, invokes the current refresh handlers, republishes accepted surfaces, rejects unknown commands, and - records typed command-drain status. + records typed command acceptance status. - Done: delete provider-side Eve command emission from - `AetheriaEveCommandBridge`; renderer/runtime command files are queued - through `AetheriaRuntimeEveCommandLog`, while the provider bridge only - validates, applies, reports, and deletes accounted commands. - - Done: replace the pending lane private raw MessagePack payload files with - CultCache-shaped `.cc` queue records for both runtime state commits and Eve - commands. `AetheriaRuntimePendingCultCacheStore` owns the temporary - envelope writer/reader until the Unity package can use generated CultCache - serializers directly. + `AetheriaEveCommandBridge`; renderer/runtime requests submit through + `AetheriaRuntimeEveCommands`, while the provider bridge only validates, + applies, reports, and deletes accounted commands. + - Done: replace the old pending-lane private raw MessagePack payload files + with CultCache-shaped command documents. `AetheriaRuntimeCultCacheDocumentStore` + owns the temporary envelope writer/reader until the Unity package can use + generated CultCache serializers directly. - Done: extend the command bridge beyond refresh commands for the first mutating settings surface; player-settings Eve commands now mutate canonical `AetheriaPlayerSettings` and republish the provider-owned @@ -1513,64 +1510,59 @@ First Aetheria surfaces to publish: inspector can still project object fields for display, but `readWrite` mode and `FieldInfo.SetValue` mutation are gone; renderer-local object edits must become provider-owned typed commands or local simulation code. - - Done: route runtime simulation tuning controls through gameplay-owned - checkpoint commits. UI fields for entity override shutdown, per-item - override shutdown, thermotoggle target temperature, and entity shutdown - performance now call `ActionGameManager` commit methods instead of writing - simulation objects directly. The item override-shutdown bit is part of the - typed loadout/entity item-slot state so the checkpoint spine can see what - the simulation sees. - - Done: route hull conductivity toggles through the same gameplay-owned - checkpoint spine. `InventoryPanel` requests conductivity edge toggles from - `ActionGameManager.CommitHullConductivityToggle`; the gameplay owner mutates - `Entity.HullConductivity` and queues the typed run checkpoint that already + - Done: route runtime simulation tuning controls through typed daemon + operations. UI fields for entity override shutdown, per-item override + shutdown, thermotoggle target temperature, and entity shutdown performance + now send daemon requests instead of writing simulation objects directly. + The item override-shutdown bit is part of the typed loadout/entity + item-slot state so the daemon checkpoint spine can see what the simulation + sees. + - Done: route hull conductivity toggles through typed daemon operations. + `InventoryPanel` requests conductivity edge toggles; the daemon mutates + `Entity.HullConductivity` and publishes the typed run checkpoint that projects `hull_conductivity_x` and `hull_conductivity_y` grids. - - Done: route inventory entity renames through - `ActionGameManager.CommitEntityName`. `InventoryPanel` may collect the name - in a dialog, but gameplay owns the entity `Name` mutation and queues the - typed entity snapshot checkpoint. - - Done: route weapon group membership changes through - `ActionGameManager.CommitWeaponGroupMembership`. The old - `WeaponGroupAssignment` uGUI shell and its prefab chain have been deleted; - gameplay owns the membership mutation and typed checkpoint outright. - - Done: route action-bar drag/drop bindings through - `ActionGameManager.CommitActionBarBinding`, and restore them from typed - run-state descriptors when the current entity changes or a typed continue - run rehydrates the session. Runtime UI may still lower the action bar, but - gameplay owns both the binding mutation and the rebind path back onto the - live current entity. - - Done: route inventory double-click transfer and drag/drop placement through - gameplay-owned checkpoint commits. `InventoryMenu` and `InventoryPanel` no + - Done: route inventory entity renames through typed daemon operations. + `InventoryPanel` may collect the name in a dialog, but the daemon owns the + entity `Name` mutation and typed entity snapshot checkpoint. + - Done: route weapon group membership changes through typed daemon + operations. The old `WeaponGroupAssignment` uGUI shell and its prefab + chain have been deleted; the daemon owns the membership mutation and typed + checkpoint outright. + - Done: route action-bar drag/drop bindings through typed daemon operations, + and restore them from typed run-state descriptors when the current entity + changes or a typed continue run rehydrates the session. Runtime UI may + still lower the action bar, but the daemon owns both the binding mutation + and the rebind path back onto the live current entity. + - Done: route inventory double-click transfer and drag/drop placement + through typed daemon operations. `InventoryMenu` and `InventoryPanel` no longer drop items, remove cargo, unequip gear, equip gear, or store cargo - directly for those UI paths; they ask `ActionGameManager` to commit - cargo-to-cargo, cargo-to-equipment, equipment-to-cargo, or - equipment-to-equipment movement and then refresh display. uGUI still owns - the presentation shell for now, not the inventory mutation. - - Done: route docked current-ship selection through - `ActionGameManager.CommitDockedCurrentShip`. `InventoryPanel` may request - a selected docked player ship and update the button color, but gameplay - owns `CurrentEntity`, `DockingBay.DockedShip`, and the typed checkpoint. - - Done: route loot pickup through `ActionGameManager.CommitLootPickup`. - `ShieldManager` may detect the collision and destroy the world pickup - after a successful commit, but it no longer stores items into cargo bays - directly; gameplay owns the cargo mutation and queues the typed run - checkpoint. - - Done: route entity destruction through - `ActionGameManager.CommitEntityDestroyed`. `EntityInstance` observes hull - death and may spawn the local destruction effect, but gameplay owns - equipment/cargo drop decisions, zone entity removal, and the typed run - checkpoint. Dropped world pickups are now projected into typed zone - snapshot state and lowered back into live scene pickups by exact typed zone - record key during zone load. + directly for those UI paths; they ask the daemon to commit cargo-to-cargo, + cargo-to-equipment, equipment-to-cargo, or equipment-to-equipment movement + and then refresh display. uGUI still owns the presentation shell for now, + not the inventory mutation. + - Done: route docked current-ship selection through typed daemon operations. + `InventoryPanel` may request a selected docked player ship and update the + button color, but the daemon owns `CurrentEntity`, `DockingBay.DockedShip`, + and the typed checkpoint. + - Done: route loot pickup through typed daemon operations. `ShieldManager` + may detect the collision and destroy the world pickup after a successful + commit, but it no longer stores items into cargo bays directly; the daemon + owns the cargo mutation and typed run checkpoint. + - Done: route entity destruction through typed daemon operations. + `EntityInstance` observes hull death and may spawn the local destruction + effect, but the daemon owns equipment/cargo drop decisions, zone entity + removal, and the typed run checkpoint. Dropped world pickups are now + projected into typed zone snapshot state and lowered back into live scene + pickups by exact typed zone record key during zone load. - Done: replace the main-menu gameplay/graphics settings screen with a locally lowered UI Toolkit projection of the shared `aetheria.player_settings` Eve surface contract. The menu shell still owns - entry/exit flow, but gameplay now owns surface command semantics through - `ActionGameManager.CommitRuntimePlayerSettingsCommand`. + entry/exit flow, but the daemon now owns surface command semantics through + typed Eve player-settings commands. - Done: move the main-menu player-name field into the shared `aetheria.player_settings` Eve surface contract. `MainMenu` no longer owns a special-case text field; UI Toolkit lowers the shared Eve text control - and gameplay still owns the typed player-settings commit. + and the daemon owns the typed player-settings command. - Done: replace the whole main-menu shell with Eve-owned UI Toolkit surfaces. Root navigation and settings/input pages now lower through a single Eve host instead of the old `PropertiesPanel`/fade shell. The menu @@ -1581,7 +1573,7 @@ First Aetheria surfaces to publish: until audio has a typed state owner. - Done: replace the live runtime input remap screen with an Eve-owned UI Toolkit surface that still calls into Aetheria's low-level InputSystem - remapping layer through typed player-settings commit methods. + remapping layer through typed player-settings commands. - Done: replace the sector-map zone details panel with an Eve-owned UI Toolkit surface. `SectorRenderer` still owns reveal/camera behavior, but zone inspection no longer rebuilds `PropertiesPanel` rows when the player @@ -1590,8 +1582,8 @@ First Aetheria surfaces to publish: Toolkit surface. `MenuPanel` now owns tab-content activation and tab metadata directly; the old `MenuTabButton` component shell is gone. - Done: replace inventory background ship tuning with an Eve-owned UI - Toolkit surface. `InventoryMenu` still owns click routing and the - gameplay commit primitive still owns mutation, but the old + Toolkit surface. `InventoryMenu` still owns click routing and the typed + daemon operation owns mutation, but the old `PropertiesPanel.AddField("Shutdown Threshold", ...)` path no longer owns that settings shell. - Done: replace the trade target-cargo selector popup with an Eve-owned UI @@ -1600,13 +1592,13 @@ First Aetheria surfaces to publish: that selector shell. - Done: replace the inventory entity/bay/loadout dropdown popup with an Eve-owned UI Toolkit surface. `InventoryPanel` still owns local display - projection and gameplay still owns entity/cargo selection plus loadout + projection while daemon operations own entity/cargo selection plus loadout restore/save effects, but the old `ContextMenu` dropdown tree no longer owns that shell. - Done: replace the remaining `TradeMenu` filter-selector and row-action popups with Eve-owned UI Toolkit surfaces. `TradeMenu` still owns local - filter state and the quantity dialog still delegates to gameplay-owned - purchase commits, but the old `ContextMenu` dropdown/option tree no longer + filter state and the quantity dialog delegates to typed daemon purchase + operations, but the old `ContextMenu` dropdown/option tree no longer owns any live trade-menu shell. - Done: replace `TradeMenu` typed-item inspection with an Eve-owned UI Toolkit surface. The surface still projects typed catalog facts from local @@ -1652,21 +1644,21 @@ First Aetheria surfaces to publish: - Done: remove the stale Unity `SaveState`/`SaveZone` command names after their bespoke serializers were deleted. - Done: replace warning-only player settings, loadout, shutdown, and - wormhole save paths with typed `.cc.pending` Verse commit commands and a - state-node applicator that writes canonical typed documents. - - Done: replace the `.cc.pending` runtime commit lane's private raw - MessagePack array protocol with `AetheriaRuntimeStateCommitDocument`, - shared by the Unity runtime package and the provider applicator. + wormhole save paths with typed Eve/daemon command records accepted by the + Verse daemon. + - Done: delete the `.cc.pending` runtime commit lane and its private raw + MessagePack array protocol; canonical writes now happen behind daemon + command acceptance. - Done: extend run checkpoint commands to carry current-zone and entity snapshots into canonical `AetheriaZoneState` and `AetheriaEntitySnapshot` documents. - - Done: add `Aetheria.State.ApplyPending` as a bounded local applicator for - queued runtime commits until the drain loop is hosted as a daemon. - - Done: move the pending runtime commit drain into `Economy.Server` startup - and daemon polling, with `--apply-pending-once` for bounded operation. - - Done: publish pending-drain health as typed CultCache state and an Eve - operations surface. - - Done: publish Eve command-drain health as typed CultCache state and include + - Done: delete `Aetheria.State.DrainCommands`; bounded acceptance now uses + `Aetheria.State.Daemon --once` so the Verse daemon remains the only command owner. + - Done: move Eve request acceptance into `Aetheria.State.Daemon` startup and + daemon ticking. + - Done: publish Eve request acceptance health as typed CultCache state and an + Eve operations surface. + - Done: publish Eve command acceptance health as typed CultCache state and include it in the operations surface. - Done: move daemon Verse/service identity into typed `aetheria.verse_host_settings.v1`, with provider advertisement and @@ -1817,22 +1809,21 @@ First Aetheria surfaces to publish: - Done: demote Unity's live `PlayerSettings` runtime object to `RuntimePlayerSettings`; `AetheriaPlayerSettings` remains the typed Verse state document owner, while Unity only keeps a session projection and - queues typed player-settings commits. + sends typed player-settings commands. - Done: route input-screen binding/action-bar edits through named typed - player-settings commit methods on the Unity runtime boundary, so the UI is + player-settings commands on the Unity runtime boundary, so the UI is no longer a direct writer of input binding or action-bar collections. - Done: route main-menu graphics settings returns through the same typed - player-settings commit primitive as gameplay settings, so graphics edits + player-settings command path as gameplay settings, so graphics edits no longer survive only as session-local `RuntimePlayerSettings`. - Done: route main-menu player name, gameplay formatting, and graphics - preference edits through named `ActionGameManager` commit methods. MainMenu + preference edits through typed Eve player-settings commands. MainMenu reads the `RuntimePlayerSettings` projection for display, but no longer assigns its player/gameplay/graphics fields directly. - Done: lower the shared `aetheria.player_settings` Eve surface contract directly inside `MainMenu` through UI Toolkit. The menu no longer owns a second bespoke gameplay/graphics widget tree for those settings; it lowers - the shared document and dispatches command ids back to gameplay-owned - commit primitives. + the shared document and dispatches command ids through typed Eve commands. - Done: delete `ItemManager`'s unused zone dictionary, commented corporation controller/galaxy-zone GUID caches, force-load GUID sketch, and dead time property sketch. ItemManager no longer pretends to own a zone/time cache @@ -1887,14 +1878,12 @@ First Aetheria surfaces to publish: corporation/name-file links, typed behavior payloads, and dropped world pickup rows in zone snapshots. It also proves the embedded package can read provider-owned Eve surface documents from the same - CultCache store. The smoke proves the Unity runtime state commit log can - queue player-settings and run snapshot commands, the `Aetheria.State` node - can apply them into canonical typed settings/run/zone/entity state, and - commands are cleared after application. It also proves renderer-emitted Eve - commands are queued as typed command envelopes separately from state commits. - The smoke now inspects pending queue bytes directly and verifies each pending - file is a CultCache store snapshot with a schema catalog and exactly one - typed record. + CultCache store. The smoke proves legacy runtime snapshots can be + imported into canonical typed settings/run/zone/entity state and cleared + afterward, while renderer-emitted Eve commands stay typed command documents + outside that legacy import lane. The smoke now inspects pending mailbox bytes + directly and verifies each pending file is a CultCache store snapshot with a + schema catalog and exactly one typed record. - Unity batchmode compile with Editor `6000.4.2f1` is currently available and should be rerun after package/runtime C# edits; Unity may leave generated package metadata and project files that must be cleaned before commit. @@ -1924,10 +1913,8 @@ First Aetheria surfaces to publish: Package/import MessagePack usage remains explicitly bounded to CultCache transport and migration. - `Aetheria.State.Verify` also fences the Unity package serializer boundary: - MessagePack symbols may appear only in `AetheriaRuntimeCatalogStore`, - `AetheriaRuntimePendingCultCacheStore`, `AetheriaRuntimeStateCommitDocument`, - and `AetheriaRuntimeEveCommandDocument` until CultLib generated serializers - become the Unity runtime owner. + MessagePack symbols may appear only in typed CultCache transport and document + shapes until CultLib generated serializers become the Unity runtime owner. - `Aetheria.State.Verify` also guards runtime faction identity ownership: construction blueprints cannot carry a legacy faction GUID, and runtime entity/faction commits cannot rebuild `FactionKey` from `Faction.ID`. @@ -1974,9 +1961,9 @@ First Aetheria surfaces to publish: summon a missing debug dependency during Unity compilation. - Unity runtime settings projection is now `RuntimePlayerSettings`; live Unity source has no standalone `PlayerSettings` class/property/method symbols. -- Input binding and action-bar edits now queue the same typed player-settings - commit path as menu settings changes through named runtime input commit - methods; the old `SaveLayout` runtime-only warning is gone. The generated `AetheriaInput` class still calls Unity's +- Input binding and action-bar edits now send the same typed player-settings + command path as menu settings changes; the old `SaveLayout` runtime-only + warning is gone. The generated `AetheriaInput` class still calls Unity's `InputActionAsset.FromJson`, but that JSON belongs to Unity's generated input action lowering under Aetheria's remapping system. It is not durable Aetheria state and does not own remapping authority. @@ -2027,10 +2014,10 @@ First Aetheria surfaces to publish: geometry helper instead of a JSON text asset. The dead commented Ink `ToJson` write path and checked-in `ansi104.json` display file have been removed from live source. -- `Aetheria.State.Smoke` proves the provider-owned Eve command bridge drains +- `Aetheria.State.Smoke` proves the provider-owned Eve command bridge accepts `gamecult.eve.command.v1` envelopes, accepts advertised refresh commands plus player-settings mutation commands, rejects unknown commands, persists - `AetheriaEveCommandDrainStatus`, and exposes the Eve command drain through the + `AetheriaEveCommandAcceptanceStatus`, and exposes Eve command acceptance through the operations surface while preserving the `aetheria.player_settings` surface. - Unity play smoke proves runtime UI reads from Eve surfaces and sends commands through the shared state service. @@ -2047,26 +2034,27 @@ legacy DTO vocabulary, keep package-level MessagePack/JSON at explicit boundaries, and replace runtime/operator UI truth with Eve surfaces. The old Do not restore the deleted generic `PropertiesPanel` reflection inspector as a shortcut around typed command/state ownership. Runtime simulation -tuning controls may remain temporarily on uGUI only when they delegate to -gameplay-owned commit methods that queue typed checkpoint state; direct UI -mutation of entity/item/behavior settings is obsolete authority. Hull -conductivity changes follow the same rule: UI requests the toggle, gameplay owns -the grid mutation and checkpoint. Entity renames follow the same rule: UI -collects text, gameplay owns the name mutation and checkpoint. Weapon group -assignment follows the same rule: UI requests membership, gameplay owns the -group mutation and checkpoint. Inventory double-click transfer follows the same +tuning controls may remain temporarily on uGUI only when they delegate to typed +daemon operations; direct UI mutation of entity/item/behavior settings is +obsolete authority. Hull conductivity changes follow the same rule: UI requests +the toggle, and the daemon owns the grid mutation and checkpoint. Entity renames +follow the same rule: UI collects text, and the daemon owns the name mutation +and checkpoint. Weapon group assignment follows the same rule: UI requests +membership, and the daemon owns the group mutation and checkpoint. Inventory +double-click transfer follows the same rule, and drag/drop placement now shares that commit family: UI requests -cargo/equipment movement, gameplay owns the move and queues the checkpoint. -Trade purchases follow the same rule: UI requests the purchase, gameplay owns +cargo/equipment movement, and the daemon owns the move and checkpoint. Trade +purchases follow the same rule: UI requests the purchase, and the daemon owns credit changes, cargo transfer, ship creation, and checkpoint. Runtime loadout -restore follows the same rule: UI requests restoration, gameplay owns +restore follows the same rule: UI requests restoration, and the daemon owns instantiation, credits, dock assignment, current entity, and checkpoint. Docked -current-ship selection follows the same rule: UI requests selection, gameplay -owns `CurrentEntity`, `DockingBay.DockedShip`, and checkpoint. Loot pickup -follows the same rule: collision code reports the pickup opportunity, gameplay -owns cargo storage and checkpoint. Entity destruction follows the same rule: -instance code observes death, gameplay owns drop decisions, zone removal, and -checkpoint. Dropped world pickups are typed zone-snapshot state, not only +current-ship selection follows the same rule: UI requests selection, and the +daemon owns `CurrentEntity`, `DockingBay.DockedShip`, and checkpoint. Loot +pickup follows the same rule: collision code reports the pickup opportunity, +and the daemon owns cargo storage and checkpoint. Entity destruction follows +the same rule: instance code observes death, and the daemon owns drop +decisions, zone removal, and checkpoint. Dropped world pickups are typed +zone-snapshot state, not only renderer-local objects; live lowering consumes the exact typed zone record key instead of rehydrating a parallel presentation list. Any predicate that still needs legacy DTO objects must earn that dependency by