Implemented task and control module for surveying resources Implemented behavior for resource scanner Resources aren't shown until they have been surveyed Entities can now display messages, used to provide drone state feedback Orbital entities now provide correct velocity, improving drone docking precision
344 lines
No EOL
14 KiB
C#
344 lines
No EOL
14 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>();
|
|
[IgnoreMember] public readonly Dictionary<string, float> Messages = new Dictionary<string, 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();
|
|
public float Capacity
|
|
{
|
|
get
|
|
{
|
|
var hull = Context.Cache.Get<Gear>(Hull);
|
|
var hullData = Context.Cache.Get<HullData>(hull.Data);
|
|
return Context.Evaluate(hullData.Capacity, hull, this);
|
|
}
|
|
}
|
|
|
|
[IgnoreMember] public string EntryName
|
|
{
|
|
get => Name;
|
|
set => Name = value;
|
|
}
|
|
|
|
|
|
public Entity(GameContext context, Guid hull, IEnumerable<Guid> items, IEnumerable<Guid> cargo, Guid zone, Guid corporation)
|
|
{
|
|
Context = context;
|
|
Zone = zone;
|
|
Corporation = corporation;
|
|
|
|
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 int GetSwitch<T>() where T : class, IBehavior
|
|
{
|
|
var s = Behaviors
|
|
.FirstOrDefault(x => x.Value.Any(g => g is Switch) && x.Value.Any(g => g is T))
|
|
.Value.First(g => g is Switch) as Switch;
|
|
return Switches.FirstOrDefault(kvp => kvp.Value == s).Key;
|
|
}
|
|
|
|
public Switch GetSwitch(IBehavior behavior)
|
|
{
|
|
return Behaviors
|
|
.FirstOrDefault(x => x.Value.Any(g => g == behavior))
|
|
.Value.First(g => g is Switch) as Switch;
|
|
}
|
|
|
|
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 behaviors in Behaviors.Values)
|
|
foreach (var behavior in behaviors.Where(b => b is IAlwaysUpdatedBehavior).Cast<IAlwaysUpdatedBehavior>())
|
|
behavior.AlwaysUpdate(delta);
|
|
|
|
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();
|
|
}
|
|
|
|
foreach (var message in Messages.Keys.ToArray())
|
|
{
|
|
Messages[message] = Messages[message] - delta;
|
|
if (Messages[message] < 0)
|
|
Messages.Remove(message);
|
|
}
|
|
|
|
// 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 SetMessage(string message)
|
|
{
|
|
Messages[message] = Context.GlobalData.MessageDuration;
|
|
}
|
|
|
|
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;
|
|
} |