Entity Persistence Refactoring

Entities refactored to enable robust persistence
Entities detect stale data references and reload behaviors
This commit is contained in:
hyperion51 2020-05-06 04:56:36 +01:00
commit 6aeb2ae280
29 changed files with 299 additions and 202 deletions

View file

@ -7,25 +7,23 @@ using Random = Unity.Mathematics.Random;
public class AgentController
{
public Entity Entity { get; }
public Guid Entity { get; }
public EntityAgent EntityAgent { get; }
public GameContext Context { get; }
public Guid Zone { get; set; }
public Guid HomeZone;
private Locomotion _locomotion;
private VelocityMatch _velocityMatch;
private Random _random = new Random((uint) (DateTime.Now.Ticks%uint.MaxValue));
private Guid _targetOrbit;
public AgentController(GameContext context, Guid zone, Entity entity)
public AgentController(GameContext context, Guid zone, Guid entity)
{
Entity = entity;
Zone = zone;
Context = context;
EntityAgent = new EntityAgent(context, zone, entity);
_locomotion = new Locomotion(entity);
EntityAgent = new EntityAgent(context, entity, zone, Guid.Empty);
_locomotion = new Locomotion(context, entity);
_velocityMatch = new VelocityMatch(context, entity);
_velocityMatch.OnMatch += () =>
{
@ -44,6 +42,7 @@ public class AgentController
// $"Agent Distance: {distance} " +
// $"Turnaround Distance: {_stoppingDistance + _turnaroundDistance} " +
// $"Objective: {_objective.Key.ID.ToString().Substring(0, 8)}");
var entity = Context.Cache.Get<Entity>(Entity);
if (EntityAgent.CurrentBehavior == _locomotion)
{
@ -51,7 +50,7 @@ public class AgentController
_velocityMatch.TargetOrbit = _targetOrbit;
var matchDistanceTime = _velocityMatch.MatchDistanceTime;
_locomotion.Objective = Context.GetOrbitPosition(_targetOrbit) + Context.GetOrbitVelocity(_targetOrbit) * matchDistanceTime.y;
var distance = length(_locomotion.Objective - Entity.Position);
var distance = length(_locomotion.Objective - entity.Position);
if (distance < matchDistanceTime.x)
EntityAgent.CurrentBehavior = _velocityMatch;
}

View file

@ -1,13 +1,19 @@
using System;
using System.Collections;
using System.Collections.Generic;
using JsonKnownTypes;
using MessagePack;
using Newtonsoft.Json;
using UnityEngine;
[MessagePackObject,
Union(0, typeof(StationTowing)),
JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<DatabaseEntry>))]
public abstract class AgentTask
{
public abstract AgentJob JobType { get; }
public int Priority;
public Guid Zone;
[IgnoreMember] public abstract AgentJob JobType { get; }
[JsonProperty("id"), Key(0)] public int Priority;
[JsonProperty("zone"), Key(1)] public Guid Zone;
}
public enum AgentJob

View file

@ -1,24 +1,29 @@
using System;
using System.Collections;
using System.Collections.Generic;
using JsonKnownTypes;
using MessagePack;
using Newtonsoft.Json;
[MessagePackObject,
JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<Entity>))]
public class EntityAgent
{
public AgentBehavior CurrentBehavior;
[IgnoreMember] public AgentBehavior CurrentBehavior;
[IgnoreMember] public Guid Entity;
[IgnoreMember] public GameContext Context;
[JsonProperty("homeZone"), Key(0)] public Guid HomeZone;
[JsonProperty("homeColony"), Key(0)] public Guid HomeColony;
public Entity Entity { get; }
public GameContext Context { get; }
public Guid Zone { get; set; }
public EntityAgent(GameContext context, Guid zone, Entity entity)
public EntityAgent(GameContext context, Guid entity, Guid homeZone, Guid homeColony)
{
Entity = entity;
Zone = zone;
HomeZone = homeZone;
Context = context;
}
public void Update(float delta)
{
CurrentBehavior.Update(delta);
CurrentBehavior?.Update(delta);
}
}

View file

