From f85033b6c67eb67f86ce47dba708e361ab0b0e5b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 14 Jun 2026 04:56:01 +0100 Subject: [PATCH] Use typed catalog rows for equip acceptance --- .voidbot/state/aetheria.cc | Bin 65004 -> 65417 bytes Assets/Scripts/ServerShared/Entity.cs | 185 ++++++++++++++++++++------ docs/aetheria-perfect-machine-map.md | 34 +++-- 3 files changed, 164 insertions(+), 55 deletions(-) diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 8a695c28c005f938aa9334df686884cbc2abd76f..9fec9d0998fc5827f01749fe14a6555d044cce40 100644 GIT binary patch delta 1667 zcmZuyO>9(E6m~jLTZkcG2!SXb5>q5hXP~qt>PjsQ#DEEu5W6wA_nn!!_PuwX_ukib zx*3Ua;nLg%iHR{0MKmsIW=)Js+A8_^od!2ZEVN$ zBY#{N9NqEr?RVDaMb1*d_exP@m>Xyd(|{sn9Q{%wQ@q;<7l5ZsYta^*86ooqusi|D zd0DWW_gWbpLV~Gb8gjV`rA7<84%m&wo&7KAQ<2@PT{)8*VS3GT2D5o#bORPtk1!;c z3VPD7&NwWgHl1D0JvJ%`smwd5pxa9nGYI}(!In~V#pak{AQhY~)71YqlCW@*AP9r~ z40M+j^gx%Xq>zG`nx0?N{p!s9rXy5Bs_j>a*)l=pOm$={k$hR?9adtDf}kvwB26@? zk~GVZZiadq+Dc|nDETgOg9*$nyrr988O$}+zgKIVXGM@X))9GM7{OApjvBg-WCYq` zDkFSMDb~}zn{+^hQIi#XkGJX7Q6dR14>fK=?B?a%yFg%QV|=!aNa$0^64@_JD4P;u zCxT~C)j1b6e*@E06VJ9(NKSOlbX`h-agK!{mthm;qW(*)2j|(^C1T~F59I+|Ltbu^ znv_1|rPd-V(?BbVM^Nl{qx<`RU%)j3M4G%gsfjuQmC$4iS*8hOas}zFp{ia&kw;Bq z7M|3=^L~PMaP`nY8C#pw4!xO>k*AWYWNH)nO% z{lT?6&pbWBWBWsNDAFwwGiilo!Aw{CaPIm1xg##Zuc7mUU>AeY&Q=j|a4b9YqP_L$ zgkAmB)>iE9`!8Uqv(*zXf3UT~6^;ErDz&_|q6b=4!{lm?N;kj76^bkM_#(FTCxhkt zpY5!@ZFg_Z%*|A(bkTBAbg-wUw%n~{|sb()INGBtKHLn_v)jo!<|$9 P%fXSqei*N|rWgMOIEHA& delta 1231 zcmZuxPe@cj7;jyzR75Z<3bHwb1?j4wr5%E@EDJ-llTk4H-nYAh^JboJ=IO4N#iMj7 zh7KJfi2hs*j6GL$Na$vVD5Ha(`a_+{Q$c<6_MeO0=KbC`GvDveH;aGc-+#xKAJ;YY z51i`lOAMYInz|j^YhKsgEF*&h5hCOB6=X0%cGn-O`|+%BwEo@dwb}DCA@hB3hiQjNUXCUw>Cl)6{&XEA9Ye>_6vpm(1 zf8=n=ypORvS2IxBki?6GBwUI#1VqjWMJOq^aE@w9QVh6DQZgV+Xb2cC$~`5>wx*LI z087D57&n77M{6QN=KDuA+NVO!r8~34JP@9GMHS z21yp{$7psk(!n;QnCEk3B0W#KOy`OcF_A7xQr@9d&!i6yOEKpn&=DP7QdBq6$px;d zN?^mYAhZl}<5U1gP39m_MV-3S0dtAVl+RHxo)^`ZO|4MH&~)>cNCK-s~1=;0x# zn(m3d2z4CrHBx~t;!0;4+ap$Wk7m0Sn@7~zmxK(Rwc0vTC>?~3#$%BWL=Q%9IY|J#bPdvQ>S#8DB z(5-wXP|^?$R2dnf^%`>smQjV_e>v6gF)JUAcVb=w>4uJFToWm}Rn x.SortPosition).ToArray(); - var hullData = ItemManager.GetData(Hull) as HullData; - var itemData = ItemManager.GetData(item.EquippableItem); - foreach (var i in hullData.Shape.Coordinates) + var itemShape = GetItemShape(item.EquippableItem); + var itemCellCount = Math.Max(itemShape.Coordinates.Length, 1); + foreach (var i in GetHullShape().Coordinates) if (GearOccupancy[i.x, i.y] == item) { - ThermalMass[i.x, i.y] -= ItemManager.GetThermalMass(item.EquippableItem) / itemData.Shape.Coordinates.Length; + ThermalMass[i.x, i.y] -= ItemManager.GetThermalMass(item.EquippableItem) / itemCellCount; GearOccupancy[i.x, i.y] = null; } - Mass -= itemData.Mass; + Mass -= ItemManager.GetMass(item.EquippableItem); foreach (var b in item.Behaviors) { if (b is Weapon weapon) @@ -519,44 +519,136 @@ public abstract class Entity return item.EquippableItem; } - // Check whether the given item will fit when its origin is placed at the given coordinate - private bool ItemFits(EquippableItemData itemData, HullData hullData, EquippableItem item, int2 hullCoord) + private Shape GetHullShape() { + return GetHullShape(ItemManager.GetRuntimeItem(Hull)); + } + + private static Shape GetHullShape(AetheriaRuntimeCatalogItem typedHull) + { + return ToShape(typedHull?.ShapeWidth ?? 1, typedHull?.ShapeHeight ?? 1, typedHull?.ShapeCells); + } + + private static Shape GetHullInteriorShape(AetheriaRuntimeCatalogItem typedHull) + { + return ToShape(typedHull?.InteriorShapeWidth ?? 1, typedHull?.InteriorShapeHeight ?? 1, typedHull?.InteriorShapeCells); + } + + private Shape GetItemShape(EquippableItem item) + { + return GetItemShape(ItemManager.GetRuntimeItem(item)); + } + + private static Shape GetItemShape(AetheriaRuntimeCatalogItem typedItem) + { + return ToShape(typedItem?.ShapeWidth ?? 1, typedItem?.ShapeHeight ?? 1, typedItem?.ShapeCells); + } + + private static AetheriaRuntimeHardpoint GetHardpointAt(AetheriaRuntimeCatalogItem typedHull, int2 hullCoord) + { + if (typedHull == null) return null; + + foreach (var hardpoint in typedHull.Hardpoints) + { + var hardpointShape = ToShape(hardpoint.ShapeWidth, hardpoint.ShapeHeight, hardpoint.ShapeCells); + var localCoord = hullCoord - new int2(hardpoint.PositionX, hardpoint.PositionY); + if (hardpointShape[localCoord]) + return hardpoint; + } + + return null; + } + + private static HardpointType GetHardpointType(AetheriaRuntimeCatalogItem typedItem, HardpointType fallback) + { + return typedItem != null && Enum.TryParse(typedItem.HardpointType, out HardpointType hardpointType) + ? hardpointType + : fallback; + } + + private static HardpointType GetHardpointType(AetheriaRuntimeHardpoint hardpoint, HardpointType fallback) + { + return hardpoint != null && Enum.TryParse(hardpoint.Type, out HardpointType hardpointType) + ? hardpointType + : fallback; + } + + private static ItemRotation GetRotation(AetheriaRuntimeHardpoint hardpoint) + { + return hardpoint != null && Enum.TryParse(hardpoint.Rotation, out ItemRotation rotation) + ? rotation + : ItemRotation.None; + } + + private static bool IsCargoBay(AetheriaRuntimeCatalogItem typedItem) + { + return typedItem != null && + (string.Equals(typedItem.Category, "CargoBayData", StringComparison.Ordinal) || + string.Equals(typedItem.Category, "DockingBayData", StringComparison.Ordinal)); + } + + private static bool IsDockingBay(AetheriaRuntimeCatalogItem typedItem) + { + return typedItem != null && string.Equals(typedItem.Category, "DockingBayData", StringComparison.Ordinal); + } + + private static Shape ToShape(int width, int height, IReadOnlyList cells) + { + var shape = new Shape(Math.Max(width, 1), Math.Max(height, 1)); + if (cells == null) return shape; + + foreach (var cell in cells) + shape[new int2(cell.X, cell.Y)] = true; + + return shape; + } + + // Check whether the given item will fit when its origin is placed at the given coordinate + private bool ItemFits(AetheriaRuntimeCatalogItem typedItem, AetheriaRuntimeCatalogItem typedHull, EquippableItem item, int2 hullCoord) + { + var hullShape = GetHullShape(typedHull); + var itemShape = GetItemShape(typedItem); + var itemHardpointType = GetHardpointType(typedItem, HardpointType.Tool); + if (itemShape.Coordinates.Length == 0) return false; + // If the given coordinate isn't even in the ship it obviously won't fit - if (!hullData.Shape[hullCoord]) return false; + if (!hullShape[hullCoord]) return false; // Items without specific hardpoints on the ship can be freely rotated and placed anywhere - if (itemData.HardpointType == HardpointType.Tool) + if (itemHardpointType == HardpointType.Tool) { + var hullInterior = GetHullInteriorShape(typedHull); // Check every cell of the item's shape - foreach (var i in itemData.Shape.Coordinates) + foreach (var i in itemShape.Coordinates) { // If there is any gear already occupying that space, it won't fit // If there's a hardpoint there, it won't fit // Thermal items have their own layer and do not collide with gear - var itemCoord = hullCoord + itemData.Shape.Rotate(i, item.Rotation); - if (!hullData.InteriorCells[itemCoord] || - itemData.HardpointType == HardpointType.Tool && Hardpoints[itemCoord.x, itemCoord.y] != null || + var itemCoord = hullCoord + itemShape.Rotate(i, item.Rotation); + if (!hullInterior[itemCoord] || + GetHardpointAt(typedHull, itemCoord) != null || GearOccupancy[itemCoord.x, itemCoord.y] != null) return false; } } else { - var hardpoint = Hardpoints[hullCoord.x, hullCoord.y]; + var hardpoint = GetHardpointAt(typedHull, hullCoord); // If there's no hardpoint there, it won't fit if (hardpoint == null) return false; // If the hardpoint type doesn't match the item, it won't fit - if (hardpoint.Type != itemData.HardpointType) return false; + if (GetHardpointType(hardpoint, HardpointType.Hull) != itemHardpointType) return false; // Items placed in hardpoints are automatically aligned to hardpoint rotation - item.Rotation = hardpoint.Rotation; + item.Rotation = GetRotation(hardpoint); // Inset the shapes of both item and hardpoint - var itemShapeInset = hullData.Shape.Inset(itemData.Shape, hullCoord, item.Rotation); - var hardpointShapeInset = hullData.Shape.Inset(hardpoint.Shape, hardpoint.Position); + var itemShapeInset = hullShape.Inset(itemShape, hullCoord, item.Rotation); + var hardpointShapeInset = hullShape.Inset( + ToShape(hardpoint.ShapeWidth, hardpoint.ShapeHeight, hardpoint.ShapeCells), + new int2(hardpoint.PositionX, hardpoint.PositionY)); // Check every cell of the hardpoint shape for existing items foreach(var v in hardpointShapeInset.Coordinates) @@ -583,9 +675,9 @@ public abstract class Entity // Don't allow equipping while deployed if (_active) return false; - var itemData = ItemManager.GetData(item); - var hullData = ItemManager.GetData(Hull) as HullData; - return ItemFits(itemData, hullData, item, hullCoord); + var typedItem = ItemManager.GetRuntimeItem(item); + var typedHull = ItemManager.GetRuntimeItem(Hull); + return typedItem != null && typedHull != null && ItemFits(typedItem, typedHull, item, hullCoord); } public bool TryFindSpace(EquippableItem item, out int2 hullCoord) @@ -596,16 +688,23 @@ public abstract class Entity hullCoord = int2.zero; return false; } - var itemData = ItemManager.GetData(item); - var hullData = ItemManager.GetData(Hull) as HullData; + var typedItem = ItemManager.GetRuntimeItem(item); + var typedHull = ItemManager.GetRuntimeItem(Hull); + if (typedItem == null || typedHull == null) + { + hullCoord = int2.zero; + return false; + } + + var itemHardpointType = GetHardpointType(typedItem, HardpointType.Tool); // Tools and thermal equipment can be installed anywhere on the ship // Search the whole ship for somewhere the item will fit - if (itemData.HardpointType == HardpointType.Tool) + if (itemHardpointType == HardpointType.Tool) { - foreach (var hullCoord2 in hullData.InteriorCells.Coordinates) + foreach (var hullCoord2 in GetHullInteriorShape(typedHull).Coordinates) { - if (ItemFits(itemData, hullData, item, hullCoord2)) + if (ItemFits(typedItem, typedHull, item, hullCoord2)) { hullCoord = hullCoord2; return true; @@ -617,14 +716,15 @@ public abstract class Entity // Search the ship for an empty hardpoint that matches the type and shape of the item else { - foreach (var hardpoint in hullData.Hardpoints) + foreach (var hardpoint in typedHull.Hardpoints) { - if(hardpoint.Type == itemData.HardpointType) + if(GetHardpointType(hardpoint, HardpointType.Hull) == itemHardpointType) { - foreach (var hardpointCoord in hardpoint.Shape.Coordinates) + var hardpointShape = ToShape(hardpoint.ShapeWidth, hardpoint.ShapeHeight, hardpoint.ShapeCells); + foreach (var hardpointCoord in hardpointShape.Coordinates) { - var hullCoord2 = hardpoint.Position + hardpointCoord; - if (ItemFits(itemData, hullData, item, hullCoord2)) + var hullCoord2 = new int2(hardpoint.PositionX, hardpoint.PositionY) + hardpointCoord; + if (ItemFits(typedItem, typedHull, item, hullCoord2)) { hullCoord = hullCoord2; return true; @@ -647,17 +747,20 @@ public abstract class Entity // Don't allow equipping while deployed if (_active) return false; - var itemData = ItemManager.GetData(item); - var hullData = ItemManager.GetData(Hull) as HullData; + var typedItem = ItemManager.GetRuntimeItem(item); + var typedHull = ItemManager.GetRuntimeItem(Hull); + if (typedItem == null || typedHull == null) return false; + var itemHardpointType = GetHardpointType(typedItem, HardpointType.Tool); + var itemShape = GetItemShape(typedItem); - if (!ItemFits(itemData, hullData, item, hullCoord)) return false; + if (!ItemFits(typedItem, typedHull, item, hullCoord)) return false; EquippedItem equippedItem; - if (itemData.HardpointType == HardpointType.Tool) + if (itemHardpointType == HardpointType.Tool) { - if(itemData is CargoBayData) + if(IsCargoBay(typedItem)) { - if (itemData is DockingBayData) + if (IsDockingBay(typedItem)) { equippedItem = new EquippedDockingBay(ItemManager, item, hullCoord, this, $"{Name} Docking Bay {DockingBays.Count + 1}"); DockingBays.Add((EquippedDockingBay) equippedItem); @@ -701,15 +804,15 @@ public abstract class Entity // equippedItem.OnOnline += () => ItemOnline.OnNext(equippedItem); // equippedItem.OnOffline += () => ItemOffline.OnNext(equippedItem); - foreach (var i in itemData.Shape.Coordinates) + foreach (var i in itemShape.Coordinates) { - var occupiedCoord = hullCoord + itemData.Shape.Rotate(i, item.Rotation); + var occupiedCoord = hullCoord + itemShape.Rotate(i, item.Rotation); // TODO: Track thermal mass of cargo bay contents as reactive property - ThermalMass[occupiedCoord.x, occupiedCoord.y] += ItemManager.GetThermalMass(item) / itemData.Shape.Coordinates.Length; + ThermalMass[occupiedCoord.x, occupiedCoord.y] += ItemManager.GetThermalMass(item) / itemShape.Coordinates.Length; GearOccupancy[occupiedCoord.x, occupiedCoord.y] = equippedItem; } - Mass += itemData.Mass; + Mass += ItemManager.GetMass(item); _orderedEquipment = Equipment.OrderBy(x => x.SortPosition).ToArray(); return true; } diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index ba9962d7f..91c3bcb1b 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -180,9 +180,12 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. now reads cargo interior shape, item fit shape, simple commodity max stack, cargo bay mass, and cargo bay thermal mass from typed runtime catalog rows; `InventoryPanel` lowers cargo-grid display from that typed `InteriorShape`. - Final equipment fit/equip acceptance, docking bay max-size, and behavior - construction still belong to legacy simulation projections until those owners - are rebuilt. + Final equipment fit/equip acceptance also reads typed item shape, hardpoint + type, hull shape, hull interior, hardpoint masks, cargo/docking categories, + and mass/thermal mass from runtime catalog rows. Hull initialization, + `Entity.UnoccupiedSpace` loadout preselection, shield/thermal simulation, + docking bay max-size, and behavior construction still belong to legacy + simulation projections until those owners are rebuilt. The item properties panel also reads typed catalog title names, descriptions, manufacturer, base mass, max durability, thermal bounds, and thermal performance curve keys for basic item presentation without hydrating legacy @@ -199,9 +202,8 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. max durability only, with current runtime durability as the generic denominator for incomplete typed rows. Inventory drag preview occupancy now projects typed item shape cells only into - the local `Shape` grid; final fit and equip acceptance still belong to - `ItemFits`, `TryEquip`, and inventory mutation paths until those simulation - owners are rebuilt. + the local `Shape` grid, and final fit/equip acceptance shares the typed + runtime catalog geometry path in `Entity.ItemFits` and `TryEquip`. - `Galaxy` generation no longer accepts a runtime item catalog reader or `ItemManager`. Sector and tutorial generation receive the package-owned typed runtime catalog. `Galaxy` projects typed corporation v2 records into temporary legacy @@ -686,14 +688,18 @@ First Aetheria surfaces to publish: - Done: move `EntityInstance` hull shape and hardpoint presentation/damage masks onto typed catalog rows; legacy `HullData` no longer supplies hull shape or hardpoint lists to scene entity instances. - - Done: move `InventoryPanel` entity-grid hull shape/interior rendering and - thermal overlay masking onto typed catalog rows; legacy `HullData` no - longer supplies equipment-grid display geometry. - - Done: move `EquippedCargoBay` cargo geometry, item fit shape, simple stack - limits, cargo mass, cargo thermal mass, and `InventoryPanel` cargo-grid - lowering onto typed catalog rows; legacy `CargoBayData` remains only in - later simulation projections such as docking max-size and behavior - construction. + - Done: move `InventoryPanel` entity-grid hull shape/interior rendering and + thermal overlay masking onto typed catalog rows; legacy `HullData` no + longer supplies equipment-grid display geometry. + - Done: move `EquippedCargoBay` cargo geometry, item fit shape, simple stack + limits, cargo mass, cargo thermal mass, and `InventoryPanel` cargo-grid + lowering onto typed catalog rows; legacy `CargoBayData` remains only in + later simulation projections such as docking max-size and behavior + construction. + - Done: move final `Entity.ItemFits`, `TryFindSpace`, `TryEquip`, and + `TryUnequip` gear occupancy/mass deltas onto typed catalog rows for item + shape, hull shape/interior, hardpoint masks, cargo/docking category, and + mass/thermal mass. - Done: delete the console `give` command instead of preserving a debug operator path that hydrated typed item rows back into legacy item DTOs. - Done: delete `ItemManager.GetCatalogEntries` after all live callers