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 JetBrains.Annotations;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Client.AI;
|
using Content.Client.NPC;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ namespace Content.Client.Entry
|
|||||||
prototypes.RegisterIgnore("objective");
|
prototypes.RegisterIgnore("objective");
|
||||||
prototypes.RegisterIgnore("holiday");
|
prototypes.RegisterIgnore("holiday");
|
||||||
prototypes.RegisterIgnore("aiFaction");
|
prototypes.RegisterIgnore("aiFaction");
|
||||||
|
prototypes.RegisterIgnore("htnCompound");
|
||||||
|
prototypes.RegisterIgnore("htnPrimitive");
|
||||||
prototypes.RegisterIgnore("gameMap");
|
prototypes.RegisterIgnore("gameMap");
|
||||||
prototypes.RegisterIgnore("behaviorSet");
|
prototypes.RegisterIgnore("behaviorSet");
|
||||||
prototypes.RegisterIgnore("lobbyBackground");
|
prototypes.RegisterIgnore("lobbyBackground");
|
||||||
|
|||||||
@@ -48,15 +48,18 @@ public sealed class DamageStateVisualizerSystem : VisualizerSystem<DamageStateVi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// So they don't draw over mobs anymore
|
// So they don't draw over mobs anymore
|
||||||
if (data == DamageState.Dead && sprite.DrawDepth > (int) DrawDepth.Items)
|
if (data == DamageState.Dead)
|
||||||
{
|
{
|
||||||
component.OriginalDrawDepth = sprite.DrawDepth;
|
if (sprite.DrawDepth > (int) DrawDepth.FloorObjects)
|
||||||
sprite.DrawDepth = (int) DrawDepth.Items;
|
{
|
||||||
|
component.OriginalDrawDepth = sprite.DrawDepth;
|
||||||
|
sprite.DrawDepth = (int) DrawDepth.FloorObjects;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (component.OriginalDrawDepth != null)
|
else if (component.OriginalDrawDepth != null)
|
||||||
{
|
{
|
||||||
sprite.DrawDepth = component.OriginalDrawDepth.Value;
|
sprite.DrawDepth = component.OriginalDrawDepth.Value;
|
||||||
component. OriginalDrawDepth = null;
|
component.OriginalDrawDepth = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,23 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Shared.AI;
|
using Content.Shared.AI;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||||
|
|
||||||
namespace Content.Client.AI
|
namespace Content.Client.NPC
|
||||||
{
|
{
|
||||||
#if DEBUG
|
|
||||||
public sealed class ClientAiDebugSystem : EntitySystem
|
public sealed class ClientAiDebugSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
[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();
|
private readonly Dictionary<EntityUid, PanelContainer> _aiBoxes = new();
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
if (_tooltips == 0)
|
if (Tooltips == 0)
|
||||||
{
|
{
|
||||||
if (_aiBoxes.Count > 0)
|
if (_aiBoxes.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -76,7 +70,7 @@ namespace Content.Client.AI
|
|||||||
|
|
||||||
private void HandleUtilityAiDebugMessage(SharedAiDebug.UtilityAiDebugMessage message)
|
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?
|
// I guess if it's out of range we don't know about it?
|
||||||
var entity = message.EntityUid;
|
var entity = message.EntityUid;
|
||||||
@@ -93,7 +87,7 @@ namespace Content.Client.AI
|
|||||||
|
|
||||||
private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message)
|
private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message)
|
||||||
{
|
{
|
||||||
if ((_tooltips & AiDebugMode.Paths) != 0)
|
if ((Tooltips & AiDebugMode.Paths) != 0)
|
||||||
{
|
{
|
||||||
var entity = message.EntityUid;
|
var entity = message.EntityUid;
|
||||||
TryCreatePanel(entity);
|
TryCreatePanel(entity);
|
||||||
@@ -107,7 +101,7 @@ namespace Content.Client.AI
|
|||||||
|
|
||||||
private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message)
|
private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message)
|
||||||
{
|
{
|
||||||
if ((_tooltips & AiDebugMode.Paths) != 0)
|
if ((Tooltips & AiDebugMode.Paths) != 0)
|
||||||
{
|
{
|
||||||
var entity = message.EntityUid;
|
var entity = message.EntityUid;
|
||||||
TryCreatePanel(entity);
|
TryCreatePanel(entity);
|
||||||
@@ -126,23 +120,23 @@ namespace Content.Client.AI
|
|||||||
tooltip.Dispose();
|
tooltip.Dispose();
|
||||||
}
|
}
|
||||||
_aiBoxes.Clear();
|
_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)
|
public void ToggleTooltip(AiDebugMode tooltip)
|
||||||
{
|
{
|
||||||
if ((_tooltips & tooltip) != 0)
|
if ((Tooltips & tooltip) != 0)
|
||||||
{
|
{
|
||||||
DisableTooltip(tooltip);
|
DisableTooltip(tooltip);
|
||||||
}
|
}
|
||||||
@@ -200,5 +194,4 @@ namespace Content.Client.AI
|
|||||||
Paths = 1 << 1,
|
Paths = 1 << 1,
|
||||||
Thonk = 1 << 2,
|
Thonk = 1 << 2,
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,21 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Shared.AI;
|
using Content.Shared.AI;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
using Robust.Shared.Enums;
|
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.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Client.AI
|
namespace Content.Client.NPC
|
||||||
{
|
{
|
||||||
#if DEBUG
|
|
||||||
public sealed class ClientPathfindingDebugSystem : EntitySystem
|
public sealed class ClientPathfindingDebugSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
|
||||||
|
public PathfindingDebugMode Modes { get; private set; } = PathfindingDebugMode.None;
|
||||||
private PathfindingDebugMode _modes = PathfindingDebugMode.None;
|
|
||||||
private float _routeDuration = 4.0f; // How long before we remove a route from the overlay
|
private float _routeDuration = 4.0f; // How long before we remove a route from the overlay
|
||||||
private DebugPathfindingOverlay? _overlay;
|
private DebugPathfindingOverlay? _overlay;
|
||||||
|
|
||||||
@@ -47,8 +39,8 @@ namespace Content.Client.AI
|
|||||||
|
|
||||||
private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message)
|
private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message)
|
||||||
{
|
{
|
||||||
if ((_modes & PathfindingDebugMode.Nodes) != 0 ||
|
if ((Modes & PathfindingDebugMode.Nodes) != 0 ||
|
||||||
(_modes & PathfindingDebugMode.Route) != 0)
|
(Modes & PathfindingDebugMode.Route) != 0)
|
||||||
{
|
{
|
||||||
_overlay?.AStarRoutes.Add(message);
|
_overlay?.AStarRoutes.Add(message);
|
||||||
Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () =>
|
Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () =>
|
||||||
@@ -61,8 +53,8 @@ namespace Content.Client.AI
|
|||||||
|
|
||||||
private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message)
|
private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message)
|
||||||
{
|
{
|
||||||
if ((_modes & PathfindingDebugMode.Nodes) != 0 ||
|
if ((Modes & PathfindingDebugMode.Nodes) != 0 ||
|
||||||
(_modes & PathfindingDebugMode.Route) != 0)
|
(Modes & PathfindingDebugMode.Route) != 0)
|
||||||
{
|
{
|
||||||
_overlay?.JpsRoutes.Add(message);
|
_overlay?.JpsRoutes.Add(message);
|
||||||
Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () =>
|
Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () =>
|
||||||
@@ -96,7 +88,7 @@ namespace Content.Client.AI
|
|||||||
}
|
}
|
||||||
|
|
||||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
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);
|
overlayManager.AddOverlay(_overlay);
|
||||||
|
|
||||||
return _overlay;
|
return _overlay;
|
||||||
@@ -117,22 +109,22 @@ namespace Content.Client.AI
|
|||||||
|
|
||||||
public void Disable()
|
public void Disable()
|
||||||
{
|
{
|
||||||
_modes = PathfindingDebugMode.None;
|
Modes = PathfindingDebugMode.None;
|
||||||
DisableOverlay();
|
DisableOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void EnableMode(PathfindingDebugMode tooltip)
|
public void EnableMode(PathfindingDebugMode tooltip)
|
||||||
{
|
{
|
||||||
_modes |= tooltip;
|
Modes |= tooltip;
|
||||||
if (_modes != 0)
|
if (Modes != 0)
|
||||||
{
|
{
|
||||||
EnableOverlay();
|
EnableOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_overlay != null)
|
if (_overlay != null)
|
||||||
{
|
{
|
||||||
_overlay.Modes = _modes;
|
_overlay.Modes = Modes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tooltip == PathfindingDebugMode.Graph)
|
if (tooltip == PathfindingDebugMode.Graph)
|
||||||
@@ -149,27 +141,27 @@ namespace Content.Client.AI
|
|||||||
// So need further investigation.
|
// 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());
|
RaiseNetworkEvent(new SharedAiDebug.UnsubscribeReachableMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
_modes &= ~mode;
|
Modes &= ~mode;
|
||||||
if (_modes == 0)
|
if (Modes == 0)
|
||||||
{
|
{
|
||||||
DisableOverlay();
|
DisableOverlay();
|
||||||
}
|
}
|
||||||
else if (_overlay != null)
|
else if (_overlay != null)
|
||||||
{
|
{
|
||||||
_overlay.Modes = _modes;
|
_overlay.Modes = Modes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleTooltip(PathfindingDebugMode mode)
|
public void ToggleTooltip(PathfindingDebugMode mode)
|
||||||
{
|
{
|
||||||
if ((_modes & mode) != 0)
|
if ((Modes & mode) != 0)
|
||||||
{
|
{
|
||||||
DisableMode(mode);
|
DisableMode(mode);
|
||||||
}
|
}
|
||||||
@@ -525,5 +517,4 @@ namespace Content.Client.AI
|
|||||||
CachedRegions = 1 << 3,
|
CachedRegions = 1 << 3,
|
||||||
Regions = 1 << 4,
|
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.Administration.Managers;
|
||||||
using Content.Client.Decals.UI;
|
using Content.Client.Decals.UI;
|
||||||
|
using Content.Client.Eui;
|
||||||
using Content.Client.HUD;
|
using Content.Client.HUD;
|
||||||
using Content.Client.Markers;
|
using Content.Client.Markers;
|
||||||
using Content.Client.SubFloor;
|
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)
|
protected override void CreateEffect(EntityUid uid, MuzzleFlashEvent message, EntityUid? user = null)
|
||||||
{
|
{
|
||||||
if (!Timing.IsFirstTimePredicted || !TryComp<TransformComponent>(uid, out var xform)) return;
|
if (!Timing.IsFirstTimePredicted)
|
||||||
var ent = Spawn(message.Prototype, xform.Coordinates);
|
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);
|
var effectXform = Transform(ent);
|
||||||
effectXform.LocalRotation -= MathF.PI / 2;
|
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