diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index a9beac4df..157ea7557 100644 Binary files a/.voidbot/state/aetheria.cc and b/.voidbot/state/aetheria.cc differ diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 74e970bdd..0add36451 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -166,21 +166,11 @@ public class ActionGameManager : MonoBehaviour try { var loadouts = AetheriaRuntimeCatalogStore.ReadLoadoutTemplates(stateFilePath); - LoadoutBlueprints.Clear(); - foreach (var loadout in loadouts) - { - var blueprint = CreateRuntimeBlueprint(loadout.RootEntity); - if (blueprint == null) - continue; + LoadoutTemplates.Clear(); + LoadoutTemplates.AddRange(loadouts); - if (string.IsNullOrWhiteSpace(blueprint.Name)) - blueprint.Name = loadout.Name; - - LoadoutBlueprints.Add(blueprint); - } - - if (LoadoutBlueprints.Count > 0) - Debug.Log($"Loaded {LoadoutBlueprints.Count} Aetheria Verse loadout templates from typed state."); + if (LoadoutTemplates.Count > 0) + Debug.Log($"Loaded {LoadoutTemplates.Count} Aetheria Verse loadout templates from typed state."); } catch (FileNotFoundException) { @@ -191,6 +181,14 @@ public class ActionGameManager : MonoBehaviour } } + public RuntimeEntityBlueprint CreateRuntimeBlueprint(AetheriaRuntimeLoadoutTemplateSnapshot template) + { + var blueprint = CreateRuntimeBlueprint(template.RootEntity); + if (blueprint != null && string.IsNullOrWhiteSpace(blueprint.Name)) + blueprint.Name = template.Name; + return blueprint; + } + private RuntimeEntityBlueprint CreateRuntimeBlueprint(AetheriaRuntimeEntityLoadoutSnapshot entity) { var hull = CreateEquippableLoadoutItem(entity.Hull); @@ -407,7 +405,7 @@ public class ActionGameManager : MonoBehaviour public ItemManager ItemManager { get; private set; } public Zone Zone { get; private set; } - public List LoadoutBlueprints { get; } = new List(); + public List LoadoutTemplates { get; } = new List(); private readonly (float2 direction, string name)[] _directions = { (float2(0, 1), "Front"), @@ -1281,13 +1279,14 @@ public class ActionGameManager : MonoBehaviour public void QueueRuntimeLoadoutTemplateCommit(RuntimeEntityBlueprint blueprint) { - LoadoutBlueprints.RemoveAll(loadout => loadout.Name == blueprint.Name); - LoadoutBlueprints.Add(blueprint); + var loadout = ProjectLoadoutTemplate(blueprint); + LoadoutTemplates.RemoveAll(template => template.Name == loadout.Name); + LoadoutTemplates.Add(CreateRuntimeLoadoutTemplateSnapshot(loadout)); try { var commit = AetheriaRuntimeStateCommitLog.QueueLoadoutTemplate( RuntimeStateFilePath, - ProjectLoadoutTemplate(blueprint)); + loadout); Debug.Log($"Queued Aetheria Verse loadout commit: {commit.Path}"); } catch (Exception ex) @@ -1296,6 +1295,78 @@ public class ActionGameManager : MonoBehaviour } } + private static AetheriaRuntimeLoadoutTemplateSnapshot CreateRuntimeLoadoutTemplateSnapshot( + AetheriaRuntimeLoadoutTemplateCommit template) + { + var timestamp = DateTime.UtcNow.ToString("O"); + return new AetheriaRuntimeLoadoutTemplateSnapshot( + template.Name ?? "", + template.OwnerPlayerKey ?? "", + CreateRuntimeEntityLoadoutSnapshot(template.RootEntity), + timestamp, + timestamp); + } + + private static AetheriaRuntimeEntityLoadoutSnapshot CreateRuntimeEntityLoadoutSnapshot( + AetheriaRuntimeEntityLoadoutCommit entity) + { + entity ??= new AetheriaRuntimeEntityLoadoutCommit(); + return new AetheriaRuntimeEntityLoadoutSnapshot( + entity.Name ?? "", + entity.Kind ?? "", + ReferenceKey("aetheria.corporation", entity.CorporationLegacyId ?? ""), + CreateRuntimeLoadoutItemSnapshot(entity.Hull), + CreateRuntimeLoadoutItemSlotSnapshots(entity.Equipment), + CreateRuntimeLoadoutItemSlotSnapshots(entity.CargoBays), + CreateRuntimeLoadoutItemSlotSnapshots(entity.DockingBays), + CreateRuntimeCargoBayLoadoutSnapshots(entity.CargoContents), + CreateRuntimeCargoBayLoadoutSnapshots(entity.DockingBayContents), + (entity.DockingBayAssignments ?? Array.Empty()).ToArray(), + (entity.WeaponGroups ?? Array.Empty>()) + .Select(group => (IReadOnlyList)group.ToArray()) + .ToArray(), + (entity.Children ?? Array.Empty()) + .Select(CreateRuntimeEntityLoadoutSnapshot) + .ToArray()); + } + + private static AetheriaRuntimeLoadoutItemSnapshot CreateRuntimeLoadoutItemSnapshot( + AetheriaRuntimeLoadoutItemCommit item) + { + item ??= new AetheriaRuntimeLoadoutItemCommit(); + return new AetheriaRuntimeLoadoutItemSnapshot( + ReferenceKey("aetheria.item_definition", item.ItemDefinitionLegacyId ?? ""), + item.Quality, + item.Durability, + item.Quantity, + item.Enabled); + } + + private static IReadOnlyList CreateRuntimeLoadoutItemSlotSnapshots( + IReadOnlyList slots) + { + return (slots ?? Array.Empty()) + .Select(slot => new AetheriaRuntimeLoadoutItemSlotSnapshot( + slot.X, + slot.Y, + CreateRuntimeLoadoutItemSnapshot(slot.Item))) + .ToArray(); + } + + private static IReadOnlyList CreateRuntimeCargoBayLoadoutSnapshots( + IReadOnlyList cargoBays) + { + return (cargoBays ?? Array.Empty()) + .Select(bay => new AetheriaRuntimeCargoBayLoadoutSnapshot( + CreateRuntimeLoadoutItemSlotSnapshots(bay.Items))) + .ToArray(); + } + + private static string ReferenceKey(string prefix, string legacyId) + { + return string.IsNullOrWhiteSpace(legacyId) ? "" : $"{prefix}:{legacyId}"; + } + private void OnApplicationQuit() => QueueRunCheckpoint("application-quit"); private void QueueRunCheckpoint(string reason) @@ -1852,7 +1923,6 @@ public class ActionGameManager : MonoBehaviour Zone, loadoutGenerator.GenerateShipLoadout(data => string.IsNullOrEmpty(Settings.StartingHullName) || data.Name==Settings.StartingHullName ), true); - // RuntimeEntityBlueprintProjector.InstantiateFromBlueprint(ItemManager, Zone, LoadoutBlueprints.First(x => x.Name == StarterShipTemplate), true); ((Ship) ship).IsPlayerShip = true; ship.Position = float3.zero; ship.Zone = Zone; diff --git a/Assets/Scripts/UI/Menu/InventoryPanel.cs b/Assets/Scripts/UI/Menu/InventoryPanel.cs index 9cf9f12aa..27dbdd75c 100644 --- a/Assets/Scripts/UI/Menu/InventoryPanel.cs +++ b/Assets/Scripts/UI/Menu/InventoryPanel.cs @@ -176,25 +176,10 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler GameManager.QueueRuntimeLoadoutTemplateCommit(RuntimeEntityBlueprintProjector.CaptureBlueprint(_displayedEntity)); }); - if (GameManager.LoadoutBlueprints.Any()) + if (GameManager.LoadoutTemplates.Any()) { ContextMenu.AddDropdown("Restore Loadout", - GameManager.LoadoutBlueprints.Select(blueprint => - ( - $"{blueprint.Name} - {blueprint.Price(GameManager.ItemManager):n0}", () => - { - var entity = RuntimeEntityBlueprintProjector.InstantiateFromBlueprint(GameManager.ItemManager, GameManager.Zone, blueprint, true); - entity.SetParent(GameManager.DockedEntity); - GameManager.Credits -= blueprint.Price(GameManager.ItemManager); - GameManager.CurrentEntity = entity; - if(entity is Ship ship) - { - ship.IsPlayerShip = true; - GameManager.DockingBay.DockedShip = ship; - } - Display(entity); - }, blueprint.Price(GameManager.ItemManager) < GameManager.Credits - ))); + GameManager.LoadoutTemplates.Select(LoadoutOption)); } ContextMenu.Show(); @@ -202,6 +187,28 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler } } + private (string text, Action action, bool enabled) LoadoutOption(AetheriaRuntimeLoadoutTemplateSnapshot template) + { + var blueprint = GameManager.CreateRuntimeBlueprint(template); + if (blueprint == null) + return ($"{template.Name} - unavailable", () => { }, false); + + var price = blueprint.Price(GameManager.ItemManager); + return ($"{template.Name} - {price:n0}", () => + { + var entity = RuntimeEntityBlueprintProjector.InstantiateFromBlueprint(GameManager.ItemManager, GameManager.Zone, blueprint, true); + entity.SetParent(GameManager.DockedEntity); + GameManager.Credits -= price; + GameManager.CurrentEntity = entity; + if(entity is Ship ship) + { + ship.IsPlayerShip = true; + GameManager.DockingBay.DockedShip = ship; + } + Display(entity); + }, price < GameManager.Credits); + } + private void Update() { if (_displayedEntity != null && TemperatureDisplay) diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index ead150138..bd0ddd1ca 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -1058,6 +1058,10 @@ First Aetheria surfaces to publish: - Done: remove public `GasGiantData`/`SunData` handles from runtime wrappers; wave and light calculations now use copied wrapper fields after construction. + - Done: move saved loadout template ownership off `RuntimeEntityBlueprint`; + `ActionGameManager` keeps typed `AetheriaRuntimeLoadoutTemplateSnapshot` + documents and projects blueprints only for pricing, instantiation, and + commit boundaries. - Done: delete the legacy `Behaviors` lists from `ConsumableItemData` and `EquippableItemData`; item DTOs can no longer carry behavior config state. - Done: move runtime blueprint price aggregation and conductivity restore