Aetheria/Assets/Scripts/ServerShared/Entity.cs
hyperion51 61072de924 Mega Placement
Megas now have unique placement algorithms for their home sectors
PerformanceStats can be marked as simple to hide heat and durability
PerformanceStats automatically hide exponents when min and max are the same
Some behaviors now have options to spread their effects over time
2020-05-21 01:53:06 +01:00

283 lines
No EOL
12 KiB
C#

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<Entity>))]
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<Guid> Cargo; // Type: ItemInstance
[JsonProperty("equipment"), Key(7)] public readonly List<Guid> EquippedItems; // Type: Gear
[JsonProperty("parent"), Key(8)] public Guid Parent; // Type: Entity
[JsonProperty("children"), Key(9)] public List<Guid> Children = new List<Guid>(); // Type: Entity
[JsonProperty("hull"), Key(10)] public Guid Hull; // Type: Gear
[JsonProperty("persistedBehaviorData"), Key(11)] public Dictionary<Guid, PersistentBehaviorData[]> PersistedBehaviors;
[JsonProperty("corporation"), Key(12)] public Guid Corporation;
// [IgnoreMember] protected List<IBehavior> Behaviors;
[IgnoreMember] public Guid Zone;
[IgnoreMember] public Dictionary<int, List<IBehavior>> Behaviors;
[IgnoreMember] public Dictionary<int, Switch> Switches;
[IgnoreMember] public Dictionary<int, Trigger> Triggers;
[IgnoreMember] public Dictionary<int, AxisSetting> Axes;
[IgnoreMember] public Dictionary<int, float> AxisOverrides = new Dictionary<int, float>();
[IgnoreMember] public readonly Dictionary<object, float> VisibilitySources = new Dictionary<object, float>();
private Dictionary<Gear, EquippableItemData> GearData;
private Dictionary<Gear, List<IBehavior>> 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<Guid> items, IEnumerable<Guid> 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<Gear>(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<OrderAttribute>()?.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<IAnalogBehavior>().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<IInitializableBehavior>()) behavior.Initialize();
}
public void RecalculateMass()
{
Mass = Context.Cache.Get<Gear>(Hull).Mass +
EquippedItems.Select(i => Context.Cache.Get<Gear>(i)).Sum(i => i.Mass) +
Cargo.Select(i => Context.Cache.Get<ItemInstance>(i)).Sum(ii => ii.Mass) +
Children.Select(i => Context.Cache.Get<Entity>(i)).Sum(c=>c.Mass);
SpecificHeat = Context.Cache.Get<Gear>(Hull).HeatCapacity +
EquippedItems.Select(i => Context.Cache.Get<Gear>(i)).Sum(i => i.HeatCapacity) +
Cargo.Select(i => Context.Cache.Get<ItemInstance>(i)).Sum(ii => ii.HeatCapacity);
}
public IEnumerable<T> GetBehaviors<T>() 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<T> GetBehaviorData<T>() 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<T>() 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<Gear>(item))
.Where(item => item.ItemData.Performance(Temperature) < .01f))
item.Durability -= delta;
if (Parent != Guid.Empty)
{
var parent = Context.Cache.Get<Entity>(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<Gear>(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<IPersistentBehavior>()
// .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<UpdateOrderAttribute>()?.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<IPersistentBehavior>()
// .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<IActivatedBehavior>().ToList();
// }
//
// // Remove axis bindings and overrides for analog behaviors
// foreach (var axis in staleBehaviors.Where(b => b is IAnalogBehavior).Cast<IAnalogBehavior>())
// {
// 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<IAnalogBehavior>())
// 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<IPersistentBehavior>()})
.ToDictionary(x=> x.gear.ID, x=>x.behaviors.Select(b => b.Store()).ToArray());
}
public void OnAfterDeserialize()
{
RecalculateMass();
AddItemBehaviors();
// 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<IPersistentBehavior>()
.Zip(PersistedBehaviors[itemBehaviors.Key.ID], (behavior, data) => new{behavior, data})))
persistentBehaviorData.behavior.Restore(persistentBehaviorData.data);
}
}
public class AxisSetting
{
public float Value;
public List<IAnalogBehavior> Behaviors;
}