NPC refactor (#10122)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using Content.Client.AI;
|
||||
using Content.Client.NPC;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Client.AI;
|
||||
using Content.Client.NPC;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -80,6 +80,8 @@ namespace Content.Client.Entry
|
||||
prototypes.RegisterIgnore("objective");
|
||||
prototypes.RegisterIgnore("holiday");
|
||||
prototypes.RegisterIgnore("aiFaction");
|
||||
prototypes.RegisterIgnore("htnCompound");
|
||||
prototypes.RegisterIgnore("htnPrimitive");
|
||||
prototypes.RegisterIgnore("gameMap");
|
||||
prototypes.RegisterIgnore("behaviorSet");
|
||||
prototypes.RegisterIgnore("lobbyBackground");
|
||||
|
||||
@@ -48,10 +48,13 @@ public sealed class DamageStateVisualizerSystem : VisualizerSystem<DamageStateVi
|
||||
}
|
||||
|
||||
// So they don't draw over mobs anymore
|
||||
if (data == DamageState.Dead && sprite.DrawDepth > (int) DrawDepth.Items)
|
||||
if (data == DamageState.Dead)
|
||||
{
|
||||
if (sprite.DrawDepth > (int) DrawDepth.FloorObjects)
|
||||
{
|
||||
component.OriginalDrawDepth = sprite.DrawDepth;
|
||||
sprite.DrawDepth = (int) DrawDepth.Items;
|
||||
sprite.DrawDepth = (int) DrawDepth.FloorObjects;
|
||||
}
|
||||
}
|
||||
else if (component.OriginalDrawDepth != null)
|
||||
{
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.AI;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.AI
|
||||
namespace Content.Client.NPC
|
||||
{
|
||||
#if DEBUG
|
||||
public sealed class ClientAiDebugSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
private AiDebugMode _tooltips = AiDebugMode.None;
|
||||
public AiDebugMode Tooltips { get; private set; } = AiDebugMode.None;
|
||||
private readonly Dictionary<EntityUid, PanelContainer> _aiBoxes = new();
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
if (_tooltips == 0)
|
||||
if (Tooltips == 0)
|
||||
{
|
||||
if (_aiBoxes.Count > 0)
|
||||
{
|
||||
@@ -76,7 +70,7 @@ namespace Content.Client.AI
|
||||
|
||||
private void HandleUtilityAiDebugMessage(SharedAiDebug.UtilityAiDebugMessage message)
|
||||
{
|
||||
if ((_tooltips & AiDebugMode.Thonk) != 0)
|
||||
if ((Tooltips & AiDebugMode.Thonk) != 0)
|
||||
{
|
||||
// I guess if it's out of range we don't know about it?
|
||||
var entity = message.EntityUid;
|
||||
@@ -93,7 +87,7 @@ namespace Content.Client.AI
|
||||
|
||||
private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message)
|
||||
{
|
||||
if ((_tooltips & AiDebugMode.Paths) != 0)
|
||||
if ((Tooltips & AiDebugMode.Paths) != 0)
|
||||
{
|
||||
var entity = message.EntityUid;
|
||||
TryCreatePanel(entity);
|
||||
@@ -107,7 +101,7 @@ namespace Content.Client.AI
|
||||
|
||||
private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message)
|
||||
{
|
||||
if ((_tooltips & AiDebugMode.Paths) != 0)
|
||||
if ((Tooltips & AiDebugMode.Paths) != 0)
|
||||
{
|
||||
var entity = message.EntityUid;
|
||||
TryCreatePanel(entity);
|
||||
@@ -126,23 +120,23 @@ namespace Content.Client.AI
|
||||
tooltip.Dispose();
|
||||
}
|
||||
_aiBoxes.Clear();
|
||||
_tooltips = AiDebugMode.None;
|
||||
Tooltips = AiDebugMode.None;
|
||||
}
|
||||
|
||||
|
||||
private void EnableTooltip(AiDebugMode tooltip)
|
||||
public void EnableTooltip(AiDebugMode tooltip)
|
||||
{
|
||||
_tooltips |= tooltip;
|
||||
Tooltips |= tooltip;
|
||||
}
|
||||
|
||||
private void DisableTooltip(AiDebugMode tooltip)
|
||||
public void DisableTooltip(AiDebugMode tooltip)
|
||||
{
|
||||
_tooltips &= ~tooltip;
|
||||
Tooltips &= ~tooltip;
|
||||
}
|
||||
|
||||
public void ToggleTooltip(AiDebugMode tooltip)
|
||||
{
|
||||
if ((_tooltips & tooltip) != 0)
|
||||
if ((Tooltips & tooltip) != 0)
|
||||
{
|
||||
DisableTooltip(tooltip);
|
||||
}
|
||||
@@ -200,5 +194,4 @@ namespace Content.Client.AI
|
||||
Paths = 1 << 1,
|
||||
Thonk = 1 << 2,
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,29 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.AI;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.AI
|
||||
namespace Content.Client.NPC
|
||||
{
|
||||
#if DEBUG
|
||||
public sealed class ClientPathfindingDebugSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
|
||||
private PathfindingDebugMode _modes = PathfindingDebugMode.None;
|
||||
public PathfindingDebugMode Modes { get; private set; } = PathfindingDebugMode.None;
|
||||
private float _routeDuration = 4.0f; // How long before we remove a route from the overlay
|
||||
private DebugPathfindingOverlay? _overlay;
|
||||
|
||||
@@ -47,8 +39,8 @@ namespace Content.Client.AI
|
||||
|
||||
private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message)
|
||||
{
|
||||
if ((_modes & PathfindingDebugMode.Nodes) != 0 ||
|
||||
(_modes & PathfindingDebugMode.Route) != 0)
|
||||
if ((Modes & PathfindingDebugMode.Nodes) != 0 ||
|
||||
(Modes & PathfindingDebugMode.Route) != 0)
|
||||
{
|
||||
_overlay?.AStarRoutes.Add(message);
|
||||
Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () =>
|
||||
@@ -61,8 +53,8 @@ namespace Content.Client.AI
|
||||
|
||||
private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message)
|
||||
{
|
||||
if ((_modes & PathfindingDebugMode.Nodes) != 0 ||
|
||||
(_modes & PathfindingDebugMode.Route) != 0)
|
||||
if ((Modes & PathfindingDebugMode.Nodes) != 0 ||
|
||||
(Modes & PathfindingDebugMode.Route) != 0)
|
||||
{
|
||||
_overlay?.JpsRoutes.Add(message);
|
||||
Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () =>
|
||||
@@ -96,7 +88,7 @@ namespace Content.Client.AI
|
||||
}
|
||||
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
_overlay = new DebugPathfindingOverlay(EntityManager, _eyeManager, _playerManager, _prototypeManager) {Modes = _modes};
|
||||
_overlay = new DebugPathfindingOverlay(EntityManager, _eyeManager, _playerManager, _prototypeManager) {Modes = Modes};
|
||||
overlayManager.AddOverlay(_overlay);
|
||||
|
||||
return _overlay;
|
||||
@@ -117,22 +109,22 @@ namespace Content.Client.AI
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
_modes = PathfindingDebugMode.None;
|
||||
Modes = PathfindingDebugMode.None;
|
||||
DisableOverlay();
|
||||
}
|
||||
|
||||
|
||||
private void EnableMode(PathfindingDebugMode tooltip)
|
||||
public void EnableMode(PathfindingDebugMode tooltip)
|
||||
{
|
||||
_modes |= tooltip;
|
||||
if (_modes != 0)
|
||||
Modes |= tooltip;
|
||||
if (Modes != 0)
|
||||
{
|
||||
EnableOverlay();
|
||||
}
|
||||
|
||||
if (_overlay != null)
|
||||
{
|
||||
_overlay.Modes = _modes;
|
||||
_overlay.Modes = Modes;
|
||||
}
|
||||
|
||||
if (tooltip == PathfindingDebugMode.Graph)
|
||||
@@ -149,27 +141,27 @@ namespace Content.Client.AI
|
||||
// So need further investigation.
|
||||
}
|
||||
|
||||
private void DisableMode(PathfindingDebugMode mode)
|
||||
public void DisableMode(PathfindingDebugMode mode)
|
||||
{
|
||||
if (mode == PathfindingDebugMode.Regions && (_modes & PathfindingDebugMode.Regions) != 0)
|
||||
if (mode == PathfindingDebugMode.Regions && (Modes & PathfindingDebugMode.Regions) != 0)
|
||||
{
|
||||
RaiseNetworkEvent(new SharedAiDebug.UnsubscribeReachableMessage());
|
||||
}
|
||||
|
||||
_modes &= ~mode;
|
||||
if (_modes == 0)
|
||||
Modes &= ~mode;
|
||||
if (Modes == 0)
|
||||
{
|
||||
DisableOverlay();
|
||||
}
|
||||
else if (_overlay != null)
|
||||
{
|
||||
_overlay.Modes = _modes;
|
||||
_overlay.Modes = Modes;
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleTooltip(PathfindingDebugMode mode)
|
||||
{
|
||||
if ((_modes & mode) != 0)
|
||||
if ((Modes & mode) != 0)
|
||||
{
|
||||
DisableMode(mode);
|
||||
}
|
||||
@@ -525,5 +517,4 @@ namespace Content.Client.AI
|
||||
CachedRegions = 1 << 3,
|
||||
Regions = 1 << 4,
|
||||
}
|
||||
#endif
|
||||
}
|
||||
7
Content.Client/NPC/HTN/HTNComponent.cs
Normal file
7
Content.Client/NPC/HTN/HTNComponent.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Content.Client.NPC.HTN;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class HTNComponent : NPCComponent
|
||||
{
|
||||
public string DebugText = string.Empty;
|
||||
}
|
||||
41
Content.Client/NPC/HTN/HTNOverlay.cs
Normal file
41
Content.Client/NPC/HTN/HTNOverlay.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Client.NPC.HTN;
|
||||
|
||||
public sealed class HTNOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entManager = default!;
|
||||
private readonly Font _font = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
public HTNOverlay(IEntityManager entManager, IResourceCache resourceCache)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.ViewportControl == null)
|
||||
return;
|
||||
|
||||
var handle = args.ScreenHandle;
|
||||
|
||||
foreach (var (comp, xform) in _entManager.EntityQuery<HTNComponent, TransformComponent>(true))
|
||||
{
|
||||
if (string.IsNullOrEmpty(comp.DebugText) || xform.MapID != args.MapId)
|
||||
continue;
|
||||
|
||||
var worldPos = xform.WorldPosition;
|
||||
|
||||
if (!args.WorldAABB.Contains(worldPos))
|
||||
continue;
|
||||
|
||||
var screenPos = args.ViewportControl.WorldToScreen(worldPos);
|
||||
handle.DrawString(_font, screenPos + new Vector2(0, 10f), comp.DebugText);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Content.Client/NPC/HTN/HTNSystem.cs
Normal file
54
Content.Client/NPC/HTN/HTNSystem.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Content.Shared.NPC;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
|
||||
namespace Content.Client.NPC.HTN;
|
||||
|
||||
public sealed class HTNSystem : EntitySystem
|
||||
{
|
||||
/*
|
||||
* Mainly handles clientside debugging for HTN NPCs.
|
||||
*/
|
||||
public bool EnableOverlay
|
||||
{
|
||||
get => _enableOverlay;
|
||||
set
|
||||
{
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
_enableOverlay = value;
|
||||
|
||||
if (_enableOverlay)
|
||||
{
|
||||
overlayManager.AddOverlay(new HTNOverlay(EntityManager, IoCManager.Resolve<IResourceCache>()));
|
||||
RaiseNetworkEvent(new RequestHTNMessage()
|
||||
{
|
||||
Enabled = true,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
overlayManager.RemoveOverlay<HTNOverlay>();
|
||||
RaiseNetworkEvent(new RequestHTNMessage()
|
||||
{
|
||||
Enabled = false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableOverlay;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<HTNMessage>(OnHTNMessage);
|
||||
}
|
||||
|
||||
private void OnHTNMessage(HTNMessage ev)
|
||||
{
|
||||
if (!TryComp<HTNComponent>(ev.Uid, out var htn))
|
||||
return;
|
||||
|
||||
htn.DebugText = ev.Text;
|
||||
}
|
||||
}
|
||||
8
Content.Client/NPC/NPCComponent.cs
Normal file
8
Content.Client/NPC/NPCComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.NPC;
|
||||
|
||||
namespace Content.Client.NPC;
|
||||
|
||||
public abstract class NPCComponent : SharedNPCComponent
|
||||
{
|
||||
|
||||
}
|
||||
22
Content.Client/NPC/NPCEui.cs
Normal file
22
Content.Client/NPC/NPCEui.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Content.Client.Eui;
|
||||
|
||||
namespace Content.Client.NPC;
|
||||
|
||||
public sealed class NPCEui : BaseEui
|
||||
{
|
||||
private NPCWindow? _window = new();
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
_window = new NPCWindow();
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
base.Closed();
|
||||
_window?.Close();
|
||||
_window = null;
|
||||
}
|
||||
}
|
||||
24
Content.Client/NPC/NPCWindow.xaml
Normal file
24
Content.Client/NPC/NPCWindow.xaml
Normal file
@@ -0,0 +1,24 @@
|
||||
<userInterface:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:Content.Client.HUD.UI"
|
||||
xmlns:userInterface="clr-namespace:Content.Client.UserInterface"
|
||||
Title="NPC debug"
|
||||
MinSize="200 200">
|
||||
<BoxContainer Name="Options" Orientation="Vertical">
|
||||
<ui:StripeBack>
|
||||
<Label Text="NPC" HorizontalAlignment="Center"/>
|
||||
</ui:StripeBack>
|
||||
<BoxContainer Name="NPCBox" Orientation="Vertical">
|
||||
<CheckBox Name="NPCPath" Text="Path"/>
|
||||
<CheckBox Name="NPCThonk" Text="Thonk"/>
|
||||
</BoxContainer>
|
||||
<ui:StripeBack>
|
||||
<Label Text="Pathfinder" HorizontalAlignment="Center"/>
|
||||
</ui:StripeBack>
|
||||
<BoxContainer Name="PathfinderBox" Orientation="Vertical">
|
||||
<CheckBox Name="PathNodes" Text="Nodes"/>
|
||||
<CheckBox Name="PathRoutes" Text="Routes"/>
|
||||
<CheckBox Name="PathRegions" Text="Regions"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</userInterface:FancyWindow>
|
||||
33
Content.Client/NPC/NPCWindow.xaml.cs
Normal file
33
Content.Client/NPC/NPCWindow.xaml.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Content.Client.UserInterface;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.NPC;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NPCWindow : FancyWindow
|
||||
{
|
||||
public NPCWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
var sysManager = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var debugSys = sysManager.GetEntitySystem<ClientAiDebugSystem>();
|
||||
var path = sysManager.GetEntitySystem<ClientPathfindingDebugSystem>();
|
||||
|
||||
NPCPath.Pressed = (debugSys.Tooltips & AiDebugMode.Paths) != 0x0;
|
||||
NPCThonk.Pressed = (debugSys.Tooltips & AiDebugMode.Thonk) != 0x0;
|
||||
|
||||
NPCPath.OnToggled += args => debugSys.ToggleTooltip(AiDebugMode.Paths);
|
||||
NPCThonk.OnToggled += args => debugSys.ToggleTooltip(AiDebugMode.Thonk);
|
||||
|
||||
PathNodes.Pressed = (path.Modes & PathfindingDebugMode.Nodes) != 0x0;
|
||||
PathRegions.Pressed = (path.Modes & PathfindingDebugMode.Regions) != 0x0;
|
||||
PathRoutes.Pressed = (path.Modes & PathfindingDebugMode.Route) != 0x0;
|
||||
|
||||
PathNodes.OnToggled += args => path.ToggleTooltip(PathfindingDebugMode.Nodes);
|
||||
PathRegions.OnToggled += args => path.ToggleTooltip(PathfindingDebugMode.Regions);
|
||||
PathRoutes.OnToggled += args => path.ToggleTooltip(PathfindingDebugMode.Route);
|
||||
}
|
||||
}
|
||||
16
Content.Client/NPC/ShowHTNCommand.cs
Normal file
16
Content.Client/NPC/ShowHTNCommand.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Content.Client.NPC.HTN;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.NPC;
|
||||
|
||||
public sealed class ShowHTNCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showhtn";
|
||||
public string Description => "Shows the current status for HTN NPCs";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var npcs = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<HTNSystem>();
|
||||
npcs.EnableOverlay ^= true;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.Decals.UI;
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.HUD;
|
||||
using Content.Client.Markers;
|
||||
using Content.Client.SubFloor;
|
||||
|
||||
@@ -235,8 +235,19 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
|
||||
protected override void CreateEffect(EntityUid uid, MuzzleFlashEvent message, EntityUid? user = null)
|
||||
{
|
||||
if (!Timing.IsFirstTimePredicted || !TryComp<TransformComponent>(uid, out var xform)) return;
|
||||
var ent = Spawn(message.Prototype, xform.Coordinates);
|
||||
if (!Timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
EntityCoordinates coordinates;
|
||||
|
||||
if (message.MatchRotation)
|
||||
coordinates = new EntityCoordinates(uid, Vector2.Zero);
|
||||
else if (TryComp<TransformComponent>(uid, out var xform))
|
||||
coordinates = xform.Coordinates;
|
||||
else
|
||||
return;
|
||||
|
||||
var ent = Spawn(message.Prototype, coordinates);
|
||||
|
||||
var effectXform = Transform(ent);
|
||||
effectXform.LocalRotation -= MathF.PI / 2;
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.AI.Utility;
|
||||
using Content.Server.AI.Utility.Actions;
|
||||
using Content.Server.AI.Utility.AiLogic;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.AI
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(BehaviorSetPrototype))]
|
||||
public sealed class BehaviorSetsTest
|
||||
{
|
||||
[Test]
|
||||
public async Task TestBehaviorSets()
|
||||
{
|
||||
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true});
|
||||
var server = pairTracker.Pair.Server;
|
||||
|
||||
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var reflectionManager = server.ResolveDependency<IReflectionManager>();
|
||||
|
||||
Dictionary<string, List<string>> behaviorSets = new();
|
||||
|
||||
// Test that all BehaviorSet actions exist.
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
foreach (var proto in protoManager.EnumeratePrototypes<BehaviorSetPrototype>())
|
||||
{
|
||||
behaviorSets[proto.ID] = proto.Actions.ToList();
|
||||
|
||||
foreach (var action in proto.Actions)
|
||||
{
|
||||
if (!reflectionManager.TryLooseGetType(action, out var actionType) ||
|
||||
!typeof(IAiUtility).IsAssignableFrom(actionType))
|
||||
{
|
||||
Assert.Fail($"Action {action} is not valid within BehaviorSet {proto.ID}");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Test that all BehaviorSets on NPCs exist.
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
foreach (var entity in protoManager.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (!entity.TryGetComponent<UtilityNPCComponent>("UtilityAI", out var npcNode)) continue;
|
||||
|
||||
foreach (var entry in npcNode.BehaviorSets)
|
||||
{
|
||||
Assert.That(behaviorSets.ContainsKey(entry), $"BehaviorSet {entry} in entity {entity.ID} not found");
|
||||
}
|
||||
}
|
||||
});
|
||||
await pairTracker.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Content.IntegrationTests/Tests/NPC/NPCTest.cs
Normal file
56
Content.IntegrationTests/Tests/NPC/NPCTest.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.NPC.HTN;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.NPC;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class NPCTest
|
||||
{
|
||||
[Test]
|
||||
public async Task CompoundRecursion()
|
||||
{
|
||||
var pool = await PoolManager.GetServerClient(new PoolSettings() { NoClient = true });
|
||||
var server = pool.Pair.Server;
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var counts = new Dictionary<string, int>();
|
||||
|
||||
foreach (var compound in protoManager.EnumeratePrototypes<HTNCompoundTask>())
|
||||
{
|
||||
Count(compound, counts);
|
||||
counts.Clear();
|
||||
}
|
||||
});
|
||||
|
||||
await pool.CleanReturnAsync();
|
||||
}
|
||||
|
||||
private static void Count(HTNCompoundTask compound, Dictionary<string, int> counts)
|
||||
{
|
||||
foreach (var branch in compound.Branches)
|
||||
{
|
||||
foreach (var task in branch.Tasks)
|
||||
{
|
||||
if (task is HTNCompoundTask compoundTask)
|
||||
{
|
||||
var count = counts.GetOrNew(compound.ID);
|
||||
count++;
|
||||
|
||||
Assert.That(count, Is.LessThan(50));
|
||||
counts[compound.ID] = count;
|
||||
Count(compoundTask, counts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.AI.Components;
|
||||
using Content.Server.AI.EntitySystems;
|
||||
using Content.Server.AI.Utility;
|
||||
using Content.Server.AI.Utility.AiLogic;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.AI.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class AddAiCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
|
||||
public string Command => "addai";
|
||||
public string Description => "Add an ai component with a given processor to an entity.";
|
||||
public string Help => "Usage: addai <entityId> <behaviorSet1> <behaviorSet2>..."
|
||||
+ "\n entityID: Uid of entity to add the AiControllerComponent to. Open its VV menu to find this."
|
||||
+ "\n behaviorSet: Name of a behaviorset to add to the component on initialize.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if(args.Length < 1)
|
||||
{
|
||||
shell.WriteLine("Wrong number of args.");
|
||||
return;
|
||||
}
|
||||
|
||||
var entId = new EntityUid(int.Parse(args[0]));
|
||||
|
||||
if (!_entities.EntityExists(entId))
|
||||
{
|
||||
shell.WriteLine($"Unable to find entity with uid {entId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_entities.HasComponent<NPCComponent>(entId))
|
||||
{
|
||||
shell.WriteLine("Entity already has an AI component.");
|
||||
return;
|
||||
}
|
||||
|
||||
var comp = _entities.AddComponent<UtilityNPCComponent>(entId);
|
||||
var npcSystem = IoCManager.Resolve<IEntityManager>().EntitySysManager.GetEntitySystem<NPCSystem>();
|
||||
|
||||
for (var i = 1; i < args.Length; i++)
|
||||
{
|
||||
var bSet = args[i];
|
||||
npcSystem.AddBehaviorSet(comp, bSet, false);
|
||||
}
|
||||
|
||||
npcSystem.RebuildActions(comp);
|
||||
shell.WriteLine("AI component added.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Content.Server.AI.EntitySystems;
|
||||
|
||||
namespace Content.Server.AI.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class AiFactionTagComponent : Component
|
||||
{
|
||||
[DataField("factions")]
|
||||
public Faction Factions { get; private set; } = Faction.None;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Content.Server.AI.EntitySystems;
|
||||
|
||||
namespace Content.Server.AI.Components
|
||||
{
|
||||
[Access(typeof(NPCSystem))]
|
||||
public abstract class NPCComponent : Component
|
||||
{
|
||||
// TODO: Soon. I didn't realise how much effort it was to deprecate the old one.
|
||||
/// <summary>
|
||||
/// Contains all of the world data for a particular NPC in terms of how it sees the world.
|
||||
/// </summary>
|
||||
//[ViewVariables, DataField("blackboardA")]
|
||||
//public Dictionary<string, object> BlackboardA = new();
|
||||
|
||||
public float VisionRadius => 7f;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.Tracking;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Server.Silicons.Bots;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Bot
|
||||
{
|
||||
public sealed class CanInjectCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (target == null || !entMan.TryGetComponent(target, out DamageableComponent? damageableComponent))
|
||||
return 0;
|
||||
|
||||
if (entMan.TryGetComponent(target, out RecentlyInjectedComponent? recently))
|
||||
return 0f;
|
||||
|
||||
if (!entMan.TryGetComponent(target, out MobStateComponent? mobState) || mobState.IsDead())
|
||||
return 0f;
|
||||
|
||||
if (damageableComponent.TotalDamage == 0)
|
||||
return 0f;
|
||||
|
||||
if (damageableComponent.TotalDamage <= MedibotComponent.StandardMedDamageThreshold)
|
||||
return 1f;
|
||||
|
||||
if (damageableComponent.TotalDamage >= MedibotComponent.EmergencyMedDamageThreshold)
|
||||
return 1f;
|
||||
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Content.Server.Fluids.Components;
|
||||
|
||||
namespace Content.Server.AI.EntitySystems
|
||||
{
|
||||
public sealed class GoToPuddleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
|
||||
public EntityUid GetNearbyPuddle(EntityUid cleanbot, float range = 10)
|
||||
{
|
||||
foreach (var entity in _lookup.GetEntitiesInRange(cleanbot, range))
|
||||
{
|
||||
if (HasComp<PuddleComponent>(entity))
|
||||
return entity;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
using Content.Server.Chemistry.Components.SolutionManager;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Server.AI.Tracking;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Silicons.Bots;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.AI.EntitySystems
|
||||
{
|
||||
public sealed class InjectNearbySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
|
||||
public EntityUid GetNearbyInjectable(EntityUid medibot, float range = 4)
|
||||
{
|
||||
foreach (var entity in _lookup.GetEntitiesInRange(medibot, range))
|
||||
{
|
||||
if (HasComp<InjectableSolutionComponent>(entity) && HasComp<MobStateComponent>(entity))
|
||||
return entity;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public bool Inject(EntityUid medibot, EntityUid target)
|
||||
{
|
||||
if (!TryComp<MedibotComponent>(medibot, out var botComp))
|
||||
return false;
|
||||
|
||||
if (!TryComp<DamageableComponent>(target, out var damage))
|
||||
return false;
|
||||
|
||||
if (!_solutionSystem.TryGetInjectableSolution(target, out var injectable))
|
||||
return false;
|
||||
|
||||
if (!_interactionSystem.InRangeUnobstructed(medibot, target))
|
||||
return true; // return true lets the bot reattempt the action on the same target
|
||||
|
||||
if (damage.TotalDamage == 0)
|
||||
return false;
|
||||
|
||||
if (damage.TotalDamage <= MedibotComponent.StandardMedDamageThreshold)
|
||||
{
|
||||
_solutionSystem.TryAddReagent(target, injectable, botComp.StandardMed, botComp.StandardMedInjectAmount, out var accepted);
|
||||
EnsureComp<RecentlyInjectedComponent>(target);
|
||||
_popupSystem.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, Filter.Entities(target));
|
||||
SoundSystem.Play("/Audio/Items/hypospray.ogg", Filter.Pvs(target), target);
|
||||
_chat.TrySendInGameICMessage(medibot, Loc.GetString("medibot-finish-inject"), InGameICChatType.Speak, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (damage.TotalDamage >= MedibotComponent.EmergencyMedDamageThreshold)
|
||||
{
|
||||
_solutionSystem.TryAddReagent(target, injectable, botComp.EmergencyMed, botComp.EmergencyMedInjectAmount, out var accepted);
|
||||
EnsureComp<RecentlyInjectedComponent>(target);
|
||||
_popupSystem.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, Filter.Entities(target));
|
||||
SoundSystem.Play("/Audio/Items/hypospray.ogg", Filter.Pvs(target), target);
|
||||
_chat.TrySendInGameICMessage(medibot, Loc.GetString("medibot-finish-inject"), InGameICChatType.Speak, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.AI.Components;
|
||||
|
||||
namespace Content.Server.AI.EntitySystems;
|
||||
|
||||
public sealed partial class NPCSystem
|
||||
{
|
||||
/*
|
||||
/// <summary>
|
||||
/// Tries to get the blackboard data for a particular key. Returns default if not found
|
||||
/// </summary>
|
||||
public T? GetValueOrDefault<T>(NPCComponent component, string key)
|
||||
{
|
||||
if (component.BlackboardA.TryGetValue(key, out var value))
|
||||
{
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the blackboard data for a particular key.
|
||||
/// </summary>
|
||||
public bool TryGetValue<T>(NPCComponent component, string key, [NotNullWhen(true)] out T? value)
|
||||
{
|
||||
if (component.BlackboardA.TryGetValue(key, out var data))
|
||||
{
|
||||
value = (T) data;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Constants to make development easier
|
||||
*/
|
||||
|
||||
public const string VisionRadius = "VisionRadius";
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
using Content.Server.AI.Components;
|
||||
using Content.Server.AI.LoadBalancer;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Utility;
|
||||
using Content.Server.AI.Utility.Actions;
|
||||
using Content.Server.AI.Utility.AiLogic;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States.Utility;
|
||||
using Content.Server.CPUJob.JobQueues;
|
||||
using Content.Server.CPUJob.JobQueues.Queues;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Content.Server.AI.EntitySystems;
|
||||
|
||||
public sealed partial class NPCSystem
|
||||
{
|
||||
/*
|
||||
* Handles Utility AI, implemented via IAUS
|
||||
*/
|
||||
|
||||
private readonly NpcActionComparer _comparer = new();
|
||||
|
||||
private Dictionary<string, List<Type>> _behaviorSets = new();
|
||||
|
||||
private readonly AiActionJobQueue _aiRequestQueue = new();
|
||||
|
||||
private void InitializeUtility()
|
||||
{
|
||||
SubscribeLocalEvent<UtilityNPCComponent, ComponentStartup>(OnUtilityStartup);
|
||||
|
||||
foreach (var bSet in _prototypeManager.EnumeratePrototypes<BehaviorSetPrototype>())
|
||||
{
|
||||
var actions = new List<Type>();
|
||||
|
||||
foreach (var act in bSet.Actions)
|
||||
{
|
||||
if (!_reflectionManager.TryLooseGetType(act, out var parsedType) ||
|
||||
!typeof(IAiUtility).IsAssignableFrom(parsedType))
|
||||
{
|
||||
_sawmill.Error($"Unable to parse AI action for {act}");
|
||||
}
|
||||
else
|
||||
{
|
||||
actions.Add(parsedType);
|
||||
}
|
||||
}
|
||||
|
||||
_behaviorSets[bSet.ID] = actions;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUtilityStartup(EntityUid uid, UtilityNPCComponent component, ComponentStartup args)
|
||||
{
|
||||
if (component.BehaviorSets.Count > 0)
|
||||
{
|
||||
RebuildActions(component);
|
||||
}
|
||||
|
||||
component._planCooldownRemaining = component.PlanCooldown;
|
||||
component._blackboard = new Blackboard(component.Owner);
|
||||
}
|
||||
|
||||
public AiActionRequestJob RequestAction(UtilityNPCComponent component, AiActionRequest request, CancellationTokenSource cancellationToken)
|
||||
{
|
||||
var job = new AiActionRequestJob(0.002, request, cancellationToken.Token);
|
||||
// AI should already know if it shouldn't request again
|
||||
_aiRequestQueue.EnqueueJob(job);
|
||||
return job;
|
||||
}
|
||||
|
||||
private void UpdateUtility(float frameTime)
|
||||
{
|
||||
foreach (var (_, comp) in EntityQuery<ActiveNPCComponent, UtilityNPCComponent>())
|
||||
{
|
||||
if (_count >= _maxUpdates) break;
|
||||
|
||||
Update(comp, frameTime);
|
||||
_count++;
|
||||
}
|
||||
|
||||
_aiRequestQueue.Process();
|
||||
}
|
||||
|
||||
private void ReceivedAction(UtilityNPCComponent component)
|
||||
{
|
||||
if (component._actionRequest == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (component._actionRequest.Exception)
|
||||
{
|
||||
case null:
|
||||
break;
|
||||
default:
|
||||
_sawmill.Fatal(component._actionRequest.Exception.ToString());
|
||||
ExceptionDispatchInfo.Capture(component._actionRequest.Exception).Throw();
|
||||
// The code never actually reaches here, because the above throws.
|
||||
// This is to tell the compiler that the flow never leaves here.
|
||||
throw component._actionRequest.Exception;
|
||||
}
|
||||
var action = component._actionRequest.Result;
|
||||
component._actionRequest = null;
|
||||
// Actions with lower scores should be implicitly dumped by GetAction
|
||||
// If we're not allowed to replace the action with an action of the same type then dump.
|
||||
if (action == null || !action.CanOverride && component.CurrentAction?.GetType() == action.GetType())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentOp = component.CurrentAction?.ActionOperators.Peek();
|
||||
if (currentOp != null && currentOp.HasStartup)
|
||||
{
|
||||
currentOp.Shutdown(Outcome.Failed);
|
||||
}
|
||||
|
||||
component.CurrentAction = action;
|
||||
action.SetupOperators(component._blackboard);
|
||||
}
|
||||
|
||||
private void Update(UtilityNPCComponent component, float frameTime)
|
||||
{
|
||||
// If we asked for a new action we don't want to dump the existing one.
|
||||
if (component._actionRequest != null)
|
||||
{
|
||||
if (component._actionRequest.Status != JobStatus.Finished)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReceivedAction(component);
|
||||
// Do something next tick
|
||||
return;
|
||||
}
|
||||
|
||||
component._planCooldownRemaining -= frameTime;
|
||||
|
||||
// Might find a better action while we're doing one already
|
||||
if (component._planCooldownRemaining <= 0.0f)
|
||||
{
|
||||
component._planCooldownRemaining = component.PlanCooldown;
|
||||
component._actionCancellation = new CancellationTokenSource();
|
||||
component._actionRequest = RequestAction(component, new AiActionRequest(component.Owner, component._blackboard, component.AvailableActions), component._actionCancellation);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// When we spawn in we won't get an action for a bit
|
||||
if (component.CurrentAction == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var outcome = component.CurrentAction.Execute(frameTime);
|
||||
|
||||
switch (outcome)
|
||||
{
|
||||
case Outcome.Success:
|
||||
if (component.CurrentAction.ActionOperators.Count == 0)
|
||||
{
|
||||
component.CurrentAction.Shutdown();
|
||||
component.CurrentAction = null;
|
||||
// Nothing to compare new action to
|
||||
component._blackboard.GetState<LastUtilityScoreState>().SetValue(0.0f);
|
||||
}
|
||||
break;
|
||||
case Outcome.Continuing:
|
||||
break;
|
||||
case Outcome.Failed:
|
||||
component.CurrentAction.Shutdown();
|
||||
component.CurrentAction = null;
|
||||
component._blackboard.GetState<LastUtilityScoreState>().SetValue(0.0f);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the BehaviorSet to the NPC.
|
||||
/// </summary>
|
||||
/// <param name="npc"></param>
|
||||
/// <param name="behaviorSet"></param>
|
||||
/// <param name="rebuild">Set to false if you want to manually rebuild it after bulk updates.</param>
|
||||
public void AddBehaviorSet(UtilityNPCComponent npc, string behaviorSet, bool rebuild = true)
|
||||
{
|
||||
if (!_behaviorSets.ContainsKey(behaviorSet))
|
||||
{
|
||||
_sawmill.Error($"Tried to add BehaviorSet {behaviorSet} to {npc} but no such BehaviorSet found!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!npc.BehaviorSets.Add(behaviorSet))
|
||||
{
|
||||
_sawmill.Error($"Tried to add BehaviorSet {behaviorSet} to {npc} which already has the BehaviorSet!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (rebuild)
|
||||
RebuildActions(npc);
|
||||
|
||||
if (npc.BehaviorSets.Count == 1 && !IsAwake(npc))
|
||||
WakeNPC(npc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the BehaviorSet from the NPC.
|
||||
/// </summary>
|
||||
/// <param name="npc"></param>
|
||||
/// <param name="behaviorSet"></param>
|
||||
/// <param name="rebuild">Set to false if yo uwant to manually rebuild it after bulk updates.</param>
|
||||
public void RemoveBehaviorSet(UtilityNPCComponent npc, string behaviorSet, bool rebuild = true)
|
||||
{
|
||||
if (!_behaviorSets.TryGetValue(behaviorSet, out var actions))
|
||||
{
|
||||
Logger.Error($"Tried to remove BehaviorSet {behaviorSet} from {npc} but no such BehaviorSet found!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!npc.BehaviorSets.Remove(behaviorSet))
|
||||
{
|
||||
Logger.Error($"Tried to remove BehaviorSet {behaviorSet} from {npc} but it doesn't have that BehaviorSet!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (rebuild)
|
||||
RebuildActions(npc);
|
||||
|
||||
if (npc.BehaviorSets.Count == 0)
|
||||
SleepNPC(npc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear our actions and re-instantiate them from our BehaviorSets.
|
||||
/// Will ensure each action is unique.
|
||||
/// </summary>
|
||||
/// <param name="npc"></param>
|
||||
public void RebuildActions(UtilityNPCComponent npc)
|
||||
{
|
||||
npc.AvailableActions.Clear();
|
||||
foreach (var bSet in npc.BehaviorSets)
|
||||
{
|
||||
foreach (var action in GetActions(bSet))
|
||||
{
|
||||
if (npc.AvailableActions.Contains(action)) continue;
|
||||
// Setup
|
||||
action.Owner = npc.Owner;
|
||||
|
||||
// Ad to actions.
|
||||
npc.AvailableActions.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
SortActions(npc);
|
||||
}
|
||||
|
||||
private IEnumerable<IAiUtility> GetActions(string behaviorSet)
|
||||
{
|
||||
foreach (var action in _behaviorSets[behaviorSet])
|
||||
{
|
||||
yield return (IAiUtility) _typeFactory.CreateInstance(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whenever the behavior sets are changed we'll re-sort the actions by bonus
|
||||
/// </summary>
|
||||
private void SortActions(UtilityNPCComponent npc)
|
||||
{
|
||||
npc.AvailableActions.Sort(_comparer);
|
||||
}
|
||||
|
||||
private sealed class NpcActionComparer : Comparer<IAiUtility>
|
||||
{
|
||||
public override int Compare(IAiUtility? x, IAiUtility? y)
|
||||
{
|
||||
if (x == null || y == null) return 0;
|
||||
return y.Bonus.CompareTo(x.Bonus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Content.Server.AI.LoadBalancer;
|
||||
using Content.Shared.AI;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.AI.EntitySystems
|
||||
{
|
||||
#if DEBUG
|
||||
[UsedImplicitly]
|
||||
public sealed class ServerAiDebugSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
AiActionRequestJob.FoundAction += NotifyActionJob;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
AiActionRequestJob.FoundAction -= NotifyActionJob;
|
||||
}
|
||||
|
||||
private void NotifyActionJob(SharedAiDebug.UtilityAiDebugMessage message)
|
||||
{
|
||||
RaiseNetworkEvent(message);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Content.Server.AI.Utility.Actions;
|
||||
using Content.Server.AI.WorldState;
|
||||
|
||||
namespace Content.Server.AI.LoadBalancer
|
||||
{
|
||||
public sealed class AiActionRequest
|
||||
{
|
||||
public EntityUid EntityUid { get; }
|
||||
public Blackboard? Context { get; }
|
||||
public IEnumerable<IAiUtility>? Actions { get; }
|
||||
|
||||
public AiActionRequest(EntityUid uid, Blackboard context, IEnumerable<IAiUtility> actions)
|
||||
{
|
||||
EntityUid = uid;
|
||||
Context = context;
|
||||
Actions = actions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.AI.Components;
|
||||
using Content.Server.AI.Utility.Actions;
|
||||
using Content.Server.AI.Utility.ExpandableActions;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Utility;
|
||||
using Content.Server.CPUJob.JobQueues;
|
||||
using Content.Shared.AI;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.AI.LoadBalancer
|
||||
{
|
||||
public sealed class AiActionRequestJob : Job<UtilityAction>
|
||||
{
|
||||
#if DEBUG
|
||||
public static event Action<SharedAiDebug.UtilityAiDebugMessage>? FoundAction;
|
||||
#endif
|
||||
private readonly AiActionRequest _request;
|
||||
|
||||
public AiActionRequestJob(
|
||||
double maxTime,
|
||||
AiActionRequest request,
|
||||
CancellationToken cancellationToken = default) : base(maxTime, cancellationToken)
|
||||
{
|
||||
_request = request;
|
||||
}
|
||||
|
||||
protected override async Task<UtilityAction?> Process()
|
||||
{
|
||||
if (_request.Context == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var entity = _request.Context.GetState<SelfState>().GetValue();
|
||||
|
||||
if (!IoCManager.Resolve<IEntityManager>().HasComponent<NPCComponent>(entity))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_request.Actions == null || _request.Context == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var consideredTaskCount = 0;
|
||||
// Actions are pre-sorted
|
||||
var actions = new Stack<IAiUtility>(_request.Actions);
|
||||
|
||||
// So essentially we go through and once we have a valid score that score becomes the cutoff;
|
||||
// once the bonus of new tasks is below the cutoff we can stop evaluating.
|
||||
|
||||
// Use last action as the basis for the cutoff
|
||||
var cutoff = _request.Context.GetState<LastUtilityScoreState>().GetValue();
|
||||
UtilityAction? foundAction = null;
|
||||
|
||||
// To see what I was trying to do watch these 2 videos about Infinite Axis Utility System (IAUS):
|
||||
// Architecture Tricks: Managing Behaviors in Time, Space, and Depth
|
||||
// Building a Better Centaur
|
||||
|
||||
// We'll want to cap the considered entities at some point, e.g. if 500 guns are in a stack cap it at 256 or whatever
|
||||
while (actions.Count > 0)
|
||||
{
|
||||
if (consideredTaskCount > 0 && consideredTaskCount % 5 == 0)
|
||||
{
|
||||
await SuspendIfOutOfTime();
|
||||
|
||||
// If this happens then that means something changed when we resumed so ABORT
|
||||
if (actions.Count == 0 || _request.Context == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var action = actions.Pop();
|
||||
switch (action)
|
||||
{
|
||||
case ExpandableUtilityAction expandableUtilityAction:
|
||||
if (!expandableUtilityAction.IsValid(_request.Context))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var expanded in expandableUtilityAction.GetActions(_request.Context))
|
||||
{
|
||||
actions.Push(expanded);
|
||||
}
|
||||
break;
|
||||
case UtilityAction utilityAction:
|
||||
consideredTaskCount++;
|
||||
var bonus = utilityAction.Bonus;
|
||||
|
||||
if (bonus < cutoff)
|
||||
{
|
||||
// We know none of the other actions can beat this as they're pre-sorted
|
||||
actions.Clear();
|
||||
break;
|
||||
}
|
||||
|
||||
var score = utilityAction.GetScore(_request.Context, cutoff);
|
||||
if (score > cutoff)
|
||||
{
|
||||
foundAction = utilityAction;
|
||||
cutoff = score;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
_request.Context.GetState<LastUtilityScoreState>().SetValue(cutoff);
|
||||
#if DEBUG
|
||||
if (foundAction != null)
|
||||
{
|
||||
var selfState = _request.Context.GetState<SelfState>().GetValue();
|
||||
|
||||
DebugTools.AssertNotNull(selfState);
|
||||
|
||||
FoundAction?.Invoke(new SharedAiDebug.UtilityAiDebugMessage(
|
||||
selfState!,
|
||||
DebugTime,
|
||||
cutoff,
|
||||
foundAction.GetType().Name,
|
||||
consideredTaskCount));
|
||||
}
|
||||
|
||||
#endif
|
||||
_request.Context.ResetPlanning();
|
||||
|
||||
return foundAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
|
||||
namespace Content.Server.AI.Operators
|
||||
{
|
||||
public abstract class AiOperator
|
||||
{
|
||||
public bool HasStartup { get; private set; }
|
||||
|
||||
public bool HasShutdown { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called once when the NPC starts this action
|
||||
/// </summary>
|
||||
/// <returns>true if it hasn't started up previously</returns>
|
||||
public virtual bool Startup()
|
||||
{
|
||||
// If we've already startup then no point continuing
|
||||
// This signals to the override that it's already startup
|
||||
// Should probably throw but it made some code elsewhere marginally easier
|
||||
if (HasStartup)
|
||||
return false;
|
||||
|
||||
HasStartup = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once when the NPC is done with this action if the outcome is successful or fails.
|
||||
/// </summary>
|
||||
public virtual bool Shutdown(Outcome outcome)
|
||||
{
|
||||
if (HasShutdown)
|
||||
return false;
|
||||
|
||||
HasShutdown = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every tick for the AI
|
||||
/// </summary>
|
||||
/// <param name="frameTime"></param>
|
||||
/// <returns></returns>
|
||||
public abstract Outcome Execute(float frameTime);
|
||||
}
|
||||
|
||||
public enum Outcome
|
||||
{
|
||||
Success,
|
||||
Continuing,
|
||||
Failed,
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using Content.Server.AI.EntitySystems;
|
||||
|
||||
namespace Content.Server.AI.Operators.Bots
|
||||
{
|
||||
public sealed class InjectOperator : AiOperator
|
||||
{
|
||||
private EntityUid _medibot;
|
||||
private EntityUid _target;
|
||||
public InjectOperator(EntityUid medibot, EntityUid target)
|
||||
{
|
||||
_medibot = medibot;
|
||||
_target = target;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
var injectSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<InjectNearbySystem>();
|
||||
if (injectSystem.Inject(_medibot, _target))
|
||||
return Outcome.Success;
|
||||
|
||||
return Outcome.Failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
using Content.Server.CombatMode;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Weapon.Melee.Components;
|
||||
|
||||
namespace Content.Server.AI.Operators.Combat.Melee
|
||||
{
|
||||
public sealed class SwingMeleeWeaponOperator : AiOperator
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
private readonly float _burstTime;
|
||||
private float _elapsedTime;
|
||||
|
||||
private readonly EntityUid _owner;
|
||||
private readonly EntityUid _target;
|
||||
|
||||
public SwingMeleeWeaponOperator(EntityUid owner, EntityUid target, float burstTime = 1.0f)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_owner = owner;
|
||||
_target = target;
|
||||
_burstTime = burstTime;
|
||||
}
|
||||
|
||||
public override bool Startup()
|
||||
{
|
||||
if (!base.Startup())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_entMan.TryGetComponent(_owner, out CombatModeComponent? combatModeComponent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!combatModeComponent.IsInCombatMode)
|
||||
{
|
||||
combatModeComponent.IsInCombatMode = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Shutdown(Outcome outcome)
|
||||
{
|
||||
if (!base.Shutdown(outcome))
|
||||
return false;
|
||||
|
||||
if (_entMan.TryGetComponent(_owner, out CombatModeComponent? combatModeComponent))
|
||||
{
|
||||
combatModeComponent.IsInCombatMode = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
if (_burstTime <= _elapsedTime)
|
||||
{
|
||||
return Outcome.Success;
|
||||
}
|
||||
|
||||
if (!_entMan.TryGetComponent(_owner, out HandsComponent? hands) || hands.ActiveHandEntity == null)
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
var meleeWeapon = hands.ActiveHandEntity;
|
||||
_entMan.TryGetComponent(meleeWeapon, out MeleeWeaponComponent? meleeWeaponComponent);
|
||||
|
||||
if ((_entMan.GetComponent<TransformComponent>(_target).Coordinates.Position - _entMan.GetComponent<TransformComponent>(_owner).Coordinates.Position).Length >
|
||||
meleeWeaponComponent?.Range)
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
var interactionSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<InteractionSystem>();
|
||||
|
||||
interactionSystem.AiUseInteraction(_owner, _entMan.GetComponent<TransformComponent>(_target).Coordinates, _target);
|
||||
_elapsedTime += frameTime;
|
||||
return Outcome.Continuing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using Content.Server.CombatMode;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Weapon.Melee.Components;
|
||||
|
||||
namespace Content.Server.AI.Operators.Combat.Melee
|
||||
{
|
||||
public sealed class UnarmedCombatOperator : AiOperator
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
private readonly float _burstTime;
|
||||
private float _elapsedTime;
|
||||
|
||||
private readonly EntityUid _owner;
|
||||
private readonly EntityUid _target;
|
||||
private MeleeWeaponComponent? _unarmedCombat;
|
||||
|
||||
public UnarmedCombatOperator(EntityUid owner, EntityUid target, float burstTime = 1.0f)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_owner = owner;
|
||||
_target = target;
|
||||
_burstTime = burstTime;
|
||||
}
|
||||
|
||||
public override bool Startup()
|
||||
{
|
||||
if (!base.Startup())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_entMan.TryGetComponent(_owner, out CombatModeComponent? combatModeComponent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!combatModeComponent.IsInCombatMode)
|
||||
{
|
||||
combatModeComponent.IsInCombatMode = true;
|
||||
}
|
||||
|
||||
if (_entMan.TryGetComponent(_owner, out MeleeWeaponComponent? unarmedCombatComponent))
|
||||
{
|
||||
_unarmedCombat = unarmedCombatComponent;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Shutdown(Outcome outcome)
|
||||
{
|
||||
if (!base.Shutdown(outcome))
|
||||
return false;
|
||||
|
||||
if (_entMan.TryGetComponent(_owner, out CombatModeComponent? combatModeComponent))
|
||||
{
|
||||
combatModeComponent.IsInCombatMode = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
if (_unarmedCombat == null ||
|
||||
!_entMan.GetComponent<TransformComponent>(_target).Coordinates.TryDistance(_entMan, _entMan.GetComponent<TransformComponent>(_owner).Coordinates, out var distance) || distance >
|
||||
_unarmedCombat.Range)
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
if (_burstTime <= _elapsedTime)
|
||||
{
|
||||
return Outcome.Success;
|
||||
}
|
||||
|
||||
if (_unarmedCombat?.Deleted ?? true)
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
var interactionSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<InteractionSystem>();
|
||||
interactionSystem.AiUseInteraction(_owner, _entMan.GetComponent<TransformComponent>(_target).Coordinates, _target);
|
||||
_elapsedTime += frameTime;
|
||||
return Outcome.Continuing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
namespace Content.Server.AI.Operators.Generic
|
||||
{
|
||||
public sealed class WaitOperator : AiOperator
|
||||
{
|
||||
private readonly float _waitTime;
|
||||
private float _accumulatedTime = 0.0f;
|
||||
|
||||
public WaitOperator(float waitTime)
|
||||
{
|
||||
_waitTime = waitTime;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
if (_accumulatedTime < _waitTime)
|
||||
{
|
||||
_accumulatedTime += frameTime;
|
||||
return Outcome.Continuing;
|
||||
}
|
||||
|
||||
return Outcome.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using Content.Server.AI.Utility;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
|
||||
namespace Content.Server.AI.Operators.Inventory
|
||||
{
|
||||
/// <summary>
|
||||
/// Close the last EntityStorage we opened
|
||||
/// This will also update the State for it (which a regular InteractWith won't do)
|
||||
/// </summary>
|
||||
public sealed class CloseLastStorageOperator : AiOperator
|
||||
{
|
||||
private readonly EntityUid _owner;
|
||||
private EntityUid _target;
|
||||
|
||||
public CloseLastStorageOperator(EntityUid owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public override bool Startup()
|
||||
{
|
||||
if (!base.Startup())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var blackboard = UtilityAiHelpers.GetBlackboard(_owner);
|
||||
|
||||
if (blackboard == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_target = blackboard.GetState<LastOpenedStorageState>().GetValue();
|
||||
|
||||
return _target != default;
|
||||
}
|
||||
|
||||
public override bool Shutdown(Outcome outcome)
|
||||
{
|
||||
if (!base.Shutdown(outcome))
|
||||
return false;
|
||||
|
||||
var blackboard = UtilityAiHelpers.GetBlackboard(_owner);
|
||||
|
||||
blackboard?.GetState<LastOpenedStorageState>().SetValue(default);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
if (_target == default || !EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(_owner, _target, popup: true))
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
if (!entMan.TryGetComponent(_target, out EntityStorageComponent? storageComponent) ||
|
||||
storageComponent.IsWeldedShut)
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
if (entMan.EntitySysManager.TryGetEntitySystem<EntityStorageSystem>(out var entStorage) && storageComponent.Open)
|
||||
{
|
||||
entStorage.ToggleOpen(_owner, _target, storageComponent);
|
||||
}
|
||||
|
||||
return Outcome.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
|
||||
namespace Content.Server.AI.Operators.Inventory
|
||||
{
|
||||
public sealed class DropEntityOperator : AiOperator
|
||||
{
|
||||
private readonly EntityUid _owner;
|
||||
private readonly EntityUid _entity;
|
||||
public DropEntityOperator(EntityUid owner, EntityUid entity)
|
||||
{
|
||||
_owner = owner;
|
||||
_entity = entity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requires EquipEntityOperator to put it in the active hand first
|
||||
/// </summary>
|
||||
/// <param name="frameTime"></param>
|
||||
/// <returns></returns>
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
return IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedHandsSystem>().TryDrop(_owner, _entity) ? Outcome.Success : Outcome.Failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
|
||||
namespace Content.Server.AI.Operators.Inventory
|
||||
{
|
||||
public sealed class DropHandItemsOperator : AiOperator
|
||||
{
|
||||
private readonly EntityUid _owner;
|
||||
|
||||
public DropHandItemsOperator(EntityUid owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(_owner, out HandsComponent? handsComponent))
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedHandsSystem>();
|
||||
|
||||
foreach (var hand in handsComponent.Hands.Values)
|
||||
{
|
||||
if (!hand.IsEmpty)
|
||||
sys.TryDrop(_owner, hand, handsComp: handsComponent);
|
||||
}
|
||||
|
||||
return Outcome.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
|
||||
namespace Content.Server.AI.Operators.Inventory
|
||||
{
|
||||
public sealed class EquipEntityOperator : AiOperator
|
||||
{
|
||||
private readonly EntityUid _owner;
|
||||
private readonly EntityUid _entity;
|
||||
public EquipEntityOperator(EntityUid owner, EntityUid entity)
|
||||
{
|
||||
_owner = owner;
|
||||
_entity = entity;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedHandsSystem>();
|
||||
|
||||
// TODO: If in clothing then click on it
|
||||
|
||||
if (sys.TrySelect(_owner, _entity))
|
||||
return Outcome.Success;
|
||||
|
||||
// TODO: Get free hand count; if no hands free then fail right here
|
||||
|
||||
// TODO: Go through inventory
|
||||
return Outcome.Failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using Content.Server.CombatMode;
|
||||
using Content.Server.Interaction;
|
||||
|
||||
namespace Content.Server.AI.Operators.Inventory
|
||||
{
|
||||
/// <summary>
|
||||
/// A Generic interacter; if you need to check stuff then make your own
|
||||
/// </summary>
|
||||
public sealed class InteractWithEntityOperator : AiOperator
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
private readonly EntityUid _owner;
|
||||
private readonly EntityUid _useTarget;
|
||||
|
||||
public InteractWithEntityOperator(EntityUid owner, EntityUid useTarget)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_owner = owner;
|
||||
_useTarget = useTarget;
|
||||
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
var targetTransform = _entMan.GetComponent<TransformComponent>(_useTarget);
|
||||
|
||||
if (targetTransform.GridUid != _entMan.GetComponent<TransformComponent>(_owner).GridUid)
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
var interactionSystem = EntitySystem.Get<InteractionSystem>();
|
||||
|
||||
if (!interactionSystem.InRangeUnobstructed(_owner, _useTarget, popup: true))
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
if (_entMan.TryGetComponent(_owner, out CombatModeComponent? combatModeComponent))
|
||||
{
|
||||
combatModeComponent.IsInCombatMode = false;
|
||||
}
|
||||
|
||||
// Click on da thing
|
||||
interactionSystem.AiUseInteraction(_owner, targetTransform.Coordinates, _useTarget);
|
||||
|
||||
return Outcome.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using Content.Server.AI.Utility;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.AI.Operators.Inventory
|
||||
{
|
||||
/// <summary>
|
||||
/// If the target is in EntityStorage will open its parent container
|
||||
/// </summary>
|
||||
public sealed class OpenStorageOperator : AiOperator
|
||||
{
|
||||
private readonly EntityUid _owner;
|
||||
private readonly EntityUid _target;
|
||||
|
||||
public OpenStorageOperator(EntityUid owner, EntityUid target)
|
||||
{
|
||||
_owner = owner;
|
||||
_target = target;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
if (!_target.TryGetContainer(out var container))
|
||||
{
|
||||
return Outcome.Success;
|
||||
}
|
||||
|
||||
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(_owner, container.Owner, popup: true))
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(container.Owner, out EntityStorageComponent? storageComponent) ||
|
||||
storageComponent.IsWeldedShut)
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
if (!storageComponent.Open)
|
||||
{
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<EntityStorageSystem>().ToggleOpen(_owner, _target, storageComponent);
|
||||
}
|
||||
|
||||
var blackboard = UtilityAiHelpers.GetBlackboard(_owner);
|
||||
blackboard?.GetState<LastOpenedStorageState>().SetValue(container.Owner);
|
||||
|
||||
return Outcome.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Content.Server.Interaction;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.AI.Operators.Inventory
|
||||
{
|
||||
public sealed class PickupEntityOperator : AiOperator
|
||||
{
|
||||
// Input variables
|
||||
private readonly EntityUid _owner;
|
||||
private readonly EntityUid _target;
|
||||
|
||||
public PickupEntityOperator(EntityUid owner, EntityUid target)
|
||||
{
|
||||
_owner = owner;
|
||||
_target = target;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var sysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var interactionSystem = sysMan.GetEntitySystem<InteractionSystem>();
|
||||
var handsSys = sysMan.GetEntitySystem<SharedHandsSystem>();
|
||||
|
||||
if (entMan.Deleted(_target)
|
||||
|| !entMan.HasComponent<ItemComponent>(_target)
|
||||
|| _target.IsInContainer()
|
||||
|| !interactionSystem.InRangeUnobstructed(_owner, _target, popup: true))
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
// select empty hand
|
||||
if (!handsSys.TrySelectEmptyHand(_owner))
|
||||
return Outcome.Failed;
|
||||
|
||||
interactionSystem.InteractHand(_owner, _target);
|
||||
return Outcome.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
|
||||
namespace Content.Server.AI.Operators.Inventory
|
||||
{
|
||||
/// <summary>
|
||||
/// Will find the item in storage, put it in an active hand, then use it
|
||||
/// </summary>
|
||||
public sealed class UseItemInInventoryOperator : AiOperator
|
||||
{
|
||||
private readonly EntityUid _owner;
|
||||
private readonly EntityUid _target;
|
||||
|
||||
public UseItemInInventoryOperator(EntityUid owner, EntityUid target)
|
||||
{
|
||||
_owner = owner;
|
||||
_target = target;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var sysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var sys = sysMan.GetEntitySystem<SharedHandsSystem>();
|
||||
|
||||
// TODO: Also have this check storage a la backpack etc.
|
||||
if (!entMan.TryGetComponent(_owner, out HandsComponent? handsComponent)
|
||||
|| !sys.TrySelect(_owner, _target, handsComponent)
|
||||
|| !sys.TryUseItemInHand(_owner, false, handsComponent))
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
return Outcome.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using Content.Server.AI.Steering;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.AI.Operators.Movement
|
||||
{
|
||||
public sealed class MoveToEntityOperator : AiOperator
|
||||
{
|
||||
// TODO: This and steering need to support InRangeUnobstructed now
|
||||
private readonly EntityUid _owner;
|
||||
private readonly EntityUid _target;
|
||||
// For now we'll just get as close as we can because we're not doing LOS checks to be able to pick up at the max interaction range
|
||||
public float ArrivalDistance { get; }
|
||||
public float PathfindingProximity { get; }
|
||||
|
||||
private readonly bool _requiresInRangeUnobstructed;
|
||||
|
||||
public MoveToEntityOperator(
|
||||
EntityUid owner,
|
||||
EntityUid target,
|
||||
float arrivalDistance = 1.0f,
|
||||
float pathfindingProximity = 1.5f,
|
||||
bool requiresInRangeUnobstructed = false)
|
||||
{
|
||||
_owner = owner;
|
||||
_target = target;
|
||||
ArrivalDistance = arrivalDistance;
|
||||
PathfindingProximity = pathfindingProximity;
|
||||
_requiresInRangeUnobstructed = requiresInRangeUnobstructed;
|
||||
}
|
||||
|
||||
public override bool Startup()
|
||||
{
|
||||
if (!base.Startup())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var steering = EntitySystem.Get<NPCSteeringSystem>();
|
||||
var comp = steering.Register(_owner, new EntityCoordinates(_target, Vector2.Zero));
|
||||
comp.Range = ArrivalDistance;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Shutdown(Outcome outcome)
|
||||
{
|
||||
if (!base.Shutdown(outcome))
|
||||
return false;
|
||||
|
||||
var steering = EntitySystem.Get<NPCSteeringSystem>();
|
||||
steering.Unregister(_owner);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<NPCSteeringComponent>(_owner, out var steering))
|
||||
return Outcome.Failed;
|
||||
|
||||
switch (steering.Status)
|
||||
{
|
||||
case SteeringStatus.NoPath:
|
||||
return Outcome.Failed;
|
||||
case SteeringStatus.InRange:
|
||||
return Outcome.Success;
|
||||
case SteeringStatus.Moving:
|
||||
return Outcome.Continuing;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using Content.Server.AI.Steering;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.AI.Operators.Movement
|
||||
{
|
||||
public sealed class MoveToGridOperator : AiOperator
|
||||
{
|
||||
private readonly EntityUid _owner;
|
||||
private readonly EntityCoordinates _target;
|
||||
public float DesiredRange { get; set; }
|
||||
|
||||
public MoveToGridOperator(EntityUid owner, EntityCoordinates target, float desiredRange = 1.5f)
|
||||
{
|
||||
_owner = owner;
|
||||
_target = target;
|
||||
DesiredRange = desiredRange;
|
||||
}
|
||||
|
||||
public override bool Startup()
|
||||
{
|
||||
if (!base.Startup())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var steering = EntitySystem.Get<NPCSteeringSystem>();
|
||||
var comp = steering.Register(_owner, _target);
|
||||
comp.Range = DesiredRange;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Shutdown(Outcome outcome)
|
||||
{
|
||||
if (!base.Shutdown(outcome))
|
||||
return false;
|
||||
|
||||
var steering = EntitySystem.Get<NPCSteeringSystem>();
|
||||
steering.Unregister(_owner);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<NPCSteeringComponent>(_owner, out var steering))
|
||||
return Outcome.Failed;
|
||||
|
||||
switch (steering.Status)
|
||||
{
|
||||
case SteeringStatus.NoPath:
|
||||
return Outcome.Failed;
|
||||
case SteeringStatus.InRange:
|
||||
return Outcome.Success;
|
||||
case SteeringStatus.Moving:
|
||||
return Outcome.Continuing;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Nutrition.Components;
|
||||
using Content.Server.Nutrition.EntitySystems;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.AI.Operators.Nutrition
|
||||
{
|
||||
public sealed class UseDrinkInInventoryOperator : AiOperator
|
||||
{
|
||||
private readonly EntityUid _owner;
|
||||
private readonly EntityUid _target;
|
||||
private float _interactionCooldown;
|
||||
|
||||
public UseDrinkInInventoryOperator(EntityUid owner, EntityUid target)
|
||||
{
|
||||
_owner = owner;
|
||||
_target = target;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
if (_interactionCooldown >= 0)
|
||||
{
|
||||
_interactionCooldown -= frameTime;
|
||||
return Outcome.Continuing;
|
||||
}
|
||||
|
||||
var entities = IoCManager.Resolve<IEntityManager>();
|
||||
var sysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var handsSys = sysMan.GetEntitySystem<SharedHandsSystem>();
|
||||
|
||||
// TODO: Also have this check storage a la backpack etc.
|
||||
if (entities.Deleted(_target) ||
|
||||
!entities.TryGetComponent(_owner, out HandsComponent? handsComponent))
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
if (!handsSys.TrySelect<DrinkComponent>(_owner, out var drinkComponent, handsComponent))
|
||||
return Outcome.Failed;
|
||||
|
||||
if (!handsSys.TryUseItemInHand(_owner, false, handsComponent))
|
||||
return Outcome.Failed;
|
||||
|
||||
_interactionCooldown = IoCManager.Resolve<IRobustRandom>().NextFloat() + 0.5f;
|
||||
|
||||
if (drinkComponent.Deleted || EntitySystem.Get<DrinkSystem>().IsEmpty(drinkComponent.Owner, drinkComponent)
|
||||
|| entities.TryGetComponent(_owner, out ThirstComponent? thirstComponent) &&
|
||||
thirstComponent.CurrentThirst >= thirstComponent.ThirstThresholds[ThirstThreshold.Okay])
|
||||
{
|
||||
return Outcome.Success;
|
||||
}
|
||||
|
||||
/// uuhhh do afters for drinks might mess this up?
|
||||
return Outcome.Continuing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Nutrition.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
|
||||
namespace Content.Server.AI.Operators.Nutrition
|
||||
{
|
||||
public sealed class UseFoodInInventoryOperator : AiOperator
|
||||
{
|
||||
private readonly EntityUid _owner;
|
||||
private readonly EntityUid _target;
|
||||
private float _interactionCooldown;
|
||||
|
||||
public UseFoodInInventoryOperator(EntityUid owner, EntityUid target)
|
||||
{
|
||||
_owner = owner;
|
||||
_target = target;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
if (_interactionCooldown >= 0)
|
||||
{
|
||||
_interactionCooldown -= frameTime;
|
||||
return Outcome.Continuing;
|
||||
}
|
||||
|
||||
var entities = IoCManager.Resolve<IEntityManager>();
|
||||
var sysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var handsSys = sysMan.GetEntitySystem<SharedHandsSystem>();
|
||||
|
||||
// TODO: Also have this check storage a la backpack etc.
|
||||
if (entities.Deleted(_target) ||
|
||||
!entities.TryGetComponent(_owner, out HandsComponent? handsComponent))
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
if (!handsSys.TrySelect<FoodComponent>(_owner, out var foodComponent, handsComponent))
|
||||
return Outcome.Failed;
|
||||
|
||||
if (!handsSys.TryUseItemInHand(_owner, false, handsComponent))
|
||||
return Outcome.Failed;
|
||||
|
||||
if ((!entities.EntityExists(_target) ? EntityLifeStage.Deleted : entities.GetComponent<MetaDataComponent>(_target).EntityLifeStage) >= EntityLifeStage.Deleted ||
|
||||
foodComponent.UsesRemaining == 0 ||
|
||||
entities.TryGetComponent(_owner, out HungerComponent? hungerComponent) &&
|
||||
hungerComponent.CurrentHunger >= hungerComponent.HungerThresholds[HungerThreshold.Okay])
|
||||
{
|
||||
return Outcome.Success;
|
||||
}
|
||||
|
||||
/// do afters for food might mess this up?
|
||||
return Outcome.Continuing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
|
||||
namespace Content.Server.AI.Operators.Sequences
|
||||
{
|
||||
public sealed class GoPickupEntitySequence : SequenceOperator
|
||||
{
|
||||
public GoPickupEntitySequence(EntityUid owner, EntityUid target)
|
||||
{
|
||||
Sequence = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new MoveToEntityOperator(owner, target, requiresInRangeUnobstructed: true),
|
||||
new OpenStorageOperator(owner, target),
|
||||
new PickupEntityOperator(owner, target),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
namespace Content.Server.AI.Operators.Sequences
|
||||
{
|
||||
/// <summary>
|
||||
/// Sequential chain of operators
|
||||
/// Saves having to duplicate stuff like MoveTo and PickUp everywhere
|
||||
/// </summary>
|
||||
public abstract class SequenceOperator : AiOperator
|
||||
{
|
||||
public Queue<AiOperator> Sequence { get; protected set; } = new();
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
if (Sequence.Count == 0)
|
||||
{
|
||||
return Outcome.Success;
|
||||
}
|
||||
|
||||
var op = Sequence.Peek();
|
||||
op.Startup();
|
||||
var outcome = op.Execute(frameTime);
|
||||
|
||||
switch (outcome)
|
||||
{
|
||||
case Outcome.Success:
|
||||
op.Shutdown(outcome);
|
||||
// Not over until all operators are done
|
||||
Sequence.Dequeue();
|
||||
return Outcome.Continuing;
|
||||
case Outcome.Continuing:
|
||||
return Outcome.Continuing;
|
||||
case Outcome.Failed:
|
||||
op.Shutdown(outcome);
|
||||
Sequence.Clear();
|
||||
return Outcome.Failed;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Content.Server.Chat.Systems;
|
||||
|
||||
namespace Content.Server.AI.Operators.Speech
|
||||
{
|
||||
public sealed class SpeakOperator : AiOperator
|
||||
{
|
||||
private EntityUid _speaker;
|
||||
private string _speechString;
|
||||
public SpeakOperator(EntityUid speaker, string speechString)
|
||||
{
|
||||
_speaker = speaker;
|
||||
_speechString = speechString;
|
||||
}
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
var chatSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ChatSystem>();
|
||||
chatSystem.TrySendInGameICMessage(_speaker, _speechString, InGameICChatType.Speak, false);
|
||||
return Outcome.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Content.Server.AI.Pathfinding;
|
||||
|
||||
[RegisterComponent]
|
||||
[Access(typeof(PathfindingSystem))]
|
||||
public sealed class GridPathfindingComponent : Component, IPathfindingGraph
|
||||
{
|
||||
public Dictionary<Vector2i, PathfindingChunk> Graph = new();
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Content.Server.AI.Pathfinding;
|
||||
|
||||
public interface IPathfindingGraph
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.AI.Steering
|
||||
{
|
||||
public interface IAiSteeringRequest
|
||||
{
|
||||
SteeringStatus Status { get; set; }
|
||||
MapCoordinates TargetMap { get; }
|
||||
EntityCoordinates TargetGrid { get; }
|
||||
/// <summary>
|
||||
/// How close we have to get before we've arrived
|
||||
/// </summary>
|
||||
float ArrivalDistance { get; }
|
||||
|
||||
/// <summary>
|
||||
/// How close the pathfinder needs to get. Typically you want this set lower than ArrivalDistance
|
||||
/// </summary>
|
||||
float PathfindingProximity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If we need LOS on the entity first before interaction
|
||||
/// </summary>
|
||||
bool RequiresInRangeUnobstructed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// To avoid spamming InRangeUnobstructed we'll apply a cd to it.
|
||||
/// </summary>
|
||||
public float TimeUntilInteractionCheck { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
namespace Content.Server.AI.Tracking
|
||||
{
|
||||
public sealed class RecentlyInjectedSystem : EntitySystem
|
||||
{
|
||||
|
||||
Queue<EntityUid> RemQueue = new();
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
foreach (var toRemove in RemQueue)
|
||||
{
|
||||
RemComp<RecentlyInjectedComponent>(toRemove);
|
||||
}
|
||||
RemQueue.Clear();
|
||||
foreach (var entity in EntityQuery<RecentlyInjectedComponent>())
|
||||
{
|
||||
entity.Accumulator += frameTime;
|
||||
if (entity.Accumulator < entity.RemoveTime.TotalSeconds)
|
||||
continue;
|
||||
entity.Accumulator = 0;
|
||||
RemQueue.Enqueue(entity.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Generic;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.ActionBlocker;
|
||||
using Content.Server.AI.WorldState.States.Movement;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Bots
|
||||
{
|
||||
public sealed class GoToPuddleAndWait : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
MoveToEntityOperator moveOperator = new MoveToEntityOperator(Owner, Target, 0, 0);
|
||||
float waitTime = 3f;
|
||||
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
moveOperator,
|
||||
new WaitOperator(waitTime),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
context.GetState<MoveTargetState>().SetValue(Target);
|
||||
// Can just set ourselves as entity given unarmed just inherits from meleeweapon
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<CanMoveCon>()
|
||||
.BoolCurve(context),
|
||||
considerationsManager.Get<TargetAccessibleCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Generic;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Operators.Bots;
|
||||
using Content.Server.AI.Operators.Speech;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.ActionBlocker;
|
||||
using Content.Server.AI.WorldState.States.Movement;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.Utility.Considerations.Bot;
|
||||
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Bots
|
||||
{
|
||||
public sealed class InjectNearby : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
MoveToEntityOperator moveOperator = new MoveToEntityOperator(Owner, Target);
|
||||
float waitTime = 3f;
|
||||
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
moveOperator,
|
||||
new SpeakOperator(Owner, Loc.GetString("medibot-start-inject")),
|
||||
new WaitOperator(waitTime),
|
||||
new InjectOperator(Owner, Target),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
context.GetState<MoveTargetState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<CanMoveCon>()
|
||||
.BoolCurve(context),
|
||||
considerationsManager.Get<TargetAccessibleCon>()
|
||||
.BoolCurve(context),
|
||||
considerationsManager.Get<CanInjectCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.Gloves
|
||||
{
|
||||
public sealed class EquipGloves : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, Target),
|
||||
new UseItemInInventoryOperator(Owner, Target),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<CanPutTargetInInventoryCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.Gloves
|
||||
{
|
||||
public sealed class PickUpGloves : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<CanPutTargetInInventoryCon>()
|
||||
.BoolCurve(context),
|
||||
considerationsManager.Get<TargetDistanceCon>()
|
||||
.PresetCurve(context, PresetCurve.Distance),
|
||||
considerationsManager.Get<TargetAccessibleCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.Head
|
||||
{
|
||||
public sealed class EquipHead : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, Target),
|
||||
new UseItemInInventoryOperator(Owner, Target),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<CanPutTargetInInventoryCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.Head
|
||||
{
|
||||
public sealed class PickUpHead : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<CanPutTargetInInventoryCon>()
|
||||
.BoolCurve(context),
|
||||
considerationsManager.Get<TargetDistanceCon>()
|
||||
.PresetCurve(context, PresetCurve.Distance),
|
||||
considerationsManager.Get<TargetAccessibleCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing
|
||||
{
|
||||
public sealed class EquipOuterClothing : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, Target),
|
||||
new UseItemInInventoryOperator(Owner, Target),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<CanPutTargetInInventoryCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing
|
||||
{
|
||||
public sealed class PickUpOuterClothing : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<CanPutTargetInInventoryCon>()
|
||||
.BoolCurve(context),
|
||||
considerationsManager.Get<TargetDistanceCon>()
|
||||
.PresetCurve(context, PresetCurve.Distance),
|
||||
considerationsManager.Get<TargetAccessibleCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.Shoes
|
||||
{
|
||||
public sealed class EquipShoes : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, Target),
|
||||
new UseItemInInventoryOperator(Owner, Target),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<CanPutTargetInInventoryCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.Shoes
|
||||
{
|
||||
public sealed class PickUpShoes : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<CanPutTargetInInventoryCon>()
|
||||
.BoolCurve(context),
|
||||
considerationsManager.Get<TargetDistanceCon>()
|
||||
.PresetCurve(context, PresetCurve.Distance),
|
||||
considerationsManager.Get<TargetAccessibleCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
||||
{
|
||||
public sealed class EquipMelee : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, Target)
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<WeaponEntityState>().SetValue(Target);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<CanPutTargetInInventoryCon>()
|
||||
.BoolCurve(context),
|
||||
considerationsManager.Get<MeleeWeaponSpeedCon>()
|
||||
.QuadraticCurve(context, 1.0f, 0.5f, 0.0f, 0.0f),
|
||||
considerationsManager.Get<MeleeWeaponDamageCon>()
|
||||
.QuadraticCurve(context, 1.0f, 0.25f, 0.0f, 0.0f),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Combat.Melee;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Server.AI.WorldState.States.Movement;
|
||||
using Content.Server.Weapon.Melee.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
||||
{
|
||||
public sealed class MeleeWeaponAttackEntity : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
MoveToEntityOperator moveOperator;
|
||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||
if (equipped != default && IoCManager.Resolve<IEntityManager>().TryGetComponent(equipped, out MeleeWeaponComponent? meleeWeaponComponent))
|
||||
{
|
||||
moveOperator = new MoveToEntityOperator(Owner, Target, meleeWeaponComponent.Range - 0.01f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Abort
|
||||
moveOperator = new MoveToEntityOperator(Owner, Target);
|
||||
}
|
||||
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
moveOperator,
|
||||
new SwingMeleeWeaponOperator(Owner, Target),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
context.GetState<MoveTargetState>().SetValue(Target);
|
||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||
context.GetState<WeaponEntityState>().SetValue(equipped);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<TargetIsDeadCon>()
|
||||
.InverseBoolCurve(context),
|
||||
considerationsManager.Get<TargetIsCritCon>()
|
||||
.QuadraticCurve(context, -0.8f, 1.0f, 1.0f, 0.0f),
|
||||
considerationsManager.Get<TargetDistanceCon>()
|
||||
.PresetCurve(context, PresetCurve.Distance),
|
||||
considerationsManager.Get<TargetHealthCon>()
|
||||
.PresetCurve(context, PresetCurve.TargetHealth),
|
||||
considerationsManager.Get<MeleeWeaponSpeedCon>()
|
||||
.QuadraticCurve(context, 1.0f, 0.5f, 0.0f, 0.0f),
|
||||
considerationsManager.Get<MeleeWeaponDamageCon>()
|
||||
.QuadraticCurve(context, 1.0f, 0.25f, 0.0f, 0.0f),
|
||||
considerationsManager.Get<TargetAccessibleCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
||||
{
|
||||
public sealed class PickUpMeleeWeapon : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
context.GetState<WeaponEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<TargetDistanceCon>()
|
||||
.PresetCurve(context, PresetCurve.Distance),
|
||||
considerationsManager.Get<MeleeWeaponDamageCon>()
|
||||
.QuadraticCurve(context, 1.0f, 0.25f, 0.0f, 0.0f),
|
||||
considerationsManager.Get<MeleeWeaponSpeedCon>()
|
||||
.QuadraticCurve(context, -1.0f, 0.5f, 1.0f, 0.0f),
|
||||
considerationsManager.Get<TargetAccessibleCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Combat.Melee;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Content.Server.AI.WorldState.States.Movement;
|
||||
using Content.Server.Weapon.Melee.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
||||
{
|
||||
public sealed class UnarmedAttackEntity : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
MoveToEntityOperator moveOperator;
|
||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(Owner, out MeleeWeaponComponent? unarmedCombatComponent))
|
||||
{
|
||||
moveOperator = new MoveToEntityOperator(Owner, Target, unarmedCombatComponent.Range - 0.01f);
|
||||
}
|
||||
// I think it's possible for this to happen given planning is time-sliced?
|
||||
// TODO: At this point we should abort
|
||||
else
|
||||
{
|
||||
moveOperator = new MoveToEntityOperator(Owner, Target);
|
||||
}
|
||||
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
moveOperator,
|
||||
new UnarmedCombatOperator(Owner, Target),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
context.GetState<MoveTargetState>().SetValue(Target);
|
||||
// Can just set ourselves as entity given unarmed just inherits from meleeweapon
|
||||
context.GetState<WeaponEntityState>().SetValue(Owner);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<TargetIsDeadCon>()
|
||||
.InverseBoolCurve(context),
|
||||
considerationsManager.Get<TargetIsCritCon>()
|
||||
.QuadraticCurve(context, -0.8f, 1.0f, 1.0f, 0.0f),
|
||||
considerationsManager.Get<TargetDistanceCon>()
|
||||
.PresetCurve(context, PresetCurve.Distance),
|
||||
considerationsManager.Get<TargetHealthCon>()
|
||||
.PresetCurve(context, PresetCurve.TargetHealth),
|
||||
considerationsManager.Get<TargetAccessibleCon>()
|
||||
.BoolCurve(context),
|
||||
// TODO: Consider our Speed and Damage to compare this to using a weapon
|
||||
// Also need to unequip our weapon if we have one (xenos can't hold one so no issue for now)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace Content.Server.AI.Utility.Actions
|
||||
{
|
||||
public interface IAiUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// NPC this action is attached to.
|
||||
/// </summary>
|
||||
EntityUid Owner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Highest possible score for this action.
|
||||
/// </summary>
|
||||
float Bonus { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Considerations.State;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Idle
|
||||
{
|
||||
/// <summary>
|
||||
/// If we just picked up a bunch of stuff and have time then close it
|
||||
/// </summary>
|
||||
public sealed class CloseLastEntityStorage : UtilityAction
|
||||
{
|
||||
public override float Bonus => IdleBonus + 0.01f;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
var lastStorage = context.GetState<LastOpenedStorageState>().GetValue();
|
||||
|
||||
if (!lastStorage.IsValid())
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new CloseLastStorageOperator(Owner),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new MoveToEntityOperator(Owner, lastStorage),
|
||||
new CloseLastStorageOperator(Owner),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
var lastStorage = context.GetState<LastOpenedStorageState>();
|
||||
context.GetState<TargetEntityState>().SetValue(lastStorage.GetValue());
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<StoredStateEntityIsNullCon>().Set(typeof(LastOpenedStorageState), context)
|
||||
.InverseBoolCurve(context),
|
||||
considerationsManager.Get<TargetDistanceCon>()
|
||||
.PresetCurve(context, PresetCurve.Distance),
|
||||
considerationsManager.Get<TargetAccessibleCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Generic;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Pathfinding;
|
||||
using Content.Server.AI.Pathfinding.Accessible;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.ActionBlocker;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Idle
|
||||
{
|
||||
/// <summary>
|
||||
/// Will move to a random spot close by
|
||||
/// </summary>
|
||||
public sealed class WanderAndWait : UtilityAction
|
||||
{
|
||||
public override bool CanOverride => false;
|
||||
public override float Bonus => 1.0f;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||
var randomGrid = FindRandomGrid(robustRandom);
|
||||
float waitTime;
|
||||
if (randomGrid != EntityCoordinates.Invalid)
|
||||
{
|
||||
waitTime = robustRandom.Next(3, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
waitTime = 0.0f;
|
||||
}
|
||||
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new MoveToGridOperator(Owner, randomGrid),
|
||||
new WaitOperator(waitTime),
|
||||
});
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<CanMoveCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
|
||||
private EntityCoordinates FindRandomGrid(IRobustRandom robustRandom, IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
|
||||
// Very inefficient (should weight each region by its node count) but better than the old system
|
||||
var reachableSystem = entMan.EntitySysManager.GetEntitySystem<AiReachableSystem>();
|
||||
var reachableArgs = ReachableArgs.GetArgs(Owner);
|
||||
var entityRegion = reachableSystem.GetRegion(Owner);
|
||||
var reachableRegions = reachableSystem.GetReachableRegions(reachableArgs, entityRegion);
|
||||
|
||||
// TODO: When SetupOperators can fail this should be null and fail the setup.
|
||||
if (reachableRegions.Count == 0)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var reachableNodes = new List<PathfindingNode>();
|
||||
|
||||
foreach (var region in reachableRegions)
|
||||
{
|
||||
foreach (var node in region.Nodes)
|
||||
{
|
||||
reachableNodes.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
var targetNode = robustRandom.Pick(reachableNodes);
|
||||
|
||||
if (!entMan.TryGetComponent(entMan.GetComponent<TransformComponent>(Owner).GridUid, out IMapGridComponent? grid))
|
||||
return default;
|
||||
|
||||
var targetGrid = grid.Grid.GridTileToLocal(targetNode.TileRef.GridIndices);
|
||||
|
||||
return targetGrid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Considerations.Nutrition.Drink;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
|
||||
{
|
||||
public sealed class PickUpDrink : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<TargetDistanceCon>()
|
||||
.PresetCurve(context, PresetCurve.Distance),
|
||||
considerationsManager.Get<DrinkValueCon>()
|
||||
.QuadraticCurve(context, 1.0f, 0.4f, 0.0f, 0.0f),
|
||||
considerationsManager.Get<TargetAccessibleCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Operators.Nutrition;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations.Nutrition.Drink;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
|
||||
{
|
||||
public sealed class UseDrinkInInventory : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, Target),
|
||||
new UseDrinkInInventoryOperator(Owner, Target),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<TargetInOurInventoryCon>()
|
||||
.BoolCurve(context),
|
||||
considerationsManager.Get<DrinkValueCon>()
|
||||
.QuadraticCurve(context, 1.0f, 0.4f, 0.0f, 0.0f),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Considerations.Nutrition.Food;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Nutrition.Food
|
||||
{
|
||||
public sealed class PickUpFood : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<TargetDistanceCon>()
|
||||
.PresetCurve(context, PresetCurve.Distance),
|
||||
considerationsManager.Get<FoodValueCon>()
|
||||
.QuadraticCurve(context, 1.0f, 0.4f, 0.0f, 0.0f),
|
||||
considerationsManager.Get<TargetAccessibleCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Operators.Nutrition;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations.Nutrition.Food;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Nutrition.Food
|
||||
{
|
||||
public sealed class UseFoodInInventory : UtilityAction
|
||||
{
|
||||
public EntityUid Target { get; set; } = default!;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, Target),
|
||||
new UseFoodInInventoryOperator(Owner, Target),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(Target);
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<TargetInOurInventoryCon>()
|
||||
.BoolCurve(context),
|
||||
considerationsManager.Get<FoodValueCon>()
|
||||
.QuadraticCurve(context, 1.0f, 0.4f, 0.0f, 0.0f),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.WorldState;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Test
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for pathfinding debugging
|
||||
/// </summary>
|
||||
public sealed class MoveRightAndLeftTen : UtilityAction
|
||||
{
|
||||
public override bool CanOverride => false;
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var currentPosition = entMan.GetComponent<TransformComponent>(Owner).Coordinates;
|
||||
var nextPosition = entMan.GetComponent<TransformComponent>(Owner).Coordinates.Offset(new Vector2(10.0f, 0.0f));
|
||||
var originalPosOp = new MoveToGridOperator(Owner, currentPosition, 0.25f);
|
||||
var newPosOp = new MoveToGridOperator(Owner, nextPosition, 0.25f);
|
||||
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
newPosOp,
|
||||
originalPosOp
|
||||
});
|
||||
}
|
||||
|
||||
protected override IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context)
|
||||
{
|
||||
var considerationsManager = IoCManager.Resolve<ConsiderationsManager>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
considerationsManager.Get<DummyCon>()
|
||||
.BoolCurve(context),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States.Utility;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// The same DSE can be used across multiple actions.
|
||||
/// </summary>
|
||||
public abstract class UtilityAction : IAiUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// If we're trying to find a new action can we replace a currently running one with one of the same type.
|
||||
/// e.g. If you're already wandering you don't want to replace it with a different wander.
|
||||
/// </summary>
|
||||
public virtual bool CanOverride => false;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to sort actions; if there's a top-tier action available we won't bother checking the lower tiers.
|
||||
/// Threshold doesn't necessarily mean we'll do an action at a higher threshold;
|
||||
/// if it's really un-optimal (i.e. low score) then we'll also check lower tiers
|
||||
/// </summary>
|
||||
public virtual float Bonus { get; set; } = IdleBonus;
|
||||
// For GW2 they had the bonuses close together but IMO it feels better when they're more like discrete tiers.
|
||||
|
||||
// These are just baselines to make mass-updates easier; actions can do whatever
|
||||
// e.g. if you want shooting a gun to be considered before picking up a gun you could + 1.0f it or w/e
|
||||
public const float IdleBonus = 1.0f;
|
||||
public const float NormalBonus = 5.0f;
|
||||
public const float NeedsBonus = 10.0f;
|
||||
public const float CombatPrepBonus = 20.0f;
|
||||
public const float CombatBonus = 30.0f;
|
||||
public const float DangerBonus = 50.0f;
|
||||
|
||||
public EntityUid Owner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All the considerations are multiplied together to get the final score; a consideration of 0.0 means the action is not possible.
|
||||
/// Ideally you put anything that's easy to assess and can cause an early-out first just so the rest aren't evaluated.
|
||||
/// </summary>
|
||||
/// Uses Func<float> as you don't want to eval the later considerations unless necessary, but we also need the total count
|
||||
/// so can't use IEnumerable
|
||||
protected abstract IReadOnlyCollection<Func<float>> GetConsiderations(Blackboard context);
|
||||
|
||||
/// <summary>
|
||||
/// To keep the operators simple we can chain them together here, e.g. move to can be chained with other operators.
|
||||
/// </summary>
|
||||
public Queue<AiOperator> ActionOperators { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sometimes we may need to set the target for an action or the likes.
|
||||
/// This is mainly useful for expandable states so each one can have a separate target.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
protected virtual void UpdateBlackboard(Blackboard context) {}
|
||||
|
||||
// Needs to be able to be instantiated without args via typefactory.
|
||||
public UtilityAction()
|
||||
{
|
||||
Owner = default!;
|
||||
ActionOperators = default!;
|
||||
}
|
||||
|
||||
public virtual void Shutdown() {}
|
||||
|
||||
/// <summary>
|
||||
/// If this action is chosen then setup the operators to run. This also allows for operators to be reset.
|
||||
/// </summary>
|
||||
public abstract void SetupOperators(Blackboard context);
|
||||
|
||||
// Call the task's operator with Execute and get the outcome
|
||||
public Outcome Execute(float frameTime)
|
||||
{
|
||||
if (!ActionOperators.TryPeek(out var op))
|
||||
{
|
||||
return Outcome.Success;
|
||||
}
|
||||
|
||||
op.Startup();
|
||||
var outcome = op.Execute(frameTime);
|
||||
|
||||
switch (outcome)
|
||||
{
|
||||
case Outcome.Success:
|
||||
op.Shutdown(outcome);
|
||||
ActionOperators.Dequeue();
|
||||
break;
|
||||
case Outcome.Continuing:
|
||||
break;
|
||||
case Outcome.Failed:
|
||||
op.Shutdown(outcome);
|
||||
ActionOperators.Clear();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return outcome;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AKA the Decision Score Evaluator (DSE)
|
||||
/// This is where the magic happens
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="min"></param>
|
||||
/// <returns></returns>
|
||||
public float GetScore(Blackboard context, float min)
|
||||
{
|
||||
UpdateBlackboard(context);
|
||||
var considerations = GetConsiderations(context);
|
||||
DebugTools.Assert(considerations.Count > 0);
|
||||
|
||||
// Overall structure is based on Building a better centaur
|
||||
// Ideally we should early-out each action as cheaply as possible if it's not valid, thus
|
||||
// the finalScore can only go down over time.
|
||||
|
||||
var finalScore = 1.0f;
|
||||
var minThreshold = min / Bonus;
|
||||
context.GetState<ConsiderationState>().SetValue(considerations.Count);
|
||||
|
||||
foreach (var consideration in considerations)
|
||||
{
|
||||
var score = consideration.Invoke();
|
||||
finalScore *= score;
|
||||
DebugTools.Assert(!float.IsNaN(score));
|
||||
|
||||
// The score can only ever go down from each consideration so if we're below minimum no point continuing.
|
||||
if (0.0f >= finalScore || finalScore < minThreshold) {
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
DebugTools.Assert(finalScore <= 1.0f);
|
||||
|
||||
return finalScore * Bonus;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using Content.Server.AI.Components;
|
||||
using Content.Server.AI.LoadBalancer;
|
||||
using Content.Server.AI.Utility.Actions;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
using System.Threading;
|
||||
using Content.Server.AI.EntitySystems;
|
||||
|
||||
namespace Content.Server.AI.Utility.AiLogic
|
||||
{
|
||||
[RegisterComponent, Access(typeof(NPCSystem))]
|
||||
[ComponentReference(typeof(NPCComponent))]
|
||||
public sealed class UtilityNPCComponent : NPCComponent
|
||||
{
|
||||
public Blackboard Blackboard => _blackboard;
|
||||
public Blackboard _blackboard = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The sum of all BehaviorSets gives us what actions the AI can take
|
||||
/// </summary>
|
||||
[DataField("behaviorSets", customTypeSerializer:typeof(PrototypeIdHashSetSerializer<BehaviorSetPrototype>))]
|
||||
public HashSet<string> BehaviorSets { get; } = new();
|
||||
|
||||
public List<IAiUtility> AvailableActions { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The currently running action; most importantly are the operators.
|
||||
/// </summary>
|
||||
public UtilityAction? CurrentAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How frequently we can re-plan. If an AI's in combat you could decrease the cooldown,
|
||||
/// or if there's no players nearby increase it.
|
||||
/// </summary>
|
||||
public float PlanCooldown { get; } = 0.5f;
|
||||
|
||||
public float _planCooldownRemaining;
|
||||
|
||||
/// <summary>
|
||||
/// If we've requested a plan then wait patiently for the action
|
||||
/// </summary>
|
||||
public AiActionRequestJob? _actionRequest;
|
||||
|
||||
public CancellationTokenSource? _actionCancellation;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.AI.Utility
|
||||
{
|
||||
[Prototype("behaviorSet")]
|
||||
public sealed class BehaviorSetPrototype : IPrototype
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the BehaviorSet.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[IdDataFieldAttribute]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Actions that this BehaviorSet grants to the entity.
|
||||
/// </summary>
|
||||
[DataField("actions")]
|
||||
public IReadOnlyList<string> Actions { get; private set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.ActionBlocker;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.ActionBlocker
|
||||
{
|
||||
public sealed class CanMoveCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var self = context.GetState<SelfState>().GetValue();
|
||||
|
||||
if (!EntitySystem.Get<ActionBlockerSystem>().CanMove(self))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States.Clothing;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Server.Clothing.Components;
|
||||
using Content.Shared.Inventory;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Clothing
|
||||
{
|
||||
public sealed class ClothingInInventoryCon : Consideration
|
||||
{
|
||||
public ClothingInInventoryCon Slot(SlotFlags slotFlags, Blackboard context)
|
||||
{
|
||||
// Ideally we'd just use a variable but then if we were iterating through multiple AI at once it'd be
|
||||
// Stuffed so we need to store it on the AI's context.
|
||||
context.GetState<ClothingSlotFlagConState>().SetValue(slotFlags);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var slots = context.GetState<ClothingSlotConState>().GetValue();
|
||||
if (slots == null) return 0.0f;
|
||||
|
||||
foreach (var entity in context.GetState<EnumerableInventoryState>().GetValue())
|
||||
{
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(entity, out ClothingComponent? clothingComponent) ||
|
||||
!EntitySystem.Get<InventorySystem>().TryGetSlot(entity, slots, out var slotDef))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((clothingComponent.Slots & slotDef.SlotFlags) != 0)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States.Clothing;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Clothing
|
||||
{
|
||||
public sealed class ClothingInSlotCon : Consideration
|
||||
{
|
||||
|
||||
public ClothingInSlotCon Slot(string slot, Blackboard context)
|
||||
{
|
||||
context.GetState<ClothingSlotConState>().SetValue(slot);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var slot = context.GetState<ClothingSlotConState>().GetValue();
|
||||
var inventory = context.GetState<EquippedClothingState>().GetValue();
|
||||
return slot != null && inventory.ContainsKey(slot) ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.Weapon.Melee.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Combat.Melee
|
||||
{
|
||||
public sealed class CanUnarmedCombatCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var entity = context.GetState<SelfState>().GetValue();
|
||||
return entityManager.HasComponent<MeleeWeaponComponent>(entity) ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Server.Weapon.Melee.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Combat.Melee
|
||||
{
|
||||
public sealed class HasMeleeWeaponCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
foreach (var item in context.GetState<EnumerableInventoryState>().GetValue())
|
||||
{
|
||||
if (IoCManager.Resolve<IEntityManager>().HasComponent<MeleeWeaponComponent>(item))
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Content.Server.Weapon.Melee.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Combat.Melee
|
||||
{
|
||||
public sealed class MeleeWeaponDamageCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var target = context.GetState<WeaponEntityState>().GetValue();
|
||||
|
||||
if (target == null || !IoCManager.Resolve<IEntityManager>().TryGetComponent(target, out MeleeWeaponComponent? meleeWeaponComponent))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// Just went with max health
|
||||
return (meleeWeaponComponent.Damage.Total / 300.0f).Float();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Server.Weapon.Melee.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Combat.Melee
|
||||
{
|
||||
public sealed class MeleeWeaponEquippedCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||
|
||||
if (equipped == null)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return IoCManager.Resolve<IEntityManager>().HasComponent<MeleeWeaponComponent>(equipped) ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Content.Server.Weapon.Melee.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Combat.Melee
|
||||
{
|
||||
public sealed class MeleeWeaponSpeedCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var target = context.GetState<WeaponEntityState>().GetValue();
|
||||
|
||||
if (target == null || !IoCManager.Resolve<IEntityManager>().TryGetComponent(target, out MeleeWeaponComponent? meleeWeaponComponent))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return meleeWeaponComponent.ArcCooldownTime / 10.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||
{
|
||||
public sealed class TargetHealthCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (target == null || !IoCManager.Resolve<IEntityManager>().TryGetComponent(target, out DamageableComponent? damageableComponent))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return (float) damageableComponent.TotalDamage / 300.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.MobState.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||
{
|
||||
public sealed class TargetIsCritCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (target == null || !IoCManager.Resolve<IEntityManager>().TryGetComponent(target, out MobStateComponent? mobState))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (mobState.IsCritical())
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.MobState.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||
{
|
||||
public sealed class TargetIsDeadCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (target == null || !IoCManager.Resolve<IEntityManager>().TryGetComponent(target, out MobStateComponent? mobState))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (mobState.IsDead())
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States.Utility;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations
|
||||
{
|
||||
public abstract class Consideration
|
||||
{
|
||||
protected abstract float GetScore(Blackboard context);
|
||||
|
||||
private float GetAdjustedScore(Blackboard context)
|
||||
{
|
||||
var score = GetScore(context);
|
||||
/*
|
||||
* Now using the geometric mean
|
||||
* for n scores you take the n-th root of the scores multiplied
|
||||
* e.g. a, b, c scores you take Math.Pow(a * b * c, 1/3)
|
||||
* To get the ACTUAL geometric mean at any one stage you'd need to divide by the running consideration count
|
||||
* however, the downside to this is it will fluctuate up and down over time.
|
||||
* For our purposes if we go below the minimum threshold we want to cut it off, thus we take a
|
||||
* "running geometric mean" which can only ever go down (and by the final value will equal the actual geometric mean).
|
||||
*/
|
||||
|
||||
// Previously we used a makeupvalue method although the geometric mean is less punishing for more considerations
|
||||
var considerationsCount = context.GetState<ConsiderationState>().GetValue();
|
||||
var adjustedScore = MathF.Pow(score, 1 / (float) considerationsCount);
|
||||
return MathHelper.Clamp(adjustedScore, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
private static float BoolCurve(float x)
|
||||
{
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
return x > 0.0f ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
public Func<float> BoolCurve(Blackboard context)
|
||||
{
|
||||
float Result()
|
||||
{
|
||||
var adjustedScore = GetAdjustedScore(context);
|
||||
return BoolCurve(adjustedScore);
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
private static float InverseBoolCurve(float x)
|
||||
{
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
return x == 0.0f ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
public Func<float> InverseBoolCurve(Blackboard context)
|
||||
{
|
||||
float Result()
|
||||
{
|
||||
var adjustedScore = GetAdjustedScore(context);
|
||||
return InverseBoolCurve(adjustedScore);
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
private static float LogisticCurve(float x, float slope, float exponent, float yOffset, float xOffset)
|
||||
{
|
||||
return MathHelper.Clamp(
|
||||
exponent * (1 / (1 + (float) Math.Pow(Math.Log(1000) * slope, -1 * x + xOffset))) + yOffset, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
public Func<float> LogisticCurve(Blackboard context, float slope, float exponent, float yOffset, float xOffset)
|
||||
{
|
||||
float Result()
|
||||
{
|
||||
var adjustedScore = GetAdjustedScore(context);
|
||||
return LogisticCurve(adjustedScore, slope, exponent, yOffset, xOffset);
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
private static float QuadraticCurve(float x, float slope, float exponent, float yOffset, float xOffset)
|
||||
{
|
||||
return MathHelper.Clamp(slope * (float) Math.Pow(x - xOffset, exponent) + yOffset, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
public Func<float> QuadraticCurve(Blackboard context, float slope, float exponent, float yOffset, float xOffset)
|
||||
{
|
||||
float Result()
|
||||
{
|
||||
var adjustedScore = GetAdjustedScore(context);
|
||||
return QuadraticCurve(adjustedScore, slope, exponent, yOffset, xOffset);
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For any curves that are re-used across actions so you only need to update it once.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="preset"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public Func<float> PresetCurve(Blackboard context, PresetCurve preset)
|
||||
{
|
||||
float Result()
|
||||
{
|
||||
var adjustedScore = GetAdjustedScore(context);
|
||||
|
||||
switch (preset)
|
||||
{
|
||||
case Considerations.PresetCurve.Distance:
|
||||
return QuadraticCurve(adjustedScore, -1.0f, 1.0f, 1.0f, 0.02f);
|
||||
case Considerations.PresetCurve.Nutrition:
|
||||
return QuadraticCurve(adjustedScore, 2.0f, 1.0f, -1.0f, -0.2f);
|
||||
case Considerations.PresetCurve.TargetHealth:
|
||||
return QuadraticCurve(adjustedScore, 1.0f, 0.4f, 0.0f, -0.02f);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(preset), preset, null);
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preset response curves for considerations
|
||||
/// </summary>
|
||||
public enum PresetCurve
|
||||
{
|
||||
Distance,
|
||||
Nutrition,
|
||||
TargetHealth,
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations
|
||||
{
|
||||
public sealed class ConsiderationsManager
|
||||
{
|
||||
private readonly Dictionary<Type, Consideration> _considerations = new();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
|
||||
var typeFactory = IoCManager.Resolve<IDynamicTypeFactory>();
|
||||
|
||||
foreach (var conType in reflectionManager.GetAllChildren(typeof(Consideration)))
|
||||
{
|
||||
var con = (Consideration) typeFactory.CreateInstance(conType);
|
||||
_considerations.Add(conType, con);
|
||||
}
|
||||
}
|
||||
|
||||
public T Get<T>() where T : Consideration
|
||||
{
|
||||
return (T) _considerations[typeof(T)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using Content.Server.AI.Pathfinding.Accessible;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns 1.0f if the item is freely accessible (e.g. in storage we can open, on ground, etc.)
|
||||
/// This can be expensive so consider using this last for the considerations
|
||||
/// </summary>
|
||||
public sealed class TargetAccessibleCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
if (!entMan.EntityExists(target))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (target!.Value.TryGetContainer(out var container))
|
||||
{
|
||||
if (entMan.TryGetComponent(container.Owner, out EntityStorageComponent? storageComponent))
|
||||
{
|
||||
if (storageComponent.IsWeldedShut && !storageComponent.Open)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're in a container (e.g. held or whatever) then we probably can't get it. Only exception
|
||||
// Is a locker / crate
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
var owner = context.GetState<SelfState>().GetValue();
|
||||
|
||||
return EntitySystem.Get<AiReachableSystem>().CanAccess(owner, target.Value, SharedInteractionSystem.InteractionRange) ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations
|
||||
{
|
||||
public sealed class DummyCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context) => 1.0f;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.Hands.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Hands
|
||||
{
|
||||
public sealed class FreeHandCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var owner = context.GetState<SelfState>().GetValue();
|
||||
|
||||
if (!owner.IsValid() || !IoCManager.Resolve<IEntityManager>().TryGetComponent(owner, out SharedHandsComponent? handsComponent))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return (float) handsComponent.CountFreeHands() / handsComponent.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Hands;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Shared.Item;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Inventory
|
||||
{
|
||||
public sealed class CanPutTargetInInventoryCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
// First check if target in inventory already
|
||||
// If not then check if we have a free hand
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (target == null || !IoCManager.Resolve<IEntityManager>().HasComponent<ItemComponent>(target))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
foreach (var item in context.GetState<EnumerableInventoryState>().GetValue())
|
||||
{
|
||||
if (item == target)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return context.GetState<AnyFreeHandState>().GetValue() ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Shared.Item;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Inventory
|
||||
{
|
||||
public sealed class TargetInOurInventoryCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (target == null || !IoCManager.Resolve<IEntityManager>().HasComponent<ItemComponent>(target))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
foreach (var item in context.GetState<EnumerableInventoryState>().GetValue())
|
||||
{
|
||||
if (item == target)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Movement
|
||||
{
|
||||
public sealed class TargetDistanceCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var self = context.GetState<SelfState>().GetValue();
|
||||
var entities = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
if (context.GetState<TargetEntityState>().GetValue() is not {Valid: true} target || entities.Deleted(target) ||
|
||||
entities.GetComponent<TransformComponent>(target).GridUid != entities.GetComponent<TransformComponent>(self).GridUid)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// Anything further than 100 tiles gets clamped
|
||||
return (entities.GetComponent<TransformComponent>(target).Coordinates.Position - entities.GetComponent<TransformComponent>(self).Coordinates.Position).Length / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Server.Nutrition.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Nutrition.Drink
|
||||
{
|
||||
public sealed class DrinkValueCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (target == null
|
||||
|| IoCManager.Resolve<IEntityManager>().Deleted(target)
|
||||
|| !EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(target.Value, DrinkComponent.DefaultSolutionName, out var drink))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
var nutritionValue = 0;
|
||||
|
||||
foreach (var reagent in drink.Contents)
|
||||
{
|
||||
// TODO
|
||||
nutritionValue += (reagent.Quantity * 30).Int();
|
||||
}
|
||||
|
||||
return nutritionValue / 1000.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.Nutrition.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Nutrition.Drink
|
||||
{
|
||||
public sealed class ThirstCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var owner = context.GetState<SelfState>().GetValue();
|
||||
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(owner, out ThirstComponent? thirst))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1 - (thirst.CurrentThirst / thirst.ThirstThresholds[ThirstThreshold.OverHydrated]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Server.Nutrition.Components;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Nutrition.Food
|
||||
{
|
||||
public sealed class FoodValueCon : Consideration
|
||||
{
|
||||
protected override float GetScore(Blackboard context)
|
||||
{
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (target == null || !IoCManager.Resolve<IEntityManager>().TryGetComponent<FoodComponent?>(target.Value, out var foodComp)
|
||||
|| !EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(target.Value, foodComp.SolutionName, out var food))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
var nutritionValue = 0;
|
||||
|
||||
foreach (var reagent in food.Contents)
|
||||
{
|
||||
// TODO
|
||||
nutritionValue += (reagent.Quantity * 30).Int();
|
||||
}
|
||||
|
||||
return nutritionValue / 1000.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user