AI Wander & Barker (#286)

* Retrofit the AI system with new IoC features.
Fixed bug with turret rotation.

* Added new AI WanderProcessor, and it works.

* RNG walking directions are a bit more random now.

* Wander now actually uses the MoverSystem to move.
Wander now talks when he reaches his destination.

* Adds a new Static Barker AI for vending machines, so that they periodically advertise their brand.

* Barker now says some generic slogans.
Misc bug cleanup.

* Removed useless UsedImplicitly attribute from AI dependencies, suppressed unused variable warnings instead.
This commit is contained in:
Acruid
2019-08-10 05:19:52 -07:00
committed by Pieter-Jan Briers
parent 4b30c7e710
commit 8b593d28c6
8 changed files with 464 additions and 26 deletions

View File

@@ -20,9 +20,11 @@ namespace Content.Server.AI
[AiLogicProcessor("AimShootLife")]
class AimShootLifeProcessor : AiLogicProcessor
{
private readonly IPhysicsManager _physMan;
private readonly IServerEntityManager _entMan;
private readonly IGameTiming _timeMan;
#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>();
@@ -32,16 +34,6 @@ namespace Content.Server.AI
private IEntity _curTarget;
/// <summary>
/// Creates an instance of this LogicProcessor.
/// </summary>
public AimShootLifeProcessor()
{
_physMan = IoCManager.Resolve<IPhysicsManager>();
_entMan = IoCManager.Resolve<IServerEntityManager>();
_timeMan = IoCManager.Resolve<IGameTiming>();
}
/// <inheritdoc />
public override void Update(float frameTime)
{

View File

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Content.Server.Interfaces.Chat;
using JetBrains.Annotations;
using Robust.Server.AI;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
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;
}
}
}

View File

