From 2a871e4cfb55712d8707a076327ccbcf8be544cf Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 14 Jun 2026 09:31:17 +0100 Subject: [PATCH] Use typed loadout candidate selectors --- .voidbot/state/aetheria.cc | Bin 82014 -> 84295 bytes .../Scripts/ServerShared/LoadoutGenerator.cs | 87 +++++++++++------- docs/aetheria-perfect-machine-map.md | 18 ++-- 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 7d8f7aca911d52b658b30a18cd7b9031b78e5fa2..54d048ce2e62c5e272bb9ec8619ab006532fcdde 100644 GIT binary patch delta 1544 zcmaJ>-)kII6y_!^)Py1`i8ihDh#&~rVSl7G$p(car5mgoN-z*n=Vs>Y?4>hvxA)%J zuu|L>!52krM;zbPT7Q5-kwRaz55+gp$5!xfXy}Uwf@glMNxS&8?3r`UJ?Hzr^Zj}8 z;4fDW{&00{qETIJ%v;rkxvk4=D!x7T!7#FrMdbK9MWj}(XOTk(j*l%&Tz~$+&A-@Z z$xAa-_VMyt*Ej$9`DFFOYXJsQ_1D}xRgR{EO7My=xho@6G5s#O6^DneaJj+CIT0q^ z8TRI2{Y;(#9)_s65i$ggJ~~E2Rs;?=+?O8x7R4HNgy~RM@X_N=A67SB%gcqMtuGy) zi{LKv?V|JPys=a^gc5uidZ>Wwa~j%G4ZuE2ePytczp`d9SZYPSze@jKv}Z?sK8;-c z&*v4cJh{yKWr|cIn&_od=OfYWzQMPBOdrj&Em$eHEKo@C2;~k$8Z~s4=n;Cc8mb+c%ME(m%@16K$l)P_?FYUY9~6v6HHp{-;9cD%bj zJxE8gLCGC$ePuAzG{#*vxskHChY*7v5f0iY5qg?_h40gls8RJ00^t#%R8Y`FMCSQE z-$tL(Bd0?|fL-Ek11%3W;oHgpwwpjaYlNa8oxD_oOZ?0dG6q(j&4f~{Noezks+2H& z3zlP6otnt!N)b#JEmk?~$gnLu*jB>zFh2j|)L>O+t!Q#1>5)u}!pVe{i%}{~ppkkq zIqwmx1#9j9qzNQGoiY_7?X84ES3rXWDb|jj1@5{JsJhJ^G%X0w@N}h&8RRa=cB)Z= zl^{?u@;W7oDT&!&#Z%TdJalN^v)I;G;&(UKTdne>h8MF(OZ#+G9@w6%bcZ zF6P)=2kglIc12+~IwF;@`%Plpb7rA@$(o(j5EgP;T9| mdb~USWb*b27L`f$MwWE{1iLg0Wo~{WKFMx9QpOsMt$zU*#wh~; delta 191 zcmX>;iS=Fs>xN}aTt+5V#)f*PriM`y8AT@_W$K2Dn1My|nX5RUYRsk^+A)ex=3tR$ zGEm&C$+GJQcz|2<$f&6ZJh`W|COzR4GvC1BdjCx2uXpPpsQxF2S; oxjEEmw&{L$jQKEO3uBP*^gDKpjc_5b%I!7wj0ZVkVwQ$c0GlB-CjbBd 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