Actors/Actor.cs
using Opium.AI;
namespace Opium;
/// <summary>
/// An actor. This could be an AI actor, or a player.
/// </summary>
public partial class Actor : Component, Component.IDamageable, IKillable
{
/// <summary>
/// The voice list for this actor.
/// </summary>
[Property] public VoiceListResource VoiceList { get; set; }
/// <summary>
/// The relationship setup of this Actor
/// </summary>
[RequireComponent] public Relationship Relationship { get; set; }
/// <summary>
/// How much health does this actor have?
/// </summary>
[Property] public float Health { get; set; } = 100f;
/// <summary>
/// Is this actor alive?
/// </summary>
[Property] public bool IsAlive { get; set; } = true;
/// <summary>
/// The actor's current weapon (if any).
/// </summary>
public virtual BaseWeapon ActiveWeapon { get; }
/// <summary>
/// The GameObject for this actor's Camera (if any)
/// This can also be known as the actor's eyes. So where they're looking from.
/// </summary>
public virtual GameObject CameraObject { get; }
/// <summary>
/// The last damage info this actor took.
/// </summary>
public Sandbox.DamageInfo LastDamage { get; set; }
/// <summary>
/// When did this actor take damage last?
/// </summary>
public TimeSince TimeSinceLastDamage { get; set; } = 100f;
/// <summary>
/// Input layer for actors.
/// </summary>
public ActorInput Input { get; set; } = new();
/// <summary>
/// A list of damage sounds.
/// </summary>
[Property, Category( "Audio" )] public List<SoundEvent> DamageSounds { get; set; } = new();
/// <summary>
/// Get a random damage sound.
/// </summary>
/// <returns></returns>
public SoundEvent GetDamageSound() => Game.Random.FromList( DamageSounds );
/// <summary>
/// A list of death sounds.
/// </summary>
[Property, Category( "Audio" )] public List<SoundEvent> DeathSounds { get; set; } = new();
/// <summary>
/// Get a random death sound.
/// </summary>
/// <returns></returns>
public SoundEvent GetDeathSound() => Game.Random.FromList( DeathSounds );
/// <summary>
/// Called every mechanics update.
/// </summary>
protected virtual void OnMechanicsUpdate()
{
}
/// <summary>
/// Should we actually inflict damage?
/// </summary>
/// <param name="damage"></param>
/// <returns></returns>
public virtual bool ShouldDamage( in Sandbox.DamageInfo damage )
{
// Awful demo stuff
if ( this is Agent agent )
{
if ( agent.StateMachine.CurrentState is BlockingState )
{
// Don't damage AI players in block state.
if ( agent.StateMachine.TimeInState > 0.3f ) return false;
}
}
// Blocking
if ( ActiveWeapon is MeleeWeapon melee && ( melee.BlockAttack?.IsActive ?? false ) )
{
// Let the weapon know that we got hit
melee.BlockAttack.State = AttackState.Hit;
// Can instantly react
melee.TimeSinceShoot = 1f;
// Trigger event to the attacker's actor if we have one
damage.Attacker.Components.Get<Actor>( FindMode.EverythingInSelfAndAncestors )?.TriggerEvent( "block_react", this );
var didBreak = Components.Get<Actor>( FindMode.EnabledInSelfAndChildren )?.GetMechanic<PostureMechanic>()
?.Compensate( damage ) ?? false;
// Only block if we didn't break posture from this attack
if ( !didBreak && melee.BlockDamageFactor <= 0 )
return false;
}
return true;
}
/// <summary>
/// Called when the actor is damaged.
/// </summary>
public virtual void OnDamage( in Sandbox.DamageInfo damage )
{
if ( !ShouldDamage( damage ) )
{
// Make sure damgeinfo knows
damage.Damage = 0;
return;
}
TimeSinceLastDamage = 0;
LastDamage = damage;
Health -= damage.Damage;
TriggerEvent( "damage", damage );
TimeSinceLastDamage = 0;
// Weapons can do stuff?
ActiveWeapon?.OnDamage( damage );
if ( Health <= 0 )
{
OnKilled( damage );
if ( GetDeathSound() is { } sound )
{
PlayVoice( sound );
}
}
else
{
if ( GetDamageSound() is { } sound )
{
PlayVoice( sound );
}
}
}
/// <summary>
/// Called when the actor is killed.
/// </summary>
/// <param name="damageInfo"></param>
public virtual void OnKilled( in Sandbox.DamageInfo damageInfo )
{
IsAlive = false;
}
/// <summary>
/// Called every Scene update
/// </summary>
protected override void OnUpdate()
{
// Poll for input
Input?.Update();
UpdateVoice();
OnMechanicsUpdate();
}
/// <summary>
/// Tries to set up a viewmodel for a specific weapon. Will not do anything for AI actors, only players.
/// </summary>
/// <returns></returns>
public virtual bool SetupViewModel( BaseWeapon weapon, bool isActive )
{
return false;
}
/// <summary>
/// Can this actor fire a weapon?
/// This is for stuff that's over-arching, like stamina.
/// </summary>
/// <returns></returns>
public virtual bool CanShoot( BaseWeapon weapon )
{
return true;
}
public virtual bool CanAim( BaseWeapon weapon )
{
return true;
}
/// <summary>
/// Tries to drop a weapon if the actor owns it.
/// </summary>
/// <param name="weapon"></param>
/// <returns></returns>
public virtual WeaponPickup DropWeapon( BaseWeapon weapon )
{
return null;
}
public interface IEventListener
{
public void OnEvent( Actor actor, string eventName, params object[] obj );
}
/// <summary>
/// Triggers an event on this actor. This is pushed down to mechanics, or any state machines.
/// </summary>
/// <param name="eventName"></param>
/// <param name="obj"></param>
public void TriggerEvent( string eventName, params object[] obj )
{
foreach ( var eventListener in Scene.GetAllComponents<IEventListener>() )
{
eventListener.OnEvent( this, eventName, obj );
}
foreach ( var mechanic in Mechanics )
{
mechanic.OnEvent( eventName, obj );
}
OnEvent( eventName, obj );
}
/// <summary>
/// Called when an event is triggered by <see cref="TriggerEvent(string, object[])"/>
/// </summary>
/// <param name="eventName"></param>
/// <param name="obj"></param>
public virtual void OnEvent( string eventName, params object[] obj )
{
}
// TODO: move this
protected readonly float DefaultDetectionRange = 600f;
/// <summary>
/// How detected are we in relation to a specific actor?
/// Maybe this belongs in its own component. Idk.
/// </summary>
/// <param name="actor"></param>
/// <returns></returns>
public virtual float GetDetectionFactor( Actor actor )
{
return DefaultDetectionRange - Transform.Position.Distance( actor.Transform.Position );
}
protected override void OnEnabled()
{
Tags.Add( "actor" );
}
}