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> Behaviors; [IgnoreMember] public Dictionary Switches; [IgnoreMember] public Dictionary Triggers; [IgnoreMember] public Dictionary Axes; [IgnoreMember] public Dictionary AxisOverrides = new Dictionary(); [IgnoreMember] public readonly Dictionary VisibilitySources = new Dictionary(); private Dictionary GearData; private Dictionary> ItemBehaviors; [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); // Associate items with behavior list, used only for persistence ItemBehaviors = gear .Where(i=> gearData[i].ItemData.Behaviors?.Any()??false) .ToDictionary(i=> gearData[i], i => gearData[i].ItemData.Behaviors .OrderBy(bd => bd.GetType().GetCustomAttribute()?.Order ?? 0) .Select(bd => bd.CreateInstance(Context, this, gearData[i])) .ToList()); var behaviorGroups = ItemBehaviors .SelectMany(x => x.Value .Select(b => new {item = x.Key, behavior = b})) .GroupBy(ib => new {ib.item, ib.behavior.Data.Group}); Behaviors = behaviorGroups .Select((grouping, i) => new {index = i, group = grouping}) .ToDictionary(ig => ig.index, ig => ig.group.Select(x => x.behavior).ToList()); Switches = Behaviors .Where(x => x.Value.Any(g => g is Switch)) .Select((x, i) => new {index = i, s = x.Value.First(g => g is Switch) as Switch}) .ToDictionary(x => x.index, x => x.s); Triggers = Behaviors .Where(x => x.Value.Any(g => g is Trigger)) .Select((x, i) => new {index = i, s = x.Value.First(g => g is Trigger) as Trigger}) .ToDictionary(x => x.index, x => x.s); Axes = Behaviors .Where(x => x.Value.Any(g => g is IAnalogBehavior)) .Select((x, i) => new {index = i, behaviors = x.Value .Where(g => g is IAnalogBehavior) .Cast().ToList()}) .ToDictionary(x => x.index, x => new AxisSetting{Behaviors = x.behaviors, Value = 0f}); // Axes = Behaviors.Values // .SelectMany(behaviors=>behaviors) // .Where(b => b is IAnalogBehavior) // .ToDictionary(b => b as IAnalogBehavior, b => 0f); foreach (var behavior in Behaviors.Values .SelectMany(behaviors=>behaviors) .Where(behavior => behavior is IInitializableBehavior) .Cast()) 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 Behaviors.Values) foreach (var behavior in behaviors) if (behavior is T b) yield return b; } public IEnumerable GetBehaviorData() where T : BehaviorData { foreach (var behaviors in Behaviors.Values) foreach (var behavior in behaviors) if (behavior.Data is T b) yield return b; } public int GetAxis(IAnalogBehavior behavior) { var axis = Axes.FirstOrDefault(x => x.Value.Behaviors.Contains(behavior)); if (axis.IsNull()) return -1; return axis.Key; } public int GetAxis() where T : class, IBehavior { foreach (var axis in Axes) foreach (var behavior in axis.Value.Behaviors) if (behavior is T) return axis.Key; return -1; } public void AddHeat(float heat) { Temperature += heat / SpecificHeat; } public virtual void Update(float delta) { foreach(var axis in Axes) foreach(var analogBehavior in axis.Value.Behaviors) analogBehavior.SetAxis(AxisOverrides.ContainsKey(axis.Key) ? AxisOverrides[axis.Key] : Axes[axis.Key].Value); foreach (var group in Behaviors.Values) foreach(var behavior in group) if(!behavior.Update(delta)) break; 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; } // Processing stale item data properly was a pain in my ass, so the shortcut is just persisting and restoring everything if (GearData.Any(kvp => kvp.Key.ItemData != kvp.Value)) { OnBeforeSerialize(); OnAfterDeserialize(); } // Here's the PITA way // 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; // } } 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); } } public class AxisSetting { public float Value; public List Behaviors; }