using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using JsonKnownTypes; using MessagePack; using Newtonsoft.Json; using Unity.Mathematics; using static Unity.Mathematics.math; [MessagePackObject, Union(0, typeof(Ship)), Union(1, typeof(OrbitalEntity)), JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter))] public abstract class Entity : DatabaseEntry, IMessagePackSerializationCallbackReceiver { [JsonProperty("temperature"), Key(1)] public float Temperature; [JsonProperty("energy"), Key(2)] public float Energy; [JsonProperty("position"), Key(3)] public float2 Position; [JsonProperty("direction"), Key(4)] public float2 Direction = float2(0,1); [JsonProperty("velocity"), Key(5)] public float2 Velocity; [JsonProperty("cargo"), Key(6)] public readonly List Cargo; // Type: ItemInstance [JsonProperty("equipment"), Key(7)] public readonly List EquippedItems; // Type: Gear [JsonProperty("parent"), Key(8)] public Guid Parent; // Type: Entity [JsonProperty("children"), Key(9)] public List Children = new List(); // Type: Entity [JsonProperty("hull"), Key(10)] public Guid Hull; // Type: Gear [JsonProperty("persistedBehaviorData"), Key(11)] public Dictionary PersistedBehaviors; [JsonProperty("corporation"), Key(12)] public Guid Corporation; // [IgnoreMember] protected List Behaviors; [IgnoreMember] public Guid Zone; [IgnoreMember] public Dictionary> ItemBehaviors; [IgnoreMember] public Dictionary> Bindings; [IgnoreMember] public Dictionary Axes; [IgnoreMember] public Dictionary AxisOverrides = new Dictionary(); [IgnoreMember] public readonly Dictionary VisibilitySources = new Dictionary(); private Dictionary GearData; [IgnoreMember] public float Mass { get; private set; } [IgnoreMember] public float SpecificHeat { get; private set; } [IgnoreMember] public float Visibility => VisibilitySources.Values.Sum(); public Entity(GameContext context, Guid hull, IEnumerable items, IEnumerable cargo, Guid zone) { ID = Guid.NewGuid(); Context = context; Zone = zone; EquippedItems = items.ToList(); Cargo = cargo.ToList(); Hull = hull; RecalculateMass(); AddItemBehaviors(); } public void AddItemBehaviors() { var gear = EquippedItems.Append(Hull); var gearData = gear.ToDictionary(id => id, id => Context.Cache.Get(id)); GearData = gearData.Values.ToDictionary(g => g, g => g.ItemData); ItemBehaviors = gear .Where(i=> gearData[i].ItemData.Behaviors?.Any()??false) .ToDictionary(i=> gearData[i], i => gearData[i].ItemData.Behaviors .Select(bd => bd.CreateInstance(Context, this, gearData[i])) .OrderBy(b => b.GetType().GetCustomAttribute()?.Order ?? 0) .ToList()); Bindings = ItemBehaviors .Where(x => x.Value.Any(g => g is IActivatedBehavior)) .Select(x=> new {gear = x.Key, behaviors = x.Value .Where(g => g is IActivatedBehavior) .Cast().ToList()}) .ToDictionary(x => x.gear, x => x.behaviors); Axes = ItemBehaviors.Values .SelectMany(behaviors=>behaviors) .Where(b => b is IAnalogBehavior) .ToDictionary(b => b as IAnalogBehavior, b => 0f); foreach (var behavior in ItemBehaviors.Values .SelectMany(behaviors=>behaviors)) behavior.Initialize(); } public void RecalculateMass() { Mass = Context.Cache.Get(Hull).Mass + EquippedItems.Select(i => Context.Cache.Get(i)).Sum(i => i.Mass) + Cargo.Select(i => Context.Cache.Get(i)).Sum(ii => ii.Mass) + Children.Select(i => Context.Cache.Get(i)).Sum(c=>c.Mass); SpecificHeat = Context.Cache.Get(Hull).HeatCapacity + EquippedItems.Select(i => Context.Cache.Get(i)).Sum(i => i.HeatCapacity) + Cargo.Select(i => Context.Cache.Get(i)).Sum(ii => ii.HeatCapacity); } public IEnumerable GetBehaviors() where T : class, IBehavior { foreach (var behaviors in ItemBehaviors.Values) foreach (var behavior in behaviors) if (behavior is T b) yield return b; } public IEnumerable GetBehaviorData() where T : BehaviorData { foreach (var behaviors in ItemBehaviors.Values) foreach (var behavior in behaviors) if (behavior.Data is T b) yield return b; } public void AddHeat(float heat) { Temperature += heat / SpecificHeat; } public virtual void Update(float delta) { foreach(var analogBehavior in Axes.Keys) analogBehavior.SetAxis(AxisOverrides.ContainsKey(analogBehavior) ? AxisOverrides[analogBehavior] : Axes[analogBehavior]); foreach (var behavior in ItemBehaviors.SelectMany(item => item.Value)) behavior.Update(delta); foreach (var staleItem in GearData .Where(kvp=>kvp.Key.ItemData!=kvp.Value) .Select(kvp=>kvp.Key)) { // Item is stale, there's an updated one in the database cache //var actualItem = Context.Cache.Get(staleItem.ID); var staleBehaviors = ItemBehaviors[staleItem]; ItemBehaviors.Remove(staleItem); // We'll be reloading the item along with its behaviors, some data must be persisted var persistentData = staleBehaviors .Where(b => b is IPersistentBehavior) .Cast() .Select(b => b.Store()); // Regenerate the behavior list for the updated item ItemBehaviors[staleItem] = staleItem.ItemData.Behaviors .Select(bd => bd.CreateInstance(Context, this, staleItem)) .OrderBy(b => b.GetType().GetCustomAttribute()?.Order ?? 0).ToList(); foreach(var behavior in ItemBehaviors[staleItem]) behavior.Initialize(); // Restore state for persisted behaviors foreach(var persistentBehaviorData in ItemBehaviors[staleItem] .Where(b => b is IPersistentBehavior) .Cast() .Zip(persistentData, (behavior, data) => new {behavior, data})) persistentBehaviorData.behavior.Restore(persistentBehaviorData.data); // Regenerate activated behavior bindings if (Bindings.ContainsKey(staleItem)) { Bindings.Remove(staleItem); Bindings[staleItem] = ItemBehaviors[staleItem] .Where(b => b is IActivatedBehavior) .Cast().ToList(); } // Remove axis bindings and overrides for analog behaviors foreach (var axis in staleBehaviors.Where(b => b is IAnalogBehavior).Cast()) { Axes.Remove(axis); if (AxisOverrides.ContainsKey(axis)) AxisOverrides.Remove(axis); } // Regenerate axis bindings for analog behaviors foreach (var axis in ItemBehaviors[staleItem].Where(b => b is IAnalogBehavior).Cast()) Axes[axis] = 0; } foreach (var item in EquippedItems .Select(item => Context.Cache.Get(item)) .Where(item => item.ItemData.Performance(Temperature) < .01f)) item.Durability -= delta; if (Parent != Guid.Empty) { var parent = Context.Cache.Get(Parent); Position = parent.Position; Velocity = parent.Velocity; } } public void OnBeforeSerialize() { // Filter item behavior collections by those with any persistent behaviors // For each item create an object containing the item ID and a list of persistent behaviors // Then turn that into a dictionary mapping from item ID to an array of every behaviors persistent data PersistedBehaviors = ItemBehaviors .Where(x => x.Value.Any(b=>b is IPersistentBehavior)) .Select(x => new {gear=x.Key, behaviors = x.Value .Where(b=>b is IPersistentBehavior) .Cast()}) .ToDictionary(x=> x.gear.ID, x=>x.behaviors.Select(b => b.Store()).ToArray()); } public void OnAfterDeserialize() { RecalculateMass(); AddItemBehaviors(); var index = 0; // Iterate only over the behaviors of items which contain persistent data // Filter the behaviors for each item to get the persistent ones, then cast them and combine with the persisted data array for that item foreach (var persistentBehaviorData in ItemBehaviors .Where(itemBehaviors => PersistedBehaviors.ContainsKey(itemBehaviors.Key.ID)). SelectMany(itemBehaviors => itemBehaviors.Value .Where(b=> b is IPersistentBehavior) .Cast() .Zip(PersistedBehaviors[itemBehaviors.Key.ID], (behavior, data) => new{behavior, data}))) persistentBehaviorData.behavior.Restore(persistentBehaviorData.data); } }