@ -1,4 +1,5 @@
using System.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;
@ -7,28 +8,33 @@ using static Unity.Mathematics.math;
public class Locomotion : AgentBehavior
{
public float2 Objective;
private Entity _entity;
private IAnalogBehavior _thrust;
private IAnalogBehavior _turning;
private const float Sensitivity = 1;
private const float TangentSensitivity = 2;
private GameContext _context;
private Guid _entity;
private IAnalogBehavior _thrust;
private IAnalogBehavior _turning;
public Locomotion(Entity entity)
public Locomotion(GameContext context, Guid entity)
{
_context = context;
_entity = entity;
_thrust = entity.Axes.Keys.FirstOrDefault(x => x is Thruster);
_turning = entity.Axes.Keys.FirstOrDefault(x => x is Turning);
var entityObject = _context.Cache.Get<Entity>(_entity);
_thrust = entityObject.Axes.Keys.FirstOrDefault(x => x is Thruster);
_turning = entityObject.Axes.Keys.FirstOrDefault(x => x is Turning);
}
public override void Update(float delta)
{
var entity = _context.Cache.Get<Entity>(_entity);
if (_thrust != null && _turning != null)
{
float2 diff = Objective - _entity.Position;
float2 tangential = _entity.Velocity - dot(normalize(diff), normalize(_entity.Velocity)) * _entity.Velocity;
float angleDiff = _entity.Direction.AngleDiff(normalize(diff) - tangential * TangentSensitivity);
_entity.Axes[_turning] = clamp(-angleDiff * Sensitivity, -1, 1);
_entity.Axes[_thrust] = 1;
float2 diff = Objective - entity.Position;
float2 tangential = entity.Velocity - dot(normalize(diff), normalize(entity.Velocity)) * entity.Velocity;
float angleDiff = entity.Direction.AngleDiff(normalize(diff) - tangential * TangentSensitivity);
entity.Axes[_turning] = clamp(-angleDiff * Sensitivity, -1, 1);
entity.Axes[_thrust] = 1;
}
}
}

View file

