Aetheria/Assets/Scripts/ServerShared/Entity.cs
hyperion51 698263ce63 Entity Properties & Thermal Regulation
Entities such as colonies and ships can now be clicked on to reveal their properties or context menus
Orbital Colony loadout now has a new item (thermostatic heater) which itself has a new behavior type (Thermotoggle) allowing it to regulate its temperature for more agreeable living conditions
Fixed stat modifiers for hull stats (e.g. Hull Expansion)
Widened property label in DB inspector
2020-05-26 01:12:06 +01:00

302 lines
No EOL
13 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, INamedEntry
{
[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;
[JsonProperty("name"), Key(13)] public string Name;
// [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 OccupiedCapacity { get; private set; }
[IgnoreMember] public float SpecificHeat { get; private set; }
[IgnoreMember] public float Visibility => VisibilitySources.Values.Sum();
[IgnoreMember] public string EntryName
{
get => Name;
set => Name = value;
}
public Entity(GameContext context, Guid hull, IEnumerable<Guid> items, IEnumerable<Guid> cargo, Guid zone)
{
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) +
Children.Select(i => Context.Cache.Get<Entity>(i)).Sum(c=>c.SpecificHeat);
OccupiedCapacity = EquippedItems.Select(i => Context.Cache.Get<Gear>(i)).Sum(i => i.ItemData.Size) +
Cargo.Select(i => Context.Cache.Get<ItemInstance>(i)).Sum(ii => Context.GetSize(ii));
}
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)
{
if (Parent != Guid.Empty)
{
var parent = Context.Cache.Get<Entity>(Parent);
parent.AddHeat(heat);
}
else
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;
Temperature = parent.Temperature;
}
// 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;
}