Add utility AI (#806)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com> Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
This commit is contained in:
61
Content.Client/Commands/DebugAiCommand.cs
Normal file
61
Content.Client/Commands/DebugAiCommand.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using Content.Client.GameObjects.EntitySystems.AI;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.Interfaces.Console;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Client.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is used to handle the tooltips above AI mobs
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
internal sealed class DebugAiCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
// ReSharper disable once StringLiteralTypo
|
||||||
|
public string Command => "debugai";
|
||||||
|
public string Description => "Handles all tooltip debugging above AI mobs";
|
||||||
|
public string Help => "debugai [hide/paths/thonk]";
|
||||||
|
|
||||||
|
public bool Execute(IDebugConsole console, params string[] args)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
if (args.Length < 1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var anyAction = false;
|
||||||
|
var debugSystem = EntitySystem.Get<ClientAiDebugSystem>();
|
||||||
|
|
||||||
|
foreach (var arg in args)
|
||||||
|
{
|
||||||
|
switch (arg)
|
||||||
|
{
|
||||||
|
case "hide":
|
||||||
|
debugSystem.Disable();
|
||||||
|
anyAction = true;
|
||||||
|
break;
|
||||||
|
// This will show the pathfinding numbers above the mob's head
|
||||||
|
case "paths":
|
||||||
|
debugSystem.ToggleTooltip(AiDebugMode.Paths);
|
||||||
|
anyAction = true;
|
||||||
|
break;
|
||||||
|
// Shows stats on what the AI was thinking.
|
||||||
|
case "thonk":
|
||||||
|
debugSystem.ToggleTooltip(AiDebugMode.Thonk);
|
||||||
|
anyAction = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !anyAction;
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
Content.Client/Commands/DebugPathfindingCommand.cs
Normal file
62
Content.Client/Commands/DebugPathfindingCommand.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using Content.Client.GameObjects.EntitySystems.AI;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.Interfaces.Console;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Client.Commands
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
internal sealed class DebugPathfindingCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
// ReSharper disable once StringLiteralTypo
|
||||||
|
public string Command => "pathfinder";
|
||||||
|
public string Description => "Toggles visibility of pathfinding debuggers.";
|
||||||
|
public string Help => "pathfinder [hide/nodes/routes/graph]";
|
||||||
|
|
||||||
|
public bool Execute(IDebugConsole console, params string[] args)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
if (args.Length < 1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var anyAction = false;
|
||||||
|
var debugSystem = EntitySystem.Get<ClientPathfindingDebugSystem>();
|
||||||
|
|
||||||
|
foreach (var arg in args)
|
||||||
|
{
|
||||||
|
switch (arg)
|
||||||
|
{
|
||||||
|
case "hide":
|
||||||
|
debugSystem.Disable();
|
||||||
|
anyAction = true;
|
||||||
|
break;
|
||||||
|
// Shows all nodes on the closed list
|
||||||
|
case "nodes":
|
||||||
|
debugSystem.ToggleTooltip(PathfindingDebugMode.Nodes);
|
||||||
|
anyAction = true;
|
||||||
|
break;
|
||||||
|
// Will show just the constructed route
|
||||||
|
case "routes":
|
||||||
|
debugSystem.ToggleTooltip(PathfindingDebugMode.Route);
|
||||||
|
anyAction = true;
|
||||||
|
break;
|
||||||
|
// Shows all of the pathfinding chunks
|
||||||
|
case "graph":
|
||||||
|
debugSystem.ToggleTooltip(PathfindingDebugMode.Graph);
|
||||||
|
anyAction = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !anyAction;
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.AI;
|
||||||
|
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||||
|
using Robust.Client.Interfaces.UserInterface;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.EntitySystems.AI
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
public class ClientAiDebugSystem : EntitySystem
|
||||||
|
{
|
||||||
|
private AiDebugMode _tooltips = AiDebugMode.None;
|
||||||
|
private readonly Dictionary<IEntity, PanelContainer> _aiBoxes = new Dictionary<IEntity,PanelContainer>();
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
if (_tooltips == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var eyeManager = IoCManager.Resolve<IEyeManager>();
|
||||||
|
foreach (var (entity, panel) in _aiBoxes)
|
||||||
|
{
|
||||||
|
if (entity == null) continue;
|
||||||
|
|
||||||
|
if (!eyeManager.GetWorldViewport().Contains(entity.Transform.WorldPosition))
|
||||||
|
{
|
||||||
|
panel.Visible = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (x, y) = eyeManager.WorldToScreen(entity.Transform.GridPosition).Position;
|
||||||
|
var offsetPosition = new Vector2(x - panel.Width / 2, y - panel.Height - 50f);
|
||||||
|
panel.Visible = true;
|
||||||
|
|
||||||
|
LayoutContainer.SetPosition(panel, offsetPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeNetworkEvent<SharedAiDebug.UtilityAiDebugMessage>(HandleUtilityAiDebugMessage);
|
||||||
|
SubscribeNetworkEvent<SharedAiDebug.AStarRouteMessage>(HandleAStarRouteMessage);
|
||||||
|
SubscribeNetworkEvent<SharedAiDebug.JpsRouteMessage>(HandleJpsRouteMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleUtilityAiDebugMessage(SharedAiDebug.UtilityAiDebugMessage message)
|
||||||
|
{
|
||||||
|
if ((_tooltips & AiDebugMode.Thonk) != 0)
|
||||||
|
{
|
||||||
|
// I guess if it's out of range we don't know about it?
|
||||||
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
var entity = entityManager.GetEntity(message.EntityUid);
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryCreatePanel(entity);
|
||||||
|
|
||||||
|
// Probably shouldn't access by index but it's a debugging tool so eh
|
||||||
|
var label = (Label) _aiBoxes[entity].GetChild(0).GetChild(0);
|
||||||
|
label.Text = $"Current Task: {message.FoundTask}\n" +
|
||||||
|
$"Task score: {message.ActionScore}\n" +
|
||||||
|
$"Planning time (ms): {message.PlanningTime * 1000:0.0000}\n" +
|
||||||
|
$"Considered {message.ConsideredTaskCount} tasks";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message)
|
||||||
|
{
|
||||||
|
if ((_tooltips & AiDebugMode.Paths) != 0)
|
||||||
|
{
|
||||||
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
var entity = entityManager.GetEntity(message.EntityUid);
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryCreatePanel(entity);
|
||||||
|
|
||||||
|
var label = (Label) _aiBoxes[entity].GetChild(0).GetChild(1);
|
||||||
|
label.Text = $"Pathfinding time (ms): {message.TimeTaken * 1000:0.0000}\n" +
|
||||||
|
$"Nodes traversed: {message.ClosedTiles.Count}\n" +
|
||||||
|
$"Nodes per ms: {message.ClosedTiles.Count / (message.TimeTaken * 1000)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message)
|
||||||
|
{
|
||||||
|
if ((_tooltips & AiDebugMode.Paths) != 0)
|
||||||
|
{
|
||||||
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
var entity = entityManager.GetEntity(message.EntityUid);
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryCreatePanel(entity);
|
||||||
|
|
||||||
|
var label = (Label) _aiBoxes[entity].GetChild(0).GetChild(1);
|
||||||
|
label.Text = $"Pathfinding time (ms): {message.TimeTaken * 1000:0.0000}\n" +
|
||||||
|
$"Jump Nodes: {message.JumpNodes.Count}\n" +
|
||||||
|
$"Jump Nodes per ms: {message.JumpNodes.Count / (message.TimeTaken * 1000)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disable()
|
||||||
|
{
|
||||||
|
foreach (var tooltip in _aiBoxes.Values)
|
||||||
|
{
|
||||||
|
tooltip.Dispose();
|
||||||
|
}
|
||||||
|
_aiBoxes.Clear();
|
||||||
|
_tooltips = AiDebugMode.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void EnableTooltip(AiDebugMode tooltip)
|
||||||
|
{
|
||||||
|
_tooltips |= tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisableTooltip(AiDebugMode tooltip)
|
||||||
|
{
|
||||||
|
_tooltips &= ~tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleTooltip(AiDebugMode tooltip)
|
||||||
|
{
|
||||||
|
if ((_tooltips & tooltip) != 0)
|
||||||
|
{
|
||||||
|
DisableTooltip(tooltip);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EnableTooltip(tooltip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryCreatePanel(IEntity entity)
|
||||||
|
{
|
||||||
|
if (!_aiBoxes.ContainsKey(entity))
|
||||||
|
{
|
||||||
|
var userInterfaceManager = IoCManager.Resolve<IUserInterfaceManager>();
|
||||||
|
|
||||||
|
var actionLabel = new Label
|
||||||
|
{
|
||||||
|
MouseFilter = Control.MouseFilterMode.Ignore,
|
||||||
|
};
|
||||||
|
|
||||||
|
var pathfindingLabel = new Label
|
||||||
|
{
|
||||||
|
MouseFilter = Control.MouseFilterMode.Ignore,
|
||||||
|
};
|
||||||
|
|
||||||
|
var vBox = new VBoxContainer()
|
||||||
|
{
|
||||||
|
SeparationOverride = 15,
|
||||||
|
Children = {actionLabel, pathfindingLabel},
|
||||||
|
};
|
||||||
|
|
||||||
|
var panel = new PanelContainer
|
||||||
|
{
|
||||||
|
StyleClasses = {"tooltipBox"},
|
||||||
|
Children = {vBox},
|
||||||
|
MouseFilter = Control.MouseFilterMode.Ignore,
|
||||||
|
ModulateSelfOverride = Color.White.WithAlpha(0.75f),
|
||||||
|
};
|
||||||
|
|
||||||
|
userInterfaceManager.StateRoot.AddChild(panel);
|
||||||
|
|
||||||
|
_aiBoxes[entity] = panel;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum AiDebugMode : byte
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Paths = 1 << 1,
|
||||||
|
Thonk = 1 << 2,
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,355 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.AI;
|
||||||
|
using Robust.Client.Graphics.Drawing;
|
||||||
|
using Robust.Client.Graphics.Overlays;
|
||||||
|
using Robust.Client.Graphics.Shaders;
|
||||||
|
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||||
|
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Timers;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.EntitySystems.AI
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
public class ClientPathfindingDebugSystem : EntitySystem
|
||||||
|
{
|
||||||
|
private PathfindingDebugMode _modes = PathfindingDebugMode.None;
|
||||||
|
private float _routeDuration = 4.0f; // How long before we remove a route from the overlay
|
||||||
|
private DebugPathfindingOverlay _overlay;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeNetworkEvent<SharedAiDebug.AStarRouteMessage>(HandleAStarRouteMessage);
|
||||||
|
SubscribeNetworkEvent<SharedAiDebug.JpsRouteMessage>(HandleJpsRouteMessage);
|
||||||
|
SubscribeNetworkEvent<SharedAiDebug.PathfindingGraphMessage>(HandleGraphMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message)
|
||||||
|
{
|
||||||
|
if ((_modes & PathfindingDebugMode.Nodes) != 0 ||
|
||||||
|
(_modes & PathfindingDebugMode.Route) != 0)
|
||||||
|
{
|
||||||
|
_overlay.AStarRoutes.Add(message);
|
||||||
|
Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () =>
|
||||||
|
{
|
||||||
|
if (_overlay == null) return;
|
||||||
|
_overlay.AStarRoutes.Remove(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message)
|
||||||
|
{
|
||||||
|
if ((_modes & PathfindingDebugMode.Nodes) != 0 ||
|
||||||
|
(_modes & PathfindingDebugMode.Route) != 0)
|
||||||
|
{
|
||||||
|
_overlay.JpsRoutes.Add(message);
|
||||||
|
Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () =>
|
||||||
|
{
|
||||||
|
if (_overlay == null) return;
|
||||||
|
_overlay.JpsRoutes.Remove(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleGraphMessage(SharedAiDebug.PathfindingGraphMessage message)
|
||||||
|
{
|
||||||
|
if ((_modes & PathfindingDebugMode.Graph) != 0)
|
||||||
|
{
|
||||||
|
_overlay.UpdateGraph(message.Graph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnableOverlay()
|
||||||
|
{
|
||||||
|
if (_overlay != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||||
|
_overlay = new DebugPathfindingOverlay {Modes = _modes};
|
||||||
|
overlayManager.AddOverlay(_overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisableOverlay()
|
||||||
|
{
|
||||||
|
if (_overlay == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_overlay.Modes = 0;
|
||||||
|
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||||
|
overlayManager.RemoveOverlay(_overlay.ID);
|
||||||
|
_overlay = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disable()
|
||||||
|
{
|
||||||
|
_modes = PathfindingDebugMode.None;
|
||||||
|
DisableOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void EnableMode(PathfindingDebugMode tooltip)
|
||||||
|
{
|
||||||
|
_modes |= tooltip;
|
||||||
|
if (_modes != 0)
|
||||||
|
{
|
||||||
|
EnableOverlay();
|
||||||
|
}
|
||||||
|
_overlay.Modes = _modes;
|
||||||
|
|
||||||
|
if (tooltip == PathfindingDebugMode.Graph)
|
||||||
|
{
|
||||||
|
var systemMessage = new SharedAiDebug.RequestPathfindingGraphMessage();
|
||||||
|
EntityManager.EntityNetManager.SendSystemNetworkMessage(systemMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisableMode(PathfindingDebugMode mode)
|
||||||
|
{
|
||||||
|
_modes &= ~mode;
|
||||||
|
if (_modes == 0)
|
||||||
|
{
|
||||||
|
DisableOverlay();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_overlay.Modes = _modes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleTooltip(PathfindingDebugMode mode)
|
||||||
|
{
|
||||||
|
if ((_modes & mode) != 0)
|
||||||
|
{
|
||||||
|
DisableMode(mode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EnableMode(mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DebugPathfindingOverlay : Overlay
|
||||||
|
{
|
||||||
|
// TODO: Add a box like the debug one and show the most recent path stuff
|
||||||
|
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||||
|
|
||||||
|
public PathfindingDebugMode Modes { get; set; } = PathfindingDebugMode.None;
|
||||||
|
|
||||||
|
// Graph debugging
|
||||||
|
public readonly Dictionary<int, List<Vector2>> Graph = new Dictionary<int, List<Vector2>>();
|
||||||
|
private readonly Dictionary<int, Color> _graphColors = new Dictionary<int, Color>();
|
||||||
|
|
||||||
|
// Route debugging
|
||||||
|
// As each pathfinder is very different you'll likely want to draw them completely different
|
||||||
|
public readonly List<SharedAiDebug.AStarRouteMessage> AStarRoutes = new List<SharedAiDebug.AStarRouteMessage>();
|
||||||
|
public readonly List<SharedAiDebug.JpsRouteMessage> JpsRoutes = new List<SharedAiDebug.JpsRouteMessage>();
|
||||||
|
|
||||||
|
public DebugPathfindingOverlay() : base(nameof(DebugPathfindingOverlay))
|
||||||
|
{
|
||||||
|
Shader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>("unshaded").Instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateGraph(Dictionary<int, List<Vector2>> graph)
|
||||||
|
{
|
||||||
|
Graph.Clear();
|
||||||
|
_graphColors.Clear();
|
||||||
|
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||||
|
foreach (var (chunk, nodes) in graph)
|
||||||
|
{
|
||||||
|
Graph[chunk] = nodes;
|
||||||
|
_graphColors[chunk] = new Color(robustRandom.NextFloat(), robustRandom.NextFloat(),
|
||||||
|
robustRandom.NextFloat(), 0.3f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawGraph(DrawingHandleScreen screenHandle)
|
||||||
|
{
|
||||||
|
var eyeManager = IoCManager.Resolve<IEyeManager>();
|
||||||
|
var viewport = IoCManager.Resolve<IEyeManager>().GetWorldViewport();
|
||||||
|
|
||||||
|
foreach (var (chunk, nodes) in Graph)
|
||||||
|
{
|
||||||
|
foreach (var tile in nodes)
|
||||||
|
{
|
||||||
|
if (!viewport.Contains(tile)) continue;
|
||||||
|
|
||||||
|
var screenTile = eyeManager.WorldToScreen(tile);
|
||||||
|
|
||||||
|
var box = new UIBox2(
|
||||||
|
screenTile.X - 15.0f,
|
||||||
|
screenTile.Y - 15.0f,
|
||||||
|
screenTile.X + 15.0f,
|
||||||
|
screenTile.Y + 15.0f);
|
||||||
|
|
||||||
|
screenHandle.DrawRect(box, _graphColors[chunk]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region pathfinder
|
||||||
|
|
||||||
|
private void DrawAStarRoutes(DrawingHandleScreen screenHandle)
|
||||||
|
{
|
||||||
|
var eyeManager = IoCManager.Resolve<IEyeManager>();
|
||||||
|
var viewport = eyeManager.GetWorldViewport();
|
||||||
|
|
||||||
|
foreach (var route in AStarRoutes)
|
||||||
|
{
|
||||||
|
// Draw box on each tile of route
|
||||||
|
foreach (var position in route.Route)
|
||||||
|
{
|
||||||
|
if (!viewport.Contains(position)) continue;
|
||||||
|
var screenTile = eyeManager.WorldToScreen(position);
|
||||||
|
// worldHandle.DrawLine(position, nextWorld.Value, Color.Blue);
|
||||||
|
var box = new UIBox2(
|
||||||
|
screenTile.X - 15.0f,
|
||||||
|
screenTile.Y - 15.0f,
|
||||||
|
screenTile.X + 15.0f,
|
||||||
|
screenTile.Y + 15.0f);
|
||||||
|
screenHandle.DrawRect(box, Color.Orange.WithAlpha(0.25f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawAStarNodes(DrawingHandleScreen screenHandle)
|
||||||
|
{
|
||||||
|
var eyeManager = IoCManager.Resolve<IEyeManager>();
|
||||||
|
var viewport = eyeManager.GetWorldViewport();
|
||||||
|
|
||||||
|
foreach (var route in AStarRoutes)
|
||||||
|
{
|
||||||
|
var highestgScore = route.GScores.Values.Max();
|
||||||
|
|
||||||
|
foreach (var (tile, score) in route.GScores)
|
||||||
|
{
|
||||||
|
if ((route.Route.Contains(tile) && (Modes & PathfindingDebugMode.Route) != 0) ||
|
||||||
|
!viewport.Contains(tile))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var screenTile = eyeManager.WorldToScreen(tile);
|
||||||
|
|
||||||
|
var box = new UIBox2(
|
||||||
|
screenTile.X - 15.0f,
|
||||||
|
screenTile.Y - 15.0f,
|
||||||
|
screenTile.X + 15.0f,
|
||||||
|
screenTile.Y + 15.0f);
|
||||||
|
|
||||||
|
screenHandle.DrawRect(box, new Color(
|
||||||
|
0.0f,
|
||||||
|
score / highestgScore,
|
||||||
|
1.0f - (score / highestgScore),
|
||||||
|
0.1f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawJpsRoutes(DrawingHandleScreen screenHandle)
|
||||||
|
{
|
||||||
|
var eyeManager = IoCManager.Resolve<IEyeManager>();
|
||||||
|
var viewport = eyeManager.GetWorldViewport();
|
||||||
|
|
||||||
|
foreach (var route in JpsRoutes)
|
||||||
|
{
|
||||||
|
// Draw box on each tile of route
|
||||||
|
foreach (var position in route.Route)
|
||||||
|
{
|
||||||
|
if (!viewport.Contains(position)) continue;
|
||||||
|
var screenTile = eyeManager.WorldToScreen(position);
|
||||||
|
// worldHandle.DrawLine(position, nextWorld.Value, Color.Blue);
|
||||||
|
var box = new UIBox2(
|
||||||
|
screenTile.X - 15.0f,
|
||||||
|
screenTile.Y - 15.0f,
|
||||||
|
screenTile.X + 15.0f,
|
||||||
|
screenTile.Y + 15.0f);
|
||||||
|
screenHandle.DrawRect(box, Color.Orange.WithAlpha(0.25f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawJpsNodes(DrawingHandleScreen screenHandle)
|
||||||
|
{
|
||||||
|
var eyeManager = IoCManager.Resolve<IEyeManager>();
|
||||||
|
var viewport = eyeManager.GetWorldViewport();
|
||||||
|
|
||||||
|
foreach (var route in JpsRoutes)
|
||||||
|
{
|
||||||
|
foreach (var tile in route.JumpNodes)
|
||||||
|
{
|
||||||
|
if ((route.Route.Contains(tile) && (Modes & PathfindingDebugMode.Route) != 0) ||
|
||||||
|
!viewport.Contains(tile))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var screenTile = eyeManager.WorldToScreen(tile);
|
||||||
|
|
||||||
|
var box = new UIBox2(
|
||||||
|
screenTile.X - 15.0f,
|
||||||
|
screenTile.Y - 15.0f,
|
||||||
|
screenTile.X + 15.0f,
|
||||||
|
screenTile.Y + 15.0f);
|
||||||
|
|
||||||
|
screenHandle.DrawRect(box, new Color(
|
||||||
|
0.0f,
|
||||||
|
1.0f,
|
||||||
|
0.0f,
|
||||||
|
0.2f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
protected override void Draw(DrawingHandleBase handle)
|
||||||
|
{
|
||||||
|
if (Modes == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var screenHandle = (DrawingHandleScreen) handle;
|
||||||
|
|
||||||
|
if ((Modes & PathfindingDebugMode.Route) != 0)
|
||||||
|
{
|
||||||
|
DrawAStarRoutes(screenHandle);
|
||||||
|
DrawJpsRoutes(screenHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Modes & PathfindingDebugMode.Nodes) != 0)
|
||||||
|
{
|
||||||
|
DrawAStarNodes(screenHandle);
|
||||||
|
DrawJpsNodes(screenHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Modes & PathfindingDebugMode.Graph) != 0)
|
||||||
|
{
|
||||||
|
DrawGraph(screenHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum PathfindingDebugMode {
|
||||||
|
None = 0,
|
||||||
|
Route = 1 << 0,
|
||||||
|
Graph = 1 << 1,
|
||||||
|
Nodes = 1 << 2,
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Server.Interfaces.GameObjects.Components.Movement;
|
|
||||||
using Content.Shared.Physics;
|
|
||||||
using Robust.Server.AI;
|
|
||||||
using Robust.Server.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects.Components;
|
|
||||||
using Robust.Shared.Interfaces.Physics;
|
|
||||||
using Robust.Shared.Interfaces.Timing;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
|
|
||||||
namespace Content.Server.AI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The object stays stationary. The object will periodically scan for *any* life forms in its radius, and engage them.
|
|
||||||
/// The object will rotate itself to point at the locked entity, and if it has a weapon will shoot at the entity.
|
|
||||||
/// </summary>
|
|
||||||
[AiLogicProcessor("AimShootLife")]
|
|
||||||
class AimShootLifeProcessor : AiLogicProcessor
|
|
||||||
{
|
|
||||||
#pragma warning disable 649
|
|
||||||
[Dependency] private readonly IPhysicsManager _physMan;
|
|
||||||
[Dependency] private readonly IServerEntityManager _entMan;
|
|
||||||
[Dependency] private readonly IGameTiming _timeMan;
|
|
||||||
#pragma warning restore 649
|
|
||||||
|
|
||||||
private readonly List<IEntity> _workList = new List<IEntity>();
|
|
||||||
|
|
||||||
private const float MaxAngSpeed = (float)(Math.PI / 2); // how fast our turret can rotate
|
|
||||||
private const float ScanPeriod = 1.0f; // tweak this for performance and gameplay experience
|
|
||||||
private float _lastScan;
|
|
||||||
|
|
||||||
private IEntity _curTarget;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
if (SelfEntity == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
DoScanning();
|
|
||||||
DoTracking(frameTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DoScanning()
|
|
||||||
{
|
|
||||||
var curTime = _timeMan.CurTime.TotalSeconds;
|
|
||||||
if (curTime - _lastScan > ScanPeriod)
|
|
||||||
{
|
|
||||||
_lastScan = (float)curTime;
|
|
||||||
_curTarget = FindBestTarget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DoTracking(float frameTime)
|
|
||||||
{
|
|
||||||
// not valid entity to target.
|
|
||||||
if (_curTarget == null || !_curTarget.IsValid())
|
|
||||||
{
|
|
||||||
_curTarget = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// point me at the target
|
|
||||||
var tarPos = _curTarget.GetComponent<ITransformComponent>().WorldPosition;
|
|
||||||
var selfTransform = SelfEntity.GetComponent<ITransformComponent>();
|
|
||||||
var myPos = selfTransform.WorldPosition;
|
|
||||||
|
|
||||||
var curDir = selfTransform.LocalRotation.ToVec();
|
|
||||||
var tarDir = (tarPos - myPos).Normalized;
|
|
||||||
|
|
||||||
var fwdAng = Vector2.Dot(curDir, tarDir);
|
|
||||||
|
|
||||||
Vector2 newDir;
|
|
||||||
if (fwdAng < 0) // target behind turret, just rotate in a direction to get target in front
|
|
||||||
{
|
|
||||||
var curRight = new Vector2(-curDir.Y, curDir.X); // right handed coord system
|
|
||||||
var rightAngle = Vector2.Dot(curDir, new Vector2(-tarDir.Y, tarDir.X)); // right handed coord system
|
|
||||||
var rotateSign = -Math.Sign(rightAngle);
|
|
||||||
newDir = curDir + curRight * rotateSign * MaxAngSpeed * frameTime;
|
|
||||||
}
|
|
||||||
else // target in front, adjust to aim at him
|
|
||||||
{
|
|
||||||
newDir = MoveTowards(curDir, tarDir, MaxAngSpeed, frameTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
selfTransform.LocalRotation = new Angle(newDir);
|
|
||||||
|
|
||||||
if (fwdAng > -0.9999)
|
|
||||||
{
|
|
||||||
// TODO: shoot gun, prob need aimbot because entity rotation lags behind moving target
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEntity FindBestTarget()
|
|
||||||
{
|
|
||||||
// "best" target is the closest one with LOS
|
|
||||||
|
|
||||||
var ents = _entMan.GetEntitiesInRange(SelfEntity, VisionRadius);
|
|
||||||
var myTransform = SelfEntity.GetComponent<ITransformComponent>();
|
|
||||||
var maxRayLen = VisionRadius * 2.5f; // circle inscribed in square, square diagonal = 2*r*sqrt(2)
|
|
||||||
|
|
||||||
_workList.Clear();
|
|
||||||
foreach (var entity in ents)
|
|
||||||
{
|
|
||||||
// filter to "people" entities (entities with controllers)
|
|
||||||
if (!entity.HasComponent<IMoverComponent>())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// build the ray
|
|
||||||
var dir = entity.GetComponent<ITransformComponent>().WorldPosition - myTransform.WorldPosition;
|
|
||||||
var ray = new CollisionRay(myTransform.WorldPosition, dir.Normalized, (int)(CollisionGroup.MobImpassable | CollisionGroup.Impassable));
|
|
||||||
|
|
||||||
// cast the ray
|
|
||||||
var result = _physMan.IntersectRay(myTransform.MapID, ray, maxRayLen, SelfEntity).First();
|
|
||||||
|
|
||||||
// add to visible list
|
|
||||||
if (result.HitEntity == entity)
|
|
||||||
_workList.Add(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get closest entity in list
|
|
||||||
var closestEnt = GetClosest(myTransform.WorldPosition, _workList);
|
|
||||||
|
|
||||||
// return closest
|
|
||||||
return closestEnt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEntity GetClosest(Vector2 origin, IEnumerable<IEntity> list)
|
|
||||||
{
|
|
||||||
IEntity closest = null;
|
|
||||||
var minDistSqrd = float.PositiveInfinity;
|
|
||||||
|
|
||||||
foreach (var ent in list)
|
|
||||||
{
|
|
||||||
var pos = ent.GetComponent<ITransformComponent>().WorldPosition;
|
|
||||||
var distSqrd = (pos - origin).LengthSquared;
|
|
||||||
|
|
||||||
if (distSqrd > minDistSqrd)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
closest = ent;
|
|
||||||
minDistSqrd = distSqrd;
|
|
||||||
}
|
|
||||||
|
|
||||||
return closest;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Vector2 MoveTowards(Vector2 current, Vector2 target, float speed, float delta)
|
|
||||||
{
|
|
||||||
var maxDeltaDist = speed * delta;
|
|
||||||
var a = target - current;
|
|
||||||
var magnitude = a.Length;
|
|
||||||
if (magnitude <= maxDeltaDist)
|
|
||||||
{
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
return current + a / magnitude * maxDeltaDist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
55
Content.Server/AI/Operators/AiOperator.cs
Normal file
55
Content.Server/AI/Operators/AiOperator.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators
|
||||||
|
{
|
||||||
|
public abstract class AiOperator
|
||||||
|
{
|
||||||
|
private bool _hasStartup = false;
|
||||||
|
private bool _hasShutdown = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called once when the AiLogicProcessor starts this action
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool TryStartup()
|
||||||
|
{
|
||||||
|
// 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 AiLogicProcessor is done with this action if the outcome is successful or fails.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Shutdown(Outcome outcome)
|
||||||
|
{
|
||||||
|
if (_hasShutdown)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("AiOperator has already shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasShutdown = 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
using Content.Server.GameObjects;
|
||||||
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Combat.Ranged
|
||||||
|
{
|
||||||
|
public class ShootAtEntityOperator : AiOperator
|
||||||
|
{
|
||||||
|
private IEntity _owner;
|
||||||
|
private IEntity _target;
|
||||||
|
private float _accuracy;
|
||||||
|
|
||||||
|
private float _burstTime;
|
||||||
|
|
||||||
|
private float _elapsedTime;
|
||||||
|
|
||||||
|
public ShootAtEntityOperator(IEntity owner, IEntity target, float accuracy, float burstTime = 0.5f)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
_target = target;
|
||||||
|
_accuracy = accuracy;
|
||||||
|
_burstTime = burstTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool TryStartup()
|
||||||
|
{
|
||||||
|
if (!base.TryStartup())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_owner.TryGetComponent(out CombatModeComponent combatModeComponent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!combatModeComponent.IsInCombatMode)
|
||||||
|
{
|
||||||
|
combatModeComponent.IsInCombatMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown(Outcome outcome)
|
||||||
|
{
|
||||||
|
base.Shutdown(outcome);
|
||||||
|
if (_owner.TryGetComponent(out CombatModeComponent combatModeComponent))
|
||||||
|
{
|
||||||
|
combatModeComponent.IsInCombatMode = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
// TODO: Probably just do all the checks on first try and then after that repeat the fire.
|
||||||
|
if (_burstTime <= _elapsedTime)
|
||||||
|
{
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
_elapsedTime += frameTime;
|
||||||
|
|
||||||
|
if (_target.TryGetComponent(out DamageableComponent damageableComponent))
|
||||||
|
{
|
||||||
|
if (damageableComponent.IsDead())
|
||||||
|
{
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_owner.TryGetComponent(out HandsComponent hands) || hands.GetActiveHand == null)
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var equippedWeapon = hands.GetActiveHand.Owner;
|
||||||
|
|
||||||
|
if ((_target.Transform.GridPosition.Position - _owner.Transform.GridPosition.Position).Length >
|
||||||
|
_owner.GetComponent<AiControllerComponent>().VisionRadius)
|
||||||
|
{
|
||||||
|
// Not necessarily a hard fail, more of a soft fail
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unless RangedWeaponComponent is removed from hitscan weapons this shouldn't happen
|
||||||
|
if (!equippedWeapon.TryGetComponent(out RangedWeaponComponent rangedWeaponComponent))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Accuracy
|
||||||
|
rangedWeaponComponent.AiFire(_owner, _target.Transform.GridPosition);
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Combat.Ranged
|
||||||
|
{
|
||||||
|
public class WaitForHitscanChargeOperator : AiOperator
|
||||||
|
{
|
||||||
|
private float _lastCharge = 0.0f;
|
||||||
|
private float _lastFill = 0.0f;
|
||||||
|
private HitscanWeaponComponent _hitscan;
|
||||||
|
|
||||||
|
public WaitForHitscanChargeOperator(IEntity entity)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out HitscanWeaponComponent hitscanWeaponComponent))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
_hitscan = hitscanWeaponComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (_hitscan.CapacitorComponent.Capacity - _hitscan.CapacitorComponent.Charge < 0.01f)
|
||||||
|
{
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're not charging then just stop
|
||||||
|
_lastFill = _hitscan.CapacitorComponent.Charge - _lastCharge;
|
||||||
|
_lastCharge = _hitscan.CapacitorComponent.Charge;
|
||||||
|
|
||||||
|
if (_lastFill == 0.0f)
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
using Content.Server.GameObjects;
|
||||||
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Melee;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Combat
|
||||||
|
{
|
||||||
|
public class SwingMeleeWeaponOperator : AiOperator
|
||||||
|
{
|
||||||
|
private float _burstTime;
|
||||||
|
private float _elapsedTime;
|
||||||
|
|
||||||
|
private readonly IEntity _owner;
|
||||||
|
private readonly IEntity _target;
|
||||||
|
|
||||||
|
public SwingMeleeWeaponOperator(IEntity owner, IEntity target, float burstTime = 1.0f)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
_target = target;
|
||||||
|
_burstTime = burstTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool TryStartup()
|
||||||
|
{
|
||||||
|
if (!base.TryStartup())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_owner.TryGetComponent(out CombatModeComponent combatModeComponent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!combatModeComponent.IsInCombatMode)
|
||||||
|
{
|
||||||
|
combatModeComponent.IsInCombatMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (_burstTime <= _elapsedTime)
|
||||||
|
{
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_owner.TryGetComponent(out HandsComponent hands) || hands.GetActiveHand == null)
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var meleeWeapon = hands.GetActiveHand.Owner;
|
||||||
|
meleeWeapon.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent);
|
||||||
|
|
||||||
|
if ((_target.Transform.GridPosition.Position - _owner.Transform.GridPosition.Position).Length >
|
||||||
|
meleeWeaponComponent.Range)
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var interactionSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<InteractionSystem>();
|
||||||
|
|
||||||
|
interactionSystem.UseItemInHand(_owner, _target.Transform.GridPosition, _target.Uid);
|
||||||
|
_elapsedTime += frameTime;
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
24
Content.Server/AI/Operators/Generic/WaitOperator.cs
Normal file
24
Content.Server/AI/Operators/Generic/WaitOperator.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace Content.Server.AI.Operators.Generic
|
||||||
|
{
|
||||||
|
public 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using Content.Server.AI.Utility;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.GameObjects.Components;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Server.Utility;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
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 IEntity _owner;
|
||||||
|
private IEntity _target;
|
||||||
|
|
||||||
|
public CloseLastStorageOperator(IEntity owner)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool TryStartup()
|
||||||
|
{
|
||||||
|
if (!base.TryStartup())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var blackboard = UtilityAiHelpers.GetBlackboard(_owner);
|
||||||
|
|
||||||
|
if (blackboard == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_target = blackboard.GetState<LastOpenedStorageState>().GetValue();
|
||||||
|
|
||||||
|
return _target != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown(Outcome outcome)
|
||||||
|
{
|
||||||
|
base.Shutdown(outcome);
|
||||||
|
var blackboard = UtilityAiHelpers.GetBlackboard(_owner);
|
||||||
|
|
||||||
|
blackboard?.GetState<LastOpenedStorageState>().SetValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_target.TryGetComponent(out EntityStorageComponent storageComponent) ||
|
||||||
|
storageComponent.IsWeldedShut)
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storageComponent.Open)
|
||||||
|
{
|
||||||
|
var activateArgs = new ActivateEventArgs {User = _owner, Target = _target};
|
||||||
|
storageComponent.Activate(activateArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Content.Server/AI/Operators/Inventory/DropEntityOperator.cs
Normal file
32
Content.Server/AI/Operators/Inventory/DropEntityOperator.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Content.Server.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Inventory
|
||||||
|
{
|
||||||
|
public class DropEntityOperator : AiOperator
|
||||||
|
{
|
||||||
|
private readonly IEntity _owner;
|
||||||
|
private readonly IEntity _entity;
|
||||||
|
public DropEntityOperator(IEntity owner, IEntity 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)
|
||||||
|
{
|
||||||
|
if (!_owner.TryGetComponent(out HandsComponent handsComponent))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handsComponent.Drop(_entity) ? Outcome.Success : Outcome.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Content.Server.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Inventory
|
||||||
|
{
|
||||||
|
public class DropHandItemsOperator : AiOperator
|
||||||
|
{
|
||||||
|
private readonly IEntity _owner;
|
||||||
|
|
||||||
|
public DropHandItemsOperator(IEntity owner)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (!_owner.TryGetComponent(out HandsComponent handsComponent))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in handsComponent.GetAllHeldItems())
|
||||||
|
{
|
||||||
|
handsComponent.Drop(item.Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs
Normal file
38
Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using Content.Server.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Inventory
|
||||||
|
{
|
||||||
|
public sealed class EquipEntityOperator : AiOperator
|
||||||
|
{
|
||||||
|
private readonly IEntity _owner;
|
||||||
|
private readonly IEntity _entity;
|
||||||
|
public EquipEntityOperator(IEntity owner, IEntity entity)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
_entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (!_owner.TryGetComponent(out HandsComponent handsComponent))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
// TODO: If in clothing then click on it
|
||||||
|
foreach (var hand in handsComponent.ActivePriorityEnumerable())
|
||||||
|
{
|
||||||
|
if (handsComponent.GetHand(hand)?.Owner == _entity)
|
||||||
|
{
|
||||||
|
handsComponent.ActiveIndex = hand;
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Get free hand count; if no hands free then fail right here
|
||||||
|
|
||||||
|
// TODO: Go through inventory
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Server.Utility;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Inventory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A Generic interacter; if you need to check stuff then make your own
|
||||||
|
/// </summary>
|
||||||
|
public class InteractWithEntityOperator : AiOperator
|
||||||
|
{
|
||||||
|
private readonly IEntity _owner;
|
||||||
|
private readonly IEntity _useTarget;
|
||||||
|
|
||||||
|
public InteractWithEntityOperator(IEntity owner, IEntity useTarget)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
_useTarget = useTarget;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (_useTarget.Transform.GridID != _owner.Transform.GridID)
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!InteractionChecks.InRangeUnobstructed(_owner, _useTarget.Transform.MapPosition))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_owner.TryGetComponent(out CombatModeComponent combatModeComponent))
|
||||||
|
{
|
||||||
|
combatModeComponent.IsInCombatMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click on da thing
|
||||||
|
var interactionSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<InteractionSystem>();
|
||||||
|
interactionSystem.UseItemInHand(_owner, _useTarget.Transform.GridPosition, _useTarget.Uid);
|
||||||
|
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs
Normal file
55
Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using Content.Server.AI.Utility;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.GameObjects.Components;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Server.Utility;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
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 IEntity _owner;
|
||||||
|
private readonly IEntity _target;
|
||||||
|
|
||||||
|
public OpenStorageOperator(IEntity owner, IEntity target)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
_target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ContainerHelpers.TryGetContainer(_target, out var container))
|
||||||
|
{
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!container.Owner.TryGetComponent(out EntityStorageComponent storageComponent) ||
|
||||||
|
storageComponent.IsWeldedShut)
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!storageComponent.Open)
|
||||||
|
{
|
||||||
|
var activateArgs = new ActivateEventArgs {User = _owner, Target = _target};
|
||||||
|
storageComponent.Activate(activateArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
var blackboard = UtilityAiHelpers.GetBlackboard(_owner);
|
||||||
|
blackboard?.GetState<LastOpenedStorageState>().SetValue(container.Owner);
|
||||||
|
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Content.Server.GameObjects;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Server.Utility;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Inventory
|
||||||
|
{
|
||||||
|
public class PickupEntityOperator : AiOperator
|
||||||
|
{
|
||||||
|
// Input variables
|
||||||
|
private readonly IEntity _owner;
|
||||||
|
private readonly IEntity _target;
|
||||||
|
|
||||||
|
public PickupEntityOperator(IEntity owner, IEntity target)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
_target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: When I spawn new entities they seem to duplicate clothing or something?
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (_target == null ||
|
||||||
|
_target.Deleted ||
|
||||||
|
!_target.HasComponent<ItemComponent>() ||
|
||||||
|
ContainerHelpers.IsInContainer(_target) ||
|
||||||
|
!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_owner.TryGetComponent(out HandsComponent handsComponent))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyHands = false;
|
||||||
|
|
||||||
|
foreach (var hand in handsComponent.ActivePriorityEnumerable())
|
||||||
|
{
|
||||||
|
if (handsComponent.GetHand(hand) == null)
|
||||||
|
{
|
||||||
|
if (handsComponent.ActiveIndex != hand)
|
||||||
|
{
|
||||||
|
handsComponent.ActiveIndex = hand;
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyHands = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emptyHands)
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var interactionSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<InteractionSystem>();
|
||||||
|
interactionSystem.Interaction(_owner, _target);
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using Content.Server.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Inventory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Will find the item in storage, put it in an active hand, then use it
|
||||||
|
/// </summary>
|
||||||
|
public class UseItemInHandsOperator : AiOperator
|
||||||
|
{
|
||||||
|
private readonly IEntity _owner;
|
||||||
|
private readonly IEntity _target;
|
||||||
|
|
||||||
|
public UseItemInHandsOperator(IEntity owner, IEntity target)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
_target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (_target == null)
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Also have this check storage a la backpack etc.
|
||||||
|
if (!_owner.TryGetComponent(out HandsComponent handsComponent))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_target.TryGetComponent(out ItemComponent itemComponent))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var slot in handsComponent.ActivePriorityEnumerable())
|
||||||
|
{
|
||||||
|
if (handsComponent.GetHand(slot) != itemComponent) continue;
|
||||||
|
handsComponent.ActiveIndex = slot;
|
||||||
|
handsComponent.ActivateItem();
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
295
Content.Server/AI/Operators/Movement/BaseMover.cs
Normal file
295
Content.Server/AI/Operators/Movement/BaseMover.cs
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Timer = Robust.Shared.Timers.Timer;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Movement
|
||||||
|
{
|
||||||
|
public abstract class BaseMover : AiOperator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked every time we move across a tile
|
||||||
|
/// </summary>
|
||||||
|
public event Action MovedATile;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How close the pathfinder needs to get before returning a route
|
||||||
|
/// Set at 1.42f just in case there's rounding and diagonally adjacent tiles aren't counted.
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public float PathfindingProximity { get; set; } = 1.42f;
|
||||||
|
protected Queue<TileRef> Route = new Queue<TileRef>();
|
||||||
|
/// <summary>
|
||||||
|
/// The final spot we're trying to get to
|
||||||
|
/// </summary>
|
||||||
|
protected GridCoordinates TargetGrid;
|
||||||
|
/// <summary>
|
||||||
|
/// As the pathfinder is tilebased we'll move to each tile's grid.
|
||||||
|
/// </summary>
|
||||||
|
protected GridCoordinates NextGrid;
|
||||||
|
private const float TileTolerance = 0.2f;
|
||||||
|
|
||||||
|
// Stuck checkers
|
||||||
|
/// <summary>
|
||||||
|
/// How long we're stuck in general before trying to unstuck
|
||||||
|
/// </summary>
|
||||||
|
private float _stuckTimerRemaining = 0.5f;
|
||||||
|
private GridCoordinates _ourLastPosition;
|
||||||
|
|
||||||
|
// Anti-stuck measures. See the AntiStuck() method for more details
|
||||||
|
private bool _tryingAntiStuck;
|
||||||
|
public bool IsStuck;
|
||||||
|
private AntiStuckMethod _antiStuckMethod = AntiStuckMethod.Angle;
|
||||||
|
private Angle _addedAngle = Angle.Zero;
|
||||||
|
public event Action Stuck;
|
||||||
|
private int _antiStuckAttempts = 0;
|
||||||
|
|
||||||
|
private CancellationTokenSource _routeCancelToken;
|
||||||
|
protected Job<Queue<TileRef>> RouteJob;
|
||||||
|
private IMapManager _mapManager;
|
||||||
|
private PathfindingSystem _pathfinder;
|
||||||
|
private AiControllerComponent _controller;
|
||||||
|
|
||||||
|
// Input
|
||||||
|
protected IEntity Owner;
|
||||||
|
|
||||||
|
protected void Setup(IEntity owner)
|
||||||
|
{
|
||||||
|
Owner = owner;
|
||||||
|
_mapManager = IoCManager.Resolve<IMapManager>();
|
||||||
|
_pathfinder = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<PathfindingSystem>();
|
||||||
|
if (!Owner.TryGetComponent(out AiControllerComponent controllerComponent))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
_controller = controllerComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void NextTile()
|
||||||
|
{
|
||||||
|
MovedATile?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will move the AI towards the next position
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if movement to be done</returns>
|
||||||
|
protected bool TryMove()
|
||||||
|
{
|
||||||
|
// Use collidable just so we don't get stuck on corners as much
|
||||||
|
// var targetDiff = NextGrid.Position - _ownerCollidable.WorldAABB.Center;
|
||||||
|
var targetDiff = NextGrid.Position - Owner.Transform.GridPosition.Position;
|
||||||
|
|
||||||
|
// Check distance
|
||||||
|
if (targetDiff.Length < TileTolerance)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Move towards it
|
||||||
|
if (_controller == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_controller.VelocityDir = _addedAngle.RotateVec(targetDiff).Normalized;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will try and get around obstacles if stuck
|
||||||
|
/// </summary>
|
||||||
|
protected void AntiStuck(float frameTime)
|
||||||
|
{
|
||||||
|
// TODO: More work because these are sketchy af
|
||||||
|
// TODO: Check if a wall was spawned in front of us and then immediately dump route if it was
|
||||||
|
|
||||||
|
// First check if we're still in a stuck state from last frame
|
||||||
|
if (IsStuck && !_tryingAntiStuck)
|
||||||
|
{
|
||||||
|
switch (_antiStuckMethod)
|
||||||
|
{
|
||||||
|
case AntiStuckMethod.None:
|
||||||
|
break;
|
||||||
|
case AntiStuckMethod.Jiggle:
|
||||||
|
var randomRange = IoCManager.Resolve<IRobustRandom>().Next(0, 359);
|
||||||
|
var angle = Angle.FromDegrees(randomRange);
|
||||||
|
Owner.TryGetComponent(out AiControllerComponent mover);
|
||||||
|
mover.VelocityDir = angle.ToVec().Normalized;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case AntiStuckMethod.PhaseThrough:
|
||||||
|
if (Owner.TryGetComponent(out CollidableComponent collidableComponent))
|
||||||
|
{
|
||||||
|
// TODO Fix this because they are yeeting themselves when they charge
|
||||||
|
// TODO: If something updates this this will fuck it
|
||||||
|
collidableComponent.CanCollide = false;
|
||||||
|
|
||||||
|
Timer.Spawn(100, () =>
|
||||||
|
{
|
||||||
|
if (!collidableComponent.CanCollide)
|
||||||
|
{
|
||||||
|
collidableComponent.CanCollide = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AntiStuckMethod.Teleport:
|
||||||
|
Owner.Transform.DetachParent();
|
||||||
|
Owner.Transform.GridPosition = NextGrid;
|
||||||
|
break;
|
||||||
|
case AntiStuckMethod.ReRoute:
|
||||||
|
GetRoute();
|
||||||
|
break;
|
||||||
|
case AntiStuckMethod.Angle:
|
||||||
|
var random = IoCManager.Resolve<IRobustRandom>();
|
||||||
|
_addedAngle = new Angle(random.Next(-60, 60));
|
||||||
|
IsStuck = false;
|
||||||
|
Timer.Spawn(100, () =>
|
||||||
|
{
|
||||||
|
_addedAngle = Angle.Zero;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_stuckTimerRemaining -= frameTime;
|
||||||
|
|
||||||
|
// Stuck check cooldown
|
||||||
|
if (_stuckTimerRemaining > 0.0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_tryingAntiStuck = false;
|
||||||
|
_stuckTimerRemaining = 0.5f;
|
||||||
|
|
||||||
|
// Are we actually stuck
|
||||||
|
if ((_ourLastPosition.Position - Owner.Transform.GridPosition.Position).Length < TileTolerance)
|
||||||
|
{
|
||||||
|
_antiStuckAttempts++;
|
||||||
|
|
||||||
|
// Maybe it's just 1 tile that's borked so try next 1?
|
||||||
|
if (_antiStuckAttempts >= 2 && _antiStuckAttempts < 5 && Route.Count > 1)
|
||||||
|
{
|
||||||
|
var nextTile = Route.Dequeue();
|
||||||
|
NextGrid = _mapManager.GetGrid(nextTile.GridIndex).GridTileToLocal(nextTile.GridIndices);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_antiStuckAttempts >= 5 || Route.Count == 0)
|
||||||
|
{
|
||||||
|
Logger.DebugS("ai", $"{Owner} is stuck at {Owner.Transform.GridPosition}, trying new route");
|
||||||
|
_antiStuckAttempts = 0;
|
||||||
|
IsStuck = false;
|
||||||
|
_ourLastPosition = Owner.Transform.GridPosition;
|
||||||
|
GetRoute();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Stuck?.Invoke();
|
||||||
|
IsStuck = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsStuck = false;
|
||||||
|
|
||||||
|
_ourLastPosition = Owner.Transform.GridPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tells us we don't need to keep moving and resets everything
|
||||||
|
/// </summary>
|
||||||
|
public void HaveArrived()
|
||||||
|
{
|
||||||
|
_routeCancelToken?.Cancel(); // oh thank god no more pathfinding
|
||||||
|
Route.Clear();
|
||||||
|
if (_controller == null) return;
|
||||||
|
_controller.VelocityDir = Vector2.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void GetRoute()
|
||||||
|
{
|
||||||
|
_routeCancelToken?.Cancel();
|
||||||
|
_routeCancelToken = new CancellationTokenSource();
|
||||||
|
Route.Clear();
|
||||||
|
|
||||||
|
int collisionMask;
|
||||||
|
if (!Owner.TryGetComponent(out CollidableComponent collidableComponent))
|
||||||
|
{
|
||||||
|
collisionMask = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
collisionMask = collidableComponent.CollisionMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startGrid = _mapManager.GetGrid(Owner.Transform.GridID).GetTileRef(Owner.Transform.GridPosition);
|
||||||
|
var endGrid = _mapManager.GetGrid(TargetGrid.GridID).GetTileRef(TargetGrid);;
|
||||||
|
// _routeCancelToken = new CancellationTokenSource();
|
||||||
|
|
||||||
|
RouteJob = _pathfinder.RequestPath(new PathfindingArgs(
|
||||||
|
Owner.Uid,
|
||||||
|
collisionMask,
|
||||||
|
startGrid,
|
||||||
|
endGrid,
|
||||||
|
PathfindingProximity
|
||||||
|
), _routeCancelToken.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ReceivedRoute()
|
||||||
|
{
|
||||||
|
Route = RouteJob.Result;
|
||||||
|
RouteJob = null;
|
||||||
|
|
||||||
|
if (Route == null)
|
||||||
|
{
|
||||||
|
Route = new Queue<TileRef>();
|
||||||
|
// Couldn't find a route to target
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because the entity may be half on 2 tiles we'll just cut out the first tile.
|
||||||
|
// This may not be the best solution but sometimes if the AI is chasing for example it will
|
||||||
|
// stutter backwards to the first tile again.
|
||||||
|
Route.Dequeue();
|
||||||
|
|
||||||
|
var nextTile = Route.Peek();
|
||||||
|
NextGrid = _mapManager.GetGrid(nextTile.GridIndex).GridTileToLocal(nextTile.GridIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (RouteJob != null && RouteJob.Status == JobStatus.Finished)
|
||||||
|
{
|
||||||
|
ReceivedRoute();
|
||||||
|
}
|
||||||
|
|
||||||
|
return !ActionBlockerSystem.CanMove(Owner) ? Outcome.Failed : Outcome.Continuing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AntiStuckMethod
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
ReRoute,
|
||||||
|
Jiggle, // Just pick a random direction for a bit and hope for the best
|
||||||
|
Teleport, // The Half-Life 2 method
|
||||||
|
PhaseThrough, // Just makes it non-collidable
|
||||||
|
Angle, // Add a different angle for a bit
|
||||||
|
}
|
||||||
|
}
|
||||||
142
Content.Server/AI/Operators/Movement/MoveToEntityOperator.cs
Normal file
142
Content.Server/AI/Operators/Movement/MoveToEntityOperator.cs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Movement
|
||||||
|
{
|
||||||
|
public sealed class MoveToEntityOperator : BaseMover
|
||||||
|
{
|
||||||
|
// Instance
|
||||||
|
private GridCoordinates _lastTargetPosition;
|
||||||
|
private IMapManager _mapManager;
|
||||||
|
|
||||||
|
// Input
|
||||||
|
public IEntity Target { get; }
|
||||||
|
public float DesiredRange { get; set; }
|
||||||
|
|
||||||
|
public MoveToEntityOperator(IEntity owner, IEntity target, float desiredRange = 1.5f)
|
||||||
|
{
|
||||||
|
Setup(owner);
|
||||||
|
Target = target;
|
||||||
|
_mapManager = IoCManager.Resolve<IMapManager>();
|
||||||
|
DesiredRange = desiredRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
var baseOutcome = base.Execute(frameTime);
|
||||||
|
// TODO: Given this is probably the most common operator whatever speed boosts you can do here will be gucci
|
||||||
|
// Could also look at running it every other tick.
|
||||||
|
|
||||||
|
if (baseOutcome == Outcome.Failed ||
|
||||||
|
Target == null ||
|
||||||
|
Target.Deleted ||
|
||||||
|
Target.Transform.GridID != Owner.Transform.GridID)
|
||||||
|
{
|
||||||
|
HaveArrived();
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RouteJob != null)
|
||||||
|
{
|
||||||
|
if (RouteJob.Status != JobStatus.Finished)
|
||||||
|
{
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
ReceivedRoute();
|
||||||
|
return Route.Count == 0 ? Outcome.Failed : Outcome.Continuing;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetRange = (Target.Transform.GridPosition.Position - Owner.Transform.GridPosition.Position).Length;
|
||||||
|
|
||||||
|
// If they move near us
|
||||||
|
if (targetRange <= DesiredRange)
|
||||||
|
{
|
||||||
|
HaveArrived();
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the target's moved we may need to re-route.
|
||||||
|
// First we'll check if they're near another tile on the existing route and if so
|
||||||
|
// we can trim up until that point.
|
||||||
|
if (_lastTargetPosition != default &&
|
||||||
|
(Target.Transform.GridPosition.Position - _lastTargetPosition.Position).Length > 1.5f)
|
||||||
|
{
|
||||||
|
var success = false;
|
||||||
|
// Technically it should be Route.Count - 1 but if the route's empty it'll throw
|
||||||
|
var newRoute = new Queue<TileRef>(Route.Count);
|
||||||
|
|
||||||
|
for (var i = 0; i < Route.Count; i++)
|
||||||
|
{
|
||||||
|
var tile = Route.Dequeue();
|
||||||
|
newRoute.Enqueue(tile);
|
||||||
|
var tileGrid = _mapManager.GetGrid(tile.GridIndex).GridTileToLocal(tile.GridIndices);
|
||||||
|
|
||||||
|
// Don't use DesiredRange here or above in case it's smaller than a tile;
|
||||||
|
// when we get close we run straight at them anyway so it shooouullddd be okay...
|
||||||
|
if ((Target.Transform.GridPosition.Position - tileGrid.Position).Length < 1.5f)
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
Route = newRoute;
|
||||||
|
_lastTargetPosition = Target.Transform.GridPosition;
|
||||||
|
TargetGrid = Target.Transform.GridPosition;
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastTargetPosition = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If they move too far or no route
|
||||||
|
if (_lastTargetPosition == default)
|
||||||
|
{
|
||||||
|
// If they're further we could try pathfinding from the furthest tile potentially?
|
||||||
|
_lastTargetPosition = Target.Transform.GridPosition;
|
||||||
|
TargetGrid = Target.Transform.GridPosition;
|
||||||
|
GetRoute();
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
|
||||||
|
AntiStuck(frameTime);
|
||||||
|
|
||||||
|
if (IsStuck)
|
||||||
|
{
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryMove())
|
||||||
|
{
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're really close just try bee-lining it?
|
||||||
|
if (Route.Count == 0)
|
||||||
|
{
|
||||||
|
if (targetRange < 1.9f)
|
||||||
|
{
|
||||||
|
// TODO: If they have a phat hitbox they could block us
|
||||||
|
NextGrid = TargetGrid;
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
if (targetRange > DesiredRange)
|
||||||
|
{
|
||||||
|
HaveArrived();
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextTile = Route.Dequeue();
|
||||||
|
NextTile();
|
||||||
|
NextGrid = _mapManager.GetGrid(nextTile.GridIndex).GridTileToLocal(nextTile.GridIndices);
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
94
Content.Server/AI/Operators/Movement/MoveToGridOperator.cs
Normal file
94
Content.Server/AI/Operators/Movement/MoveToGridOperator.cs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Movement
|
||||||
|
{
|
||||||
|
public class MoveToGridOperator : BaseMover
|
||||||
|
{
|
||||||
|
private IMapManager _mapManager;
|
||||||
|
private float _desiredRange;
|
||||||
|
|
||||||
|
public MoveToGridOperator(
|
||||||
|
IEntity owner,
|
||||||
|
GridCoordinates gridPosition,
|
||||||
|
float desiredRange = 1.5f)
|
||||||
|
{
|
||||||
|
Setup(owner);
|
||||||
|
TargetGrid = gridPosition;
|
||||||
|
_mapManager = IoCManager.Resolve<IMapManager>();
|
||||||
|
PathfindingProximity = 0.2f; // Accept no substitutes
|
||||||
|
_desiredRange = desiredRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateTarget(GridCoordinates newTarget)
|
||||||
|
{
|
||||||
|
TargetGrid = newTarget;
|
||||||
|
HaveArrived();
|
||||||
|
GetRoute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
var baseOutcome = base.Execute(frameTime);
|
||||||
|
|
||||||
|
if (baseOutcome == Outcome.Failed ||
|
||||||
|
TargetGrid.GridID != Owner.Transform.GridID)
|
||||||
|
{
|
||||||
|
HaveArrived();
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RouteJob != null)
|
||||||
|
{
|
||||||
|
if (RouteJob.Status != JobStatus.Finished)
|
||||||
|
{
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
ReceivedRoute();
|
||||||
|
return Route.Count == 0 ? Outcome.Failed : Outcome.Continuing;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetRange = (TargetGrid.Position - Owner.Transform.GridPosition.Position).Length;
|
||||||
|
|
||||||
|
// We there
|
||||||
|
if (targetRange <= _desiredRange)
|
||||||
|
{
|
||||||
|
HaveArrived();
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No route
|
||||||
|
if (Route.Count == 0 && RouteJob == null)
|
||||||
|
{
|
||||||
|
GetRoute();
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
|
||||||
|
AntiStuck(frameTime);
|
||||||
|
|
||||||
|
if (IsStuck)
|
||||||
|
{
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryMove())
|
||||||
|
{
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Route.Count == 0 && targetRange > 1.5f)
|
||||||
|
{
|
||||||
|
HaveArrived();
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextTile = Route.Dequeue();
|
||||||
|
NextTile();
|
||||||
|
NextGrid = _mapManager.GetGrid(nextTile.GridIndex).GridTileToLocal(nextTile.GridIndices);
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators.Inventory;
|
||||||
|
using Content.Server.AI.Operators.Movement;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Sequences
|
||||||
|
{
|
||||||
|
public class GoPickupEntitySequence : SequenceOperator
|
||||||
|
{
|
||||||
|
public GoPickupEntitySequence(IEntity owner, IEntity target)
|
||||||
|
{
|
||||||
|
Sequence = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new MoveToEntityOperator(owner, target),
|
||||||
|
new OpenStorageOperator(owner, target),
|
||||||
|
new PickupEntityOperator(owner, target),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
Content.Server/AI/Operators/Sequences/SequenceOperator.cs
Normal file
44
Content.Server/AI/Operators/Sequences/SequenceOperator.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (Sequence.Count == 0)
|
||||||
|
{
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
var op = Sequence.Peek();
|
||||||
|
op.TryStartup();
|
||||||
|
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,70 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.Interfaces.Chat;
|
|
||||||
using Robust.Server.AI;
|
|
||||||
using Robust.Shared.Interfaces.Timing;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using CannyFastMath;
|
|
||||||
using Math = CannyFastMath.Math;
|
|
||||||
using MathF = CannyFastMath.MathF;
|
|
||||||
|
|
||||||
namespace Content.Server.AI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Designed for a a stationary entity that regularly advertises things (vending machine).
|
|
||||||
/// </summary>
|
|
||||||
[AiLogicProcessor("StaticBarker")]
|
|
||||||
class StaticBarkerProcessor : AiLogicProcessor
|
|
||||||
{
|
|
||||||
#pragma warning disable 649
|
|
||||||
[Dependency] private readonly IGameTiming _timeMan;
|
|
||||||
[Dependency] private readonly IChatManager _chatMan;
|
|
||||||
#pragma warning restore 649
|
|
||||||
|
|
||||||
private static readonly TimeSpan MinimumDelay = TimeSpan.FromSeconds(15);
|
|
||||||
private TimeSpan _nextBark;
|
|
||||||
|
|
||||||
|
|
||||||
private static List<string> slogans = new List<string>
|
|
||||||
{
|
|
||||||
"Come try my great products today!",
|
|
||||||
"More value for the way you live.",
|
|
||||||
"Quality you'd expect at prices you wouldn't.",
|
|
||||||
"The right stuff. The right price.",
|
|
||||||
};
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
if(_timeMan.CurTime < _nextBark)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var rngState = GenSeed();
|
|
||||||
_nextBark = _timeMan.CurTime + MinimumDelay + TimeSpan.FromSeconds(Random01(ref rngState) * 10);
|
|
||||||
|
|
||||||
var pick = (int)Math.Round(Random01(ref rngState) * (slogans.Count - 1));
|
|
||||||
_chatMan.EntitySay(SelfEntity, slogans[pick]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private uint GenSeed()
|
|
||||||
{
|
|
||||||
return RotateRight((uint)_timeMan.CurTick.GetHashCode(), 11) ^ (uint)SelfEntity.Uid.GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
private uint RotateRight(uint n, int s)
|
|
||||||
{
|
|
||||||
return (n << (32 - s)) | (n >> s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private float Random01(ref uint state)
|
|
||||||
{
|
|
||||||
DebugTools.Assert(state != 0);
|
|
||||||
|
|
||||||
//xorshift32
|
|
||||||
state ^= state << 13;
|
|
||||||
state ^= state >> 17;
|
|
||||||
state ^= state << 5;
|
|
||||||
return state / (float)uint.MaxValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Clothing.Gloves
|
||||||
|
{
|
||||||
|
public sealed class EquipGloves : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public EquipGloves(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new EquipEntityOperator(Owner, _entity),
|
||||||
|
new UseItemInHandsOperator(Owner, _entity),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new ClothingInSlotCon(EquipmentSlotDefines.Slots.GLOVES,
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Content.Server.AI.Operators.Sequences;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Clothing.Gloves
|
||||||
|
{
|
||||||
|
public sealed class PickUpGloves : UtilityAction
|
||||||
|
{
|
||||||
|
private readonly IEntity _entity;
|
||||||
|
|
||||||
|
public PickUpGloves(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new ClothingInSlotCon(EquipmentSlotDefines.Slots.GLOVES,
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new ClothingInInventoryCon(EquipmentSlotDefines.SlotFlags.GLOVES,
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
47
Content.Server/AI/Utility/Actions/Clothing/Head/EquipHead.cs
Normal file
47
Content.Server/AI/Utility/Actions/Clothing/Head/EquipHead.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Clothing.Head
|
||||||
|
{
|
||||||
|
public sealed class EquipHead : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public EquipHead(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new EquipEntityOperator(Owner, _entity),
|
||||||
|
new UseItemInHandsOperator(Owner, _entity),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new ClothingInSlotCon(EquipmentSlotDefines.Slots.HEAD,
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Content.Server.AI.Operators.Sequences;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Clothing.Head
|
||||||
|
{
|
||||||
|
public sealed class PickUpHead : UtilityAction
|
||||||
|
{
|
||||||
|
private readonly IEntity _entity;
|
||||||
|
|
||||||
|
public PickUpHead(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new ClothingInSlotCon(EquipmentSlotDefines.Slots.HEAD,
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new ClothingInInventoryCon(EquipmentSlotDefines.SlotFlags.HEAD,
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing
|
||||||
|
{
|
||||||
|
public sealed class EquipOuterClothing : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public EquipOuterClothing(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new EquipEntityOperator(Owner, _entity),
|
||||||
|
new UseItemInHandsOperator(Owner, _entity),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new ClothingInSlotCon(EquipmentSlotDefines.Slots.OUTERCLOTHING,
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Content.Server.AI.Operators.Sequences;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing
|
||||||
|
{
|
||||||
|
public sealed class PickUpOuterClothing : UtilityAction
|
||||||
|
{
|
||||||
|
private readonly IEntity _entity;
|
||||||
|
|
||||||
|
public PickUpOuterClothing(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new ClothingInSlotCon(EquipmentSlotDefines.Slots.OUTERCLOTHING,
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new ClothingInInventoryCon(EquipmentSlotDefines.SlotFlags.OUTERCLOTHING,
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Clothing.Shoes
|
||||||
|
{
|
||||||
|
public sealed class EquipShoes : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public EquipShoes(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new EquipEntityOperator(Owner, _entity),
|
||||||
|
new UseItemInHandsOperator(Owner, _entity),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new ClothingInSlotCon(EquipmentSlotDefines.Slots.SHOES,
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Content.Server.AI.Operators.Sequences;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Clothing.Shoes
|
||||||
|
{
|
||||||
|
public sealed class PickUpShoes : UtilityAction
|
||||||
|
{
|
||||||
|
private readonly IEntity _entity;
|
||||||
|
|
||||||
|
public PickUpShoes(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new ClothingInSlotCon(EquipmentSlotDefines.Slots.SHOES,
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new ClothingInInventoryCon(EquipmentSlotDefines.SlotFlags.SHOES,
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
55
Content.Server/AI/Utility/Actions/Combat/Melee/EquipMelee.cs
Normal file
55
Content.Server/AI/Utility/Actions/Combat/Melee/EquipMelee.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
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.Combat.Ranged;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.AI.WorldState.States.Combat;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
||||||
|
{
|
||||||
|
public sealed class EquipMelee : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public EquipMelee(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new EquipEntityOperator(Owner, _entity)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new MeleeWeaponEquippedCon(
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
// We'll prioritise equipping ranged weapons; If we try and score this then it'll just keep swapping between ranged and melee
|
||||||
|
new RangedWeaponEquippedCon(
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new MeleeWeaponSpeedCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||||
|
new MeleeWeaponDamageCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Combat;
|
||||||
|
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.Movement;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
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.GameObjects.Components.Weapon.Melee;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
||||||
|
{
|
||||||
|
public sealed class MeleeAttackEntity : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public MeleeAttackEntity(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
var moveOperator = new MoveToEntityOperator(Owner, _entity);
|
||||||
|
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||||
|
if (equipped != null && equipped.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent))
|
||||||
|
{
|
||||||
|
moveOperator.DesiredRange = meleeWeaponComponent.Range - 0.01f;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
moveOperator,
|
||||||
|
new SwingMeleeWeaponOperator(Owner, _entity),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||||
|
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||||
|
context.GetState<WeaponEntityState>().SetValue(equipped);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
// Check if we have a weapon; easy-out
|
||||||
|
new MeleeWeaponEquippedCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
// Don't attack a dead target
|
||||||
|
new TargetIsDeadCon(
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
// Deprioritise a target in crit
|
||||||
|
new TargetIsCritCon(
|
||||||
|
new QuadraticCurve(-0.8f, 1.0f, 1.0f, 0.0f)),
|
||||||
|
// Somewhat prioritise distance
|
||||||
|
new DistanceCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||||
|
// Prefer weaker targets
|
||||||
|
new TargetHealthCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.4f, 0.0f, -0.02f)),
|
||||||
|
new MeleeWeaponSpeedCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||||
|
new MeleeWeaponDamageCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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.Hands;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Movement;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.AI.WorldState.States.Combat;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
||||||
|
{
|
||||||
|
public sealed class PickUpMeleeWeapon : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public PickUpMeleeWeapon(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new TargetAccessibleCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new FreeHandCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new HasMeleeWeaponCon(
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new DistanceCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||||
|
new MeleeWeaponDamageCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
||||||
|
new MeleeWeaponSpeedCon(
|
||||||
|
new QuadraticCurve(-1.0f, 0.5f, 1.0f, 0.0f)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Combat.Ranged;
|
||||||
|
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.Ranged.Ballistic;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Movement;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.Utils;
|
||||||
|
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 Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
||||||
|
{
|
||||||
|
public sealed class BallisticAttackEntity : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
private MoveToEntityOperator _moveOperator;
|
||||||
|
|
||||||
|
public BallisticAttackEntity(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
if (_moveOperator != null)
|
||||||
|
{
|
||||||
|
_moveOperator.MovedATile -= InLos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
_moveOperator = new MoveToEntityOperator(Owner, _entity);
|
||||||
|
_moveOperator.MovedATile += InLos;
|
||||||
|
|
||||||
|
// TODO: Accuracy in blackboard
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
_moveOperator,
|
||||||
|
new ShootAtEntityOperator(Owner, _entity, 0.7f),
|
||||||
|
});
|
||||||
|
|
||||||
|
// We will do a quick check now to see if we even need to move which also saves a pathfind
|
||||||
|
InLos();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||||
|
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||||
|
context.GetState<WeaponEntityState>().SetValue(equipped);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
// Check if we have a weapon; easy-out
|
||||||
|
new BallisticWeaponEquippedCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new BallisticAmmoCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.15f, 0.0f, 0.0f)),
|
||||||
|
// Don't attack a dead target
|
||||||
|
new TargetIsDeadCon(
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
// Deprioritise a target in crit
|
||||||
|
new TargetIsCritCon(
|
||||||
|
new QuadraticCurve(-0.8f, 1.0f, 1.0f, 0.0f)),
|
||||||
|
// Somewhat prioritise distance
|
||||||
|
new DistanceCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.07f, 0.0f)),
|
||||||
|
// Prefer weaker targets
|
||||||
|
new TargetHealthCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.4f, 0.0f, -0.02f)),
|
||||||
|
};
|
||||||
|
|
||||||
|
private void InLos()
|
||||||
|
{
|
||||||
|
// This should only be called if the movement operator is the current one;
|
||||||
|
// if that turns out not to be the case we can just add a check here.
|
||||||
|
if (Visibility.InLineOfSight(Owner, _entity))
|
||||||
|
{
|
||||||
|
_moveOperator.HaveArrived();
|
||||||
|
var mover = ActionOperators.Dequeue();
|
||||||
|
mover.Shutdown(Outcome.Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.AI.WorldState.States.Combat;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
||||||
|
{
|
||||||
|
public sealed class DropEmptyBallistic : UtilityAction
|
||||||
|
{
|
||||||
|
public sealed override float Bonus => 20.0f;
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public DropEmptyBallistic(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new EquipEntityOperator(Owner, _entity),
|
||||||
|
new DropEntityOperator(Owner, _entity)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new TargetInOurInventoryCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
// Need to put in hands to drop
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
// Drop that sucker
|
||||||
|
new BallisticAmmoCon(
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
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.Combat.Ranged;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Combat;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
||||||
|
{
|
||||||
|
public sealed class EquipBallistic : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public EquipBallistic(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new EquipEntityOperator(Owner, _entity)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new EquippedBallisticCon(
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new MeleeWeaponEquippedCon(
|
||||||
|
new QuadraticCurve(0.9f, 1.0f, 0.1f, 0.0f)),
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new BallisticAmmoCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.15f, 0.0f, 0.0f)),
|
||||||
|
new RangedWeaponFireRateCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
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.Hands;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Movement;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.AI.WorldState.States.Movement;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
||||||
|
{
|
||||||
|
public sealed class PickUpAmmo : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public PickUpAmmo(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
//TODO: Consider ammo's type and what guns we have
|
||||||
|
new TargetAccessibleCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new FreeHandCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new DistanceCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Content.Server.AI.Operators.Sequences;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Containers;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Hands;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Movement;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
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 Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
||||||
|
{
|
||||||
|
public sealed class PickUpBallisticMagWeapon : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public PickUpBallisticMagWeapon(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new HeldRangedWeaponsCon(
|
||||||
|
new QuadraticCurve(-1.0f, 1.0f, 1.0f, 0.0f)),
|
||||||
|
new TargetAccessibleCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new FreeHandCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
// For now don't grab empty guns - at least until we can start storing stuff in inventory
|
||||||
|
new BallisticAmmoCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new DistanceCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||||
|
new RangedWeaponFireRateCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||||
|
// TODO: Ballistic accuracy? Depends how the design transitions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
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.Combat.Ranged.Hitscan;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Movement;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.AI.WorldState.States.Movement;
|
||||||
|
using Content.Server.GameObjects.Components.Power.Chargers;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
||||||
|
{
|
||||||
|
public sealed class PutHitscanInCharger : UtilityAction
|
||||||
|
{
|
||||||
|
// Maybe a bad idea to not allow override
|
||||||
|
public override bool CanOverride => false;
|
||||||
|
private readonly IEntity _charger;
|
||||||
|
|
||||||
|
public PutHitscanInCharger(IEntity owner, IEntity charger, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_charger = charger;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
var weapon = context.GetState<EquippedEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (weapon == null || _charger.GetComponent<WeaponCapacitorChargerComponent>().HeldItem != null)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new MoveToEntityOperator(Owner, _charger),
|
||||||
|
new InteractWithEntityOperator(Owner, _charger),
|
||||||
|
// Separate task will deal with picking it up
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<MoveTargetState>().SetValue(_charger);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_charger);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } =
|
||||||
|
{
|
||||||
|
new HitscanWeaponEquippedCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new HitscanChargerFullCon(
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new HitscanChargerRateCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||||
|
new DistanceCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||||
|
new HitscanChargeCon(
|
||||||
|
new QuadraticCurve(-1.2f, 2.0f, 1.2f, 0.0f)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.AI.WorldState.States.Combat;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
||||||
|
{
|
||||||
|
public sealed class DropEmptyHitscan : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public DropEmptyHitscan(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new EquipEntityOperator(Owner, _entity),
|
||||||
|
new DropEntityOperator(Owner, _entity)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new TargetInOurInventoryCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
// Need to put in hands to drop
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
// If completely empty then drop that sucker
|
||||||
|
new HitscanChargeCon(
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
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.Combat.Ranged;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Combat;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
||||||
|
{
|
||||||
|
public sealed class EquipHitscan : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public EquipHitscan(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new EquipEntityOperator(Owner, _entity)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new EquippedHitscanCon(
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new MeleeWeaponEquippedCon(
|
||||||
|
new QuadraticCurve(0.9f, 1.0f, 0.1f, 0.0f)),
|
||||||
|
new CanPutTargetInHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new HitscanChargeCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.0f, 0.0f)),
|
||||||
|
new RangedWeaponFireRateCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||||
|
new HitscanWeaponDamageCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Combat.Ranged;
|
||||||
|
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.Ranged.Hitscan;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Movement;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.Utils;
|
||||||
|
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 Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
||||||
|
{
|
||||||
|
public sealed class HitscanAttackEntity : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
private MoveToEntityOperator _moveOperator;
|
||||||
|
|
||||||
|
public HitscanAttackEntity(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
if (_moveOperator != null)
|
||||||
|
{
|
||||||
|
_moveOperator.MovedATile -= InLos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
_moveOperator = new MoveToEntityOperator(Owner, _entity);
|
||||||
|
_moveOperator.MovedATile += InLos;
|
||||||
|
|
||||||
|
// TODO: Accuracy in blackboard
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
_moveOperator,
|
||||||
|
new ShootAtEntityOperator(Owner, _entity, 0.7f),
|
||||||
|
});
|
||||||
|
|
||||||
|
InLos();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||||
|
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||||
|
context.GetState<WeaponEntityState>().SetValue(equipped);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
// Check if we have a weapon; easy-out
|
||||||
|
new HitscanWeaponEquippedCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new HitscanChargeCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.1f, 0.0f, 0.0f)),
|
||||||
|
// Don't attack a dead target
|
||||||
|
new TargetIsDeadCon(
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
// Deprioritise a target in crit
|
||||||
|
new TargetIsCritCon(
|
||||||
|
new QuadraticCurve(-0.8f, 1.0f, 1.0f, 0.0f)),
|
||||||
|
// Somewhat prioritise distance
|
||||||
|
new DistanceCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.07f, 0.0f)),
|
||||||
|
// Prefer weaker targets
|
||||||
|
new TargetHealthCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.4f, 0.0f, -0.02f)),
|
||||||
|
};
|
||||||
|
|
||||||
|
private void InLos()
|
||||||
|
{
|
||||||
|
// This should only be called if the movement operator is the current one;
|
||||||
|
// if that turns out not to be the case we can just add a check here.
|
||||||
|
if (Visibility.InLineOfSight(Owner, _entity))
|
||||||
|
{
|
||||||
|
_moveOperator.HaveArrived();
|
||||||
|
var mover = ActionOperators.Dequeue();
|
||||||
|
mover.Shutdown(Outcome.Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Combat.Ranged;
|
||||||
|
using Content.Server.AI.Operators.Inventory;
|
||||||
|
using Content.Server.AI.Operators.Movement;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Containers;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Hands;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Movement;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.AI.WorldState.States.Movement;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
||||||
|
{
|
||||||
|
public sealed class PickUpHitscanFromCharger : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
private IEntity _charger;
|
||||||
|
|
||||||
|
public PickUpHitscanFromCharger(IEntity owner, IEntity entity, IEntity charger, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
_charger = charger;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new MoveToEntityOperator(Owner, _charger),
|
||||||
|
new WaitForHitscanChargeOperator(_entity),
|
||||||
|
new PickupEntityOperator(Owner, _entity),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new HeldRangedWeaponsCon(
|
||||||
|
new QuadraticCurve(-1.0f, 1.0f, 1.0f, 0.0f)),
|
||||||
|
new TargetAccessibleCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new FreeHandCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new DistanceCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||||
|
// TODO: ChargerHasPower
|
||||||
|
new RangedWeaponFireRateCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||||
|
new HitscanWeaponDamageCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using Content.Server.AI.Operators.Sequences;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Containers;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Hands;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Movement;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
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 Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
||||||
|
{
|
||||||
|
public sealed class PickUpHitscanWeapon : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public PickUpHitscanWeapon(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new HeldRangedWeaponsCon(
|
||||||
|
new QuadraticCurve(-1.0f, 1.0f, 1.0f, 0.0f)),
|
||||||
|
new TargetAccessibleCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new FreeHandCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
// For now don't grab empty guns - at least until we can start storing stuff in inventory
|
||||||
|
new HitscanChargeCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new DistanceCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||||
|
// TODO: Weapon charge level
|
||||||
|
new RangedWeaponFireRateCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||||
|
new HitscanWeaponDamageCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Content.Server/AI/Utility/Actions/IAiUtility.cs
Normal file
9
Content.Server/AI/Utility/Actions/IAiUtility.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Content.Server.AI.Utility.AiLogic;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions
|
||||||
|
{
|
||||||
|
public interface IAiUtility
|
||||||
|
{
|
||||||
|
float Bonus { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
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.Movement;
|
||||||
|
using Content.Server.AI.Utility.Considerations.State;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
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 => 1.5f;
|
||||||
|
|
||||||
|
public CloseLastEntityStorage(IEntity owner) : base(owner) {}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations => new Consideration[]
|
||||||
|
{
|
||||||
|
new StoredStateIsNullCon<LastOpenedStorageState, IEntity>(
|
||||||
|
new InverseBoolCurve()),
|
||||||
|
new DistanceCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||||
|
};
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
var lastStorage = context.GetState<LastOpenedStorageState>().GetValue();
|
||||||
|
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new MoveToEntityOperator(Owner, lastStorage),
|
||||||
|
new CloseLastStorageOperator(Owner),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
83
Content.Server/AI/Utility/Actions/Idle/WanderAndWait.cs
Normal file
83
Content.Server/AI/Utility/Actions/Idle/WanderAndWait.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Generic;
|
||||||
|
using Content.Server.AI.Operators.Movement;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.ActionBlocker;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
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 => IdleBonus;
|
||||||
|
|
||||||
|
public WanderAndWait(IEntity owner) : base(owner)
|
||||||
|
{
|
||||||
|
// TODO: Need a Success method that gets called to update context (e.g. when we last did X)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
var randomGrid = FindRandomGrid();
|
||||||
|
float waitTime;
|
||||||
|
if (randomGrid != GridCoordinates.InvalidGrid)
|
||||||
|
{
|
||||||
|
var random = IoCManager.Resolve<IRobustRandom>();
|
||||||
|
waitTime = random.NextFloat() * 10;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
waitTime = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new MoveToGridOperator(Owner, randomGrid),
|
||||||
|
new WaitOperator(waitTime),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new CanMoveCon(
|
||||||
|
new BoolCurve())
|
||||||
|
// Last wander? If we also want to sit still
|
||||||
|
};
|
||||||
|
|
||||||
|
private GridCoordinates FindRandomGrid()
|
||||||
|
{
|
||||||
|
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||||
|
var grid = mapManager.GetGrid(Owner.Transform.GridID);
|
||||||
|
|
||||||
|
// Just find a random spot in bounds
|
||||||
|
// If the grid's a single-tile wide but really tall this won't really work but eh future problem
|
||||||
|
var gridBounds = grid.WorldBounds;
|
||||||
|
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||||
|
var newPosition = gridBounds.BottomLeft + new Vector2(
|
||||||
|
robustRandom.Next((int) gridBounds.Width),
|
||||||
|
robustRandom.Next((int) gridBounds.Height));
|
||||||
|
// Conversions blah blah
|
||||||
|
var mapIndex = grid.WorldToTile(grid.LocalToWorld(newPosition));
|
||||||
|
// Didn't find one? Fuck it we're not walkin' into space
|
||||||
|
if (grid.GetTileRef(mapIndex).Tile.IsEmpty)
|
||||||
|
{
|
||||||
|
return GridCoordinates.InvalidGrid;
|
||||||
|
}
|
||||||
|
var target = grid.GridTileToLocal(mapIndex);
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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.Hands;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Movement;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Nutrition.Drink;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
|
||||||
|
{
|
||||||
|
public sealed class PickUpDrink : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public PickUpDrink(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations => new Consideration[]
|
||||||
|
{
|
||||||
|
new TargetAccessibleCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new FreeHandCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new ThirstCon(
|
||||||
|
new LogisticCurve(1000f, 1.3f, -1.0f, 0.5f)),
|
||||||
|
new DistanceCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||||
|
new DrinkValueCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.4f, 0.0f, 0.0f)),
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Hands;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Nutrition.Drink;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
|
||||||
|
{
|
||||||
|
public sealed class UseDrinkInInventory : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public UseDrinkInInventory(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new EquipEntityOperator(Owner, _entity),
|
||||||
|
new UseItemInHandsOperator(Owner, _entity),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations => new Consideration[]
|
||||||
|
{
|
||||||
|
new TargetInOurHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new ThirstCon(
|
||||||
|
new LogisticCurve(1000f, 1.3f, -0.3f, 0.5f)),
|
||||||
|
new DrinkValueCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.4f, 0.0f, 0.0f))
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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.Hands;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Movement;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Nutrition;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Nutrition.Food
|
||||||
|
{
|
||||||
|
public sealed class PickUpFood : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public PickUpFood(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations => new Consideration[]
|
||||||
|
{
|
||||||
|
new TargetAccessibleCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new FreeHandCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new HungerCon(
|
||||||
|
new LogisticCurve(1000f, 1.3f, -1.0f, 0.5f)),
|
||||||
|
new DistanceCon(
|
||||||
|
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||||
|
new FoodValueCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.4f, 0.0f, 0.0f)),
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Inventory;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Hands;
|
||||||
|
using Content.Server.AI.Utility.Considerations.Nutrition;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Nutrition.Food
|
||||||
|
{
|
||||||
|
public sealed class UseFoodInInventory : UtilityAction
|
||||||
|
{
|
||||||
|
private IEntity _entity;
|
||||||
|
|
||||||
|
public UseFoodInInventory(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||||
|
{
|
||||||
|
_entity = entity;
|
||||||
|
Bonus = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
|
{
|
||||||
|
new EquipEntityOperator(Owner, _entity),
|
||||||
|
new UseItemInHandsOperator(Owner, _entity),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations => new Consideration[]
|
||||||
|
{
|
||||||
|
new TargetInOurHandsCon(
|
||||||
|
new BoolCurve()),
|
||||||
|
new HungerCon(
|
||||||
|
new LogisticCurve(1000f, 1.3f, -0.3f, 0.5f)),
|
||||||
|
new FoodValueCon(
|
||||||
|
new QuadraticCurve(1.0f, 0.4f, 0.0f, 0.0f))
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void UpdateBlackboard(Blackboard context)
|
||||||
|
{
|
||||||
|
base.UpdateBlackboard(context);
|
||||||
|
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Movement;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Actions.Test
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used for pathfinding debugging
|
||||||
|
/// </summary>
|
||||||
|
public class MoveRightAndLeftTen : UtilityAction
|
||||||
|
{
|
||||||
|
public override bool CanOverride => false;
|
||||||
|
|
||||||
|
public MoveRightAndLeftTen(IEntity owner) : base(owner) {}
|
||||||
|
|
||||||
|
protected override Consideration[] Considerations { get; } = {
|
||||||
|
new DummyCon(
|
||||||
|
new BoolCurve())
|
||||||
|
};
|
||||||
|
|
||||||
|
public override void SetupOperators(Blackboard context)
|
||||||
|
{
|
||||||
|
var currentPosition = Owner.Transform.GridPosition;
|
||||||
|
var nextPosition = Owner.Transform.GridPosition.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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
149
Content.Server/AI/Utility/Actions/UtilityAction.cs
Normal file
149
Content.Server/AI/Utility/Actions/UtilityAction.cs
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Utility.Considerations;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
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; protected 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;
|
||||||
|
|
||||||
|
protected IEntity Owner { get; }
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
protected abstract Consideration[] Considerations { get; }
|
||||||
|
|
||||||
|
/// <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) {}
|
||||||
|
|
||||||
|
protected UtilityAction(IEntity owner)
|
||||||
|
{
|
||||||
|
Owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.TryStartup();
|
||||||
|
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="bonus"></param>
|
||||||
|
/// <param name="min"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public float GetScore(Blackboard context, float min)
|
||||||
|
{
|
||||||
|
UpdateBlackboard(context);
|
||||||
|
DebugTools.Assert(Considerations.Length > 0);
|
||||||
|
// I used the IAUS video although I did have some confusion on how to structure it overall
|
||||||
|
// as some of the slides seemed contradictory
|
||||||
|
|
||||||
|
// Ideally we should early-out each action as cheaply as possible if it's not valid
|
||||||
|
|
||||||
|
// We also need some way to tell if the action isn't going to
|
||||||
|
// have a better score than the current action (if applicable) and early-out that way as well.
|
||||||
|
|
||||||
|
// 23:00 Building a better centaur
|
||||||
|
var finalScore = 1.0f;
|
||||||
|
var minThreshold = min / Bonus;
|
||||||
|
var modificationFactor = 1.0f - 1.0f / Considerations.Length;
|
||||||
|
// See 10:09 for this and the adjustments
|
||||||
|
|
||||||
|
foreach (var consideration in Considerations)
|
||||||
|
{
|
||||||
|
var score = consideration.GetScore(context);
|
||||||
|
var makeUpValue = (1.0f - score) * modificationFactor;
|
||||||
|
var adjustedScore = score + makeUpValue * score;
|
||||||
|
var response = consideration.ComputeResponseCurve(adjustedScore);
|
||||||
|
|
||||||
|
finalScore *= response;
|
||||||
|
|
||||||
|
DebugTools.Assert(!float.IsNaN(response));
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Content.Server/AI/Utility/AiLogic/Civilian.cs
Normal file
21
Content.Server/AI/Utility/AiLogic/Civilian.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Content.Server.AI.Utility.BehaviorSets;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.AI;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.AiLogic
|
||||||
|
{
|
||||||
|
[AiLogicProcessor("Civilian")]
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class Civilian : UtilityAi
|
||||||
|
{
|
||||||
|
public override void Setup()
|
||||||
|
{
|
||||||
|
base.Setup();
|
||||||
|
AddBehaviorSet(new ClothingBehaviorSet(SelfEntity), false);
|
||||||
|
AddBehaviorSet(new HungerBehaviorSet(SelfEntity), false);
|
||||||
|
AddBehaviorSet(new ThirstBehaviorSet(SelfEntity), false);
|
||||||
|
AddBehaviorSet(new IdleBehaviorSet(SelfEntity), false);
|
||||||
|
SortActions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Content.Server/AI/Utility/AiLogic/PathingDummy.cs
Normal file
18
Content.Server/AI/Utility/AiLogic/PathingDummy.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Content.Server.AI.Utility.BehaviorSets;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.AI;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.AiLogic
|
||||||
|
{
|
||||||
|
[AiLogicProcessor("PathingDummy")]
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class PathingDummy : UtilityAi
|
||||||
|
{
|
||||||
|
public override void Setup()
|
||||||
|
{
|
||||||
|
base.Setup();
|
||||||
|
BehaviorSets.Add(typeof(PathingDummyBehaviorSet), new PathingDummyBehaviorSet(SelfEntity));
|
||||||
|
SortActions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Content.Server/AI/Utility/AiLogic/Spirate.cs
Normal file
22
Content.Server/AI/Utility/AiLogic/Spirate.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Content.Server.AI.Utility.BehaviorSets;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.AI;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.AiLogic
|
||||||
|
{
|
||||||
|
[AiLogicProcessor("Spirate")]
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class Spirate : UtilityAi
|
||||||
|
{
|
||||||
|
public override void Setup()
|
||||||
|
{
|
||||||
|
base.Setup();
|
||||||
|
AddBehaviorSet(new ClothingBehaviorSet(SelfEntity), false);
|
||||||
|
AddBehaviorSet(new HungerBehaviorSet(SelfEntity), false);
|
||||||
|
AddBehaviorSet(new ThirstBehaviorSet(SelfEntity), false);
|
||||||
|
AddBehaviorSet(new IdleBehaviorSet(SelfEntity), false);
|
||||||
|
AddBehaviorSet(new SpirateBehaviorSet(SelfEntity), false);
|
||||||
|
SortActions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
228
Content.Server/AI/Utility/AiLogic/UtilityAI.cs
Normal file
228
Content.Server/AI/Utility/AiLogic/UtilityAI.cs
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using Content.Server.AI.Operators;
|
||||||
|
using Content.Server.AI.Operators.Generic;
|
||||||
|
using Content.Server.AI.Utility.Actions;
|
||||||
|
using Content.Server.AI.Utility.BehaviorSets;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Utility;
|
||||||
|
using Content.Server.GameObjects;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.AI.LoadBalancer;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
||||||
|
using Robust.Server.AI;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.AiLogic
|
||||||
|
{
|
||||||
|
public abstract class UtilityAi : AiLogicProcessor
|
||||||
|
{
|
||||||
|
// TODO: Look at having ParallelOperators (probably no more than that as then you'd have a full-blown BT)
|
||||||
|
// Also RepeatOperators (e.g. if we're following an entity keep repeating MoveToEntity)
|
||||||
|
private AiActionSystem _planner;
|
||||||
|
public Blackboard Blackboard => _blackboard;
|
||||||
|
private Blackboard _blackboard;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sum of all BehaviorSets gives us what actions the AI can take
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<Type, BehaviorSet> BehaviorSets { get; } = new Dictionary<Type, BehaviorSet>();
|
||||||
|
private readonly List<IAiUtility> _availableActions = new List<IAiUtility>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The currently running action; most importantly are the operators.
|
||||||
|
/// </summary>
|
||||||
|
public UtilityAction CurrentAction { get; private 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;
|
||||||
|
private float _planCooldownRemaining;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If we've requested a plan then wait patiently for the action
|
||||||
|
/// </summary>
|
||||||
|
private AiActionRequestJob _actionRequest;
|
||||||
|
|
||||||
|
private CancellationTokenSource _actionCancellation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If we can't do anything then stop thinking; should probably use ActionBlocker instead
|
||||||
|
/// </summary>
|
||||||
|
private bool _isDead = false;
|
||||||
|
|
||||||
|
// These 2 methods will be used eventually if / when we get a director AI
|
||||||
|
public void AddBehaviorSet<T>(T behaviorSet, bool sort = true) where T : BehaviorSet
|
||||||
|
{
|
||||||
|
if (BehaviorSets.TryAdd(typeof(T), behaviorSet) && sort)
|
||||||
|
{
|
||||||
|
SortActions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveBehaviorSet(Type behaviorSet)
|
||||||
|
{
|
||||||
|
DebugTools.Assert(behaviorSet.IsAssignableFrom(typeof(BehaviorSet)));
|
||||||
|
|
||||||
|
if (BehaviorSets.ContainsKey(behaviorSet))
|
||||||
|
{
|
||||||
|
BehaviorSets.Remove(behaviorSet);
|
||||||
|
SortActions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whenever the behavior sets are changed we'll re-sort the actions by bonus
|
||||||
|
/// </summary>
|
||||||
|
protected void SortActions()
|
||||||
|
{
|
||||||
|
_availableActions.Clear();
|
||||||
|
foreach (var set in BehaviorSets.Values)
|
||||||
|
{
|
||||||
|
foreach (var action in set.Actions)
|
||||||
|
{
|
||||||
|
var found = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < _availableActions.Count; i++)
|
||||||
|
{
|
||||||
|
if (_availableActions[i].Bonus < action.Bonus)
|
||||||
|
{
|
||||||
|
_availableActions.Insert(i, action);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
_availableActions.Add(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_availableActions.Reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Setup()
|
||||||
|
{
|
||||||
|
base.Setup();
|
||||||
|
_planCooldownRemaining = PlanCooldown;
|
||||||
|
_blackboard = new Blackboard(SelfEntity);
|
||||||
|
_planner = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AiActionSystem>();
|
||||||
|
if (SelfEntity.TryGetComponent(out DamageableComponent damageableComponent))
|
||||||
|
{
|
||||||
|
damageableComponent.DamageThresholdPassed += DeathHandle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
// TODO: If DamageableComponent removed still need to unsubscribe?
|
||||||
|
if (SelfEntity.TryGetComponent(out DamageableComponent damageableComponent))
|
||||||
|
{
|
||||||
|
damageableComponent.DamageThresholdPassed -= DeathHandle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeathHandle(object sender, DamageThresholdPassedEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (eventArgs.DamageThreshold.ThresholdType == ThresholdType.Death)
|
||||||
|
{
|
||||||
|
_isDead = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: If we get healed - double-check what it should be
|
||||||
|
if (eventArgs.DamageThreshold.ThresholdType == ThresholdType.None)
|
||||||
|
{
|
||||||
|
_isDead = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReceivedAction()
|
||||||
|
{
|
||||||
|
var action = _actionRequest.Result;
|
||||||
|
_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 && CurrentAction?.GetType() == action.GetType())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentAction = action;
|
||||||
|
action.SetupOperators(_blackboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
// If we can't do anything then there's no point thinking
|
||||||
|
if (_isDead || BehaviorSets.Count == 0)
|
||||||
|
{
|
||||||
|
_actionCancellation?.Cancel();
|
||||||
|
_blackboard.GetState<LastUtilityScoreState>().SetValue(0.0f);
|
||||||
|
CurrentAction?.Shutdown();
|
||||||
|
CurrentAction = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we asked for a new action we don't want to dump the existing one.
|
||||||
|
if (_actionRequest != null)
|
||||||
|
{
|
||||||
|
if (_actionRequest.Status != JobStatus.Finished)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReceivedAction();
|
||||||
|
// Do something next tick
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_planCooldownRemaining -= frameTime;
|
||||||
|
|
||||||
|
// Might find a better action while we're doing one already
|
||||||
|
if (_planCooldownRemaining <= 0.0f)
|
||||||
|
{
|
||||||
|
_planCooldownRemaining = PlanCooldown;
|
||||||
|
_actionCancellation = new CancellationTokenSource();
|
||||||
|
_actionRequest = _planner.RequestAction(new AiActionRequest(SelfEntity.Uid, _blackboard, _availableActions), _actionCancellation);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we spawn in we won't get an action for a bit
|
||||||
|
if (CurrentAction == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var outcome = CurrentAction.Execute(frameTime);
|
||||||
|
|
||||||
|
switch (outcome)
|
||||||
|
{
|
||||||
|
case Outcome.Success:
|
||||||
|
if (CurrentAction.ActionOperators.Count == 0)
|
||||||
|
{
|
||||||
|
CurrentAction.Shutdown();
|
||||||
|
CurrentAction = null;
|
||||||
|
// Nothing to compare new action to
|
||||||
|
_blackboard.GetState<LastUtilityScoreState>().SetValue(0.0f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Outcome.Continuing:
|
||||||
|
break;
|
||||||
|
case Outcome.Failed:
|
||||||
|
CurrentAction.Shutdown();
|
||||||
|
CurrentAction = null;
|
||||||
|
_blackboard.GetState<LastUtilityScoreState>().SetValue(0.0f);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Content.Server/AI/Utility/BehaviorSets/BehaviorSet.cs
Normal file
22
Content.Server/AI/Utility/BehaviorSets/BehaviorSet.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Utility.Actions;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.BehaviorSets
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// AKA DecisionMaker in IAUS. Just a group of actions that can be dynamically added or taken away from an AI.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class BehaviorSet
|
||||||
|
{
|
||||||
|
protected IEntity Owner;
|
||||||
|
|
||||||
|
public BehaviorSet(IEntity owner)
|
||||||
|
{
|
||||||
|
Owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IAiUtility> Actions { get; protected set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Content.Server.AI.Utility.Actions;
|
||||||
|
using Content.Server.AI.Utility.ExpandableActions.Clothing.Gloves;
|
||||||
|
using Content.Server.AI.Utility.ExpandableActions.Clothing.Head;
|
||||||
|
using Content.Server.AI.Utility.ExpandableActions.Clothing.OuterClothing;
|
||||||
|
using Content.Server.AI.Utility.ExpandableActions.Clothing.Shoes;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.BehaviorSets
|
||||||
|
{
|
||||||
|
public sealed class ClothingBehaviorSet : BehaviorSet
|
||||||
|
{
|
||||||
|
public ClothingBehaviorSet(IEntity owner) : base(owner)
|
||||||
|
{
|
||||||
|
Actions = new IAiUtility[]
|
||||||
|
{
|
||||||
|
new EquipAnyHeadExp(),
|
||||||
|
new EquipAnyOuterClothingExp(),
|
||||||
|
new EquipAnyGlovesExp(),
|
||||||
|
new EquipAnyShoesExp(),
|
||||||
|
new PickUpAnyNearbyHeadExp(),
|
||||||
|
new PickUpAnyNearbyOuterClothingExp(),
|
||||||
|
new PickUpAnyNearbyGlovesExp(),
|
||||||
|
new PickUpAnyNearbyShoesExp(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Content.Server/AI/Utility/BehaviorSets/HungerBehaviorSet.cs
Normal file
20
Content.Server/AI/Utility/BehaviorSets/HungerBehaviorSet.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Utility.Actions;
|
||||||
|
using Content.Server.AI.Utility.Actions.Nutrition;
|
||||||
|
using Content.Server.AI.Utility.ExpandableActions.Nutrition;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.BehaviorSets
|
||||||
|
{
|
||||||
|
public sealed class HungerBehaviorSet : BehaviorSet
|
||||||
|
{
|
||||||
|
public HungerBehaviorSet(IEntity owner) : base(owner)
|
||||||
|
{
|
||||||
|
Actions = new IAiUtility[]
|
||||||
|
{
|
||||||
|
new PickUpNearbyFoodExp(),
|
||||||
|
new UseFoodInInventoryExp(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Content.Server/AI/Utility/BehaviorSets/IdleBehaviorSet.cs
Normal file
18
Content.Server/AI/Utility/BehaviorSets/IdleBehaviorSet.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Content.Server.AI.Utility.Actions;
|
||||||
|
using Content.Server.AI.Utility.Actions.Idle;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.BehaviorSets
|
||||||
|
{
|
||||||
|
public class IdleBehaviorSet : BehaviorSet
|
||||||
|
{
|
||||||
|
public IdleBehaviorSet(IEntity owner) : base(owner)
|
||||||
|
{
|
||||||
|
Actions = new IAiUtility[]
|
||||||
|
{
|
||||||
|
new CloseLastEntityStorage(Owner),
|
||||||
|
new WanderAndWait(Owner),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Content.Server.AI.Utility.Actions;
|
||||||
|
using Content.Server.AI.Utility.Actions.Test;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.BehaviorSets
|
||||||
|
{
|
||||||
|
public sealed class PathingDummyBehaviorSet : BehaviorSet
|
||||||
|
{
|
||||||
|
public PathingDummyBehaviorSet(IEntity owner) : base(owner)
|
||||||
|
{
|
||||||
|
Actions = new IAiUtility[]
|
||||||
|
{
|
||||||
|
new MoveRightAndLeftTen(owner),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Content.Server/AI/Utility/BehaviorSets/SpirateBehaviorSet.cs
Normal file
37
Content.Server/AI/Utility/BehaviorSets/SpirateBehaviorSet.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.AI.Utility.Actions;
|
||||||
|
using Content.Server.AI.Utility.Actions.Combat.Ranged;
|
||||||
|
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
|
||||||
|
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
|
||||||
|
using Content.Server.AI.Utility.ExpandableActions.Combat;
|
||||||
|
using Content.Server.AI.Utility.ExpandableActions.Combat.Melee;
|
||||||
|
using Content.Server.AI.Utility.ExpandableActions.Combat.Ranged;
|
||||||
|
using Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Ballistic;
|
||||||
|
using Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Hitscan;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.BehaviorSets
|
||||||
|
{
|
||||||
|
public sealed class SpirateBehaviorSet : BehaviorSet
|
||||||
|
{
|
||||||
|
public SpirateBehaviorSet(IEntity owner) : base(owner)
|
||||||
|
{
|
||||||
|
Actions = new IAiUtility[]
|
||||||
|
{
|
||||||
|
new PickUpRangedExp(),
|
||||||
|
// TODO: Reload Ballistic
|
||||||
|
new DropEmptyBallisticExp(),
|
||||||
|
// TODO: Ideally long-term we should just store the weapons in backpack
|
||||||
|
new DropEmptyHitscanExp(),
|
||||||
|
new EquipMeleeExp(),
|
||||||
|
new EquipBallisticExp(),
|
||||||
|
new EquipHitscanExp(),
|
||||||
|
new PickUpHitscanFromChargersExp(),
|
||||||
|
new ChargeEquippedHitscanExp(),
|
||||||
|
new RangedAttackNearbySpeciesExp(),
|
||||||
|
new PickUpMeleeWeaponExp(),
|
||||||
|
new MeleeAttackNearbySpeciesExp(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Content.Server/AI/Utility/BehaviorSets/ThirstBehaviorSet.cs
Normal file
18
Content.Server/AI/Utility/BehaviorSets/ThirstBehaviorSet.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Content.Server.AI.Utility.Actions;
|
||||||
|
using Content.Server.AI.Utility.ExpandableActions.Nutrition;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.BehaviorSets
|
||||||
|
{
|
||||||
|
public sealed class ThirstBehaviorSet : BehaviorSet
|
||||||
|
{
|
||||||
|
public ThirstBehaviorSet(IEntity owner) : base(owner)
|
||||||
|
{
|
||||||
|
Actions = new IAiUtility[]
|
||||||
|
{
|
||||||
|
new PickUpNearbyDrinkExp(),
|
||||||
|
new UseDrinkInHandsExp(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.ActionBlocker
|
||||||
|
{
|
||||||
|
public sealed class CanMoveCon : Consideration
|
||||||
|
{
|
||||||
|
public CanMoveCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var self = context.GetState<SelfState>().GetValue();
|
||||||
|
if (!ActionBlockerSystem.CanMove(self))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Clothing
|
||||||
|
{
|
||||||
|
public sealed class ClothingInInventoryCon : Consideration
|
||||||
|
{
|
||||||
|
private readonly EquipmentSlotDefines.SlotFlags _slot;
|
||||||
|
|
||||||
|
public ClothingInInventoryCon(EquipmentSlotDefines.SlotFlags slotFlags, IResponseCurve curve) : base(curve)
|
||||||
|
{
|
||||||
|
_slot = slotFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var inventory = context.GetState<InventoryState>().GetValue();
|
||||||
|
|
||||||
|
foreach (var entity in inventory)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out ClothingComponent clothingComponent))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((clothingComponent.SlotFlags & _slot) != 0)
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Clothing;
|
||||||
|
using Content.Shared.GameObjects.Components.Inventory;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Clothing
|
||||||
|
{
|
||||||
|
public class ClothingInSlotCon : Consideration
|
||||||
|
{
|
||||||
|
private EquipmentSlotDefines.Slots _slot;
|
||||||
|
|
||||||
|
public ClothingInSlotCon(EquipmentSlotDefines.Slots slot, IResponseCurve curve) : base(curve)
|
||||||
|
{
|
||||||
|
_slot = slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var inventory = context.GetState<EquippedClothingState>().GetValue();
|
||||||
|
|
||||||
|
return inventory.ContainsKey(_slot) ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Melee;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Melee
|
||||||
|
{
|
||||||
|
public sealed class HasMeleeWeaponCon : Consideration
|
||||||
|
{
|
||||||
|
public HasMeleeWeaponCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
foreach (var item in context.GetState<InventoryState>().GetValue())
|
||||||
|
{
|
||||||
|
if (item.HasComponent<MeleeWeaponComponent>())
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Combat;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Melee;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Melee
|
||||||
|
{
|
||||||
|
public sealed class MeleeWeaponDamageCon : Consideration
|
||||||
|
{
|
||||||
|
public MeleeWeaponDamageCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var target = context.GetState<WeaponEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (target == null || !target.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just went with max health
|
||||||
|
return meleeWeaponComponent.Damage / 300.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Melee;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Melee
|
||||||
|
{
|
||||||
|
public sealed class MeleeWeaponEquippedCon : Consideration
|
||||||
|
{
|
||||||
|
public MeleeWeaponEquippedCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (equipped == null)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return equipped.HasComponent<MeleeWeaponComponent>() ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Combat;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Melee;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Melee
|
||||||
|
{
|
||||||
|
public sealed class MeleeWeaponSpeedCon : Consideration
|
||||||
|
{
|
||||||
|
public MeleeWeaponSpeedCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var target = context.GetState<WeaponEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (target == null || !target.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return meleeWeaponComponent.CooldownTime / 10.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Combat;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic
|
||||||
|
{
|
||||||
|
public class BallisticAmmoCon : Consideration
|
||||||
|
{
|
||||||
|
public BallisticAmmoCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var weapon = context.GetState<WeaponEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (weapon == null || !weapon.TryGetComponent(out BallisticMagazineWeaponComponent ballistic))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var contained = ballistic.MagazineSlot.ContainedEntity;
|
||||||
|
|
||||||
|
if (contained == null)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mag = contained.GetComponent<BallisticMagazineComponent>();
|
||||||
|
|
||||||
|
if (mag.CountLoaded == 0)
|
||||||
|
{
|
||||||
|
// TODO: Do this better
|
||||||
|
return ballistic.GetChambered(0) != null ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (float) mag.CountLoaded / mag.Capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic
|
||||||
|
{
|
||||||
|
public class BallisticWeaponEquippedCon : Consideration
|
||||||
|
{
|
||||||
|
public BallisticWeaponEquippedCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (equipped == null)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe change this to BallisticMagazineWeapon
|
||||||
|
return equipped.HasComponent<BallisticMagazineWeaponComponent>() ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic
|
||||||
|
{
|
||||||
|
public class EquippedBallisticCon : Consideration
|
||||||
|
{
|
||||||
|
public EquippedBallisticCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (equipped == null || !equipped.HasComponent<BallisticMagazineWeaponComponent>())
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.Utils;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged
|
||||||
|
{
|
||||||
|
public class HasTargetLosCon : Consideration
|
||||||
|
{
|
||||||
|
public HasTargetLosCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var owner = context.GetState<SelfState>().GetValue();
|
||||||
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Visibility.InLineOfSight(owner, target) ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Melee;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged
|
||||||
|
{
|
||||||
|
public sealed class HeldRangedWeaponsCon : Consideration
|
||||||
|
{
|
||||||
|
public HeldRangedWeaponsCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
const int max = 3;
|
||||||
|
|
||||||
|
foreach (var item in context.GetState<InventoryState>().GetValue())
|
||||||
|
{
|
||||||
|
if (item.HasComponent<RangedWeaponComponent>())
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (float) count / max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
|
||||||
|
{
|
||||||
|
public sealed class EquippedHitscanCon : Consideration
|
||||||
|
{
|
||||||
|
public EquippedHitscanCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (equipped == null || !equipped.HasComponent<HitscanWeaponComponent>())
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Combat;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
|
||||||
|
{
|
||||||
|
public sealed class HitscanChargeCon : Consideration
|
||||||
|
{
|
||||||
|
public HitscanChargeCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var weapon = context.GetState<WeaponEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (weapon == null || !weapon.TryGetComponent(out HitscanWeaponComponent hitscanWeaponComponent))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hitscanWeaponComponent.CapacitorComponent.Charge / hitscanWeaponComponent.CapacitorComponent.Capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.GameObjects.Components.Power.Chargers;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
|
||||||
|
{
|
||||||
|
public sealed class HitscanChargerFullCon : Consideration
|
||||||
|
{
|
||||||
|
public HitscanChargerFullCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (target == null ||
|
||||||
|
!target.TryGetComponent(out WeaponCapacitorChargerComponent chargerComponent) ||
|
||||||
|
chargerComponent.HeldItem != null)
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.GameObjects.Components.Power.Chargers;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
|
||||||
|
{
|
||||||
|
public sealed class HitscanChargerRateCon : Consideration
|
||||||
|
{
|
||||||
|
public HitscanChargerRateCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
if (target == null || !target.TryGetComponent(out WeaponCapacitorChargerComponent weaponCharger))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI don't care about efficiency, psfft!
|
||||||
|
return weaponCharger.TransferRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Combat;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
|
||||||
|
{
|
||||||
|
public sealed class HitscanWeaponDamageCon : Consideration
|
||||||
|
{
|
||||||
|
public HitscanWeaponDamageCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var weapon = context.GetState<WeaponEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (weapon == null || !weapon.TryGetComponent(out HitscanWeaponComponent hitscanWeaponComponent))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just went with max health
|
||||||
|
return hitscanWeaponComponent.Damage / 300.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
|
||||||
|
{
|
||||||
|
public sealed class HitscanWeaponEquippedCon : Consideration
|
||||||
|
{
|
||||||
|
public HitscanWeaponEquippedCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (equipped == null)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return equipped.HasComponent<HitscanWeaponComponent>() ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged
|
||||||
|
{
|
||||||
|
public sealed class RangedWeaponEquippedCon : Consideration
|
||||||
|
{
|
||||||
|
public RangedWeaponEquippedCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (equipped == null || !equipped.HasComponent<RangedWeaponComponent>())
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States.Combat;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged
|
||||||
|
{
|
||||||
|
public class RangedWeaponFireRateCon : Consideration
|
||||||
|
{
|
||||||
|
public RangedWeaponFireRateCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var weapon = context.GetState<WeaponEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (weapon == null || !weapon.TryGetComponent(out RangedWeaponComponent ranged))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranged.FireRate / 100.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.GameObjects;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||||
|
{
|
||||||
|
public sealed class TargetHealthCon : Consideration
|
||||||
|
{
|
||||||
|
public TargetHealthCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (target == null || !target.TryGetComponent(out DamageableComponent damageableComponent))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just went with max health
|
||||||
|
return damageableComponent.CurrentDamage[DamageType.Total] / 300.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.GameObjects;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||||
|
{
|
||||||
|
public sealed class TargetIsCritCon : Consideration
|
||||||
|
{
|
||||||
|
public TargetIsCritCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (target == null || !target.TryGetComponent(out SpeciesComponent speciesComponent))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (speciesComponent.CurrentDamageState is CriticalState)
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||||
|
{
|
||||||
|
public sealed class TargetIsDeadCon : Consideration
|
||||||
|
{
|
||||||
|
public TargetIsDeadCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (target == null || !target.TryGetComponent(out SpeciesComponent speciesComponent))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (speciesComponent.CurrentDamageState is DeadState)
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Content.Server/AI/Utility/Considerations/Consideration.cs
Normal file
25
Content.Server/AI/Utility/Considerations/Consideration.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations
|
||||||
|
{
|
||||||
|
public abstract class Consideration
|
||||||
|
{
|
||||||
|
protected IResponseCurve Curve { get; }
|
||||||
|
|
||||||
|
public Consideration(IResponseCurve curve)
|
||||||
|
{
|
||||||
|
Curve = curve;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract float GetScore(Blackboard context);
|
||||||
|
|
||||||
|
public float ComputeResponseCurve(float score)
|
||||||
|
{
|
||||||
|
var clampedScore = Math.Clamp(score, 0.0f, 1.0f);
|
||||||
|
var curvedResponse = Math.Clamp(Curve.GetResponse(clampedScore), 0.0f, 1.0f);
|
||||||
|
return curvedResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.GameObjects;
|
||||||
|
using Content.Server.GameObjects.Components;
|
||||||
|
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.)
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TargetAccessibleCon : Consideration
|
||||||
|
{
|
||||||
|
public TargetAccessibleCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ContainerHelpers.TryGetContainer(target, out var container))
|
||||||
|
{
|
||||||
|
if (container.Owner.TryGetComponent(out EntityStorageComponent storageComponent))
|
||||||
|
{
|
||||||
|
if (storageComponent.IsWeldedShut && !storageComponent.Open)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Content.Server/AI/Utility/Considerations/DummyCon.cs
Normal file
12
Content.Server/AI/Utility/Considerations/DummyCon.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations
|
||||||
|
{
|
||||||
|
public class DummyCon : Consideration
|
||||||
|
{
|
||||||
|
public DummyCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context) => 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Hands
|
||||||
|
{
|
||||||
|
public class FreeHandCon : Consideration
|
||||||
|
{
|
||||||
|
public FreeHandCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var owner = context.GetState<SelfState>().GetValue();
|
||||||
|
|
||||||
|
if (!owner.TryGetComponent(out HandsComponent handsComponent))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var handCount = 0;
|
||||||
|
var freeCount = 0;
|
||||||
|
|
||||||
|
foreach (var hand in handsComponent.ActivePriorityEnumerable())
|
||||||
|
{
|
||||||
|
handCount++;
|
||||||
|
if (handsComponent.GetHand(hand) == null)
|
||||||
|
{
|
||||||
|
freeCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (float) freeCount / handCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Hands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns 1 if in our hands else 0
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TargetInOurHandsCon : Consideration
|
||||||
|
{
|
||||||
|
public TargetInOurHandsCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var owner = context.GetState<SelfState>().GetValue();
|
||||||
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (target == null ||
|
||||||
|
!target.HasComponent<ItemComponent>() ||
|
||||||
|
!owner.TryGetComponent(out HandsComponent handsComponent))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handsComponent.IsHolding(target) ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
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.Server.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Inventory
|
||||||
|
{
|
||||||
|
public class CanPutTargetInHandsCon : Consideration
|
||||||
|
{
|
||||||
|
public CanPutTargetInHandsCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public 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 || !target.HasComponent<ItemComponent>())
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inventory = context.GetState<InventoryState>().GetValue();
|
||||||
|
|
||||||
|
foreach (var item in inventory)
|
||||||
|
{
|
||||||
|
if (item == target)
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.GetState<AnyFreeHandState>().GetValue() ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
|
using Content.Server.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Inventory
|
||||||
|
{
|
||||||
|
public class TargetInOurInventoryCon : Consideration
|
||||||
|
{
|
||||||
|
public TargetInOurInventoryCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var inventory = context.GetState<InventoryState>().GetValue();
|
||||||
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (target == null || !target.HasComponent<ItemComponent>())
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in inventory)
|
||||||
|
{
|
||||||
|
if (item == target)
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Movement
|
||||||
|
{
|
||||||
|
public sealed class DistanceCon : Consideration
|
||||||
|
{
|
||||||
|
public DistanceCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var self = context.GetState<SelfState>().GetValue();
|
||||||
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
if (target == null || target.Transform.GridID != self.Transform.GridID)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove 1 -
|
||||||
|
// Kind of just pulled a max distance out of nowhere. Add 0.01 just in case it's reaally far and we have no choice so it'll still be considered at least.
|
||||||
|
return 1 - ((target.Transform.GridPosition.Position - self.Transform.GridPosition.Position).Length / 100 + 0.01f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.GameObjects.Components.Chemistry;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Nutrition.Drink
|
||||||
|
{
|
||||||
|
public sealed class DrinkValueCon : Consideration
|
||||||
|
{
|
||||||
|
public DrinkValueCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (!target.TryGetComponent(out SolutionComponent drink))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nutritionValue = 0;
|
||||||
|
|
||||||
|
foreach (var reagent in drink.ReagentList)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
nutritionValue += (reagent.Quantity * 30).Int();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nutritionValue / 1000.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.GameObjects.Components.Nutrition;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Nutrition.Drink
|
||||||
|
{
|
||||||
|
public class ThirstCon : Consideration
|
||||||
|
{
|
||||||
|
public ThirstCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var owner = context.GetState<SelfState>().GetValue();
|
||||||
|
|
||||||
|
if (!owner.TryGetComponent(out ThirstComponent thirst))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1 - (thirst.CurrentThirst / thirst.ThirstThresholds[ThirstThreshold.OverHydrated]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Content.Server.AI.Utility.Curves;
|
||||||
|
using Content.Server.AI.WorldState;
|
||||||
|
using Content.Server.AI.WorldState.States;
|
||||||
|
using Content.Server.GameObjects.Components.Chemistry;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Utility.Considerations.Nutrition
|
||||||
|
{
|
||||||
|
public sealed class FoodValueCon : Consideration
|
||||||
|
{
|
||||||
|
public FoodValueCon(IResponseCurve curve) : base(curve) {}
|
||||||
|
|
||||||
|
public override float GetScore(Blackboard context)
|
||||||
|
{
|
||||||
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
|
if (!target.TryGetComponent(out SolutionComponent food))
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nutritionValue = 0;
|
||||||
|
|
||||||
|
foreach (var reagent in food.ReagentList)
|
||||||
|
{
|
||||||
|
// 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