using System; using System.Collections; using System.Collections.Generic; using System.Linq; using GameCult.Caching; using MessagePack; using CultMath; public static class EntitySerializer { public static EntityPack Pack(Entity entity) { EntityPack pack; if (entity is OrbitalEntity orbital) pack = new OrbitalEntityPack { Orbit = new CultRecordRef(orbital.OrbitData), SecurityLevel = orbital.SecurityLevel, SecurityRadius = orbital.SecurityRadius }; else if (entity is Ship ship) pack = new ShipPack { Position = ship.Position, Direction = ship.Direction, IsPlayerShip = ship.IsPlayerShip }; else throw new ArgumentException("Attempted to pack an instance of abstract class Entity!"); pack.Settings = entity.Settings; pack.Hull = entity.Hull; pack.Name = entity.Name; pack.Faction = entity.Faction == null ? default : entity.ItemManager.ItemData.RefOf(entity.Faction); pack.Equipment = entity.Equipment.Select(e => (e.Position, e.EquippableItem)).ToArray(); pack.CargoBays = entity.CargoBays.Select(e => (e.Position, e.EquippableItem)).ToArray(); pack.DockingBays = entity.DockingBays.Select(e => (e.Position, e.EquippableItem)).ToArray(); pack.DockingBayAssignments = entity.DockingBays.Select(x => entity.Children.IndexOf(x.DockedShip)).ToArray(); pack.CargoContents = entity.CargoBays.Select(b => b.Cargo.Select(i => (i.Value, i.Key)).ToArray()).ToArray(); pack.DockingBayContents = entity.DockingBays.Select(b => b.Cargo.Select(i => (i.Value, i.Key)).ToArray()).ToArray(); pack.Armor = entity.Armor; pack.Temperature = entity.Temperature; pack.Conductivity = entity.HullConductivity; pack.Children = entity.Children.Select(Pack).ToArray(); pack.WeaponGroups = entity.WeaponGroups.Select(wg => wg.items.Select(item => entity.Equipment.IndexOf(item)).ToArray()).ToArray(); return pack; } public static Entity Unpack(ItemManager itemManager, Zone zone, EntityPack pack) { return pack switch { ShipPack shipPack => Unpack(itemManager, zone, shipPack), OrbitalEntityPack orbitalEntityPack => Unpack(itemManager, zone, orbitalEntityPack), _ => null }; } private static Ship Unpack(ItemManager itemManager, Zone zone, ShipPack pack) { var entity = new Ship(itemManager, zone, pack.Hull, pack.Settings); Restore(itemManager, zone, pack, entity); entity.Position = pack.Position; entity.Direction = pack.Direction; entity.IsPlayerShip = pack.IsPlayerShip; return entity; } private static OrbitalEntity Unpack(ItemManager itemManager, Zone zone, OrbitalEntityPack pack) { var entity = new OrbitalEntity(itemManager, zone, pack.Hull, pack.Orbit.Key, pack.Settings); Restore(itemManager, zone, pack, entity); entity.SecurityLevel = pack.SecurityLevel; entity.SecurityRadius = pack.SecurityRadius; if (pack.Story >= 0) entity.Story = zone.GalaxyZone.Locations[pack.Story]; return entity; } private static void Restore(ItemManager itemManager, Zone zone, EntityPack pack, Entity entity) { entity.Name = pack.Name; entity.Faction = itemManager.ItemData.Get(pack.Faction); entity.Children = pack.Children.Select(c => { var child = Unpack(itemManager, zone, c); child.Parent = entity; return child; }).ToList(); foreach (var (position, item) in pack.Equipment) entity.TryEquip(item, position); foreach (var (position, item) in pack.CargoBays) entity.TryEquip(item, position); foreach (var (position, item) in pack.DockingBays) entity.TryEquip(item, position); for (var i = 0; i < pack.DockingBayAssignments.Length; i++) { if (pack.DockingBayAssignments[i] != -1) entity.DockingBays[i].DockedShip = entity.Children[pack.DockingBayAssignments[i]] as Ship; } for (var bayIndex = 0; bayIndex < pack.CargoContents.Length; bayIndex++) if(entity.CargoBays.Count >= bayIndex + 1) foreach (var (position, item) in pack.CargoContents[bayIndex]) entity.CargoBays[bayIndex].TryStore(item, position); for (var bayIndex = 0; bayIndex < pack.DockingBayContents.Length; bayIndex++) if(entity.DockingBays.Count >= bayIndex + 1) foreach (var (position, item) in pack.DockingBayContents[bayIndex]) entity.DockingBays[bayIndex].TryStore(item, position); entity.Temperature = pack.Temperature; entity.Armor = pack.Armor; var hullData = itemManager.GetData(entity.Hull); foreach(var v in hullData.Shape.Coordinates) entity.HullConductivity[v.x,v.y] = pack.Conductivity[v.x,v.y]; entity.WeaponGroups = pack.WeaponGroups.Select(itemIndices => { var items = itemIndices.Select(i => entity.Equipment[i]); return (items.Select(i => i.GetBehavior()).ToList(), items.ToList()); }).ToArray(); } // Every item instance an entity pack carries, recursively through its children: hull, equipment, cargo and // docking bay units, and their stored contents. The provenance ledger's GC root walk. public static IEnumerable Items(EntityPack pack) { yield return pack.Hull; foreach (var (_, item) in pack.Equipment) yield return item; foreach (var (_, item) in pack.CargoBays) yield return item; foreach (var (_, item) in pack.DockingBays) yield return item; foreach (var bay in pack.CargoContents) foreach (var (_, item) in bay) yield return item; foreach (var bay in pack.DockingBayContents) foreach (var (_, item) in bay) yield return item; foreach (var child in pack.Children) foreach (var item in Items(child)) yield return item; } } [MessagePackObject] public class SavedStory { [Key(0)] public string StoryJson; [Key(1)] public string StateJson; } [MessagePackObject] public class ShipPack : EntityPack { [Key(17)] public float3 Position; [Key(18)] public float2 Direction; [Key(19)] public bool IsPlayerShip; } [MessagePackObject] public class OrbitalEntityPack : EntityPack { [Key(17)] public CultRecordRef Orbit; [Key(18)] public int Story = -1; [Key(19)] public SecurityLevel SecurityLevel; [Key(20)] public float SecurityRadius; } [MessagePackObject, Union(0, typeof(OrbitalEntityPack)), Union(1, typeof(ShipPack))] public abstract class EntityPack { [Key(0)] public string Name; [Key(1)] public EquippableItem Hull; [Key(2)] public (int2 position, EquippableItem item)[] Equipment; [Key(3)] public (int2 position, EquippableItem item)[] CargoBays; [Key(4)] public (int2 position, EquippableItem item)[] DockingBays; // Key(5) retired 2026-09-17; a deleted field used it. Do not reuse. [Key(6)] public float[,] Temperature; [Key(7)] public float[,] Armor; [Key(8)] public bool2[,] Conductivity; [Key(10)] public int[] DockingBayAssignments; [Key(11)] public (int2 position, ItemInstance item)[][] CargoContents; [Key(12)] public (int2 position, ItemInstance item)[][] DockingBayContents; [Key(13)] public EntityPack[] Children; [Key(14)] public EntitySettings Settings; [Key(15)] public CultRecordRef Faction; [Key(16)] public int[][] WeaponGroups; private int _price; public int Price(ItemManager itemManager) { if (_price != 0) return _price; var hullData = itemManager.GetData(Hull); _price = hullData.Price; foreach (var (_, item) in Equipment) { var itemData = itemManager.GetData(item); _price += itemData.Price; } foreach (var (_, item) in CargoBays) { var itemData = itemManager.GetData(item); _price += itemData.Price; } foreach (var (_, item) in DockingBays) { var itemData = itemManager.GetData(item); _price += itemData.Price; } foreach (var t in CargoContents) { foreach (var (_, item) in t) { var itemData = itemManager.GetData(item); if (item is SimpleCommodity s) _price += itemData.Price * s.Quantity; else _price += itemData.Price; } } foreach (var t in DockingBayContents) { foreach (var (_, item) in t) { var itemData = itemManager.GetData(item); if (item is SimpleCommodity s) _price += itemData.Price * s.Quantity; else _price += itemData.Price; } } return _price; } }