2023-09-08 18:16:05 -07:00
using System.Diagnostics.CodeAnalysis ;
using System.Linq ;
2022-02-26 18:24:08 +13:00
using Content.Shared.ActionBlocker ;
2025-05-28 19:52:11 +00:00
using Content.Shared.Actions.Components ;
2023-09-08 18:16:05 -07:00
using Content.Shared.Actions.Events ;
2022-02-26 18:24:08 +13:00
using Content.Shared.Administration.Logs ;
using Content.Shared.Database ;
2025-09-08 05:55:13 -04:00
using Content.Shared.DoAfter ;
2022-02-26 18:24:08 +13:00
using Content.Shared.Hands ;
using Content.Shared.Interaction ;
using Content.Shared.Inventory.Events ;
2023-11-03 19:55:32 -04:00
using Content.Shared.Mind ;
2024-04-22 01:39:50 -07:00
using Content.Shared.Rejuvenate ;
2024-06-03 14:40:03 -07:00
using Content.Shared.Whitelist ;
2023-11-27 22:12:34 +11:00
using Robust.Shared.Audio.Systems ;
2022-02-26 18:24:08 +13:00
using Robust.Shared.GameStates ;
using Robust.Shared.Map ;
using Robust.Shared.Timing ;
2023-09-09 16:14:17 -07:00
using Robust.Shared.Utility ;
2022-02-26 18:24:08 +13:00
namespace Content.Shared.Actions ;
2025-09-08 05:55:13 -04:00
public abstract partial class SharedActionsSystem : EntitySystem
2022-02-26 18:24:08 +13:00
{
2022-10-04 14:24:19 +11:00
[Dependency] protected readonly IGameTiming GameTiming = default ! ;
2025-04-18 13:45:48 +10:00
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default ! ;
2025-05-28 19:52:11 +00:00
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default ! ;
2025-04-18 13:45:48 +10:00
[Dependency] private readonly ActionContainerSystem _actionContainer = default ! ;
2025-05-28 19:52:11 +00:00
[Dependency] private readonly EntityWhitelistSystem _whitelist = default ! ;
[Dependency] private readonly RotateToFaceSystem _rotateToFace = default ! ;
2025-04-18 13:45:48 +10:00
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
2025-05-28 19:52:11 +00:00
[Dependency] private readonly SharedInteractionSystem _interaction = default ! ;
[Dependency] private readonly SharedTransformSystem _transform = default ! ;
2025-09-08 05:55:13 -04:00
[Dependency] private readonly SharedDoAfterSystem _doAfter = default ! ;
2025-05-28 19:52:11 +00:00
private EntityQuery < ActionComponent > _actionQuery ;
private EntityQuery < ActionsComponent > _actionsQuery ;
private EntityQuery < MindComponent > _mindQuery ;
2022-02-26 18:24:08 +13:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2025-09-08 05:55:13 -04:00
InitializeActionDoAfter ( ) ;
2022-02-26 18:24:08 +13:00
2025-05-28 19:52:11 +00:00
_actionQuery = GetEntityQuery < ActionComponent > ( ) ;
_actionsQuery = GetEntityQuery < ActionsComponent > ( ) ;
_mindQuery = GetEntityQuery < MindComponent > ( ) ;
SubscribeLocalEvent < ActionComponent , MapInitEvent > ( OnActionMapInit ) ;
2024-02-19 21:08:41 -05:00
2025-05-28 19:52:11 +00:00
SubscribeLocalEvent < ActionComponent , ComponentShutdown > ( OnActionShutdown ) ;
2023-12-15 04:41:44 -05:00
2024-08-25 22:43:31 +10:00
SubscribeLocalEvent < ActionsComponent , ActionComponentChangeEvent > ( OnActionCompChange ) ;
SubscribeLocalEvent < ActionsComponent , RelayedActionComponentChangeEvent > ( OnRelayActionCompChange ) ;
2022-02-26 18:24:08 +13:00
SubscribeLocalEvent < ActionsComponent , DidEquipEvent > ( OnDidEquip ) ;
SubscribeLocalEvent < ActionsComponent , DidEquipHandEvent > ( OnHandEquipped ) ;
SubscribeLocalEvent < ActionsComponent , DidUnequipEvent > ( OnDidUnequip ) ;
SubscribeLocalEvent < ActionsComponent , DidUnequipHandEvent > ( OnHandUnequipped ) ;
2023-12-05 16:00:02 -05:00
SubscribeLocalEvent < ActionsComponent , RejuvenateEvent > ( OnRejuventate ) ;
2022-02-26 18:24:08 +13:00
2023-10-29 19:10:30 +11:00
SubscribeLocalEvent < ActionsComponent , ComponentShutdown > ( OnShutdown ) ;
2025-05-28 19:52:11 +00:00
SubscribeLocalEvent < ActionsComponent , ComponentGetState > ( OnGetState ) ;
2025-05-29 21:30:26 -07:00
SubscribeLocalEvent < ActionComponent , ActionValidateEvent > ( OnValidate ) ;
2025-05-28 19:52:11 +00:00
SubscribeLocalEvent < InstantActionComponent , ActionValidateEvent > ( OnInstantValidate ) ;
SubscribeLocalEvent < EntityTargetActionComponent , ActionValidateEvent > ( OnEntityValidate ) ;
SubscribeLocalEvent < WorldTargetActionComponent , ActionValidateEvent > ( OnWorldValidate ) ;
2023-10-29 19:10:30 +11:00
2025-05-28 19:52:11 +00:00
SubscribeLocalEvent < InstantActionComponent , ActionGetEventEvent > ( OnInstantGetEvent ) ;
SubscribeLocalEvent < EntityTargetActionComponent , ActionGetEventEvent > ( OnEntityGetEvent ) ;
SubscribeLocalEvent < WorldTargetActionComponent , ActionGetEventEvent > ( OnWorldGetEvent ) ;
2023-09-08 18:16:05 -07:00
2025-05-28 19:52:11 +00:00
SubscribeLocalEvent < InstantActionComponent , ActionSetEventEvent > ( OnInstantSetEvent ) ;
SubscribeLocalEvent < EntityTargetActionComponent , ActionSetEventEvent > ( OnEntitySetEvent ) ;
SubscribeLocalEvent < WorldTargetActionComponent , ActionSetEventEvent > ( OnWorldSetEvent ) ;
2023-09-08 18:16:05 -07:00
2025-05-28 19:52:11 +00:00
SubscribeLocalEvent < EntityTargetActionComponent , ActionSetTargetEvent > ( OnEntitySetTarget ) ;
SubscribeLocalEvent < WorldTargetActionComponent , ActionSetTargetEvent > ( OnWorldSetTarget ) ;
2022-02-26 18:24:08 +13:00
SubscribeAllEvent < RequestPerformActionEvent > ( OnActionRequest ) ;
}
2025-05-28 19:52:11 +00:00
private void OnActionMapInit ( Entity < ActionComponent > ent , ref MapInitEvent args )
2024-02-19 21:08:41 -05:00
{
2025-05-28 19:52:11 +00:00
var comp = ent . Comp ;
comp . OriginalIconColor = comp . IconColor ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . OriginalIconColor ) ) ;
2024-02-19 21:08:41 -05:00
}
2025-05-28 19:52:11 +00:00
private void OnActionShutdown ( Entity < ActionComponent > ent , ref ComponentShutdown args )
2023-12-15 04:41:44 -05:00
{
2025-05-28 19:52:11 +00:00
if ( ent . Comp . AttachedEntity is { } user & & ! TerminatingOrDeleted ( user ) )
RemoveAction ( user , ( ent , ent ) ) ;
2023-12-15 04:41:44 -05:00
}
2025-05-28 19:52:11 +00:00
private void OnShutdown ( Entity < ActionsComponent > ent , ref ComponentShutdown args )
2023-10-29 19:10:30 +11:00
{
2025-05-28 19:52:11 +00:00
foreach ( var actionId in ent . Comp . Actions )
2023-10-29 19:10:30 +11:00
{
2025-05-28 19:52:11 +00:00
RemoveAction ( ( ent , ent ) , actionId ) ;
2023-10-29 19:10:30 +11:00
}
}
2025-05-28 19:52:11 +00:00
private void OnGetState ( Entity < ActionsComponent > ent , ref ComponentGetState args )
2023-09-08 18:16:05 -07:00
{
2025-05-28 19:52:11 +00:00
args . State = new ActionsComponentState ( GetNetEntitySet ( ent . Comp . Actions ) ) ;
2023-09-08 18:16:05 -07:00
}
2025-05-28 19:52:11 +00:00
/// <summary>
/// Resolving an action's <see cref="ActionComponent"/>, only returning a value if it exists and has it.
/// </summary>
public Entity < ActionComponent > ? GetAction ( Entity < ActionComponent ? > ? action , bool logError = true )
2023-09-08 18:16:05 -07:00
{
2025-06-17 07:18:13 +02:00
if ( action is not { } ent | | Deleted ( ent ) )
2025-05-28 19:52:11 +00:00
return null ;
2023-09-08 18:16:05 -07:00
2025-05-28 19:52:11 +00:00
if ( ! _actionQuery . Resolve ( ent , ref ent . Comp , logError ) )
return null ;
2024-08-08 02:47:08 -07:00
2025-05-28 19:52:11 +00:00
return ( ent , ent . Comp ) ;
2023-09-08 18:16:05 -07:00
}
2025-05-28 19:52:11 +00:00
public void SetCooldown ( Entity < ActionComponent ? > ? action , TimeSpan start , TimeSpan end )
2023-09-08 18:16:05 -07:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( action ) is not { } ent )
return ;
2023-09-08 18:16:05 -07:00
2025-05-28 19:52:11 +00:00
ent . Comp . Cooldown = new ActionCooldown
2023-09-23 04:49:39 -04:00
{
2025-05-28 19:52:11 +00:00
Start = start ,
End = end
} ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . Cooldown ) ) ;
2023-09-09 16:14:17 -07:00
}
2025-05-28 19:52:11 +00:00
public void RemoveCooldown ( Entity < ActionComponent ? > ? action )
2023-09-08 18:16:05 -07:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( action ) is not { } ent )
2023-09-08 18:16:05 -07:00
return ;
2025-05-28 19:52:11 +00:00
ent . Comp . Cooldown = null ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . Cooldown ) ) ;
2023-09-08 18:16:05 -07:00
}
2025-05-28 19:52:11 +00:00
/// <summary>
/// Starts a cooldown starting now, lasting for <c>cooldown</c> seconds.
/// </summary>
public void SetCooldown ( Entity < ActionComponent ? > ? action , TimeSpan cooldown )
2023-11-07 17:24:43 -08:00
{
var start = GameTiming . CurTime ;
2025-05-28 19:52:11 +00:00
SetCooldown ( action , start , start + cooldown ) ;
2023-11-07 17:24:43 -08:00
}
2025-05-28 19:52:11 +00:00
public void ClearCooldown ( Entity < ActionComponent ? > ? action )
2023-11-07 17:24:43 -08:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( action ) is not { } ent )
2023-11-07 17:24:43 -08:00
return ;
2025-05-28 19:52:11 +00:00
if ( ent . Comp . Cooldown is not { } cooldown )
2023-11-07 17:24:43 -08:00
return ;
2025-05-28 19:52:11 +00:00
ent . Comp . Cooldown = new ActionCooldown
{
Start = cooldown . Start ,
End = GameTiming . CurTime
} ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . Cooldown ) ) ;
2023-11-07 17:24:43 -08:00
}
2024-05-10 17:04:01 -07:00
/// <summary>
/// Sets the cooldown for this action only if it is bigger than the one it already has.
/// </summary>
2025-05-28 19:52:11 +00:00
public void SetIfBiggerCooldown ( Entity < ActionComponent ? > ? action , TimeSpan cooldown )
2024-05-10 17:04:01 -07:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( action ) is not { } ent | | cooldown < TimeSpan . Zero )
2024-05-10 17:04:01 -07:00
return ;
var start = GameTiming . CurTime ;
var end = start + cooldown ;
2025-05-28 19:52:11 +00:00
if ( ent . Comp . Cooldown ? . End > end )
2024-05-10 17:04:01 -07:00
return ;
2025-05-28 19:52:11 +00:00
SetCooldown ( ( ent , ent ) , start , end ) ;
2024-05-10 17:04:01 -07:00
}
2025-05-28 19:52:11 +00:00
/// <summary>
/// Set an action's cooldown to its use delay, if it has one.
/// If there is no set use delay this does nothing.
/// </summary>
public void StartUseDelay ( Entity < ActionComponent ? > ? action )
2023-09-22 16:01:05 -04:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( action ) is not { } ent | | ent . Comp . UseDelay is not { } delay )
2023-09-22 16:01:05 -04:00
return ;
2025-05-28 19:52:11 +00:00
SetCooldown ( ( ent , ent ) , delay ) ;
2023-09-22 16:01:05 -04:00
}
2025-05-28 19:52:11 +00:00
public void SetUseDelay ( Entity < ActionComponent ? > ? action , TimeSpan ? delay )
2023-12-15 04:41:44 -05:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( action ) is not { } ent | | ent . Comp . UseDelay = = delay )
2023-12-15 04:41:44 -05:00
return ;
2025-05-28 19:52:11 +00:00
ent . Comp . UseDelay = delay ;
UpdateAction ( ent ) ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . UseDelay ) ) ;
2023-12-15 04:41:44 -05:00
}
2025-05-28 19:52:11 +00:00
public void ReduceUseDelay ( Entity < ActionComponent ? > ? action , TimeSpan ? lowerDelay )
2023-12-15 04:41:44 -05:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( action ) is not { } ent )
2023-12-15 04:41:44 -05:00
return ;
2025-05-28 19:52:11 +00:00
if ( ent . Comp . UseDelay ! = null & & lowerDelay ! = null )
ent . Comp . UseDelay - = lowerDelay ;
2023-12-15 04:41:44 -05:00
2025-05-28 19:52:11 +00:00
if ( ent . Comp . UseDelay < TimeSpan . Zero )
ent . Comp . UseDelay = null ;
2023-12-15 04:41:44 -05:00
2025-05-28 19:52:11 +00:00
UpdateAction ( ent ) ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . UseDelay ) ) ;
2023-12-15 04:41:44 -05:00
}
2025-05-28 19:52:11 +00:00
private void OnRejuventate ( Entity < ActionsComponent > ent , ref RejuvenateEvent args )
2023-12-05 16:00:02 -05:00
{
2025-05-28 19:52:11 +00:00
foreach ( var act in ent . Comp . Actions )
2023-12-05 16:00:02 -05:00
{
ClearCooldown ( act ) ;
}
}
2022-02-26 18:24:08 +13:00
#region ComponentStateManagement
2025-05-28 19:52:11 +00:00
public virtual void UpdateAction ( Entity < ActionComponent > ent )
2022-02-26 18:24:08 +13:00
{
2023-09-23 04:49:39 -04:00
// See client-side code.
2022-02-26 18:24:08 +13:00
}
2025-05-28 19:52:11 +00:00
public void SetToggled ( Entity < ActionComponent ? > ? action , bool toggled )
2022-02-26 18:24:08 +13:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( action ) is not { } ent | | ent . Comp . Toggled = = toggled )
2022-02-26 18:24:08 +13:00
return ;
2025-05-28 19:52:11 +00:00
ent . Comp . Toggled = toggled ;
UpdateAction ( ent ) ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . Toggled ) ) ;
2022-02-26 18:24:08 +13:00
}
2025-05-28 19:52:11 +00:00
public void SetEnabled ( Entity < ActionComponent ? > ? action , bool enabled )
2022-02-26 18:24:08 +13:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( action ) is not { } ent | | ent . Comp . Enabled = = enabled )
2022-02-26 18:24:08 +13:00
return ;
2025-05-28 19:52:11 +00:00
ent . Comp . Enabled = enabled ;
UpdateAction ( ent ) ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . Enabled ) ) ;
2022-02-26 18:24:08 +13:00
}
#endregion
#region Execution
/// <summary>
/// When receiving a request to perform an action, this validates whether the action is allowed. If it is, it
2025-09-08 05:55:13 -04:00
/// will raise the relevant action event
2022-02-26 18:24:08 +13:00
/// </summary>
private void OnActionRequest ( RequestPerformActionEvent ev , EntitySessionEventArgs args )
{
2023-05-01 04:29:18 -04:00
if ( args . SenderSession . AttachedEntity is not { } user )
2022-02-26 18:24:08 +13:00
return ;
2025-09-08 05:55:13 -04:00
TryPerformAction ( ev , user ) ;
}
/// <summary>
/// <see cref="OnActionRequest"/>
/// </summary>
/// <param name="ev">The Request Perform Action Event</param>
/// <param name="user">The user/performer of the action</param>
/// <param name="skipDoActionRequest">Should this skip the initial doaction request?</param>
private bool TryPerformAction ( RequestPerformActionEvent ev , EntityUid user , bool skipDoActionRequest = false )
{
2025-05-28 19:52:11 +00:00
if ( ! _actionsQuery . TryComp ( user , out var component ) )
2025-09-08 05:55:13 -04:00
return false ;
2022-02-26 18:24:08 +13:00
2023-09-11 09:42:41 +10:00
var actionEnt = GetEntity ( ev . Action ) ;
if ( ! TryComp ( actionEnt , out MetaDataComponent ? metaData ) )
2025-09-08 05:55:13 -04:00
return false ;
2023-09-08 18:16:05 -07:00
2023-09-11 09:42:41 +10:00
var name = Name ( actionEnt , metaData ) ;
2023-09-08 18:16:05 -07:00
2022-02-26 18:24:08 +13:00
// Does the user actually have the requested action?
2023-09-11 09:42:41 +10:00
if ( ! component . Actions . Contains ( actionEnt ) )
2022-02-26 18:24:08 +13:00
{
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . Action ,
2023-09-08 18:16:05 -07:00
$"{ToPrettyString(user):user} attempted to perform an action that they do not have: {name}." ) ;
2025-09-08 05:55:13 -04:00
return false ;
2022-02-26 18:24:08 +13:00
}
2025-05-28 19:52:11 +00:00
if ( GetAction ( actionEnt ) is not { } action )
2025-09-08 05:55:13 -04:00
return false ;
2023-09-23 04:49:39 -04:00
2025-05-28 19:52:11 +00:00
DebugTools . Assert ( action . Comp . AttachedEntity = = user ) ;
if ( ! action . Comp . Enabled )
2025-09-08 05:55:13 -04:00
return false ;
2022-02-26 18:24:08 +13:00
2025-04-18 13:45:48 +10:00
var curTime = GameTiming . CurTime ;
if ( IsCooldownActive ( action , curTime ) )
2025-09-08 05:55:13 -04:00
return false ;
2025-04-18 13:45:48 +10:00
2024-03-01 02:48:43 +00:00
// check for action use prevention
var attemptEv = new ActionAttemptEvent ( user ) ;
2025-05-28 19:52:11 +00:00
RaiseLocalEvent ( action , ref attemptEv ) ;
2024-03-01 02:48:43 +00:00
if ( attemptEv . Cancelled )
2025-09-08 05:55:13 -04:00
return false ;
2024-03-01 02:48:43 +00:00
2025-05-28 19:52:11 +00:00
// Validate request by checking action blockers and the like
var provider = action . Comp . Container ? ? user ;
var validateEv = new ActionValidateEvent ( )
{
Input = ev ,
User = user ,
Provider = provider
} ;
RaiseLocalEvent ( action , ref validateEv ) ;
if ( validateEv . Invalid )
2025-09-08 05:55:13 -04:00
return false ;
if ( TryComp < DoAfterArgsComponent > ( action , out var actionDoAfterComp ) & & TryComp < DoAfterComponent > ( user , out var performerDoAfterComp ) & & ! skipDoActionRequest )
{
return TryStartActionDoAfter ( ( action , actionDoAfterComp ) , ( user , performerDoAfterComp ) , action . Comp . UseDelay , ev ) ;
}
2025-05-28 19:52:11 +00:00
// All checks passed. Perform the action!
PerformAction ( ( user , component ) , action ) ;
2025-09-08 05:55:13 -04:00
return true ;
2025-05-28 19:52:11 +00:00
}
2022-02-26 18:24:08 +13:00
2025-05-28 19:52:11 +00:00
private void OnValidate ( Entity < ActionComponent > ent , ref ActionValidateEvent args )
{
2025-05-29 21:30:26 -07:00
if ( ( ent . Comp . CheckConsciousness & & ! _actionBlocker . CanConsciouslyPerformAction ( args . User ) )
| | ( ent . Comp . CheckCanInteract & & ! _actionBlocker . CanInteract ( args . User , null ) ) )
2025-05-28 19:52:11 +00:00
args . Invalid = true ;
2022-02-26 18:24:08 +13:00
}
2025-05-28 19:52:11 +00:00
private void OnInstantValidate ( Entity < InstantActionComponent > ent , ref ActionValidateEvent args )
2024-04-22 01:39:50 -07:00
{
2025-05-28 19:52:11 +00:00
_adminLogger . Add ( LogType . Action ,
$"{ToPrettyString(args.User):user} is performing the {Name(ent):action} action provided by {ToPrettyString(args.Provider):provider}." ) ;
}
private void OnEntityValidate ( Entity < EntityTargetActionComponent > ent , ref ActionValidateEvent args )
{
// let WorldTargetAction handle it
if ( ent . Comp . Event is not { } ev )
{
DebugTools . Assert ( HasComp < WorldTargetActionComponent > ( ent ) , $"Entity-world targeting action {ToPrettyString(ent)} requires WorldTargetActionComponent" ) ;
return ;
}
if ( args . Input . EntityTarget is not { } netTarget )
{
args . Invalid = true ;
return ;
}
var user = args . User ;
var target = GetEntity ( netTarget ) ;
var targetWorldPos = _transform . GetWorldPosition ( target ) ;
2025-05-30 05:56:16 -07:00
if ( ent . Comp . RotateOnUse )
_rotateToFace . TryFaceCoordinates ( user , targetWorldPos ) ;
2025-05-28 19:52:11 +00:00
if ( ! ValidateEntityTarget ( user , target , ent ) )
return ;
_adminLogger . Add ( LogType . Action ,
$"{ToPrettyString(user):user} is performing the {Name(ent):action} action (provided by {ToPrettyString(args.Provider):provider}) targeted at {ToPrettyString(target):target}." ) ;
2024-04-22 01:39:50 -07:00
2025-05-28 19:52:11 +00:00
ev . Target = target ;
2024-04-22 01:39:50 -07:00
}
2025-05-28 19:52:11 +00:00
private void OnWorldValidate ( Entity < WorldTargetActionComponent > ent , ref ActionValidateEvent args )
2022-02-26 18:24:08 +13:00
{
2025-05-28 19:52:11 +00:00
if ( args . Input . EntityCoordinatesTarget is not { } netTarget )
{
args . Invalid = true ;
return ;
}
var user = args . User ;
var target = GetCoordinates ( netTarget ) ;
2025-05-30 05:56:16 -07:00
if ( ent . Comp . RotateOnUse )
_rotateToFace . TryFaceCoordinates ( user , _transform . ToMapCoordinates ( target ) . Position ) ;
2025-05-28 19:52:11 +00:00
if ( ! ValidateWorldTarget ( user , target , ent ) )
return ;
// if the client specified an entity it needs to be valid
var targetEntity = GetEntity ( args . Input . EntityTarget ) ;
if ( targetEntity ! = null & & (
! TryComp < EntityTargetActionComponent > ( ent , out var entTarget ) | |
! ValidateEntityTarget ( user , targetEntity . Value , ( ent , entTarget ) ) ) )
{
args . Invalid = true ;
return ;
}
_adminLogger . Add ( LogType . Action ,
$"{ToPrettyString(user):user} is performing the {Name(ent):action} action (provided by {args.Provider}) targeting {targetEntity} at {target:target}." ) ;
if ( ent . Comp . Event is { } ev )
{
ev . Target = target ;
ev . Entity = targetEntity ;
}
}
public bool ValidateEntityTarget ( EntityUid user , EntityUid target , Entity < EntityTargetActionComponent > ent )
{
var ( uid , comp ) = ent ;
if ( ! target . IsValid ( ) | | Deleted ( target ) )
2022-02-26 18:24:08 +13:00
return false ;
2025-05-28 19:52:11 +00:00
if ( _whitelist . IsWhitelistFail ( comp . Whitelist , target ) )
2022-02-26 18:24:08 +13:00
return false ;
2025-05-28 19:52:11 +00:00
if ( _whitelist . IsBlacklistPass ( comp . Blacklist , target ) )
2025-02-12 23:46:02 -05:00
return false ;
2025-05-28 19:52:11 +00:00
if ( _actionQuery . Comp ( uid ) . CheckCanInteract & & ! _actionBlocker . CanInteract ( user , target ) )
2022-02-26 18:24:08 +13:00
return false ;
if ( user = = target )
2025-05-28 19:52:11 +00:00
return comp . CanTargetSelf ;
2022-02-26 18:24:08 +13:00
2025-05-28 19:52:11 +00:00
var targetAction = Comp < TargetActionComponent > ( uid ) ;
2025-07-22 15:34:39 -04:00
2025-05-31 20:29:51 +00:00
// not using the ValidateBaseTarget logic since its raycast fails if the target is e.g. a wall
if ( targetAction . CheckCanAccess )
2025-07-22 15:34:39 -04:00
return _interaction . InRangeAndAccessible ( user , target , targetAction . Range , targetAction . AccessMask ) ;
// Just check normal in range, allowing <= 0 range to mean infinite range.
if ( targetAction . Range > 0
& & ! _transform . InRange ( user , target , targetAction . Range ) )
return false ;
2022-02-26 18:24:08 +13:00
2025-07-22 15:34:39 -04:00
// If checkCanAccess isn't set, we allow targeting things in containers
return _interaction . IsAccessible ( user , target ) ;
2022-02-26 18:24:08 +13:00
}
2025-05-28 19:52:11 +00:00
public bool ValidateWorldTarget ( EntityUid user , EntityCoordinates target , Entity < WorldTargetActionComponent > ent )
2024-04-22 01:39:50 -07:00
{
2025-05-28 19:52:11 +00:00
var targetAction = Comp < TargetActionComponent > ( ent ) ;
return ValidateBaseTarget ( user , target , ( ent , targetAction ) ) ;
2024-04-22 01:39:50 -07:00
}
2025-05-28 19:52:11 +00:00
private bool ValidateBaseTarget ( EntityUid user , EntityCoordinates coords , Entity < TargetActionComponent > ent )
2022-02-26 18:24:08 +13:00
{
2025-05-31 20:29:51 +00:00
var comp = ent . Comp ;
2025-05-28 19:52:11 +00:00
if ( comp . CheckCanAccess )
return _interaction . InRangeUnobstructed ( user , coords , range : comp . Range ) ;
2024-08-08 02:47:08 -07:00
2025-05-28 19:52:11 +00:00
// even if we don't check for obstructions, we may still need to check the range.
var xform = Transform ( user ) ;
if ( xform . MapID ! = _transform . GetMapId ( coords ) )
2022-02-26 18:24:08 +13:00
return false ;
2025-05-28 19:52:11 +00:00
if ( comp . Range < = 0 )
return true ;
2022-02-26 18:24:08 +13:00
2025-05-28 19:52:11 +00:00
return _transform . InRange ( coords , xform . Coordinates , comp . Range ) ;
}
2022-02-26 18:24:08 +13:00
2025-05-28 19:52:11 +00:00
private void OnInstantGetEvent ( Entity < InstantActionComponent > ent , ref ActionGetEventEvent args )
{
if ( ent . Comp . Event is { } ev )
args . Event = ev ;
}
2022-02-26 18:24:08 +13:00
2025-05-28 19:52:11 +00:00
private void OnEntityGetEvent ( Entity < EntityTargetActionComponent > ent , ref ActionGetEventEvent args )
{
if ( ent . Comp . Event is { } ev )
args . Event = ev ;
}
private void OnWorldGetEvent ( Entity < WorldTargetActionComponent > ent , ref ActionGetEventEvent args )
{
if ( ent . Comp . Event is { } ev )
args . Event = ev ;
2024-08-08 02:47:08 -07:00
}
2025-05-28 19:52:11 +00:00
private void OnInstantSetEvent ( Entity < InstantActionComponent > ent , ref ActionSetEventEvent args )
2024-08-08 02:47:08 -07:00
{
2025-05-28 19:52:11 +00:00
if ( args . Event is InstantActionEvent ev )
{
ent . Comp . Event = ev ;
args . Handled = true ;
}
}
2024-08-08 02:47:08 -07:00
2025-05-28 19:52:11 +00:00
private void OnEntitySetEvent ( Entity < EntityTargetActionComponent > ent , ref ActionSetEventEvent args )
{
if ( args . Event is EntityTargetActionEvent ev )
{
ent . Comp . Event = ev ;
args . Handled = true ;
}
}
2024-08-08 02:47:08 -07:00
2025-05-28 19:52:11 +00:00
private void OnWorldSetEvent ( Entity < WorldTargetActionComponent > ent , ref ActionSetEventEvent args )
{
if ( args . Event is WorldTargetActionEvent ev )
{
ent . Comp . Event = ev ;
args . Handled = true ;
}
}
2024-08-08 02:47:08 -07:00
2025-05-28 19:52:11 +00:00
private void OnEntitySetTarget ( Entity < EntityTargetActionComponent > ent , ref ActionSetTargetEvent args )
{
if ( ent . Comp . Event is { } ev )
{
ev . Target = args . Target ;
args . Handled = true ;
}
2022-02-26 18:24:08 +13:00
}
2025-05-28 19:52:11 +00:00
private void OnWorldSetTarget ( Entity < WorldTargetActionComponent > ent , ref ActionSetTargetEvent args )
{
if ( ent . Comp . Event is { } ev )
{
ev . Target = Transform ( args . Target ) . Coordinates ;
// only set Entity if the action also has EntityTargetAction
ev . Entity = HasComp < EntityTargetActionComponent > ( ent ) ? args . Target : null ;
args . Handled = true ;
}
}
/// <summary>
/// Perform an action, bypassing validation checks.
/// </summary>
/// <param name="performer">The entity performing the action</param>
/// <param name="action">The action being performed</param>
/// <param name="actionEvent">An event override to perform. If null, uses <see cref="GetEvent"/></param>
/// <param name="predicted">If false, prevents playing the action's sound on the client</param>
public void PerformAction ( Entity < ActionsComponent ? > performer , Entity < ActionComponent > action , BaseActionEvent ? actionEvent = null , bool predicted = true )
2022-02-26 18:24:08 +13:00
{
var handled = false ;
2023-11-03 19:55:32 -04:00
// Note that attached entity and attached container are allowed to be null here.
2025-05-28 19:52:11 +00:00
if ( action . Comp . AttachedEntity ! = null & & action . Comp . AttachedEntity ! = performer )
2023-09-23 04:49:39 -04:00
{
2025-05-28 19:52:11 +00:00
Log . Error ( $"{ToPrettyString(performer)} is attempting to perform an action {ToPrettyString(action)} that is attached to another entity {ToPrettyString(action.Comp.AttachedEntity)}" ) ;
2023-09-23 04:49:39 -04:00
return ;
}
2025-05-28 19:52:11 +00:00
actionEvent ? ? = GetEvent ( action ) ;
2023-11-03 19:55:32 -04:00
2025-05-28 19:52:11 +00:00
if ( actionEvent is not { } ev )
return ;
2023-11-03 19:55:32 -04:00
2025-05-28 19:52:11 +00:00
ev . Performer = performer ;
2025-02-08 22:56:08 +01:00
2025-05-28 19:52:11 +00:00
// This here is required because of client-side prediction (RaisePredictiveEvent results in event re-use).
ev . Handled = false ;
var target = performer . Owner ;
ev . Performer = performer ;
ev . Action = action ;
2025-09-08 05:55:13 -04:00
// TODO: This is where we'd add support for event lists
2025-05-28 19:52:11 +00:00
if ( ! action . Comp . RaiseOnUser & & action . Comp . Container is { } container & & ! _mindQuery . HasComp ( container ) )
target = container ;
if ( action . Comp . RaiseOnAction )
target = action ;
RaiseLocalEvent ( target , ( object ) ev , broadcast : true ) ;
handled = ev . Handled ;
2022-02-26 18:24:08 +13:00
if ( ! handled )
return ; // no interaction occurred.
2025-09-08 05:55:13 -04:00
// play sound, start cooldown
if ( ev . Toggle )
2025-05-28 19:52:11 +00:00
SetToggled ( ( action , action ) , ! action . Comp . Toggled ) ;
2022-02-26 18:24:08 +13:00
2025-05-28 19:52:11 +00:00
_audio . PlayPredicted ( action . Comp . Sound , performer , predicted ? performer : null ) ;
2022-02-26 18:24:08 +13:00
2025-05-28 19:52:11 +00:00
RemoveCooldown ( ( action , action ) ) ;
StartUseDelay ( ( action , action ) ) ;
2022-02-26 18:24:08 +13:00
2025-05-28 19:52:11 +00:00
UpdateAction ( action ) ;
2024-05-10 17:04:01 -07:00
2025-05-28 19:52:11 +00:00
var performed = new ActionPerformedEvent ( performer ) ;
RaiseLocalEvent ( action , ref performed ) ;
2022-02-26 18:24:08 +13:00
}
#endregion
#region AddRemoveActions
2023-09-23 04:49:39 -04:00
public EntityUid ? AddAction ( EntityUid performer ,
2025-06-20 18:11:12 -04:00
[ForbidLiteral] string? actionPrototypeId ,
2023-09-23 04:49:39 -04:00
EntityUid container = default ,
ActionsComponent ? component = null )
{
EntityUid ? actionId = null ;
AddAction ( performer , ref actionId , out _ , actionPrototypeId , container , component ) ;
return actionId ;
}
2022-02-26 18:24:08 +13:00
/// <summary>
2023-09-23 04:49:39 -04:00
/// Adds an action to an action holder. If the given entity does not exist, it will attempt to spawn one.
2023-09-08 18:16:05 -07:00
/// If the holder has no actions component, this will give them one.
2022-02-26 18:24:08 +13:00
/// </summary>
2023-09-23 04:49:39 -04:00
/// <param name="performer">Entity to receive the actions</param>
/// <param name="actionId">Action entity to add</param>
/// <param name="component">The <see cref="performer"/>'s action component of </param>
/// <param name="actionPrototypeId">The action entity prototype id to use if <see cref="actionId"/> is invalid.</param>
2024-01-14 10:52:07 +03:00
/// <param name="container">The entity that contains/enables this action (e.g., flashlight).</param>
2023-09-23 04:49:39 -04:00
public bool AddAction ( EntityUid performer ,
[NotNullWhen(true)] ref EntityUid ? actionId ,
2025-06-20 18:11:12 -04:00
[ForbidLiteral] string? actionPrototypeId ,
2023-09-23 04:49:39 -04:00
EntityUid container = default ,
ActionsComponent ? component = null )
{
return AddAction ( performer , ref actionId , out _ , actionPrototypeId , container , component ) ;
}
2025-05-30 05:56:16 -07:00
/// <inheritdoc cref="AddAction(Robust.Shared.GameObjects.EntityUid,ref System.Nullable{Robust.Shared.GameObjects.EntityUid},string?,Robust.Shared.GameObjects.EntityUid,ActionsComponent?)"/>
2023-09-23 04:49:39 -04:00
public bool AddAction ( EntityUid performer ,
[NotNullWhen(true)] ref EntityUid ? actionId ,
2025-05-28 19:52:11 +00:00
[NotNullWhen(true)] out ActionComponent ? action ,
2025-06-20 18:11:12 -04:00
[ForbidLiteral] string? actionPrototypeId ,
2023-09-23 04:49:39 -04:00
EntityUid container = default ,
ActionsComponent ? component = null )
{
if ( ! container . IsValid ( ) )
container = performer ;
if ( ! _actionContainer . EnsureAction ( container , ref actionId , out action , actionPrototypeId ) )
return false ;
2023-09-08 18:16:05 -07:00
2025-05-28 19:52:11 +00:00
return AddActionDirect ( ( performer , component ) , ( actionId . Value , action ) ) ;
2023-09-08 18:16:05 -07:00
}
/// <summary>
2023-09-23 04:49:39 -04:00
/// Adds a pre-existing action.
2023-09-08 18:16:05 -07:00
/// </summary>
2025-05-28 19:52:11 +00:00
public bool AddAction ( Entity < ActionsComponent ? > performer ,
Entity < ActionComponent ? > action ,
Entity < ActionsContainerComponent ? > container )
{
if ( GetAction ( action ) is not { } ent )
2023-09-23 04:49:39 -04:00
return false ;
2025-05-28 19:52:11 +00:00
if ( ent . Comp . Container ! = container . Owner
| | ! Resolve ( container , ref container . Comp )
| | ! container . Comp . Container . Contains ( ent ) )
2022-04-14 16:17:34 +12:00
{
2025-05-28 19:52:11 +00:00
Log . Error ( $"Attempted to add an action with an invalid container: {ToPrettyString(ent)}" ) ;
2023-09-23 04:49:39 -04:00
return false ;
2022-04-14 16:17:34 +12:00
}
2025-05-28 19:52:11 +00:00
return AddActionDirect ( performer , ( ent , ent ) ) ;
2023-09-23 04:49:39 -04:00
}
2023-09-08 18:16:05 -07:00
2023-09-23 04:49:39 -04:00
/// <summary>
/// Adds a pre-existing action. This also bypasses the requirement that the given action must be stored in a
/// valid action container.
/// </summary>
2025-05-28 19:52:11 +00:00
public bool AddActionDirect ( Entity < ActionsComponent ? > performer ,
Entity < ActionComponent ? > ? action )
2023-09-23 04:49:39 -04:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( action ) is not { } ent )
2023-09-23 04:49:39 -04:00
return false ;
2025-05-28 19:52:11 +00:00
DebugTools . Assert ( ent . Comp . Container = = null | |
( TryComp ( ent . Comp . Container , out ActionsContainerComponent ? containerComp )
& & containerComp . Container . Contains ( ent ) ) ) ;
2022-02-26 18:24:08 +13:00
2025-05-28 19:52:11 +00:00
if ( ent . Comp . AttachedEntity is { } user )
RemoveAction ( user , ( ent , ent ) ) ;
2023-10-24 11:53:27 +11:00
2025-05-28 19:52:11 +00:00
// TODO: make this an event bruh
if ( ent . Comp . StartDelay & & ent . Comp . UseDelay ! = null )
SetCooldown ( ( ent , ent ) , ent . Comp . UseDelay . Value ) ;
2024-10-28 23:21:14 +01:00
2025-05-28 19:52:11 +00:00
DebugTools . AssertOwner ( performer , performer . Comp ) ;
performer . Comp ? ? = EnsureComp < ActionsComponent > ( performer ) ;
ent . Comp . AttachedEntity = performer ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . AttachedEntity ) ) ;
performer . Comp . Actions . Add ( ent ) ;
Dirty ( performer , performer . Comp ) ;
ActionAdded ( ( performer , performer . Comp ) , ( ent , ent . Comp ) ) ;
2023-09-23 04:49:39 -04:00
return true ;
2022-02-26 18:24:08 +13:00
}
2023-09-23 04:49:39 -04:00
/// <summary>
/// This method gets called after a new action got added.
/// </summary>
2025-05-28 19:52:11 +00:00
protected virtual void ActionAdded ( Entity < ActionsComponent > performer , Entity < ActionComponent > action )
2022-02-26 18:24:08 +13:00
{
2023-09-23 04:49:39 -04:00
// See client-side system for UI code.
2022-02-26 18:24:08 +13:00
}
/// <summary>
2023-09-23 04:49:39 -04:00
/// Grant pre-existing actions. If the entity has no action component, this will give them one.
2022-02-26 18:24:08 +13:00
/// </summary>
2023-09-23 04:49:39 -04:00
/// <param name="performer">Entity to receive the actions</param>
2022-02-26 18:24:08 +13:00
/// <param name="actions">The actions to add</param>
2023-09-23 04:49:39 -04:00
/// <param name="container">The entity that enables these actions (e.g., flashlight). May be null (innate actions).</param>
2025-05-28 19:52:11 +00:00
public void GrantActions ( Entity < ActionsComponent ? > performer ,
IEnumerable < EntityUid > actions ,
Entity < ActionsContainerComponent ? > container )
2022-02-26 18:24:08 +13:00
{
2025-05-28 19:52:11 +00:00
if ( ! Resolve ( container , ref container . Comp ) )
2023-09-23 04:49:39 -04:00
return ;
2022-02-26 18:24:08 +13:00
2025-05-28 19:52:11 +00:00
DebugTools . AssertOwner ( performer , performer . Comp ) ;
performer . Comp ? ? = EnsureComp < ActionsComponent > ( performer ) ;
2023-04-23 21:38:52 +02:00
2023-09-08 18:16:05 -07:00
foreach ( var actionId in actions )
2022-02-26 18:24:08 +13:00
{
2025-05-28 19:52:11 +00:00
AddAction ( performer , actionId , container ) ;
2022-02-26 18:24:08 +13:00
}
2023-09-08 18:16:05 -07:00
}
2023-10-24 11:53:27 +11:00
/// <summary>
/// Grants all actions currently contained in some action-container. If the target entity has no action
/// component, this will give them one.
/// </summary>
/// <param name="performer">Entity to receive the actions</param>
/// <param name="container">The entity that contains thee actions.</param>
public void GrantContainedActions ( Entity < ActionsComponent ? > performer , Entity < ActionsContainerComponent ? > container )
{
if ( ! Resolve ( container , ref container . Comp ) )
return ;
performer . Comp ? ? = EnsureComp < ActionsComponent > ( performer ) ;
foreach ( var actionId in container . Comp . Container . ContainedEntities )
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( actionId ) is { } action )
AddActionDirect ( performer , ( action , action ) ) ;
2023-10-24 11:53:27 +11:00
}
}
2024-01-15 01:37:38 -05:00
/// <summary>
/// Grants the provided action from the container to the target entity. If the target entity has no action
/// component, this will give them one.
/// </summary>
/// <param name="performer"></param>
/// <param name="container"></param>
/// <param name="actionId"></param>
public void GrantContainedAction ( Entity < ActionsComponent ? > performer , Entity < ActionsContainerComponent ? > container , EntityUid actionId )
{
if ( ! Resolve ( container , ref container . Comp ) )
return ;
performer . Comp ? ? = EnsureComp < ActionsComponent > ( performer ) ;
2025-05-28 19:52:11 +00:00
AddActionDirect ( performer , actionId ) ;
2024-01-15 01:37:38 -05:00
}
2025-05-28 19:52:11 +00:00
public IEnumerable < Entity < ActionComponent > > GetActions ( EntityUid holderId , ActionsComponent ? actions = null )
2023-09-08 18:16:05 -07:00
{
2023-09-09 16:14:17 -07:00
if ( ! Resolve ( holderId , ref actions , false ) )
2023-09-08 18:16:05 -07:00
yield break ;
2023-09-09 16:14:17 -07:00
foreach ( var actionId in actions . Actions )
2023-09-08 18:16:05 -07:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( actionId ) is not { } ent )
2023-09-08 18:16:05 -07:00
continue ;
2025-05-28 19:52:11 +00:00
yield return ent ;
2023-09-08 18:16:05 -07:00
}
2022-02-26 18:24:08 +13:00
}
/// <summary>
/// Remove any actions that were enabled by some other entity. Useful when unequiping items that grant actions.
/// </summary>
2023-09-23 04:49:39 -04:00
public void RemoveProvidedActions ( EntityUid performer , EntityUid container , ActionsComponent ? comp = null )
2022-02-26 18:24:08 +13:00
{
2023-09-23 04:49:39 -04:00
if ( ! Resolve ( performer , ref comp , false ) )
2023-09-08 18:16:05 -07:00
return ;
2023-09-23 04:49:39 -04:00
foreach ( var actionId in comp . Actions . ToArray ( ) )
2023-04-25 07:29:47 +12:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( actionId ) is not { } ent )
2023-09-23 04:49:39 -04:00
return ;
2023-09-08 18:16:05 -07:00
2025-05-28 19:52:11 +00:00
if ( ent . Comp . Container = = container )
RemoveAction ( ( performer , comp ) , ( ent , ent ) ) ;
2023-09-23 04:49:39 -04:00
}
2022-02-26 18:24:08 +13:00
}
2024-01-15 01:37:38 -05:00
/// <summary>
/// Removes a single provided action provided by another entity.
/// </summary>
public void RemoveProvidedAction ( EntityUid performer , EntityUid container , EntityUid actionId , ActionsComponent ? comp = null )
{
2025-05-28 19:52:11 +00:00
if ( ! _actionsQuery . Resolve ( performer , ref comp , false ) | | GetAction ( actionId ) is not { } ent )
2024-01-15 01:37:38 -05:00
return ;
2025-05-28 19:52:11 +00:00
if ( ent . Comp . Container = = container )
RemoveAction ( ( performer , comp ) , ( ent , ent ) ) ;
2024-01-15 01:37:38 -05:00
}
2025-05-28 19:52:11 +00:00
/// <summary>
/// Removes an action from its container, if it still exists.
/// </summary>
public void RemoveAction ( Entity < ActionComponent ? > ? action )
2022-02-26 18:24:08 +13:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( action ) is not { } ent | | ent . Comp . AttachedEntity is not { } actions )
2023-09-23 04:49:39 -04:00
return ;
2022-02-26 18:24:08 +13:00
2025-05-28 19:52:11 +00:00
if ( ! _actionsQuery . TryComp ( actions , out var comp ) )
2023-09-23 04:49:39 -04:00
return ;
2023-09-09 16:14:17 -07:00
2025-05-28 19:52:11 +00:00
RemoveAction ( ( actions , comp ) , ( ent , ent ) ) ;
2023-09-08 18:16:05 -07:00
}
2025-05-28 19:52:11 +00:00
public void RemoveAction ( Entity < ActionsComponent ? > performer , Entity < ActionComponent ? > ? action )
2023-09-08 18:16:05 -07:00
{
2025-05-28 19:52:11 +00:00
if ( GetAction ( action ) is not { } ent )
2023-09-08 18:16:05 -07:00
return ;
2025-05-28 19:52:11 +00:00
if ( ent . Comp . AttachedEntity ! = performer . Owner )
2023-10-12 04:50:10 +11:00
{
2025-05-28 19:52:11 +00:00
DebugTools . Assert ( ! Resolve ( performer , ref performer . Comp , false )
| | performer . Comp . LifeStage > = ComponentLifeStage . Stopping
| | ! performer . Comp . Actions . Contains ( ent . Owner ) ) ;
2023-10-29 19:10:30 +11:00
if ( ! GameTiming . ApplyingState )
2025-05-28 19:52:11 +00:00
Log . Error ( $"Attempted to remove an action {ToPrettyString(ent)} from an entity that it was never attached to: {ToPrettyString(performer)}. Trace: {Environment.StackTrace}" ) ;
2023-10-12 04:50:10 +11:00
return ;
}
2025-05-28 19:52:11 +00:00
if ( ! _actionsQuery . Resolve ( performer , ref performer . Comp , false ) )
2023-09-23 04:49:39 -04:00
{
2025-05-28 19:52:11 +00:00
DebugTools . Assert ( performer = = null | | TerminatingOrDeleted ( performer ) ) ;
ent . Comp . AttachedEntity = null ;
// TODO: should this delete the action since it's now orphaned?
2023-09-09 16:14:17 -07:00
return ;
2023-09-23 04:49:39 -04:00
}
2023-09-09 16:14:17 -07:00
2025-05-28 19:52:11 +00:00
performer . Comp . Actions . Remove ( ent . Owner ) ;
Dirty ( performer , performer . Comp ) ;
ent . Comp . AttachedEntity = null ;
2025-08-20 18:33:14 +02:00
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . AttachedEntity ) ) ;
2025-05-28 19:52:11 +00:00
ActionRemoved ( ( performer , performer . Comp ) , ent ) ;
2023-09-23 04:49:39 -04:00
2025-05-28 19:52:11 +00:00
if ( ent . Comp . Temporary )
QueueDel ( ent ) ;
2023-09-23 04:49:39 -04:00
}
/// <summary>
/// This method gets called after an action got removed.
/// </summary>
2025-05-28 19:52:11 +00:00
protected virtual void ActionRemoved ( Entity < ActionsComponent > performer , Entity < ActionComponent > action )
2023-09-23 04:49:39 -04:00
{
// See client-side system for UI code.
2022-02-26 18:24:08 +13:00
}
2025-05-28 19:52:11 +00:00
public bool ValidAction ( Entity < ActionComponent > ent , bool canReach = true )
2024-08-18 12:22:36 -04:00
{
2025-05-28 19:52:11 +00:00
var ( uid , comp ) = ent ;
if ( ! comp . Enabled )
2024-08-18 12:22:36 -04:00
return false ;
var curTime = GameTiming . CurTime ;
2025-05-28 19:52:11 +00:00
if ( comp . Cooldown . HasValue & & comp . Cooldown . Value . End > curTime )
2024-08-18 12:22:36 -04:00
return false ;
2025-05-28 19:52:11 +00:00
// TODO: use event for this
return canReach | | Comp < TargetActionComponent > ( ent ) ? . CheckCanAccess = = false ;
2024-08-18 12:22:36 -04:00
}
2022-02-26 18:24:08 +13:00
#endregion
2024-08-25 22:43:31 +10:00
private void OnRelayActionCompChange ( Entity < ActionsComponent > ent , ref RelayedActionComponentChangeEvent args )
{
if ( args . Handled )
return ;
var ev = new AttemptRelayActionComponentChangeEvent ( ) ;
RaiseLocalEvent ( ent . Owner , ref ev ) ;
var target = ev . Target ? ? ent . Owner ;
args . Handled = true ;
args . Toggle = true ;
if ( ! args . Action . Comp . Toggled )
{
EntityManager . AddComponents ( target , args . Components ) ;
}
else
{
EntityManager . RemoveComponents ( target , args . Components ) ;
}
}
private void OnActionCompChange ( Entity < ActionsComponent > ent , ref ActionComponentChangeEvent args )
{
if ( args . Handled )
return ;
args . Handled = true ;
args . Toggle = true ;
var target = ent . Owner ;
if ( ! args . Action . Comp . Toggled )
{
EntityManager . AddComponents ( target , args . Components ) ;
}
else
{
EntityManager . RemoveComponents ( target , args . Components ) ;
}
}
2022-02-26 18:24:08 +13:00
#region EquipHandlers
2025-05-28 19:52:11 +00:00
private void OnDidEquip ( Entity < ActionsComponent > ent , ref DidEquipEvent args )
2022-02-26 18:24:08 +13:00
{
2023-09-23 04:49:39 -04:00
if ( GameTiming . ApplyingState )
return ;
var ev = new GetItemActionsEvent ( _actionContainer , args . Equipee , args . Equipment , args . SlotFlags ) ;
2023-05-01 04:29:18 -04:00
RaiseLocalEvent ( args . Equipment , ev ) ;
2022-02-26 18:24:08 +13:00
if ( ev . Actions . Count = = 0 )
return ;
2025-05-28 19:52:11 +00:00
GrantActions ( ( ent , ent ) , ev . Actions , args . Equipment ) ;
2022-02-26 18:24:08 +13:00
}
2025-05-28 19:52:11 +00:00
private void OnHandEquipped ( Entity < ActionsComponent > ent , ref DidEquipHandEvent args )
2022-02-26 18:24:08 +13:00
{
2023-09-23 04:49:39 -04:00
if ( GameTiming . ApplyingState )
return ;
var ev = new GetItemActionsEvent ( _actionContainer , args . User , args . Equipped ) ;
2023-05-01 04:29:18 -04:00
RaiseLocalEvent ( args . Equipped , ev ) ;
2022-02-26 18:24:08 +13:00
if ( ev . Actions . Count = = 0 )
return ;
2025-05-28 19:52:11 +00:00
GrantActions ( ( ent , ent ) , ev . Actions , args . Equipped ) ;
2022-02-26 18:24:08 +13:00
}
private void OnDidUnequip ( EntityUid uid , ActionsComponent component , DidUnequipEvent args )
{
2023-09-23 04:49:39 -04:00
if ( GameTiming . ApplyingState )
return ;
2022-02-26 18:24:08 +13:00
RemoveProvidedActions ( uid , args . Equipment , component ) ;
}
private void OnHandUnequipped ( EntityUid uid , ActionsComponent component , DidUnequipHandEvent args )
{
2023-09-23 04:49:39 -04:00
if ( GameTiming . ApplyingState )
return ;
2022-02-26 18:24:08 +13:00
RemoveProvidedActions ( uid , args . Unequipped , component ) ;
}
#endregion
2023-09-23 04:49:39 -04:00
2025-05-28 19:52:11 +00:00
public void SetEntityIcon ( Entity < ActionComponent ? > ent , EntityUid ? icon )
{
if ( ! _actionQuery . Resolve ( ent , ref ent . Comp ) | | ent . Comp . EntityIcon = = icon )
return ;
ent . Comp . EntityIcon = icon ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . EntIcon ) ) ;
}
public void SetIcon ( Entity < ActionComponent ? > ent , SpriteSpecifier ? icon )
{
if ( ! _actionQuery . Resolve ( ent , ref ent . Comp ) | | ent . Comp . Icon = = icon )
return ;
ent . Comp . Icon = icon ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . Icon ) ) ;
}
public void SetIconOn ( Entity < ActionComponent ? > ent , SpriteSpecifier ? iconOn )
{
if ( ! _actionQuery . Resolve ( ent , ref ent . Comp ) | | ent . Comp . IconOn = = iconOn )
return ;
ent . Comp . IconOn = iconOn ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . IconOn ) ) ;
}
public void SetIconColor ( Entity < ActionComponent ? > ent , Color color )
2023-09-23 04:49:39 -04:00
{
2025-05-28 19:52:11 +00:00
if ( ! _actionQuery . Resolve ( ent , ref ent . Comp ) | | ent . Comp . IconColor = = color )
2023-09-23 04:49:39 -04:00
return ;
2025-05-28 19:52:11 +00:00
ent . Comp . IconColor = color ;
DirtyField ( ent , ent . Comp , nameof ( ActionComponent . IconColor ) ) ;
}
/// <summary>
/// Set the event of an action.
/// Since the event isn't required to be serializable this is not networked.
/// Only use this if it's predicted or for a clientside action.
/// </summary>
public void SetEvent ( EntityUid uid , BaseActionEvent ev )
{
// now this is meta
var setEv = new ActionSetEventEvent ( ev ) ;
RaiseLocalEvent ( uid , ref setEv ) ;
if ( ! setEv . Handled )
Log . Error ( $"Tried to set event of {ToPrettyString(uid):action} but nothing handled it!" ) ;
}
public BaseActionEvent ? GetEvent ( EntityUid uid )
{
DebugTools . Assert ( _actionQuery . HasComp ( uid ) , $"Entity {ToPrettyString(uid)} is missing ActionComponent" ) ;
var ev = new ActionGetEventEvent ( ) ;
RaiseLocalEvent ( uid , ref ev ) ;
return ev . Event ;
}
public bool SetEventTarget ( EntityUid uid , EntityUid target )
{
DebugTools . Assert ( _actionQuery . HasComp ( uid ) , $"Entity {ToPrettyString(uid)} is missing ActionComponent" ) ;
var ev = new ActionSetTargetEvent ( target ) ;
RaiseLocalEvent ( uid , ref ev ) ;
return ev . Handled ;
2023-09-23 04:49:39 -04:00
}
2024-09-25 10:27:28 -04:00
/// <summary>
/// Checks if the action has a cooldown and if it's still active
/// </summary>
2025-05-28 19:52:11 +00:00
public bool IsCooldownActive ( ActionComponent action , TimeSpan ? curTime = null )
2024-09-25 10:27:28 -04:00
{
// TODO: Check for charge recovery timer
2025-08-02 15:59:48 -07:00
curTime ? ? = GameTiming . CurTime ;
2024-09-25 10:27:28 -04:00
return action . Cooldown . HasValue & & action . Cooldown . Value . End > curTime ;
}
2025-06-16 12:25:44 +02:00
/// <summary>
/// Marks the action as temporary.
/// Temporary actions get deleted upon being removed from an entity.
/// </summary>
public void SetTemporary ( Entity < ActionComponent ? > ent , bool temporary )
{
if ( ! Resolve ( ent . Owner , ref ent . Comp , false ) )
return ;
ent . Comp . Temporary = temporary ;
Dirty ( ent ) ;
}
2022-02-26 18:24:08 +13:00
}