@ -1,11 +1,16 @@
using System;
using System.Collections;
using System.Collections.Generic;
using JsonKnownTypes;
using MessagePack;
using Newtonsoft.Json;
using UnityEngine;
[MessagePackObject,
JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<DatabaseEntry>))]
public class StationTowing : AgentTask
{
public override AgentJob JobType => AgentJob.Tow;
[IgnoreMember] public override AgentJob JobType => AgentJob.Tow;
private Guid _entity;
private Guid _targetOrbit;

View file

@ -9,33 +9,35 @@ public class VelocityMatch : AgentBehavior
{
public event Action OnMatch;
public Guid TargetOrbit;
private Entity _entity;
private Guid _entity;
private IAnalogBehavior _thrust;
private IAnalogBehavior _turning;
private const float TurningSensitivity = 1;
private const float ThrustSensitivity = 1;
private const float TargetThreshold = .1f;
public GameContext Context { get; }
public VelocityMatch(GameContext context, Entity entity)
private GameContext _context;
public VelocityMatch(GameContext context, Guid entity)
{
Context = context;
_context = context;
_entity = entity;
_thrust = entity.Axes.Keys.FirstOrDefault(x => x is Thruster);
_turning = entity.Axes.Keys.FirstOrDefault(x => x is Turning);
var entityObject = _context.Cache.Get<Entity>(_entity);
_thrust = entityObject.Axes.Keys.FirstOrDefault(x => x is Thruster);
_turning = entityObject.Axes.Keys.FirstOrDefault(x => x is Turning);
}
public override void Update(float delta)
{
var entity = _context.Cache.Get<Entity>(_entity);
if (TargetOrbit != Guid.Empty && _thrust != null && _turning != null)
{
var deltaV = Context.GetOrbitVelocity(TargetOrbit) - _entity.Velocity;
var deltaV = _context.GetOrbitVelocity(TargetOrbit) - entity.Velocity;
if(length(deltaV) < TargetThreshold)
OnMatch?.Invoke();
var angleDiff = _entity.Direction.AngleDiff(deltaV);
_entity.Axes[_turning] = clamp(-angleDiff * TurningSensitivity, -1, 1);
_entity.Axes[_thrust] = saturate(length(pow(saturate(dot(normalize(_entity.Direction),normalize(deltaV))), 8)*length(deltaV))*ThrustSensitivity);
var angleDiff = entity.Direction.AngleDiff(deltaV);
entity.Axes[_turning] = clamp(-angleDiff * TurningSensitivity, -1, 1);
entity.Axes[_thrust] = saturate(length(pow(saturate(dot(normalize(entity.Direction),normalize(deltaV))), 8)*length(deltaV))*ThrustSensitivity);
}
}
@ -43,14 +45,15 @@ public class VelocityMatch : AgentBehavior
{
get
{
var velocity = length(_entity.Velocity);
var deltaV = Context.GetOrbitVelocity(TargetOrbit) - _entity.Velocity;
var entity = _context.Cache.Get<Entity>(_entity);
var velocity = length(entity.Velocity);
var deltaV = _context.GetOrbitVelocity(TargetOrbit) - entity.Velocity;
var stoppingTime = length(deltaV) / (_entity.Context.Evaluate((_thrust.Data as ThrusterData).Thrust, _thrust.Item, _entity) / _entity.Mass);
var stoppingTime = length(deltaV) / (_context.Evaluate((_thrust.Data as ThrusterData).Thrust, _thrust.Item, entity) / entity.Mass);
var stoppingDistance = stoppingTime * (velocity / 2);
var angleDiff = _entity.Direction.AngleDiff(deltaV);
var turnaroundTime = angleDiff / (_entity.Context.Evaluate((_turning.Data as TurningData).Torque, _turning.Item, _entity) / _entity.Mass);
var angleDiff = entity.Direction.AngleDiff(deltaV);
var turnaroundTime = angleDiff / (_context.Evaluate((_turning.Data as TurningData).Torque, _turning.Item, entity) / entity.Mass);
var turnaroundDistance = turnaroundTime * velocity;
return float2(stoppingDistance + turnaroundDistance, stoppingTime + turnaroundTime);

View file

@ -5,7 +5,7 @@ using MessagePack;
using Newtonsoft.Json;
[InspectableField, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class AfterburnerData : IBehaviorData
public class AfterburnerData : BehaviorData
{
[InspectableField, JsonProperty("thrust"), Key(0)]
public PerformanceStat ThrustModifier = new PerformanceStat();
@ -16,7 +16,7 @@ public class AfterburnerData : IBehaviorData
[InspectableField, JsonProperty("torque"), Key(2)]
public PerformanceStat TorqueModifier = new PerformanceStat();
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new Afterburner(context, this, entity, item);
}
@ -32,7 +32,7 @@ public class Afterburner : IActivatedBehavior
public Gear Item { get; }
public GameContext Context { get; }
public IBehaviorData Data => _data;
public BehaviorData Data => _data;
public Afterburner(GameContext context, AfterburnerData data, Entity entity, Gear item)
{

View file

@ -16,7 +16,7 @@ public interface IBehavior
Entity Entity { get; }
Gear Item { get; }
GameContext Context { get; }
IBehaviorData Data { get; }
BehaviorData Data { get; }
}
public interface IActivatedBehavior : IBehavior
@ -35,9 +35,14 @@ public interface IPersistentBehavior//<T> where T : IBehavior
PersistentBehaviorData Store();
void Restore(PersistentBehaviorData data);
}
[Union(0, typeof(FactoryPersistence)),
[MessagePackObject,
Union(0, typeof(FactoryPersistence)),
JsonObject(MemberSerialization.OptIn),
JsonConverter(typeof(JsonKnownTypesConverter<PersistentBehaviorData>))]
public class PersistentBehaviorData { }
public abstract class PersistentBehaviorData
{
}
[InspectableField,
Union(0, typeof(ProjectileWeaponData)),
@ -53,8 +58,11 @@ public class PersistentBehaviorData { }
Union(10, typeof(VelocityConversionData)),
Union(11, typeof(VelocityLimitData)),
Union(12, typeof(FactoryData)),
JsonConverter(typeof(JsonKnownTypesConverter<IBehaviorData>)), JsonObject(MemberSerialization.OptIn)]
public interface IBehaviorData
JsonConverter(typeof(JsonKnownTypesConverter<BehaviorData>)), JsonObject(MemberSerialization.OptIn)]
public abstract class BehaviorData
{
IBehavior CreateInstance(GameContext context, Entity entity, Gear item);
// [InspectableField, JsonProperty("index"), Key(0)]
// public int Index;
public abstract IBehavior CreateInstance(GameContext context, Entity entity, Gear item);
}

View file

@ -8,7 +8,7 @@ using static Unity.Mathematics.math;
using Random = Unity.Mathematics.Random;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class FactoryData : IBehaviorData
public class FactoryData : BehaviorData
{
[InspectableField, JsonProperty("toolingTime"), Key(0)]
public PerformanceStat ToolingTime = new PerformanceStat();
@ -16,7 +16,7 @@ public class FactoryData : IBehaviorData
[InspectableField, JsonProperty("automation"), Key(1)]
public int AutomationPoints;
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new Factory(context, this, entity, item);
}
@ -28,7 +28,7 @@ public class Factory : IBehavior, IPersistentBehavior//<Factory>
public Gear Item { get; }
public GameContext Context { get; }
public IBehaviorData Data => _data;
public BehaviorData Data => _data;
private FactoryData _data;
@ -109,28 +109,28 @@ public class Factory : IBehavior, IPersistentBehavior//<Factory>
};
}
Context.Cache.Add(newItem);
Entity.Cargo.Add(newItem);
Entity.Cargo.Add(newItem.ID);
}
}
else
{
var simpleCommodityData = blueprintItemData as SimpleCommodityData;
var simpleCommodityInstance = Entity.Cargo.FirstOrDefault(i => i.Data == simpleCommodityData.ID) as SimpleCommodity;
if (simpleCommodityInstance == null)
var simpleCommodityInstance = Entity.Cargo.FirstOrDefault(i => i == simpleCommodityData.ID);
if (simpleCommodityInstance == Guid.Empty)
{
simpleCommodityInstance = new SimpleCommodity
var newSimpleCommodity = new SimpleCommodity
{
Context = Context,
Data = simpleCommodityData.ID,
ID = Guid.NewGuid(),
Quantity = blueprint.Quantity
};
Context.Cache.Add(simpleCommodityInstance);
Entity.Cargo.Add(simpleCommodityInstance);
Context.Cache.Add(newSimpleCommodity);
Entity.Cargo.Add(newSimpleCommodity.ID);
}
else
{
simpleCommodityInstance.Quantity += blueprint.Quantity;
Context.Cache.Get<SimpleCommodity>(simpleCommodityInstance).Quantity += blueprint.Quantity;
}
}
Entity.RecalculateMass();
@ -138,15 +138,15 @@ public class Factory : IBehavior, IPersistentBehavior//<Factory>
var simpleIngredients = new List<SimpleCommodity>();
var compoundIngredients = new List<CompoundCommodity>();
var hasAllIngredients = true;
var cargoInstances = Entity.Cargo.Select(c => Context.Cache.Get<ItemInstance>(c));
foreach (var kvp in blueprint.Ingredients)
{
var itemData = Context.Cache.Get(kvp.Key);
if (itemData is SimpleCommodityData)
{
var matchingItem = Entity.Cargo.FirstOrDefault(ii =>
var matchingItem = cargoInstances.FirstOrDefault(ii =>
{
var simpleCommodity = ii as SimpleCommodity;
if (simpleCommodity == null) return false;
if (!(ii is SimpleCommodity simpleCommodity)) return false;
return simpleCommodity.Data == itemData.ID && simpleCommodity.Quantity >= kvp.Value;
}) as SimpleCommodity;
hasAllIngredients = hasAllIngredients && matchingItem != null;
@ -156,10 +156,10 @@ public class Factory : IBehavior, IPersistentBehavior//<Factory>
else
{
var matchingItems =
Entity.Cargo.Where(ii => (ii as CompoundCommodity)?.Data == itemData.ID).Cast<CompoundCommodity>().ToArray();
cargoInstances.Where(ii => (ii as CompoundCommodity)?.Data == itemData.ID).Cast<CompoundCommodity>().ToArray();
hasAllIngredients = hasAllIngredients && matchingItems.Length >= kvp.Value;
if(matchingItems.Length >= kvp.Value)
compoundIngredients.AddRange(matchingItems);
compoundIngredients.AddRange(matchingItems.Take(kvp.Value));
}
}
@ -168,7 +168,7 @@ public class Factory : IBehavior, IPersistentBehavior//<Factory>
_reservedStock.AddRange(compoundIngredients);
foreach (var compoundCommodity in compoundIngredients)
{
Entity.Cargo.Remove(compoundCommodity);
Entity.Cargo.Remove(compoundCommodity.ID);
}
foreach (var simpleCommodity in simpleIngredients)
{
@ -177,7 +177,7 @@ public class Factory : IBehavior, IPersistentBehavior//<Factory>
if (simpleCommodity.Quantity == blueprintQuantity)
{
_reservedStock.Add(simpleCommodity);
Entity.Cargo.Remove(simpleCommodity);
Entity.Cargo.Remove(simpleCommodity.ID);
}
else
{
@ -230,11 +230,11 @@ public class Factory : IBehavior, IPersistentBehavior//<Factory>
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class FactoryPersistence : PersistentBehaviorData
{
public Guid Blueprint;
public float RemainingManHours;
public bool Retooling;
public bool Producing;
public Guid[] ReservedStock;
public int AssignedPopulation;
public float ProductionQuality;
[JsonProperty("blueprint"), Key(0)] public Guid Blueprint;
[JsonProperty("remainingManHours"), Key(1)] public float RemainingManHours;
[JsonProperty("retooling"), Key(2)] public bool Retooling;
[JsonProperty("producing"), Key(3)] public bool Producing;
[JsonProperty("reservedStock"), Key(4)] public Guid[] ReservedStock;
[JsonProperty("assignedPopulation"), Key(5)] public int AssignedPopulation;
[JsonProperty("productionQuality"), Key(6)] public float ProductionQuality;
}

View file

@ -5,7 +5,7 @@ using Newtonsoft.Json;
using Unity.Mathematics;
[InspectableField, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class LauncherData : IBehaviorData
public class LauncherData : BehaviorData
{
[InspectablePrefab, JsonProperty("missile"), Key(9)]
public string MissilePrefab;
@ -37,7 +37,7 @@ public class LauncherData : IBehaviorData
[InspectableField, JsonProperty("lockOnTime"), Key(18)]
public PerformanceStat LockOnTime = new PerformanceStat();
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new Launcher(context, this, entity, item);
}
@ -162,5 +162,5 @@ public class Launcher : IActivatedBehavior
// }
}
public IBehaviorData Data => _launcher;
public BehaviorData Data => _launcher;
}

View file

@ -2,7 +2,7 @@ using MessagePack;
using Newtonsoft.Json;
[InspectableField, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class ProjectileWeaponData : WeaponData, IBehaviorData
public class ProjectileWeaponData : WeaponData
{
[InspectablePrefab, JsonProperty("bullet"), Key(9)]
public string BulletPrefab;
@ -16,7 +16,7 @@ public class ProjectileWeaponData : WeaponData, IBehaviorData
[InspectableField, JsonProperty("bulletVelocity"), Key(12)]
public PerformanceStat Velocity = new PerformanceStat();
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new ProjectileWeapon(context, this, entity, item);
}
@ -97,5 +97,5 @@ public class ProjectileWeapon : IActivatedBehavior
}
}
public IBehaviorData Data => _projectileWeapon;
public BehaviorData Data => _projectileWeapon;
}

View file

@ -4,12 +4,12 @@ using Unity.Mathematics;
using static Unity.Mathematics.math;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class RadiatorData : IBehaviorData
public class RadiatorData : BehaviorData
{
[InspectableField, JsonProperty("emissivity"), Key(0)]
public PerformanceStat Emissivity = new PerformanceStat();
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new Radiator(context, this, entity, item);
}
@ -21,7 +21,7 @@ public class Radiator : IBehavior
public Gear Item { get; }
public GameContext Context { get; }
public IBehaviorData Data => _data;
public BehaviorData Data => _data;
private RadiatorData _data;

View file

@ -2,7 +2,7 @@ using MessagePack;
using Newtonsoft.Json;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class ReactorData : IBehaviorData
public class ReactorData : BehaviorData
{
[InspectableField, JsonProperty("charge"), Key(0)]
public PerformanceStat Charge = new PerformanceStat();
@ -19,7 +19,7 @@ public class ReactorData : IBehaviorData
[InspectableField, JsonProperty("underload"), Key(4)]
public PerformanceStat UnderloadRecovery = new PerformanceStat();
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new Reactor(context, this, entity, item);
}
@ -33,7 +33,7 @@ public class Reactor : IBehavior
public Gear Item { get; }
public GameContext Context { get; }
public IBehaviorData Data => _data;
public BehaviorData Data => _data;
public Reactor(GameContext context, ReactorData data, Entity entity, Gear item)
{

View file

@ -4,7 +4,7 @@ using Unity.Mathematics;
using static Unity.Mathematics.math;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class ReflectorData : IBehaviorData
public class ReflectorData : BehaviorData
{
[InspectableField, JsonProperty("crossSection"), Key(0)]
public PerformanceStat CrossSection = new PerformanceStat();
@ -12,7 +12,7 @@ public class ReflectorData : IBehaviorData
// [InspectableAnimationCurve, JsonProperty("visibility"), Key(1)]
// public float4[] VisibilityCurve;
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new Reflector(context, this, entity, item);
}
@ -24,7 +24,7 @@ public class Reflector : IBehavior
public Gear Item { get; }
public GameContext Context { get; }
public IBehaviorData Data => _data;
public BehaviorData Data => _data;
private ReflectorData _data;

View file

@ -3,7 +3,7 @@ using MessagePack;
using Newtonsoft.Json;
[InspectableField, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class SensorData : IBehaviorData
public class SensorData : BehaviorData
{
[InspectableField, JsonProperty("radiance"), Key(0)]
public PerformanceStat Radiance = new PerformanceStat();
@ -17,7 +17,7 @@ public class SensorData : IBehaviorData
[InspectableField, JsonProperty("range"), Key(3)]
public PerformanceStat Range = new PerformanceStat();
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new Sensor(context, this, entity, item);
}
@ -31,7 +31,7 @@ public class Sensor : IBehavior
public Gear Item { get; }
public GameContext Context { get; }
public IBehaviorData Data => _data;
public BehaviorData Data => _data;
public Sensor(GameContext context, SensorData data, Entity entity, Gear item)
{

View file

@ -4,7 +4,7 @@ using Unity.Mathematics;
using static Unity.Mathematics.math;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class ShieldData : IBehaviorData
public class ShieldData : BehaviorData
{
[InspectableField, JsonProperty("efficiency"), Key(0)]
public PerformanceStat Efficiency = new PerformanceStat();
@ -12,7 +12,7 @@ public class ShieldData : IBehaviorData
[InspectableField, JsonProperty("shielding"), Key(1)]
public PerformanceStat Shielding = new PerformanceStat();
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new Shield(context, this, entity, item);
}
@ -24,7 +24,7 @@ public class Shield : IBehavior
public Gear Item { get; }
public GameContext Context { get; }
public IBehaviorData Data => _data;
public BehaviorData Data => _data;
private ShieldData _data;

View file

@ -4,7 +4,7 @@ using Unity.Mathematics;
using static Unity.Mathematics.math;
[MessagePackObject, JsonObject(MemberSerialization.OptIn), EntityTypeRestriction(HullType.Ship)]
public class ThrusterData : IBehaviorData
public class ThrusterData : BehaviorData
{
[InspectableField, JsonProperty("thrust"), Key(0)]
public PerformanceStat Thrust = new PerformanceStat();
@ -15,7 +15,7 @@ public class ThrusterData : IBehaviorData
[InspectableField, JsonProperty("heat"), Key(2)]
public PerformanceStat Heat = new PerformanceStat();
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new Thruster(context, this, entity, item);
}
@ -27,7 +27,7 @@ public class Thruster : IAnalogBehavior
public Gear Item { get; }
public GameContext Context { get; }
public IBehaviorData Data => _data;
public BehaviorData Data => _data;
private ThrusterData _data;

View file

@ -4,7 +4,7 @@ using Unity.Mathematics;
using static Unity.Mathematics.math;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class TurningData : IBehaviorData
public class TurningData : BehaviorData
{
[InspectableField, JsonProperty("torque"), Key(0)]
public PerformanceStat Torque = new PerformanceStat();
@ -15,7 +15,7 @@ public class TurningData : IBehaviorData
[InspectableField, JsonProperty("heat"), Key(2)]
public PerformanceStat Heat = new PerformanceStat();
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new Turning(context, this, entity, item);
}
@ -27,7 +27,7 @@ public class Turning : IAnalogBehavior
public Gear Item { get; }
public GameContext Context { get; }
public IBehaviorData Data => _data;
public BehaviorData Data => _data;
private TurningData _data;

View file

@ -4,12 +4,12 @@ using Unity.Mathematics;
using static Unity.Mathematics.math;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class VelocityConversionData : IBehaviorData
public class VelocityConversionData : BehaviorData
{
[InspectableField, JsonProperty("traction"), Key(0)]
public PerformanceStat Traction = new PerformanceStat();
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new VelocityConversion(context, this, entity, item);
}
@ -21,7 +21,7 @@ public class VelocityConversion : IBehavior
public Gear Item { get; }
public GameContext Context { get; }
public IBehaviorData Data => _data;
public BehaviorData Data => _data;
private VelocityConversionData _data;

View file

@ -4,12 +4,12 @@ using Unity.Mathematics;
using static Unity.Mathematics.math;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class VelocityLimitData : IBehaviorData
public class VelocityLimitData : BehaviorData
{
[InspectableField, JsonProperty("topSpeed"), Key(0)]
public PerformanceStat TopSpeed = new PerformanceStat();
public IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
public override IBehavior CreateInstance(GameContext context, Entity entity, Gear item)
{
return new VelocityLimit(context, this, entity, item);
}
@ -22,7 +22,7 @@ public class VelocityLimit : IBehavior
public Gear Item { get; }
public GameContext Context { get; }
public IBehaviorData Data => _data;
public BehaviorData Data => _data;
private VelocityLimitData _data;

View file

@ -3,7 +3,7 @@ using Newtonsoft.Json;
[Union(0, typeof(ProjectileWeaponData)),
Union(1, typeof(LauncherData))]
public abstract class WeaponData
public abstract class WeaponData : BehaviorData
{
[InspectableField, JsonProperty("damageType"), Key(0)]
public DamageType DamageType;

View file

@ -77,7 +77,7 @@ public class BlueprintStatEffect
[InspectableField, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class StatReference
{
[InspectableType(typeof(IBehaviorData)), JsonProperty("behavior"), Key(1)]
[InspectableType(typeof(BehaviorData)), JsonProperty("behavior"), Key(1)]
public string Behavior;
[InspectableField, JsonProperty("stat"), Key(2)]

View file

@ -68,6 +68,7 @@ public class Player : DatabaseEntry, INamedEntry
public class Corporation : DatabaseEntry, INamedEntry
{
[JsonProperty("name"), Key(1)] public string Name;
[JsonProperty("tasks"), Key(2)] public List<AgentTask> Tasks = new List<AgentTask>();
[IgnoreMember] public string EntryName
{

View file

@ -15,32 +15,35 @@ using static Unity.Mathematics.math;
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("agent"), Key(12)] public EntityAgent Agent;
// [IgnoreMember] protected List<IBehavior> Behaviors;
[IgnoreMember] public Guid Zone;
[IgnoreMember] public Dictionary<Gear, List<IBehavior>> ItemBehaviors;
[IgnoreMember] public Dictionary<Gear, List<IActivatedBehavior>> Bindings;
[IgnoreMember] public Dictionary<IAnalogBehavior, float> Axes;
[IgnoreMember] public Dictionary<IAnalogBehavior, float> AxisOverrides = new Dictionary<IAnalogBehavior, float>();
[IgnoreMember] public readonly Dictionary<object, float> VisibilitySources = new Dictionary<object, float>();
public float Temperature;
public float Energy;
public float2 Position;
public float2 Direction = float2(0,1);
public float2 Velocity;
[IgnoreMember] public Dictionary<Guid, List<IActivatedBehavior>> Bindings;
[IgnoreMember] public Dictionary<IAnalogBehavior, float> Axes;
public readonly List<ItemInstance> Cargo;
public readonly List<Gear> EquippedItems;
public Entity Parent;
public List<Entity> Children = new List<Entity>();
public Guid Zone;
[IgnoreMember] protected List<IBehavior> Behaviors;
public Gear Hull { get; }
private Dictionary<Gear, EquippableItemData> GearData;
[IgnoreMember] public float Mass { get; private set; }
[IgnoreMember] public float SpecificHeat { get; private set; }
[IgnoreMember] public float Visibility => VisibilitySources.Values.Sum();
public PersistentBehaviorData[] PersistedBehaviors;
public Entity(GameContext context, Gear hull, IEnumerable<Gear> items, IEnumerable<ItemInstance> cargo)
public Entity(GameContext context, Guid hull, IEnumerable<Guid> items, IEnumerable<Guid> cargo)
{
ID = Guid.NewGuid();
Context = context;
@ -51,24 +54,35 @@ public abstract class Entity : DatabaseEntry, IMessagePackSerializationCallbackR
RecalculateMass();
var activeItems = Hull == null ? EquippedItems : EquippedItems.Append(Hull);
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);
Behaviors = activeItems
.Where(i=>i.ItemData.Behaviors?.Any()??false)
.SelectMany(i => i.ItemData.Behaviors
.Select(bd => bd.CreateInstance(Context, this, i)))
var allbehaviors = gear
.Where(i=>gearData[i].ItemData.Behaviors?.Any()??false)
.SelectMany(i => gearData[i].ItemData.Behaviors
.Select(bd => bd.CreateInstance(Context, this, gearData[i])))
.OrderBy(b => b.GetType().GetCustomAttribute<UpdateOrderAttribute>()?.Order ?? 0).ToList();
Bindings = Behaviors
ItemBehaviors = allbehaviors
.GroupBy(b => b.Item.ID, (guid, behaviors) => new {guid, behaviors})
.ToDictionary(g => gearData[g.guid], g => g.behaviors.ToList());
Bindings = allbehaviors
.Where(b => b is IActivatedBehavior)
.GroupBy(b => b.Item.ID, (guid, behaviors) => new {guid, behaviors})
.ToDictionary(g => g.guid, g => g.behaviors.Cast<IActivatedBehavior>().ToList());
.ToDictionary(g => gearData[g.guid], g => g.behaviors.Cast<IActivatedBehavior>().ToList());
Axes = Behaviors
Axes = allbehaviors
.Where(b => b is IAnalogBehavior)
.ToDictionary(b => b as IAnalogBehavior, b => 0f);
foreach (var behavior in Behaviors)
foreach (var behavior in allbehaviors)
{
behavior.Initialize();
}
@ -76,28 +90,30 @@ public abstract class Entity : DatabaseEntry, IMessagePackSerializationCallbackR
public void RecalculateMass()
{
Mass = Hull?.Mass ?? 0 +
EquippedItems.Sum(i => i.Mass) +
Cargo.Sum(ii => ii.Mass) +
Children.Sum(c=>c.Mass);
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 = (Hull?.HeatCapacity ?? 0) +
EquippedItems.Sum(i => i.HeatCapacity) +
Cargo.Sum(ii => ii.HeatCapacity);
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 behavior in Behaviors)
if (behavior is T b)
yield return b;
foreach (var behaviors in ItemBehaviors.Values)
foreach (var behavior in behaviors)
if (behavior is T b)
yield return b;
}
public IEnumerable<T> GetBehaviorData<T>() where T : class, IBehaviorData
public IEnumerable<T> GetBehaviorData<T>() where T : BehaviorData
{
foreach (var behavior in Behaviors)
if (behavior.Data is T b)
yield return b;
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)
@ -109,60 +125,103 @@ public abstract class Entity : DatabaseEntry, IMessagePackSerializationCallbackR
{
foreach(var analogBehavior in Axes.Keys)
analogBehavior.SetAxis(AxisOverrides.ContainsKey(analogBehavior) ? AxisOverrides[analogBehavior] : Axes[analogBehavior]);
foreach(var behavior in Behaviors)
foreach (var behavior in ItemBehaviors.SelectMany(item => item.Value))
behavior.Update(delta);
foreach (var item in EquippedItems.Where(item => item.ItemData.Performance(Temperature) < .01f))
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();
// 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;
}
foreach (var item in EquippedItems
.Select(item => Context.Cache.Get<Gear>(item))
.Where(item => item.ItemData.Performance(Temperature) < .01f))
item.Durability -= delta;
if (Parent != null)
if (Parent != Guid.Empty)
{
Position = Parent.Position;
Velocity = Parent.Velocity;
var parent = Context.Cache.Get<Entity>(Parent);
Position = parent.Position;
Velocity = parent.Velocity;
}
}
public void OnBeforeSerialize()
{
PersistedBehaviors = Behaviors
.Where(b => b is IPersistentBehavior)
.Cast<IPersistentBehavior>()
.Select(b => b.Store()).ToArray();
// 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();
var activeItems = Hull == null ? EquippedItems : EquippedItems.Append(Hull);
Behaviors = activeItems
.Where(i=>i.ItemData.Behaviors?.Any()??false)
.SelectMany(i => i.ItemData.Behaviors
.Select(bd => bd.CreateInstance(Context, this, i)))
.OrderBy(b => b.GetType().GetCustomAttribute<UpdateOrderAttribute>()?.Order ?? 0).ToList();
AddItemBehaviors();
var index = 0;
foreach (var persistentBehavior in Behaviors
.Where(b => b is IPersistentBehavior)
.Cast<IPersistentBehavior>())
{
persistentBehavior.Restore(PersistedBehaviors[index++]);
}
Bindings = Behaviors
.Where(b => b is IActivatedBehavior)
.GroupBy(b => b.Item.ID, (guid, behaviors) => new {guid, behaviors})
.ToDictionary(g => g.guid, g => g.behaviors.Cast<IActivatedBehavior>().ToList());
Axes = Behaviors
.Where(b => b is IAnalogBehavior)
.ToDictionary(b => b as IAnalogBehavior, b => 0f);
foreach (var behavior in Behaviors)
{
behavior.Initialize();
}
// 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);
Agent.Context = Context;
Agent.Entity = ID;
}
}

View file

@ -105,7 +105,7 @@ public abstract class EquippableItemData : CraftedItemData
public PerformanceStat HeatExponent = new PerformanceStat();
[InspectableField, JsonProperty("behaviors"), Key(13)]
public List<IBehaviorData> Behaviors = new List<IBehaviorData>();
public List<BehaviorData> Behaviors = new List<BehaviorData>();
[IgnoreMember]
public abstract HardpointType HardpointType { get; }

View file

@ -9,7 +9,7 @@ public class OrbitalEntity : Entity
{
private Guid _orbitData;
public OrbitalEntity(GameContext context, Gear hull, IEnumerable<Gear> items, IEnumerable<ItemInstance> cargo, Guid orbit) : base(context, hull, items, cargo)
public OrbitalEntity(GameContext context, Guid hull, IEnumerable<Guid> items, IEnumerable<Guid> cargo, Guid orbit) : base(context, hull, items, cargo)
{
_orbitData = orbit;
}

View file

@ -19,7 +19,7 @@ public class Ship : Entity
// [IgnoreMember] public Dictionary<Targetable, float> Contacts = new Dictionary<Targetable, float>();
// [IgnoreMember] public Targetable Target;
public Ship(GameContext context, Gear hull, IEnumerable<Gear> items, IEnumerable<ItemInstance> cargo) : base(context, hull, items, cargo)
public Ship(GameContext context, Guid hull, IEnumerable<Guid> items, IEnumerable<Guid> cargo) : base(context, hull, items, cargo)
{
}