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:
committed by
Pieter-Jan Briers
parent
4b30c7e710
commit
8b593d28c6
@@ -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)
|
||||
{
|
||||
|
||||
71
Content.Server/AI/StaticBarkerProcessor.cs
Normal file
71
Content.Server/AI/StaticBarkerProcessor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
249
Content.Server/AI/WanderProcessor.cs
Normal file
249
Content.Server/AI/WanderProcessor.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user