diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index a2323796e..4481ec5dc 100644 Binary files a/.voidbot/state/aetheria.cc and b/.voidbot/state/aetheria.cc differ diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md index b8fa63432..25fe19524 100644 --- a/Aetheria.State/README.md +++ b/Aetheria.State/README.md @@ -83,8 +83,10 @@ legacy catalog group/table annotations; their remaining metadata is projection and inspection metadata for the typed runtime bridge. Item instances now carry `AetheriaRuntimeItemReference`, a typed item-key facade. `ItemInstance.ItemKey` is the identity surface, while `ItemId` is a -derived legacy-GUID compatibility projection for unfinished cargo and -simulation paths. +derived legacy-GUID compatibility projection for behavior config and broader +simulation paths. Cargo inventory indexes, consumable activation lookup, +action-bar consumable quantity/fill, transfer lookup, and trade owned counts +now use item keys. The old generic `RuntimeCatalogLink` abstraction, `ItemManager.GetData` hydration path, vague `ItemInstance.Data` identity field, and old `RuntimeItemReference.Projection` cache have been deleted; runtime item facts diff --git a/Assets/Scripts/Gameplay/ActionBarSlot.cs b/Assets/Scripts/Gameplay/ActionBarSlot.cs index fc3a0d1f8..e9b4e3d9e 100644 --- a/Assets/Scripts/Gameplay/ActionBarSlot.cs +++ b/Assets/Scripts/Gameplay/ActionBarSlot.cs @@ -82,20 +82,20 @@ public class ActionBarConsumableBinding : ActionBarBinding public override void Update() { - Slot.QuantityRemaining.text = $"{Entity.CountItemsInCargo(TargetLegacyId)}"; - if (TargetLegacyId == Guid.Empty) + Slot.QuantityRemaining.text = $"{Entity.CountItemsInCargo(TargetItemKey)}"; + if (string.IsNullOrWhiteSpace(TargetItemKey)) { Slot.Fill.fillAmount = 0; return; } - var instance = Entity.FindActiveConsumable(TargetLegacyId); + var instance = Entity.FindActiveConsumable(TargetItemKey); if (instance == null) Slot.Fill.fillAmount = 0; else Slot.Fill.fillAmount = instance.RemainingDuration / instance.Duration; } - private Guid TargetLegacyId => - Guid.TryParse(Target.LegacyId, out var legacyId) ? legacyId : Guid.Empty; + private string TargetItemKey => + Guid.TryParse(Target.LegacyId, out var legacyId) ? AetheriaRuntimeItemReference.FromLegacyId(legacyId) : ""; public string TargetItemDefinitionLegacyId => Target?.LegacyId ?? ""; @@ -116,8 +116,11 @@ public class ActionBarGearBinding : ActionBarBinding { get { - var itemId = Item?.EquippableItem?.ItemId ?? Guid.Empty; - return itemId == Guid.Empty ? "" : itemId.ToString("D"); + var itemKey = Item?.EquippableItem?.ItemKey ?? ""; + const string legacyPrefix = "aetheria.item_definition:legacy:"; + return itemKey.StartsWith(legacyPrefix, StringComparison.OrdinalIgnoreCase) + ? itemKey.Substring(legacyPrefix.Length) + : ""; } } diff --git a/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs b/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs index b1e739689..11409bc11 100644 --- a/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs @@ -107,7 +107,7 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior var cargo = Entity.FindItemInCargo(_data.AmmoType); if (cargo != null) { - var item = cargo.ItemsOfType[_data.AmmoType][0]; + var item = cargo.GetFirstItem(_data.AmmoType); if (item is SimpleCommodity simpleCommodity) cargo.Remove(simpleCommodity, 1); diff --git a/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs b/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs index 96b67d27f..85921bae4 100644 --- a/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs @@ -132,7 +132,7 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior var cargo = Entity.FindItemInCargo(_data.AmmoType); if (cargo != null) { - var item = cargo.ItemsOfType[_data.AmmoType][0]; + var item = cargo.GetFirstItem(_data.AmmoType); if (item is SimpleCommodity simpleCommodity) cargo.Remove(simpleCommodity, 1); } diff --git a/Assets/Scripts/ServerShared/Behaviors/ItemUsage.cs b/Assets/Scripts/ServerShared/Behaviors/ItemUsage.cs index c65f368e4..949543508 100644 --- a/Assets/Scripts/ServerShared/Behaviors/ItemUsage.cs +++ b/Assets/Scripts/ServerShared/Behaviors/ItemUsage.cs @@ -39,7 +39,7 @@ public class ItemUsage : Behavior var cargo = Entity.FindItemInCargo(_data.Item); if (cargo == null) return false; - var item = cargo.ItemsOfType[_data.Item][0]; + var item = cargo.GetFirstItem(_data.Item); if (item is SimpleCommodity simpleCommodity) cargo.Remove(simpleCommodity, 1); if (item is CraftedItemInstance craftedItemInstance) @@ -47,4 +47,4 @@ public class ItemUsage : Behavior return true; } -} \ No newline at end of file +} diff --git a/Assets/Scripts/ServerShared/Entity.cs b/Assets/Scripts/ServerShared/Entity.cs index 71bf00f45..a3b56c0a1 100644 --- a/Assets/Scripts/ServerShared/Entity.cs +++ b/Assets/Scripts/ServerShared/Entity.cs @@ -315,31 +315,43 @@ public abstract class Entity _activeConsumables.Add(new ConsumableItemEffect(item, this)); } + public ConsumableItemEffect FindActiveConsumable(string itemKey) + { + return _activeConsumables.FirstOrDefault(ac => ac.Item?.ItemKey == itemKey); + } + public ConsumableItemEffect FindActiveConsumable(Guid itemId) { - return _activeConsumables.FirstOrDefault(ac => ac.Item?.ItemId == itemId); + return FindActiveConsumable(AetheriaRuntimeItemReference.FromLegacyId(itemId)); } public bool CanActivateConsumable(AetheriaRuntimeCatalogItem item) { - var itemId = GetLegacyGuid(item); - return item != null && itemId != Guid.Empty && (item.Stackable || FindActiveConsumable(itemId) == null); + var itemKey = GetItemKey(item); + return item != null && !string.IsNullOrWhiteSpace(itemKey) && (item.Stackable || FindActiveConsumable(itemKey) == null); } public bool TryActivateConsumable(AetheriaRuntimeCatalogItem typedItem) { if (!CanActivateConsumable(typedItem)) return false; - var itemId = GetLegacyGuid(typedItem); - var bay = FindItemInCargo(itemId); + var itemKey = GetItemKey(typedItem); + var bay = FindItemInCargo(itemKey); if (bay == null) return false; - var item = (ConsumableItem)bay.ItemsOfType[itemId].First(); + var item = (ConsumableItem)bay.GetFirstItem(itemKey); ActivateConsumable(item); bay.Remove(item); return true; } + private static string GetItemKey(AetheriaRuntimeCatalogItem item) + { + return GetLegacyGuid(item) is var itemId && itemId != Guid.Empty + ? AetheriaRuntimeItemReference.FromLegacyId(itemId) + : ""; + } + private static Guid GetLegacyGuid(AetheriaRuntimeCatalogItem item) { return item != null && Guid.TryParse(item.LegacyId, out var legacyId) @@ -409,23 +421,33 @@ public abstract class Entity Temperature[position.x, position.y] += heat / ThermalMass[position.x, position.y]; } - public int CountItemsInCargo(Guid itemDataID) + public int CountItemsInCargo(string itemKey) { int sum = 0; foreach (var x in CargoBays) { - if (x.ItemsOfType.ContainsKey(itemDataID)) + if (x.ItemsOfType.ContainsKey(itemKey)) { - foreach (var i in x.ItemsOfType[itemDataID]) sum += i is SimpleCommodity simpleCommodity ? simpleCommodity.Quantity : 1; + foreach (var i in x.ItemsOfType[itemKey]) sum += i is SimpleCommodity simpleCommodity ? simpleCommodity.Quantity : 1; } } return sum; } + public int CountItemsInCargo(Guid itemDataID) + { + return CountItemsInCargo(AetheriaRuntimeItemReference.FromLegacyId(itemDataID)); + } + + public EquippedCargoBay FindItemInCargo(string itemKey) + { + return CargoBays.FirstOrDefault(c => c.ContainsItem(itemKey)); + } + public EquippedCargoBay FindItemInCargo(Guid itemDataID) { - return CargoBays.FirstOrDefault(c => c.ItemsOfType.ContainsKey(itemDataID)); + return FindItemInCargo(AetheriaRuntimeItemReference.FromLegacyId(itemDataID)); } public Shape UnoccupiedSpace @@ -450,14 +472,15 @@ public abstract class Entity // Returns the number of items successfully transferred public int TryTransferItems(Entity target, Guid itemDataID, int quantity) { + var itemKey = AetheriaRuntimeItemReference.FromLegacyId(itemDataID); int quantityTransferred = 0; while (quantityTransferred < quantity) { - EquippedCargoBay originInventory = CargoBays.FirstOrDefault(c => c.ItemsOfType.ContainsKey(itemDataID)); + EquippedCargoBay originInventory = CargoBays.FirstOrDefault(c => c.ContainsItem(itemKey)); if (originInventory == null) break; - var itemInstance = originInventory.ItemsOfType[itemDataID][0]; + var itemInstance = originInventory.GetFirstItem(itemKey); if (itemInstance is SimpleCommodity simpleCommodity) { @@ -1557,7 +1580,7 @@ public class EquippedCargoBay : EquippedItem public readonly ItemInstance[,] Occupancy; - public readonly Dictionary> ItemsOfType = new Dictionary>(); + public readonly Dictionary> ItemsOfType = new Dictionary>(); public Shape InteriorShape { get; } @@ -1576,6 +1599,21 @@ public class EquippedCargoBay : EquippedItem } } + public bool ContainsItem(string itemKey) + { + return !string.IsNullOrWhiteSpace(itemKey) && ItemsOfType.ContainsKey(itemKey); + } + + public ItemInstance GetFirstItem(string itemKey) + { + return ContainsItem(itemKey) ? ItemsOfType[itemKey].FirstOrDefault() : null; + } + + public ItemInstance GetFirstItem(Guid itemId) + { + return GetFirstItem(AetheriaRuntimeItemReference.FromLegacyId(itemId)); + } + public EquippedCargoBay(ItemManager itemManager, EquippableItem item, int2 position, Entity entity, string name) : base(itemManager, item, position, entity) { var typedCargoBay = ItemManager.GetRuntimeItem(EquippableItem); @@ -1627,7 +1665,7 @@ public class EquippedCargoBay : EquippedItem // For simple commodities, search for existing item stacks to add to foreach (var cargoItem in Cargo.Keys) { - if (item.ItemId != cargoItem.ItemId) continue; + if (item.ItemKey != cargoItem.ItemKey) continue; var cargoCommodity = (SimpleCommodity) cargoItem; if (cargoCommodity.Quantity >= maxStack) continue; @@ -1719,11 +1757,9 @@ public class EquippedCargoBay : EquippedItem } Cargo[item] = cargoCoord; - if(!ItemsOfType.ContainsKey(item.ItemId)) - ItemsOfType[item.ItemId] = new List(); - ItemsOfType[item.ItemId].Add(item); + AddTypedItemIndex(item); } - else if (Occupancy[cargoCoord.x, cargoCoord.y] is SimpleCommodity cargoCommodity && cargoCommodity.ItemId == item.ItemId) + else if (Occupancy[cargoCoord.x, cargoCoord.y] is SimpleCommodity cargoCommodity && cargoCommodity.ItemKey == item.ItemKey) { if (cargoCommodity.Quantity + item.Quantity <= maxStack) { @@ -1763,9 +1799,7 @@ public class EquippedCargoBay : EquippedItem } Cargo[item] = cargoCoord; - if(!ItemsOfType.ContainsKey(item.ItemId)) - ItemsOfType[item.ItemId] = new List(); - ItemsOfType[item.ItemId].Add(item); + AddTypedItemIndex(item); Mass += ItemManager.GetMass(item); ThermalMass += ItemManager.GetThermalMass(item); @@ -1787,9 +1821,7 @@ public class EquippedCargoBay : EquippedItem Occupancy[v.x, v.y] = null; Cargo.Remove(item); - ItemsOfType[item.ItemId].Remove(item); - if (!ItemsOfType[item.ItemId].Any()) - ItemsOfType.Remove(item.ItemId); + RemoveTypedItemIndex(item); Mass -= ItemManager.GetMass(item); ThermalMass -= ItemManager.GetThermalMass(item); @@ -1815,9 +1847,7 @@ public class EquippedCargoBay : EquippedItem Occupancy[v.x, v.y] = null; Cargo.Remove(item); - ItemsOfType[item.ItemId].Remove(item); - if (!ItemsOfType[item.ItemId].Any()) - ItemsOfType.Remove(item.ItemId); + RemoveTypedItemIndex(item); Mass -= ItemManager.GetMass(item); ThermalMass -= ItemManager.GetThermalMass(item); @@ -1837,6 +1867,29 @@ public class EquippedCargoBay : EquippedItem return ToShape(typedItem?.ShapeWidth ?? 1, typedItem?.ShapeHeight ?? 1, typedItem?.ShapeCells); } + private void AddTypedItemIndex(ItemInstance item) + { + var itemKey = item?.ItemKey ?? ""; + if (string.IsNullOrWhiteSpace(itemKey)) + return; + + if (!ItemsOfType.ContainsKey(itemKey)) + ItemsOfType[itemKey] = new List(); + + ItemsOfType[itemKey].Add(item); + } + + private void RemoveTypedItemIndex(ItemInstance item) + { + var itemKey = item?.ItemKey ?? ""; + if (string.IsNullOrWhiteSpace(itemKey) || !ItemsOfType.ContainsKey(itemKey)) + return; + + ItemsOfType[itemKey].Remove(item); + if (!ItemsOfType[itemKey].Any()) + ItemsOfType.Remove(itemKey); + } + private int GetMaxStack(SimpleCommodity item) { var typedItem = ItemManager.GetRuntimeItem(item); diff --git a/Assets/Scripts/UI/Menu/TradeMenu.cs b/Assets/Scripts/UI/Menu/TradeMenu.cs index bb36ffd74..ad11db3d4 100644 --- a/Assets/Scripts/UI/Menu/TradeMenu.cs +++ b/Assets/Scripts/UI/Menu/TradeMenu.cs @@ -293,18 +293,18 @@ public class TradeMenu : MonoBehaviour x => () => { if (x.IsHull) - return GameManager.DockedEntity.Children.Count(s => s.Hull.ItemId == x.LegacyId && s is Ship {IsPlayerShip: true}).ToString(); + return GameManager.DockedEntity.Children.Count(s => s.Hull.ItemKey == x.ItemKey && s is Ship {IsPlayerShip: true}).ToString(); if(x.Item is SimpleCommodity) - return (_targetCargo.ItemsOfType.ContainsKey(x.LegacyId) ? _targetCargo.ItemsOfType[x.LegacyId].Cast().Sum(s=>s.Quantity) : 0).ToString(); - return (_targetCargo.ItemsOfType.ContainsKey(x.LegacyId) ? _targetCargo.ItemsOfType[x.LegacyId].Count : 0).ToString(); + return (_targetCargo.ItemsOfType.ContainsKey(x.ItemKey) ? _targetCargo.ItemsOfType[x.ItemKey].Cast().Sum(s=>s.Quantity) : 0).ToString(); + return (_targetCargo.ItemsOfType.ContainsKey(x.ItemKey) ? _targetCargo.ItemsOfType[x.ItemKey].Count : 0).ToString(); }, x => { if (x.IsHull) - return GameManager.DockedEntity.Children.Count(s => s.Hull.ItemId == x.LegacyId && s is Ship {IsPlayerShip: true}); + return GameManager.DockedEntity.Children.Count(s => s.Hull.ItemKey == x.ItemKey && s is Ship {IsPlayerShip: true}); if(x.Item is SimpleCommodity) - return _targetCargo.ItemsOfType.ContainsKey(x.LegacyId) ? _targetCargo.ItemsOfType[x.LegacyId].Cast().Sum(s=>s.Quantity) : 0; - return _targetCargo.ItemsOfType.ContainsKey(x.LegacyId) ? _targetCargo.ItemsOfType[x.LegacyId].Count : 0; + return _targetCargo.ItemsOfType.ContainsKey(x.ItemKey) ? _targetCargo.ItemsOfType[x.ItemKey].Cast().Sum(s=>s.Quantity) : 0; + return _targetCargo.ItemsOfType.ContainsKey(x.ItemKey) ? _targetCargo.ItemsOfType[x.ItemKey].Count : 0; })); Spreadsheet.ShowData( @@ -471,6 +471,8 @@ public class TradeMenu : MonoBehaviour public Guid LegacyId => Item?.ItemId ?? Guid.Empty; + public string ItemKey => Item?.ItemKey ?? ""; + public string Name => !string.IsNullOrWhiteSpace(TypedItem?.Name) ? TypedItem.Name : "Unknown Item"; public float Mass => TypedItem != null ? (float)TypedItem.Mass : 0f; diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 7db5aea08..69280230a 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -53,7 +53,8 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. while hosting the CultMesh state node. `Aetheria.State.ApplyPending` remains a bounded local operator applicator for both pending lanes. - Shared item domain state is still partly built around `AetheriaRuntimeItemReference`, - `ItemData`, `RuntimeBehaviorConfig`, derived GUID compatibility, and runtime catalog metadata. Dead + `ItemData`, `RuntimeBehaviorConfig`, derived GUID compatibility in behavior + config and broader simulation paths, and runtime catalog metadata. Dead user-record and galaxy-map-layer catalog roots have been deleted, and surviving runtime DTOs no longer carry legacy catalog group/table annotations. Newtonsoft, JsonKnownTypes, RethinkDB, LiteNetLib client transport, and the broken @@ -620,7 +621,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. behavior metadata instead of rebuilding config DTOs for lookup. `ItemInstance.Reference` stores the typed item key, `ItemInstance.ItemKey` is the runtime identity surface, and `ItemInstance.ItemId` is a derived - legacy-GUID compatibility projection; + legacy-GUID compatibility projection. `EquippedCargoBay.ItemsOfType`, + consumable activation, action-bar consumable fill/quantity display, item + transfer lookup, and trade owned-count rows now use item keys rather than + GUID-owned cargo indexes; there is no remaining `ItemInstance.Data` identity/backing field. The reader interface for this bridge is named `IRuntimeItemCatalogReader` and exposes only typed catalog row lookup. Behavior @@ -1199,9 +1203,9 @@ First Aetheria surfaces to publish: `Aetheria.Shared.Unity`; the remaining MessagePack reference is contained in the typed state package's `.cc` reader. - In progress: convert domain references from GUID/base-class patterns to - typed record refs. Item instances now carry typed item keys; remaining - cargo/simulation dictionaries still derive legacy GUIDs until those - structures move to typed keys. + typed record refs. Item instances and cargo inventory indexes now carry + typed item keys; remaining behavior config and broader simulation paths + still derive legacy GUIDs until those structures move to typed keys. - Remove runtime dependency on `ItemData` DTOs as item instance owners. 5. Mesh host @@ -1318,6 +1322,11 @@ First Aetheria surfaces to publish: `AetheriaRuntimeItemReference` and make the reference typed-key-first. `ItemInstance.ItemKey` is now the primary identity surface; `ItemId` is derived legacy compatibility for unfinished cargo and simulation paths. + - Done: move `EquippedCargoBay.ItemsOfType`, consumable activation lookup, + action-bar consumable quantity/fill, item transfer lookup, and trade owned + counts from derived `Guid ItemId` keys to typed `ItemKey` strings. GUID + behavior-config fields still translate at the cargo boundary until + behavior config construction moves to typed item references. - Done: delete the dead `ItemManager.GetRuntimeItemProjection` bridge after loadout generation stopped hydrating selected typed rows into `EquippableItemData` merely to instantiate equipment.