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:
metalgearsloth
2020-06-18 22:52:44 +10:00
committed by GitHub
parent 9b8cedf6c6
commit 5391d3c72a
211 changed files with 10335 additions and 527 deletions

View 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)];
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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");
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View 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);
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View File

@@ -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;
}
}
}