diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 7d8f7aca9..54d048ce2 100644 Binary files a/.voidbot/state/aetheria.cc and b/.voidbot/state/aetheria.cc differ diff --git a/Assets/Scripts/ServerShared/LoadoutGenerator.cs b/Assets/Scripts/ServerShared/LoadoutGenerator.cs index ff48572e0..9339f8ff7 100644 --- a/Assets/Scripts/ServerShared/LoadoutGenerator.cs +++ b/Assets/Scripts/ServerShared/LoadoutGenerator.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using GameCult.Aetheria.State.Unity; using Unity.Mathematics; @@ -9,6 +8,21 @@ using Random = Unity.Mathematics.Random; public class LoadoutGenerator { + private enum RuntimeItemCandidateKind + { + Equipment, + Gear, + CargoBay, + DockingBay, + Hull + } + + private const string GearCategory = "GearData"; + private const string WeaponCategory = "WeaponItemData"; + private const string CargoBayCategory = "CargoBayData"; + private const string DockingBayCategory = "DockingBayData"; + private const string HullCategory = "HullData"; + public Random Random; public ItemManager ItemManager { get; } public AetheriaRuntimeCatalogSnapshot RuntimeCatalog { get; } @@ -93,7 +107,7 @@ public class LoadoutGenerator var emptyShape = entity.UnoccupiedSpace; - var dockingBayRow = RandomCatalogItem(2, item => FitsWithin(item, emptyShape)); + var dockingBayRow = RandomCatalogItem(RuntimeItemCandidateKind.DockingBay, 2, item => FitsWithin(item, emptyShape)); if (dockingBayRow == null) throw new InvalidLoadoutException("No compatible docking bay found for station!"); ToShape(dockingBayRow).FitsWithin(emptyShape, out var cargoRotation, out var cargoPosition); @@ -107,9 +121,9 @@ public class LoadoutGenerator OutfitEntity(entity); var cargo = entity.CargoBays.First(); - IEnumerable inventory = RandomCatalogItems(16, 1, - item => item.Category != "CargoBayData" && item.Category != "DockingBayData" && - (item.Category != "HullData" || item.HullType == nameof(HullType.Ship))); + IEnumerable inventory = RandomCatalogItems(RuntimeItemCandidateKind.Equipment, 16, 1, + item => item.Category != CargoBayCategory && item.Category != DockingBayCategory && + (item.Category != HullCategory || item.HullType == nameof(HullType.Ship))); inventory = inventory .Where(item => item != null); inventory = inventory @@ -127,18 +141,19 @@ public class LoadoutGenerator public AetheriaRuntimeCatalogItem RandomHull(HullType type, Predicate hullFilter = null) { - return RandomCatalogItem(0, item => + return RandomCatalogItem(RuntimeItemCandidateKind.Hull, 0, item => string.Equals(item.HullType, type.ToString(), StringComparison.Ordinal) && (hullFilter?.Invoke(item) ?? true)); } - public AetheriaRuntimeCatalogItem[] RandomCatalogItems( + private AetheriaRuntimeCatalogItem[] RandomCatalogItems( + RuntimeItemCandidateKind candidateKind, int count, float sizeExponent, - Predicate typedFilter = null) where T : EquippableItemData + Predicate typedFilter = null) { return RuntimeCatalog.EquipmentItems - .Where(IsTypedCandidate) + .Where(item => IsTypedCandidate(item, candidateKind)) .Where(item => item.Price > 0 && Guid.TryParse(item.ManufacturerLegacyId, out var manufacturer) && @@ -168,25 +183,23 @@ public class LoadoutGenerator ); } - private static bool IsTypedCandidate(AetheriaRuntimeCatalogItem item) where T : EquippableItemData + private static bool IsTypedCandidate(AetheriaRuntimeCatalogItem item, RuntimeItemCandidateKind candidateKind) { - var requestedType = typeof(T); - if (requestedType == typeof(EquippableItemData)) + switch (candidateKind) { - return !string.IsNullOrWhiteSpace(item.HardpointType); + case RuntimeItemCandidateKind.Equipment: + return !string.IsNullOrWhiteSpace(item.HardpointType); + case RuntimeItemCandidateKind.Gear: + return item.Category == GearCategory || item.Category == WeaponCategory; + case RuntimeItemCandidateKind.CargoBay: + return item.Category == CargoBayCategory || item.Category == DockingBayCategory; + case RuntimeItemCandidateKind.DockingBay: + return item.Category == DockingBayCategory; + case RuntimeItemCandidateKind.Hull: + return item.Category == HullCategory; + default: + throw new ArgumentOutOfRangeException(nameof(candidateKind), candidateKind, null); } - - if (requestedType == typeof(GearData)) - { - return item.Category == "GearData" || item.Category == "WeaponItemData"; - } - - if (requestedType == typeof(CargoBayData)) - { - return item.Category == "CargoBayData" || item.Category == "DockingBayData"; - } - - return string.Equals(item.Category, requestedType.Name, StringComparison.Ordinal); } private float ManufacturerDistancePenalty(Guid manufacturer) @@ -202,16 +215,22 @@ public class LoadoutGenerator : 1; } - public AetheriaRuntimeCatalogItem RandomCatalogItem( + private AetheriaRuntimeCatalogItem RandomCatalogItem( + RuntimeItemCandidateKind candidateKind, float sizeExponent, - Predicate typedFilter = null) where T : EquippableItemData + Predicate typedFilter = null) { - return RandomCatalogItems(1, sizeExponent, typedFilter).FirstOrDefault(); + return RandomCatalogItems(candidateKind, 1, sizeExponent, typedFilter).FirstOrDefault(); } - public AetheriaRuntimeCatalogItem RandomCatalogItem(AetheriaRuntimeHardpoint hardpoint, float sizeExponent, Predicate filter = null) where T : EquippableItemData + private AetheriaRuntimeCatalogItem RandomCatalogItem( + RuntimeItemCandidateKind candidateKind, + AetheriaRuntimeHardpoint hardpoint, + float sizeExponent, + Predicate filter = null) { - return RandomCatalogItem( + return RandomCatalogItem( + candidateKind, sizeExponent, item => FitsHardpoint(item, hardpoint) && (filter?.Invoke(item) ?? true)); } @@ -308,7 +327,7 @@ public class LoadoutGenerator : entity is OrbitalEntity ? nameof(TurretControllerData) : null; - var controllerRow = RandomCatalogItem(hardpoint, 2, item => HasBehaviorKind(item, controllerBehaviorKind)); + var controllerRow = RandomCatalogItem(RuntimeItemCandidateKind.Gear, hardpoint, 2, item => HasBehaviorKind(item, controllerBehaviorKind)); if (controllerRow == null) throw new InvalidLoadoutException("No compatible controller found for entity!"); var controller = ItemManager.CreateEquippableInstance(controllerRow); @@ -326,7 +345,7 @@ public class LoadoutGenerator : Guid.TryParse(itemRow.LegacyId, out var previousItemId) ? entity.Equipment.FirstOrDefault(item => item.EquippableItem.Data.ItemId == previousItemId) : null; - itemRow ??= RandomCatalogItem(hardpoint, 2); + itemRow ??= RandomCatalogItem(RuntimeItemCandidateKind.Gear, hardpoint, 2); if (itemRow == null) ItemManager.Log($"No compatible item found for entity {hardpoint.Type} hardpoint!"); else { @@ -346,7 +365,7 @@ public class LoadoutGenerator var emptyShape = entity.UnoccupiedSpace; - var cargoRow = RandomCatalogItem(3, item => item.Category != "DockingBayData" && FitsWithin(item, emptyShape)); + var cargoRow = RandomCatalogItem(RuntimeItemCandidateKind.CargoBay, 3, item => item.Category != DockingBayCategory && FitsWithin(item, emptyShape)); if (cargoRow == null) throw new InvalidLoadoutException("No compatible cargo bay found for entity!"); ToShape(cargoRow).FitsWithin(emptyShape, out var cargoRotation, out var cargoPosition); @@ -357,7 +376,7 @@ public class LoadoutGenerator emptyShape = entity.UnoccupiedSpace; - var capacitorRow = RandomCatalogItem(2, + var capacitorRow = RandomCatalogItem(RuntimeItemCandidateKind.Gear, 2, item => item.BehaviorKinds.Contains(nameof(CapacitorData), StringComparer.Ordinal) && FitsWithin(item, emptyShape)); if (capacitorRow == null) throw new InvalidLoadoutException("No compatible capacitor found for entity!"); diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 5874180ad..2760872e4 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -155,11 +155,11 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. corporation projection. There is no live console item-spawn command; future operator item actions should be typed command documents or Eve/CultUI commands, not in-client catalog hydration shortcuts. `LoadoutGenerator` also - receives the typed runtime catalog and uses it to own - item candidate selection before hydrating selected legacy item DTOs by ID for - remaining behavior construction and legacy simulation checks. Hull type, - hardpoint type, shape fit, station bay fit, hull/category, and behavior-kind - prefilters now run against typed catalog rows before any DTO projection is hydrated. The unused `TradeMenuDebug` + receives the typed runtime catalog and uses typed candidate-kind selectors + for item selection and instantiation. Hull type, hardpoint type, shape fit, + station bay fit, hull/category, and behavior-kind prefilters run against typed + catalog rows; the remaining behavior bridge builds temporary `BehaviorData` + config from typed payloads only at behavior-constructor boundaries. The unused `TradeMenuDebug` script has been deleted instead of preserving an old uGUI debug path that turned typed trade rows back into legacy `ItemData` objects. That hydration now comes from typed state, not `AetherDB.msgpack`. The surviving trade menu @@ -705,8 +705,12 @@ First Aetheria surfaces to publish: - Done: move `LoadoutGenerator` weighted candidate selection, hardpoint fit, station bay fit, cargo/capacitor fit, selected-item reuse, hull/category, hull type, hull conductivity setup, and behavior-kind prefilters onto typed - runtime catalog rows; selected legacy DTO projection remains only as the - item-instantiation bridge after a typed row wins. + runtime catalog rows; selected item instantiation now receives typed rows + directly. + - Done: replace `LoadoutGenerator`'s legacy DTO generic selectors with + private typed candidate-kind selectors, so loadout code no longer asks for + `HullData`, `GearData`, `CargoBayData`, or `EquippableItemData` as + candidate-selection authority. - Done: delete unused `TradeMenuDebug`; the old debug uGUI trade path no longer hydrates typed trade rows back into legacy `ItemData` objects. - Done: move surviving trade menu size, hardpoint, and behavior filters onto