Add utility AI (#806)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com> Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
This commit is contained in:
70
Content.Server/AI/WorldState/Blackboard.cs
Normal file
70
Content.Server/AI/WorldState/Blackboard.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Reflection;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.AI.WorldState
|
||||
{
|
||||
/// <summary>
|
||||
/// The blackboard functions as an AI's repository of knowledge in a common format.
|
||||
/// </summary>
|
||||
public sealed class Blackboard
|
||||
{
|
||||
// Some stuff like "My Health" is easy to represent as components but abstract stuff like "How much food is nearby"
|
||||
// is harder. This also allows data to be cached if it's being hit frequently.
|
||||
|
||||
// This also stops you from re-writing the same boilerplate everywhere of stuff like "Do I have OuterClothing on?"
|
||||
|
||||
private readonly Dictionary<Type, IAiState> _states = new Dictionary<Type, IAiState>();
|
||||
private readonly List<IPlanningState> _planningStates = new List<IPlanningState>();
|
||||
|
||||
public Blackboard(IEntity owner)
|
||||
{
|
||||
Setup(owner);
|
||||
}
|
||||
|
||||
private void Setup(IEntity owner)
|
||||
{
|
||||
var typeFactory = IoCManager.Resolve<IDynamicTypeFactory>();
|
||||
var blackboardManager = IoCManager.Resolve<BlackboardManager>();
|
||||
|
||||
foreach (var state in blackboardManager.AiStates)
|
||||
{
|
||||
var newState = (IAiState) typeFactory.CreateInstance(state);
|
||||
newState.Setup(owner);
|
||||
_states.Add(newState.GetType(), newState);
|
||||
|
||||
switch (newState)
|
||||
{
|
||||
case IPlanningState planningState:
|
||||
_planningStates.Add(planningState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All planning states will have their values reset
|
||||
/// </summary>
|
||||
public void ResetPlanning()
|
||||
{
|
||||
foreach (var state in _planningStates)
|
||||
{
|
||||
state.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the AI state class
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public T GetState<T>() where T : IAiState
|
||||
{
|
||||
return (T) _states[typeof(T)];
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Content.Server/AI/WorldState/BlackboardManager.cs
Normal file
31
Content.Server/AI/WorldState/BlackboardManager.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Interfaces.Reflection;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.AI.WorldState
|
||||
{
|
||||
// This will also handle the global blackboard at some point
|
||||
/// <summary>
|
||||
/// Manager the AI blackboard states
|
||||
/// </summary>
|
||||
public sealed class BlackboardManager
|
||||
{
|
||||
// Cache the known types
|
||||
public IReadOnlyCollection<Type> AiStates => _aiStates;
|
||||
private List<Type> _aiStates = new List<Type>();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
|
||||
|
||||
foreach (var state in reflectionManager.GetAllChildren(typeof(IAiState)))
|
||||
{
|
||||
_aiStates.Add(state);
|
||||
}
|
||||
|
||||
DebugTools.AssertNotNull(_aiStates);
|
||||
}
|
||||
}
|
||||
}
|
||||
151
Content.Server/AI/WorldState/StateData.cs
Normal file
151
Content.Server/AI/WorldState/StateData.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState
|
||||
{
|
||||
/// <summary>
|
||||
/// Basic StateDate, no frills
|
||||
/// </summary>
|
||||
public interface IAiState
|
||||
{
|
||||
void Setup(IEntity owner);
|
||||
}
|
||||
|
||||
public interface IPlanningState
|
||||
{
|
||||
void Reset();
|
||||
}
|
||||
|
||||
public interface ICachedState
|
||||
{
|
||||
void CheckCache();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default class for state values. Also see CachedStateData and PlanningStateData
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class StateData<T> : IAiState
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
protected IEntity Owner { get; private set; }
|
||||
|
||||
public void Setup(IEntity owner)
|
||||
{
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
public abstract T GetValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For when we want to set StateData but not reset it when re-planning actions
|
||||
/// Useful for group blackboard sharing or to avoid repeating the same action (e.g. bark phrases).
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class StoredStateData<T> : IAiState
|
||||
{
|
||||
// Probably not the best class name but couldn't think of anything better
|
||||
public abstract string Name { get; }
|
||||
private IEntity Owner { get; set; }
|
||||
|
||||
private T _value;
|
||||
|
||||
public void Setup(IEntity owner)
|
||||
{
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
public virtual void SetValue(T value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public T GetValue()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is state data that is transient and forgotten every time we re-plan
|
||||
/// e.g. "Current Target" gets updated for every action we consider
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class PlanningStateData<T> : IAiState, IPlanningState
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
protected IEntity Owner { get; private set; }
|
||||
protected T Value;
|
||||
|
||||
public void Setup(IEntity owner)
|
||||
{
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
public abstract void Reset();
|
||||
|
||||
public T GetValue()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
public virtual void SetValue(T value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is state data that is cached for n seconds before being discarded.
|
||||
/// Mostly useful to get nearby components and store the value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class CachedStateData<T> : IAiState, ICachedState
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
protected IEntity Owner { get; private set; }
|
||||
private bool _cached;
|
||||
protected T Value;
|
||||
private DateTime _lastCache = DateTime.Now;
|
||||
/// <summary>
|
||||
/// How long something stays in the cache before new values are retrieved
|
||||
/// </summary>
|
||||
protected float CacheTime { get; set; } = 2.0f;
|
||||
|
||||
public void Setup(IEntity owner)
|
||||
{
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
public void CheckCache()
|
||||
{
|
||||
if (!_cached || (DateTime.Now - _lastCache).TotalSeconds >= CacheTime)
|
||||
{
|
||||
_cached = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_cached = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the cache is stale we'll retrieve the actual value and store it again
|
||||
/// </summary>
|
||||
protected abstract T GetTrueValue();
|
||||
|
||||
public T GetValue()
|
||||
{
|
||||
CheckCache();
|
||||
if (!_cached)
|
||||
{
|
||||
Value = GetTrueValue();
|
||||
_cached = true;
|
||||
_lastCache = DateTime.Now;
|
||||
}
|
||||
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Clothing
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class EquippedClothingState : StateData<Dictionary<EquipmentSlotDefines.Slots, IEntity>>
|
||||
{
|
||||
public override string Name => "EquippedClothing";
|
||||
|
||||
public override Dictionary<EquipmentSlotDefines.Slots, IEntity> GetValue()
|
||||
{
|
||||
var result = new Dictionary<EquipmentSlotDefines.Slots, IEntity>();
|
||||
|
||||
if (!Owner.TryGetComponent(out InventoryComponent inventoryComponent))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var slot in EquipmentSlotDefines.AllSlots)
|
||||
{
|
||||
if (!inventoryComponent.HasSlot(slot)) continue;
|
||||
var slotItem = inventoryComponent.GetSlotItem(slot);
|
||||
|
||||
if (slotItem != null)
|
||||
{
|
||||
result.Add(slot, slotItem.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Utils;
|
||||
using Content.Server.GameObjects;
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Clothing
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class NearbyClothingState : CachedStateData<List<IEntity>>
|
||||
{
|
||||
public override string Name => "NearbyClothing";
|
||||
|
||||
protected override List<IEntity> GetTrueValue()
|
||||
{
|
||||
var result = new List<IEntity>();
|
||||
|
||||
if (!Owner.TryGetComponent(out AiControllerComponent controller))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var entity in Visibility
|
||||
.GetNearestEntities(Owner.Transform.GridPosition, typeof(ClothingComponent), controller.VisionRadius))
|
||||
{
|
||||
if (ContainerHelpers.TryGetContainer(entity, out var container))
|
||||
{
|
||||
if (!container.Owner.HasComponent<EntityStorageComponent>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.Add(entity);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Utils;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Server.GameObjects.Components.Power.Chargers;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Combat.Nearby
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class NearbyLaserChargersState : StateData<List<IEntity>>
|
||||
{
|
||||
public override string Name => "NearbyLaserChargers";
|
||||
|
||||
public override List<IEntity> GetValue()
|
||||
{
|
||||
var nearby = new List<IEntity>();
|
||||
|
||||
if (!Owner.TryGetComponent(out AiControllerComponent controller))
|
||||
{
|
||||
return nearby;
|
||||
}
|
||||
|
||||
foreach (var result in Visibility
|
||||
.GetNearestEntities(Owner.Transform.GridPosition, typeof(WeaponCapacitorChargerComponent), controller.VisionRadius))
|
||||
{
|
||||
nearby.Add(result);
|
||||
}
|
||||
|
||||
return nearby;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Utils;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Combat.Nearby
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class NearbyLaserWeapons : StateData<List<IEntity>>
|
||||
{
|
||||
public override string Name => "NearbyLaserWeapons";
|
||||
|
||||
public override List<IEntity> GetValue()
|
||||
{
|
||||
var result = new List<IEntity>();
|
||||
|
||||
if (!Owner.TryGetComponent(out AiControllerComponent controller))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var entity in Visibility
|
||||
.GetNearestEntities(Owner.Transform.GridPosition, typeof(HitscanWeaponComponent), controller.VisionRadius))
|
||||
{
|
||||
result.Add(entity);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Utils;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Server.GameObjects.Components.Weapon.Melee;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Combat.Nearby
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class NearbyMeleeWeapons : CachedStateData<List<IEntity>>
|
||||
{
|
||||
public override string Name => "NearbyMeleeWeapons";
|
||||
|
||||
protected override List<IEntity> GetTrueValue()
|
||||
{
|
||||
var result = new List<IEntity>();
|
||||
|
||||
if (!Owner.TryGetComponent(out AiControllerComponent controller))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var entity in Visibility
|
||||
.GetNearestEntities(Owner.Transform.GridPosition, typeof(MeleeWeaponComponent), controller.VisionRadius))
|
||||
{
|
||||
result.Add(entity);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Utils;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Server.GameObjects.Components.Weapon.Ranged;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Combat.Nearby
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class NearbyRangedWeapons : CachedStateData<List<IEntity>>
|
||||
{
|
||||
public override string Name => "NearbyRangedWeapons";
|
||||
|
||||
protected override List<IEntity> GetTrueValue()
|
||||
{
|
||||
var result = new List<IEntity>();
|
||||
|
||||
if (!Owner.TryGetComponent(out AiControllerComponent controller))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var entity in Visibility
|
||||
.GetNearestEntities(Owner.Transform.GridPosition, typeof(RangedWeaponComponent), controller.VisionRadius))
|
||||
{
|
||||
result.Add(entity);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Combat.Ranged
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class Accuracy : StateData<float>
|
||||
{
|
||||
public override string Name => "Accuracy";
|
||||
|
||||
public override float GetValue()
|
||||
{
|
||||
// TODO: Maybe just make it a SetValue (maybe make a third type besides sensor / daemon called settablestate)
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Combat.Ranged
|
||||
{
|
||||
/// <summary>
|
||||
/// How long to wait between bursts
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class BurstCooldown : PlanningStateData<float>
|
||||
{
|
||||
public override string Name => "BurstCooldown";
|
||||
public override void Reset()
|
||||
{
|
||||
Value = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Content.Server.GameObjects;
|
||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Combat.Ranged
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the discrete ammo count
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class EquippedRangedWeaponAmmo : StateData<int?>
|
||||
{
|
||||
public override string Name => "EquippedRangedWeaponAmmo";
|
||||
|
||||
public override int? GetValue()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out HandsComponent handsComponent))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var equippedItem = handsComponent.GetActiveHand?.Owner;
|
||||
if (equippedItem == null) return null;
|
||||
|
||||
if (equippedItem.TryGetComponent(out HitscanWeaponComponent hitscanWeaponComponent))
|
||||
{
|
||||
return (int) hitscanWeaponComponent.CapacitorComponent.Charge / hitscanWeaponComponent.BaseFireCost;
|
||||
}
|
||||
|
||||
if (equippedItem.TryGetComponent(out BallisticMagazineWeaponComponent ballisticComponent))
|
||||
{
|
||||
return ballisticComponent.MagazineSlot.ContainedEntities.Count;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Combat.Ranged
|
||||
{
|
||||
/// <summary>
|
||||
/// How many shots to take before cooling down
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class MaxBurstCount : PlanningStateData<int>
|
||||
{
|
||||
public override string Name => "BurstCount";
|
||||
public override void Reset()
|
||||
{
|
||||
Value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Combat
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class WeaponEntityState : PlanningStateData<IEntity>
|
||||
{
|
||||
// Similar to TargetEntity
|
||||
public override string Name => "WeaponEntity";
|
||||
public override void Reset()
|
||||
{
|
||||
Value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Content.Server.GameObjects;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Hands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class AnyFreeHandState : StateData<bool>
|
||||
{
|
||||
public override string Name => "AnyFreeHand";
|
||||
public override bool GetValue()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out HandsComponent handsComponent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var hand in handsComponent.ActivePriorityEnumerable())
|
||||
{
|
||||
if (handsComponent.GetHand(hand) == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Content.Server/AI/WorldState/States/Hands/FreeHands.cs
Normal file
32
Content.Server/AI/WorldState/States/Hands/FreeHands.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Hands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class FreeHands : StateData<List<string>>
|
||||
{
|
||||
public override string Name => "FreeHands";
|
||||
|
||||
public override List<string> GetValue()
|
||||
{
|
||||
var result = new List<string>();
|
||||
|
||||
if (!Owner.TryGetComponent(out HandsComponent handsComponent))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var hand in handsComponent.ActivePriorityEnumerable())
|
||||
{
|
||||
if (handsComponent.GetHand(hand) == null)
|
||||
{
|
||||
result.Add(hand);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Content.Server/AI/WorldState/States/Hands/HandItemsState.cs
Normal file
33
Content.Server/AI/WorldState/States/Hands/HandItemsState.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Hands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class HandItemsState : StateData<List<IEntity>>
|
||||
{
|
||||
public override string Name => "HandItems";
|
||||
public override List<IEntity> GetValue()
|
||||
{
|
||||
var result = new List<IEntity>();
|
||||
if (!Owner.TryGetComponent(out HandsComponent handsComponent))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var hand in handsComponent.ActivePriorityEnumerable())
|
||||
{
|
||||
var item = handsComponent.GetHand(hand);
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
result.Add(item.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Content.Server.GameObjects;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Inventory
|
||||
{
|
||||
/// <summary>
|
||||
/// AKA what's in active hand
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class EquippedEntityState : StateData<IEntity>
|
||||
{
|
||||
public override string Name => "EquippedEntity";
|
||||
|
||||
public override IEntity GetValue()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out HandsComponent handsComponent))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return handsComponent.GetActiveHand?.Owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Inventory
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class InventoryState : StateData<List<IEntity>>
|
||||
{
|
||||
public override string Name => "Inventory";
|
||||
|
||||
public override List<IEntity> GetValue()
|
||||
{
|
||||
var inventory = new List<IEntity>();
|
||||
|
||||
if (Owner.TryGetComponent(out HandsComponent handsComponent))
|
||||
{
|
||||
foreach (var item in handsComponent.GetAllHeldItems())
|
||||
{
|
||||
inventory.Add(item.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: InventoryComponent (Pockets were throwing)
|
||||
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Inventory
|
||||
{
|
||||
/// <summary>
|
||||
/// If we open a storage locker than it will be stored here
|
||||
/// Useful if we want to close it after
|
||||
/// </summary>
|
||||
public sealed class LastOpenedStorageState : StoredStateData<IEntity>
|
||||
{
|
||||
// TODO: IF we chain lockers need to handle it.
|
||||
// Fine for now I guess
|
||||
public override string Name => "LastOpenedStorage";
|
||||
|
||||
public override void SetValue(IEntity value)
|
||||
{
|
||||
base.SetValue(value);
|
||||
if (value != null && !value.HasComponent<EntityStorageComponent>())
|
||||
{
|
||||
Logger.Warning("Set LastOpenedStorageState for an entity that doesn't have a storage component");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Mobs
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class NearbyPlayersState : CachedStateData<List<IEntity>>
|
||||
{
|
||||
public override string Name => "NearbyPlayers";
|
||||
|
||||
protected override List<IEntity> GetTrueValue()
|
||||
{
|
||||
var result = new List<IEntity>();
|
||||
|
||||
if (!Owner.TryGetComponent(out AiControllerComponent controller))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var playerManager = IoCManager.Resolve<IPlayerManager>();
|
||||
var nearbyPlayers = playerManager.GetPlayersInRange(Owner.Transform.GridPosition, (int) controller.VisionRadius);
|
||||
|
||||
foreach (var player in nearbyPlayers)
|
||||
{
|
||||
if (player.AttachedEntity != Owner && player.AttachedEntity.HasComponent<SpeciesComponent>())
|
||||
{
|
||||
result.Add(player.AttachedEntity);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Utils;
|
||||
using Content.Server.GameObjects;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Mobs
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class NearbySpeciesState : CachedStateData<List<IEntity>>
|
||||
{
|
||||
public override string Name => "NearbySpecies";
|
||||
|
||||
protected override List<IEntity> GetTrueValue()
|
||||
{
|
||||
var result = new List<IEntity>();
|
||||
|
||||
if (!Owner.TryGetComponent(out AiControllerComponent controller))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var entity in Visibility.GetEntitiesInRange(Owner.Transform.GridPosition, typeof(SpeciesComponent), controller.VisionRadius))
|
||||
{
|
||||
if (entity == Owner) continue;
|
||||
result.Add(entity);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Movement
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class MoveTargetState : PlanningStateData<IEntity>
|
||||
{
|
||||
public override string Name => "MoveTarget";
|
||||
public override void Reset()
|
||||
{
|
||||
Value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Content.Server/AI/WorldState/States/Nutrition/HungryState.cs
Normal file
39
Content.Server/AI/WorldState/States/Nutrition/HungryState.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Nutrition;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Nutrition
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class HungryState : StateData<bool>
|
||||
{
|
||||
public override string Name => "Hungry";
|
||||
|
||||
public override bool GetValue()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out HungerComponent hungerComponent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (hungerComponent.CurrentHungerThreshold)
|
||||
{
|
||||
case HungerThreshold.Overfed:
|
||||
return false;
|
||||
case HungerThreshold.Okay:
|
||||
return false;
|
||||
case HungerThreshold.Peckish:
|
||||
return true;
|
||||
case HungerThreshold.Starving:
|
||||
return true;
|
||||
case HungerThreshold.Dead:
|
||||
return true;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(hungerComponent.CurrentHungerThreshold),
|
||||
hungerComponent.CurrentHungerThreshold,
|
||||
null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Utils;
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Server.GameObjects.Components.Nutrition;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Nutrition
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class NearbyDrinkState: CachedStateData<List<IEntity>>
|
||||
{
|
||||
public override string Name => "NearbyDrink";
|
||||
|
||||
protected override List<IEntity> GetTrueValue()
|
||||
{
|
||||
var result = new List<IEntity>();
|
||||
|
||||
if (!Owner.TryGetComponent(out AiControllerComponent controller))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var entity in Visibility
|
||||
.GetNearestEntities(Owner.Transform.GridPosition, typeof(DrinkComponent), controller.VisionRadius))
|
||||
{
|
||||
if (ContainerHelpers.TryGetContainer(entity, out var container))
|
||||
{
|
||||
if (!container.Owner.HasComponent<EntityStorageComponent>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.Add(entity);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Utils;
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Server.GameObjects.Components.Nutrition;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Nutrition
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class NearbyFoodState : CachedStateData<List<IEntity>>
|
||||
{
|
||||
public override string Name => "NearbyFood";
|
||||
|
||||
protected override List<IEntity> GetTrueValue()
|
||||
{
|
||||
var result = new List<IEntity>();
|
||||
|
||||
if (!Owner.TryGetComponent(out AiControllerComponent controller))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var entity in Visibility
|
||||
.GetNearestEntities(Owner.Transform.GridPosition, typeof(FoodComponent), controller.VisionRadius))
|
||||
{
|
||||
if (ContainerHelpers.TryGetContainer(entity, out var container))
|
||||
{
|
||||
if (!container.Owner.HasComponent<EntityStorageComponent>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.Add(entity);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Nutrition;
|
||||
using JetBrains.Annotations;
|
||||
using ThirstComponent = Content.Server.GameObjects.Components.Nutrition.ThirstComponent;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Nutrition
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ThirstyState : StateData<bool>
|
||||
{
|
||||
public override string Name => "Thirsty";
|
||||
|
||||
public override bool GetValue()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out ThirstComponent thirstComponent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (thirstComponent.CurrentThirstThreshold)
|
||||
{
|
||||
case ThirstThreshold.OverHydrated:
|
||||
return false;
|
||||
case ThirstThreshold.Okay:
|
||||
return false;
|
||||
case ThirstThreshold.Thirsty:
|
||||
return true;
|
||||
case ThirstThreshold.Parched:
|
||||
return true;
|
||||
case ThirstThreshold.Dead:
|
||||
return true;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(thirstComponent.CurrentThirstThreshold),
|
||||
thirstComponent.CurrentThirstThreshold,
|
||||
null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Content.Server/AI/WorldState/States/SelfState.cs
Normal file
16
Content.Server/AI/WorldState/States/SelfState.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class SelfState : StateData<IEntity>
|
||||
{
|
||||
public override string Name => "Self";
|
||||
|
||||
public override IEntity GetValue()
|
||||
{
|
||||
return Owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Content.Server/AI/WorldState/States/TargetEntityState.cs
Normal file
19
Content.Server/AI/WorldState/States/TargetEntityState.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Could be target item to equip, target to attack, etc.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class TargetEntityState : PlanningStateData<IEntity>
|
||||
{
|
||||
public override string Name => "TargetEntity";
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
Value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.AI.WorldState.States.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for the utility AI; sets the threshold score we need to beat
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class LastUtilityScoreState : StateData<float>
|
||||
{
|
||||
public override string Name => "LastBonus";
|
||||
private float _value = 0.0f;
|
||||
|
||||
public void SetValue(float value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public override float GetValue()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user