@@ -0,0 +1,249 @@
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.Chat;
using Content.Shared.Physics;
using Robust.Server.AI;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Physics;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Content.Server.AI
{
/// <summary>
/// Designed to control a mob. The mob will wander around, then idle at a the destination for awhile.
/// </summary>
[AiLogicProcessor("Wander")]
class WanderProcessor : AiLogicProcessor
{
#pragma warning disable 649
[Dependency] private readonly IPhysicsManager _physMan;
[Dependency] private readonly IServerEntityManager _entMan;
[Dependency] private readonly IGameTiming _timeMan;
[Dependency] private readonly IEntitySystemManager _entSysMan;
[Dependency] private readonly IChatManager _chatMan;
#pragma warning restore 649
private static readonly TimeSpan IdleTimeSpan = TimeSpan.FromSeconds(1);
private static readonly TimeSpan WalkingTimeout = TimeSpan.FromSeconds(3);
private static readonly TimeSpan DisabledTimeout = TimeSpan.FromSeconds(10);
private static List<string> _normalAssistantConversation = new List<string>
{
"stat me",
"roll it easy!",
"waaaaaagh!!!",
"red wonz go fasta",
"FOR TEH EMPRAH",
"lol2cat",
"dem dwarfs man, dem dwarfs",
"SPESS MAHREENS",
"hwee did eet fhor khayosss",
"lifelike texture ;_;",
"luv can bloooom",
"PACKETS!!!",
"SARAH HALE DID IT!!!",
"Don't tell Chase",
"not so tough now huh",
"WERE NOT BAY!!",
"IF YOU DONT LIKE THE CYBORGS OR SLIMES WHY DONT YU O JUST MAKE YORE OWN!",
"DONT TALK TO ME ABOUT BALANCE!!!!",
"YOU AR JUS LAZY AND DUMB JAMITORS AND SERVICE ROLLS",
"BLAME HOSHI!!!",
"ARRPEE IZ DED!!!",
"THERE ALL JUS MEATAFRIENDS!",
"SOTP MESING WITH THE ROUNS SHITMAN!!!",
"SKELINGTON IS 4 SHITERS!",
"MOMMSI R THE WURST SCUM!!",
"How do we engiener=",
"try to live freely and automatically good bye",
"why woud i take a pin pointner??",
"How do I set up the. SHow do I set u p the Singu. how I the scrungularity????",
};
private const float MaxWalkDistance = 3; // meters
private const float AdditionalIdleTime = 2; // 0 to this many more seconds
private FsmState _CurrentState;
private TimeSpan _startStateTime;
private Vector2 _walkTargetPos;
public override void Update(float frameTime)
{
if (SelfEntity == null)
return;
ProcessState();
}
private void ProcessState()
{
switch (_CurrentState)
{
case FsmState.None:
_CurrentState = FsmState.Idle;
break;
case FsmState.Idle:
IdleState();
break;
case FsmState.Walking:
WalkingState();
break;
case FsmState.Disabled:
DisabledState();
break;
}
}
private void IdlePositiveEdge(ref uint rngState)
{
_startStateTime = _timeMan.CurTime + IdleTimeSpan + TimeSpan.FromSeconds(Random01(ref rngState) * AdditionalIdleTime);
_CurrentState = FsmState.Idle;
EmitProfanity(ref rngState);
}
private void IdleState()
{
if (!ActionBlockerSystem.CanMove(SelfEntity))
{
DisabledPositiveEdge();
return;
}
if (_timeMan.CurTime < _startStateTime + IdleTimeSpan)
return;
var entWorldPos = SelfEntity.Transform.WorldPosition;
if (SelfEntity.TryGetComponent<BoundingBoxComponent>(out var bounds))
entWorldPos = bounds.WorldAABB.Center;
var rngState = GenSeed();
for (var i = 0; i < 3; i++) // you get 3 chances to find a place to walk
{
var dir = new Vector2(Random01(ref rngState) * 2 - 1, Random01(ref rngState) *2 -1);
var ray = new Ray(entWorldPos, dir, (int) CollisionGroup.Grid);
var rayResult = _physMan.IntersectRay(ray, MaxWalkDistance, SelfEntity);
if (rayResult.DidHitObject && rayResult.Distance > 1) // hit an impassable object
{
// set the new position back from the wall a bit
_walkTargetPos = entWorldPos + dir * (rayResult.Distance - 0.5f);
WalkingPositiveEdge();
return;
}
if (!rayResult.DidHitObject) // hit nothing (path clear)
{
_walkTargetPos = dir * MaxWalkDistance;
WalkingPositiveEdge();
return;
}
}
// can't find clear spot, do nothing, sleep longer
_startStateTime = _timeMan.CurTime;
}
private void WalkingPositiveEdge()
{
_startStateTime = _timeMan.CurTime;
_CurrentState = FsmState.Walking;
}
private void WalkingState()
{
var rngState = GenSeed();
if (_timeMan.CurTime > _startStateTime + WalkingTimeout) // walked too long, go idle
{
IdlePositiveEdge(ref rngState);
return;
}
var targetDiff = _walkTargetPos - SelfEntity.Transform.WorldPosition;
if (targetDiff.LengthSquared < 0.1) // close enough
{
// stop walking
if (SelfEntity.TryGetComponent<AiControllerComponent>(out var mover))
{
mover.VelocityDir = Vector2.Zero;
}
IdlePositiveEdge(ref rngState);
return;
}
// continue walking
if (SelfEntity.TryGetComponent<AiControllerComponent>(out var moverTwo))
{
moverTwo.VelocityDir = targetDiff.Normalized;
}
}
private void DisabledPositiveEdge()
{
_startStateTime = _timeMan.CurTime;
_CurrentState = FsmState.Disabled;
}
private void DisabledState()
{
if(_timeMan.CurTime < _startStateTime + DisabledTimeout)
return;
if (ActionBlockerSystem.CanMove(SelfEntity))
{
var rngState = GenSeed();
IdlePositiveEdge(ref rngState);
}
else
DisabledPositiveEdge();
}
private void EmitProfanity(ref uint rngState)
{
if(Random01(ref rngState) < 0.5f)
return;
var pick = (int) Math.Round(Random01(ref rngState) * (_normalAssistantConversation.Count - 1));
_chatMan.EntitySay(SelfEntity, _normalAssistantConversation[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;
}
private enum FsmState
{
None,
Idle,
Walking,
Disabled
}
}
}