Aetheria/Assets/Scripts/ServerShared/EntitySerializer.cs
Meta Vens db4617d832 Boss gate: a boss zone spawns its faction's boss, and RunGoal derives whether the exit gate is open
EntityPack.Boss (key 21) carries Entity.IsBoss through save and Continue. GenerateZone adds one boss
per BossZones entry before the ambient return, flying the faction's BossHull or, with the gap logged,
its most expensive available ship hull. RunGoal (ServerShared) is the one owner of boss life and exit
state, derived from Zone.Entities.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:13:08 +02:00

246 lines
9.3 KiB
C#

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<OrbitData>(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.Boss = entity.IsBoss;
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.IsBoss = pack.Boss;
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<Weapon>()).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<ItemInstance> Items(EntityPack pack)
{
if (pack == null) throw new InvalidOperationException("This run's save is malformed: an entity is null. Start a new game.");
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<OrbitData> 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> Faction;
[Key(16)] public int[][] WeaponGroups;
// Keys 17-20 belong to the subclasses; the base's next free key is 21.
[Key(21)] public bool Boss;
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;
}
}