Hostility System
Implemented hostility system - entities can mark each other as friend or foe depending on changing conditions (currently only depends on local security level) Added "friendly" target indicators in blue
This commit is contained in:
parent
81acaf2a47
commit
1284149d47
39 changed files with 519 additions and 141 deletions
|
|
@ -11,7 +11,7 @@ public class Minion : Agent
|
|||
() => Task is PatrolOrbitsTask,
|
||||
() => patrolState.Task = Task as PatrolOrbitsTask);
|
||||
|
||||
Ship.VisibleHostiles.ObserveAdd().Where(_ => Ship.Target.Value == null).Subscribe(add => Ship.Target.Value = add.Value);
|
||||
Ship.VisibleEnemies.ObserveAdd().Where(_ => Ship.Target.Value == null).Subscribe(add => Ship.Target.Value = add.Value);
|
||||
|
||||
var combatState = new CombatState(this);
|
||||
_rootState.AddTransition(combatState,
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ public class TurretController : Behavior, IInitializableBehavior
|
|||
if (x.Firing)
|
||||
x.Deactivate();
|
||||
}
|
||||
Entity.Target.Value = Entity.VisibleHostiles.FirstOrDefault(e => e is Ship);
|
||||
Entity.Target.Value = Entity.VisibleEnemies.FirstOrDefault(e => e is Ship);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ public class Faction : DatabaseEntry, INamedEntry
|
|||
[InspectableDatabaseLink(typeof(PersonalityAttribute)), JsonProperty("personality"), Key(5)]
|
||||
public Dictionary<Guid, float> Personality = new Dictionary<Guid, float>();
|
||||
|
||||
[Inspectable, JsonProperty("hostile"), Key(6)]
|
||||
public bool PlayerHostile;
|
||||
// [Inspectable, JsonProperty("hostile"), Key(6)]
|
||||
// public bool PlayerHostile;
|
||||
|
||||
[InspectableColor, JsonProperty("primaryColor"), Key(7)]
|
||||
public float3 PrimaryColor;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ public abstract class Entity
|
|||
public readonly ReactiveCollection<EquippedCargoBay> CargoBays = new ReactiveCollection<EquippedCargoBay>();
|
||||
public readonly ReactiveCollection<EquippedDockingBay> DockingBays = new ReactiveCollection<EquippedDockingBay>();
|
||||
public readonly ReactiveCollection<Entity> VisibleEntities = new ReactiveCollection<Entity>();
|
||||
public readonly ReactiveCollection<Entity> VisibleHostiles = new ReactiveCollection<Entity>();
|
||||
public readonly ReactiveDictionary<Entity, bool> EntityHostility = new ReactiveDictionary<Entity, bool>();
|
||||
public readonly ReactiveCollection<Entity> VisibleEnemies = new ReactiveCollection<Entity>();
|
||||
public readonly ReactiveCollection<Entity> VisibleFriendlies = new ReactiveCollection<Entity>();
|
||||
|
||||
public Entity Parent;
|
||||
public List<Entity> Children = new List<Entity>();
|
||||
|
|
@ -127,6 +129,8 @@ public abstract class Entity
|
|||
public Subject<Unit> HypothermiaDeath = new Subject<Unit>();
|
||||
public Subject<Entity> TargetedBy = new Subject<Entity>();
|
||||
public ReactiveProperty<int> TargetedByCount = new ReactiveProperty<int>(0);
|
||||
public ReactiveProperty<SecurityLevel> CurrentSecurityLevel = new ReactiveProperty<SecurityLevel>(SecurityLevel.Open);
|
||||
public ReadOnlyReactiveProperty<bool> PresencePermitted;
|
||||
|
||||
public UniRx.IObservable<EquippedItem> ItemDestroyed;
|
||||
public UniRx.IObservable<int2> HullArmorDepleted;
|
||||
|
|
@ -135,6 +139,7 @@ public abstract class Entity
|
|||
public UniRx.IObservable<CauseOfDeath> Death;
|
||||
|
||||
private List<IDisposable> _subscriptions = new List<IDisposable>();
|
||||
private Dictionary<Entity, List<IDisposable>> _watchedEntitySubscriptions = new Dictionary<Entity, List<IDisposable>>();
|
||||
|
||||
public virtual void Activate()
|
||||
{
|
||||
|
|
@ -147,19 +152,84 @@ public abstract class Entity
|
|||
if(behavior is IInitializableBehavior initializableBehavior)
|
||||
initializableBehavior.Initialize();
|
||||
}
|
||||
foreach(var entity in Zone.Entities) EntityInfoGathered[entity] = 0;
|
||||
_subscriptions.Add(Zone.Entities.ObserveAdd().Subscribe(add => EntityInfoGathered[add.Value] = 0));
|
||||
foreach(var entity in Zone.Entities)
|
||||
{
|
||||
EntityInfoGathered[entity] = 0;
|
||||
EntityHostility[entity] = IsHostileTo(entity);
|
||||
}
|
||||
_subscriptions.Add(Zone.Entities.ObserveAdd().Subscribe(add =>
|
||||
{
|
||||
EntityInfoGathered[add.Value] = 0;
|
||||
EntityHostility[add.Value] = IsHostileTo(add.Value);
|
||||
}));
|
||||
_subscriptions.Add(Zone.Entities.ObserveRemove().Subscribe(remove =>
|
||||
{
|
||||
if (Target.Value == remove.Value) Target.Value = null;
|
||||
EntityInfoGathered.Remove(remove.Value);
|
||||
EntityHostility.Remove(remove.Value);
|
||||
VisibleEntities.Remove(remove.Value);
|
||||
VisibleHostiles.Remove(remove.Value);
|
||||
VisibleEnemies.Remove(remove.Value);
|
||||
VisibleFriendlies.Remove(remove.Value);
|
||||
}));
|
||||
_subscriptions.Add(VisibleEntities.ObserveRemove().Subscribe(remove =>
|
||||
_subscriptions.Add(VisibleEnemies.ObserveRemove().Subscribe(remove =>
|
||||
{
|
||||
if (Target.Value == remove.Value) Target.Value = null;
|
||||
}));
|
||||
_subscriptions.Add(Target.Subscribe(entity => entity?.TargetedBy.OnNext(this)));
|
||||
_subscriptions.Add(TargetedBy.Subscribe(enemy =>
|
||||
{
|
||||
TargetedByCount.Value++;
|
||||
enemy.Target.Where(t => t != this).Take(1).Subscribe(_ => TargetedByCount.Value--);
|
||||
}));
|
||||
|
||||
//
|
||||
_subscriptions.Add(EntityInfoGathered.ObserveReplace().Subscribe(replace =>
|
||||
{
|
||||
if (replace.OldValue < ItemManager.GameplaySettings.TargetDetectionInfoThreshold &&
|
||||
replace.NewValue > ItemManager.GameplaySettings.TargetDetectionInfoThreshold)
|
||||
{
|
||||
VisibleEntities.Add(replace.Key);
|
||||
if(EntityHostility[replace.Key])
|
||||
VisibleEnemies.Add(replace.Key);
|
||||
else VisibleFriendlies.Add(replace.Key);
|
||||
}
|
||||
if (replace.OldValue > ItemManager.GameplaySettings.TargetDetectionInfoThreshold &&
|
||||
replace.NewValue < ItemManager.GameplaySettings.TargetDetectionInfoThreshold)
|
||||
{
|
||||
VisibleEntities.Remove(replace.Key);
|
||||
VisibleEnemies.Remove(replace.Key);
|
||||
VisibleFriendlies.Remove(replace.Key);
|
||||
}
|
||||
}));
|
||||
|
||||
// Create subscriptions to events occurring on any visible entity
|
||||
_subscriptions.Add(VisibleEntities.ObserveAdd()
|
||||
.Select(add=>add.Value)
|
||||
.Subscribe(entity =>
|
||||
{
|
||||
var newVisibleEntitySubscriptions = new List<IDisposable>
|
||||
{
|
||||
// Respond to changes in the target's hostility status by updating the contents of VisibleEnemies and VisibleFriendlies
|
||||
EntityHostility.ObserveReplace()
|
||||
.Where(replace => replace.Key == entity)
|
||||
.Select(replace => replace.NewValue)
|
||||
.Subscribe(isHostile =>
|
||||
{
|
||||
(isHostile ? VisibleEnemies : VisibleFriendlies).Add(entity);
|
||||
(isHostile ? VisibleFriendlies : VisibleEnemies).Remove(entity);
|
||||
})
|
||||
};
|
||||
|
||||
_watchedEntitySubscriptions[entity] = newVisibleEntitySubscriptions;
|
||||
}));
|
||||
|
||||
// Cleanup visible entity subscriptions
|
||||
_subscriptions.Add(VisibleEntities.ObserveRemove().Subscribe(disappearingEntity =>
|
||||
{
|
||||
foreach(var subscription in _watchedEntitySubscriptions[disappearingEntity.Value]) subscription.Dispose();
|
||||
_watchedEntitySubscriptions.Remove(disappearingEntity.Value);
|
||||
}));
|
||||
|
||||
if(WeaponGroups.All(wg=>!wg.items.Any()))
|
||||
GenerateWeaponGroups();
|
||||
}
|
||||
|
|
@ -169,10 +239,13 @@ public abstract class Entity
|
|||
//ItemManager.Log($"Entity {Name} is deactivating!");
|
||||
foreach(var s in _subscriptions) s.Dispose();
|
||||
_subscriptions.Clear();
|
||||
foreach(var ss in _watchedEntitySubscriptions.Values) foreach(var s in ss) s.Dispose();
|
||||
_watchedEntitySubscriptions.Clear();
|
||||
_active = false;
|
||||
EntityInfoGathered.Clear();
|
||||
VisibleEntities.Clear();
|
||||
VisibleHostiles.Clear();
|
||||
VisibleEnemies.Clear();
|
||||
VisibleFriendlies.Clear();
|
||||
}
|
||||
|
||||
public Entity(ItemManager itemManager, Zone zone, EquippableItem hull, EntitySettings settings)
|
||||
|
|
@ -196,33 +269,30 @@ public abstract class Entity
|
|||
.Merge(HypothermiaDeath.Select(_ => CauseOfDeath.Hypothermia))
|
||||
.Merge(ItemDestroyed.Where(i=>i.GetBehavior<Cockpit>()!=null).Select(_ => CauseOfDeath.CockpitDestroyed));
|
||||
|
||||
Target.Subscribe(entity => entity?.TargetedBy.OnNext(this));
|
||||
TargetedBy.Subscribe(enemy =>
|
||||
{
|
||||
TargetedByCount.Value++;
|
||||
enemy.Target.Where(t => t != this).Take(1).Subscribe(_ => TargetedByCount.Value--);
|
||||
});
|
||||
|
||||
EntityInfoGathered.ObserveReplace().Subscribe(replace =>
|
||||
{
|
||||
if (replace.OldValue < ItemManager.GameplaySettings.TargetDetectionInfoThreshold &&
|
||||
replace.NewValue > ItemManager.GameplaySettings.TargetDetectionInfoThreshold)
|
||||
{
|
||||
VisibleEntities.Add(replace.Key);
|
||||
if(IsHostileTo(replace.Key))
|
||||
VisibleHostiles.Add(replace.Key);
|
||||
}
|
||||
if (replace.OldValue > ItemManager.GameplaySettings.TargetDetectionInfoThreshold &&
|
||||
replace.NewValue < ItemManager.GameplaySettings.TargetDetectionInfoThreshold)
|
||||
{
|
||||
VisibleEntities.Remove(replace.Key);
|
||||
if(IsHostileTo(replace.Key))
|
||||
VisibleHostiles.Remove(replace.Key);
|
||||
}
|
||||
});
|
||||
EntityInfoGathered.ObserveRemove().Subscribe(remove => VisibleEntities.Remove(remove.Key));
|
||||
// The entity's presence is permitted when its faction owns the zone, or the current security level at its location is low enough
|
||||
PresencePermitted = new ReadOnlyReactiveProperty<bool>(CurrentSecurityLevel.Select(security =>
|
||||
IsPresencePermitted(GetFactionRelationship(Zone.SectorZone.Owner), security)), true);
|
||||
}
|
||||
|
||||
public bool IsHostileTo(Entity other, bool recursive = false)
|
||||
{
|
||||
if (Faction == null)
|
||||
return !recursive && other.Faction != null && other.IsHostileTo(this, true);
|
||||
|
||||
// TODO: Inter-faction hostility
|
||||
// When the entity faction owns the zone, they are hostile to trespassers or those hostile to them
|
||||
if (Faction.ID == Zone.SectorZone.Owner.ID)
|
||||
return recursive ? !other.PresencePermitted.Value : !other.PresencePermitted.Value || other.IsHostileTo(this, true);
|
||||
|
||||
return !recursive && other.IsHostileTo(this, true);
|
||||
}
|
||||
|
||||
// TODO: Inter-faction relationships
|
||||
public FactionRelationship GetFactionRelationship(Faction faction) =>
|
||||
this is Ship {IsPlayerShip: true} ? Zone.Sector.FactionRelationships[faction] : faction==Faction ? FactionRelationship.Beloved : FactionRelationship.Neutral;
|
||||
|
||||
public static bool IsPresencePermitted(FactionRelationship relationship, SecurityLevel securityLevel) => (int) relationship - (int) securityLevel > 0;
|
||||
|
||||
public void ActivateConsumable(ConsumableItem item)
|
||||
{
|
||||
_activeConsumables.Add(new ConsumableItemEffect(item, this));
|
||||
|
|
@ -251,16 +321,6 @@ public abstract class Entity
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool IsHostileTo(Entity other)
|
||||
{
|
||||
if (Faction == null)
|
||||
{
|
||||
return other.Faction != null;
|
||||
}
|
||||
|
||||
return Faction.PlayerHostile && other.Faction == null;
|
||||
}
|
||||
|
||||
private void MapEntity()
|
||||
{
|
||||
var hullData = ItemManager.GetData(Hull) as HullData;
|
||||
|
|
@ -809,6 +869,17 @@ public abstract class Entity
|
|||
|
||||
TargetRange = Target.Value == null ? -1 : length(Position - Target.Value.Position);
|
||||
|
||||
var localSecurityLevel = Zone.GetSecurityLevel(Position.xz);
|
||||
if (CurrentSecurityLevel.Value != localSecurityLevel) CurrentSecurityLevel.Value = localSecurityLevel;
|
||||
|
||||
foreach (var entity in Zone.Entities)
|
||||
{
|
||||
var previousHostility = EntityHostility[entity];
|
||||
var newHostility = IsHostileTo(entity);
|
||||
if (newHostility != previousHostility)
|
||||
EntityHostility[entity] = newHostility;
|
||||
}
|
||||
|
||||
foreach (var v in VisibilitySources.Keys.ToArray())
|
||||
{
|
||||
VisibilitySources[v] = AetheriaMath.Decay(VisibilitySources[v], ItemManager.GameplaySettings.VisibilityDecay, delta);
|
||||
|
|
|
|||
|
|
@ -30,4 +30,14 @@ public class OrbitalEntity : Entity
|
|||
|
||||
base.Update(delta);
|
||||
}
|
||||
|
||||
public bool IsSecureArea
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SecurityRadius < 1) return false;
|
||||
if (Faction == null) return false;
|
||||
return !IsPresencePermitted(Zone.Sector.FactionRelationships[Faction], SecurityLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public class PlanetSettings
|
|||
public ExponentialLerp AsteroidRespawnTime;
|
||||
public float GravityStrength;
|
||||
public float MiningDifficulty = 500f;
|
||||
public float SecureAreaRadiusMultiplier = .45f;
|
||||
|
||||
public ExponentialCurve OrbitPeriod;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,11 +42,13 @@ public class Zone
|
|||
}
|
||||
public ZonePack Pack { get; }
|
||||
public SectorZone SectorZone { get; }
|
||||
public Sector Sector { get; }
|
||||
|
||||
public Zone(ItemManager itemManager, PlanetSettings settings, ZonePack pack, SectorZone sectorZone)
|
||||
public Zone(ItemManager itemManager, PlanetSettings settings, ZonePack pack, SectorZone sectorZone, Sector sector)
|
||||
{
|
||||
_time = pack.Time;
|
||||
SectorZone = sectorZone;
|
||||
Sector = sector;
|
||||
Pack = pack;
|
||||
_itemManager = itemManager;
|
||||
Settings = settings;
|
||||
|
|
@ -306,6 +308,24 @@ public class Zone
|
|||
//miner.AddCargo(newSimpleCommodity);
|
||||
}
|
||||
}
|
||||
|
||||
public SecurityLevel GetSecurityLevel(float2 pos)
|
||||
{
|
||||
if (SectorZone.Owner==null) return SecurityLevel.Open;
|
||||
|
||||
var security = SecurityLevel.Open;
|
||||
foreach (var entity in Entities)
|
||||
{
|
||||
if (entity is OrbitalEntity orbitalEntity && orbitalEntity.SecurityRadius > 1 && entity.Faction.ID == SectorZone.Owner.ID)
|
||||
{
|
||||
if (orbitalEntity.SecurityLevel > security && length(orbitalEntity.Position.xz - pos) < orbitalEntity.SecurityRadius * Settings.SecureAreaRadiusMultiplier)
|
||||
security = orbitalEntity.SecurityLevel;
|
||||
}
|
||||
}
|
||||
|
||||
return security;
|
||||
}
|
||||
|
||||
public float GetHeight(float2 position)
|
||||
{
|
||||
var result = -PowerPulse(length(position)/(Pack.Radius*2), Settings.ZoneDepthExponent) * Settings.ZoneDepth;
|
||||
|
|
|
|||
Loading…
Reference in a new issue