NPCs/Agent.cs
namespace Opium.AI;
public abstract partial class Agent : Actor, Actor.IReceptor
{
/// <summary>
/// An action called when the NPC is damaged by something.
/// </summary>
[Property, Category( "Actions" )] public Action< Sandbox.DamageInfo> OnDamageAction { get; set; }
/// <summary>
/// An action called when the NPC is killed by something.
/// </summary>
[Property, Category( "Actions" )] public Action<Sandbox.DamageInfo> OnKilledAction { get; set; }
/// <summary>
/// The state machine this NPC is using.
/// </summary>
[Property] public StateMachine StateMachine { get; set; }
/// <summary>
/// Ragdoll
/// </summary>
[Property] public ModelPhysics Physics { get; set; }
[Property] public ModelCollider Collider { get; set; }
/// <summary>
/// Weapon
/// </summary>
private BaseWeapon weapon;
[Property] public BaseWeapon Weapon
{
get => weapon;
set
{
weapon = value;
if ( weapon.IsValid() )
{
weapon.Actor = this;
}
}
}
public override BaseWeapon ActiveWeapon => Weapon;
/// <summary>
/// Model
/// </summary>
[Property] public SkinnedModelRenderer Model { get; set; }
[Property] public GameObject CameraGameObject { get; set; }
public override GameObject CameraObject => CameraGameObject;
[Property] public bool NoReactionToSound { get; set; } = false;
public override void OnDamage( in Sandbox.DamageInfo damage )
{
base.OnDamage( damage );
if ( OnDamageAction != null ) OnDamageAction?.Invoke( damage );
if ( Model is not null )
{
var health = Health.Remap( 0, 100, 0.6f, 0f );
Model?.SceneObject.Attributes.Set( "bloodamount", health );
}
Scene.BroadcastStimulus( new FriendGotHurtStimulus( Transform.Position ) );
}
async void RagdollAsync()
{
Tags.Add( "ragdoll" );
await GameTask.DelaySeconds( 0.2f );
Physics.GameObject.SetParent( null );
Physics.GameObject.BreakFromPrefab();
Physics.GameObject.Tags.Add( "ragdoll" );
Physics.GameObject.DestroyAsync( 10f );
// Destroy the agent immediately
GameObject.Destroy();
// Creates a nav blocker for this ragdoll
var blocker = TemporaryNavBlocker.Create( Physics.GameObject.Transform.Position, new( 32, 32, 32 ), 10f );
Physics.Enabled = true;
}
public override void OnKilled( in Sandbox.DamageInfo damage )
{
base.OnKilled( damage );
if ( OnKilledAction != null ) OnKilledAction?.Invoke( damage );
var pickup = DropWeapon( Weapon );
if ( pickup is not null )
{
pickup.Durability = Game.Random.Int( 25, 75 );
}
if ( Collider is not null )
{
Collider.Enabled = false;
}
if ( Physics is not null )
{
RagdollAsync();
}
else
{
GameObject.Destroy();
}
}
public override WeaponPickup DropWeapon( BaseWeapon weapon )
{
return weapon?.Drop( CameraObject.Transform.Position, CameraObject.Transform.Position + CameraObject.Transform.Rotation.Forward * 5, CameraObject.Transform.Rotation.Forward );
}
public override void OnEvent( string eventName, params object[] obj )
{
if ( eventName == "damage" )
{
var damageInfo = (DamageInfo)obj[0];
LastStimulus = new HurtStimulus( damageInfo.Position );
}
StateMachine?.OnEvent( eventName, obj );
}
void UpdateStateMachine()
{
StateMachine?.UpdateStateMachine();
if ( StateMachine?.CurrentState.GetWishSpeed() is { } wishSpeed )
{
WishMove *= wishSpeed;
}
}
TimeSince TimeSinceFootstepEvent = 0;
private const float FootstepEventDelay = 0.3f;
protected override void UpdateMovement()
{
BuildWishInput();
DoMechanicsUpdate();
UpdateStateMachine();
BuildWishVelocity();
Accelerate();
if ( CharacterController.Velocity.Length > 0f && TimeSinceFootstepEvent > FootstepEventDelay )
{
Scene.BroadcastStimulus( FootstepStimulus.From( this, WishMove.Length ) );
TimeSinceFootstepEvent = 0;
}
}
protected override void OnUpdate()
{
base.OnUpdate();
UpdateWalk();
UpdateMovement();
FindStimuli();
// Kill off stimuli if it's based on old information
if ( LastStimulus is not null && !LastStimulus.ShouldReact( this ) )
{
LastStimulus = null;
}
}
/// <summary>
/// The last stimulus info this actor took.
/// </summary>
public Stimulus LastStimulus { get; set; }
public void OnStimulusReceived( Stimulus stimulusInfo )
{
if ( stimulusInfo.HasExpired )
return;
if ( stimulusInfo.ShouldReact( this ) )
{
LastStimulus = stimulusInfo;
}
}
public virtual bool Hates( Actor other )
{
return true;
}
}