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 ;
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 ;
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 ;
public abstract class SharedActionsSystem : EntitySystem
{
2022-10-04 14:24:19 +11:00
[Dependency] protected readonly IGameTiming GameTiming = default ! ;
2022-05-28 23:41:17 -07:00
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default ! ;
2022-02-26 18:24:08 +13:00
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default ! ;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default ! ;
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default ! ;
2022-10-04 14:24:19 +11:00
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
2023-05-01 04:29:18 -04:00
[Dependency] private readonly SharedTransformSystem _transformSystem = default ! ;
2023-09-23 04:49:39 -04:00
[Dependency] private readonly ActionContainerSystem _actionContainer = default ! ;
2024-06-03 14:40:03 -07:00
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default ! ;
2022-02-26 18:24:08 +13:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2024-02-19 21:08:41 -05:00
SubscribeLocalEvent < InstantActionComponent , MapInitEvent > ( OnActionMapInit ) ;
SubscribeLocalEvent < EntityTargetActionComponent , MapInitEvent > ( OnActionMapInit ) ;
SubscribeLocalEvent < WorldTargetActionComponent , MapInitEvent > ( OnActionMapInit ) ;
2024-08-08 02:47:08 -07:00
SubscribeLocalEvent < EntityWorldTargetActionComponent , MapInitEvent > ( OnActionMapInit ) ;
2024-02-19 21:08:41 -05:00
SubscribeLocalEvent < InstantActionComponent , ComponentShutdown > ( OnActionShutdown ) ;
SubscribeLocalEvent < EntityTargetActionComponent , ComponentShutdown > ( OnActionShutdown ) ;
SubscribeLocalEvent < WorldTargetActionComponent , ComponentShutdown > ( OnActionShutdown ) ;
2024-08-08 02:47:08 -07:00
SubscribeLocalEvent < EntityWorldTargetActionComponent , 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 ) ;
2023-09-08 18:16:05 -07:00
SubscribeLocalEvent < ActionsComponent , ComponentGetState > ( OnActionsGetState ) ;
SubscribeLocalEvent < InstantActionComponent , ComponentGetState > ( OnInstantGetState ) ;
SubscribeLocalEvent < EntityTargetActionComponent , ComponentGetState > ( OnEntityTargetGetState ) ;
SubscribeLocalEvent < WorldTargetActionComponent , ComponentGetState > ( OnWorldTargetGetState ) ;
2024-08-08 02:47:08 -07:00
SubscribeLocalEvent < EntityWorldTargetActionComponent , ComponentGetState > ( OnEntityWorldTargetGetState ) ;
2023-09-08 18:16:05 -07:00
SubscribeLocalEvent < InstantActionComponent , GetActionDataEvent > ( OnGetActionData ) ;
SubscribeLocalEvent < EntityTargetActionComponent , GetActionDataEvent > ( OnGetActionData ) ;
SubscribeLocalEvent < WorldTargetActionComponent , GetActionDataEvent > ( OnGetActionData ) ;
2024-08-08 02:47:08 -07:00
SubscribeLocalEvent < EntityWorldTargetActionComponent , GetActionDataEvent > ( OnGetActionData ) ;
2022-02-26 18:24:08 +13:00
SubscribeAllEvent < RequestPerformActionEvent > ( OnActionRequest ) ;
}
2024-09-25 10:27:28 -04:00
public override void Update ( float frameTime )
{
base . Update ( frameTime ) ;
var worldActionQuery = EntityQueryEnumerator < WorldTargetActionComponent > ( ) ;
while ( worldActionQuery . MoveNext ( out var uid , out var action ) )
{
if ( IsCooldownActive ( action ) | | ! ShouldResetCharges ( action ) )
continue ;
ResetCharges ( uid , dirty : true ) ;
}
var instantActionQuery = EntityQueryEnumerator < InstantActionComponent > ( ) ;
while ( instantActionQuery . MoveNext ( out var uid , out var action ) )
{
if ( IsCooldownActive ( action ) | | ! ShouldResetCharges ( action ) )
continue ;
ResetCharges ( uid , dirty : true ) ;
}
var entityActionQuery = EntityQueryEnumerator < EntityTargetActionComponent > ( ) ;
while ( entityActionQuery . MoveNext ( out var uid , out var action ) )
{
if ( IsCooldownActive ( action ) | | ! ShouldResetCharges ( action ) )
continue ;
ResetCharges ( uid , dirty : true ) ;
}
}
2024-02-19 21:08:41 -05:00
private void OnActionMapInit ( EntityUid uid , BaseActionComponent component , MapInitEvent args )
{
2024-09-25 10:27:28 -04:00
component . OriginalIconColor = component . IconColor ;
2024-02-19 21:08:41 -05:00
if ( component . Charges = = null )
return ;
component . MaxCharges ? ? = component . Charges . Value ;
Dirty ( uid , component ) ;
}
private void OnActionShutdown ( EntityUid uid , BaseActionComponent component , ComponentShutdown args )
2023-12-15 04:41:44 -05:00
{
2024-02-19 21:08:41 -05:00
if ( component . AttachedEntity ! = null & & ! TerminatingOrDeleted ( component . AttachedEntity . Value ) )
RemoveAction ( component . AttachedEntity . Value , uid , action : component ) ;
2023-12-15 04:41:44 -05:00
}
2023-10-29 19:10:30 +11:00
private void OnShutdown ( EntityUid uid , ActionsComponent component , ComponentShutdown args )
{
foreach ( var act in component . Actions )
{
RemoveAction ( uid , act , component ) ;
}
}
2023-09-08 18:16:05 -07:00
private void OnInstantGetState ( EntityUid uid , InstantActionComponent component , ref ComponentGetState args )
{
2023-09-11 09:42:41 +10:00
args . State = new InstantActionComponentState ( component , EntityManager ) ;
2023-09-08 18:16:05 -07:00
}
private void OnEntityTargetGetState ( EntityUid uid , EntityTargetActionComponent component , ref ComponentGetState args )
{
2023-09-11 09:42:41 +10:00
args . State = new EntityTargetActionComponentState ( component , EntityManager ) ;
2023-09-08 18:16:05 -07:00
}
private void OnWorldTargetGetState ( EntityUid uid , WorldTargetActionComponent component , ref ComponentGetState args )
{
2023-09-11 09:42:41 +10:00
args . State = new WorldTargetActionComponentState ( component , EntityManager ) ;
2023-09-08 18:16:05 -07:00
}
2024-08-08 02:47:08 -07:00
private void OnEntityWorldTargetGetState ( EntityUid uid , EntityWorldTargetActionComponent component , ref ComponentGetState args )
{
args . State = new EntityWorldTargetActionComponentState ( component , EntityManager ) ;
}
2023-09-08 18:16:05 -07:00
private void OnGetActionData < T > ( EntityUid uid , T component , ref GetActionDataEvent args ) where T : BaseActionComponent
{
args . Action = component ;
}
2023-09-23 04:49:39 -04:00
public bool TryGetActionData (
[NotNullWhen(true)] EntityUid ? uid ,
[NotNullWhen(true)] out BaseActionComponent ? result ,
bool logError = true )
2023-09-08 18:16:05 -07:00
{
2023-09-23 04:49:39 -04:00
result = null ;
2024-05-30 17:25:13 -07:00
if ( uid = = null | | TerminatingOrDeleted ( uid . Value ) )
2023-09-23 04:49:39 -04:00
return false ;
2023-09-08 18:16:05 -07:00
var ev = new GetActionDataEvent ( ) ;
2023-09-23 04:49:39 -04:00
RaiseLocalEvent ( uid . Value , ref ev ) ;
result = ev . Action ;
2023-09-08 18:16:05 -07:00
2023-09-23 04:49:39 -04:00
if ( result ! = null )
return true ;
2023-09-08 18:16:05 -07:00
2023-11-10 22:46:42 -08:00
if ( logError )
2024-03-28 14:16:13 +13:00
Log . Error ( $"Failed to get action from action entity: {ToPrettyString(uid.Value)}. Trace: {Environment.StackTrace}" ) ;
2023-11-10 22:46:42 -08:00
2023-09-23 04:49:39 -04:00
return false ;
2023-09-08 18:16:05 -07:00
}
2023-09-23 04:49:39 -04:00
public bool ResolveActionData (
[NotNullWhen(true)] EntityUid ? uid ,
[NotNullWhen(true)] ref BaseActionComponent ? result ,
bool logError = true )
2023-09-08 18:16:05 -07:00
{
2023-09-23 04:49:39 -04:00
if ( result ! = null )
{
2023-10-19 12:34:31 -07:00
DebugTools . AssertOwner ( uid , result ) ;
2023-09-23 04:49:39 -04:00
return true ;
}
2023-09-08 18:16:05 -07:00
2023-09-23 04:49:39 -04:00
return TryGetActionData ( uid , out result , logError ) ;
2023-09-09 16:14:17 -07:00
}
2023-09-08 18:16:05 -07:00
public void SetCooldown ( EntityUid ? actionId , TimeSpan start , TimeSpan end )
{
2023-09-23 04:49:39 -04:00
if ( ! TryGetActionData ( actionId , out var action ) )
2023-09-08 18:16:05 -07:00
return ;
action . Cooldown = ( start , end ) ;
Dirty ( actionId . Value , action ) ;
}
2023-11-07 17:24:43 -08:00
public void SetCooldown ( EntityUid ? actionId , TimeSpan cooldown )
{
var start = GameTiming . CurTime ;
SetCooldown ( actionId , start , start + cooldown ) ;
}
public void ClearCooldown ( EntityUid ? actionId )
{
if ( ! TryGetActionData ( actionId , out var action ) )
return ;
if ( action . Cooldown is not { } cooldown )
return ;
action . Cooldown = ( cooldown . Start , GameTiming . CurTime ) ;
Dirty ( actionId . Value , action ) ;
}
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>
public void SetIfBiggerCooldown ( EntityUid ? actionId , TimeSpan ? cooldown )
{
if ( cooldown = = null | |
cooldown . Value < = TimeSpan . Zero | |
! TryGetActionData ( actionId , out var action ) )
{
return ;
}
var start = GameTiming . CurTime ;
var end = start + cooldown ;
if ( action . Cooldown ? . End > end )
return ;
action . Cooldown = ( start , end . Value ) ;
Dirty ( actionId . Value , action ) ;
}
2023-09-22 16:01:05 -04:00
public void StartUseDelay ( EntityUid ? actionId )
{
if ( actionId = = null )
return ;
2023-09-23 03:09:07 -07:00
if ( ! TryGetActionData ( actionId , out var action ) | | action . UseDelay = = null )
2023-09-22 16:01:05 -04:00
return ;
action . Cooldown = ( GameTiming . CurTime , GameTiming . CurTime + action . UseDelay . Value ) ;
Dirty ( actionId . Value , action ) ;
}
2024-11-07 16:04:49 +03:00
public void CP14StartCustomDelay ( EntityUid ? actionId , TimeSpan delay )
{
if ( actionId = = null )
return ;
if ( ! TryGetActionData ( actionId , out var action ) )
return ;
action . Cooldown = ( GameTiming . CurTime , GameTiming . CurTime + delay ) ;
Dirty ( actionId . Value , action ) ;
}
2023-12-15 04:41:44 -05:00
public void SetUseDelay ( EntityUid ? actionId , TimeSpan ? delay )
{
if ( ! TryGetActionData ( actionId , out var action ) | | action . UseDelay = = delay )
return ;
action . UseDelay = delay ;
UpdateAction ( actionId , action ) ;
Dirty ( actionId . Value , action ) ;
}
public void ReduceUseDelay ( EntityUid ? actionId , TimeSpan ? lowerDelay )
{
if ( ! TryGetActionData ( actionId , out var action ) )
return ;
if ( action . UseDelay ! = null & & lowerDelay ! = null )
action . UseDelay = action . UseDelay - lowerDelay ;
if ( action . UseDelay < TimeSpan . Zero )
action . UseDelay = null ;
UpdateAction ( actionId , action ) ;
Dirty ( actionId . Value , action ) ;
}
2023-12-05 16:00:02 -05:00
private void OnRejuventate ( EntityUid uid , ActionsComponent component , RejuvenateEvent args )
{
foreach ( var act in component . Actions )
{
ClearCooldown ( act ) ;
}
}
2022-02-26 18:24:08 +13:00
#region ComponentStateManagement
2024-07-19 20:03:43 -07:00
public virtual void UpdateAction ( EntityUid ? actionId , BaseActionComponent ? action = null )
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
}
2023-09-08 18:16:05 -07:00
public void SetToggled ( EntityUid ? actionId , bool toggled )
2022-02-26 18:24:08 +13:00
{
2023-09-08 18:16:05 -07:00
if ( ! TryGetActionData ( actionId , out var action ) | |
action . Toggled = = toggled )
{
2022-02-26 18:24:08 +13:00
return ;
2023-09-08 18:16:05 -07:00
}
2022-02-26 18:24:08 +13:00
action . Toggled = toggled ;
2023-09-23 04:49:39 -04:00
UpdateAction ( actionId , action ) ;
2023-09-08 18:16:05 -07:00
Dirty ( actionId . Value , action ) ;
2022-02-26 18:24:08 +13:00
}
2023-09-08 18:16:05 -07:00
public void SetEnabled ( EntityUid ? actionId , bool enabled )
2022-02-26 18:24:08 +13:00
{
2023-09-08 18:16:05 -07:00
if ( ! TryGetActionData ( actionId , out var action ) | |
action . Enabled = = enabled )
{
2022-02-26 18:24:08 +13:00
return ;
2023-09-08 18:16:05 -07:00
}
2022-02-26 18:24:08 +13:00
action . Enabled = enabled ;
2023-09-23 04:49:39 -04:00
UpdateAction ( actionId , action ) ;
2023-09-08 18:16:05 -07:00
Dirty ( actionId . Value , action ) ;
2022-02-26 18:24:08 +13:00
}
2023-09-08 18:16:05 -07:00
public void SetCharges ( EntityUid ? actionId , int? charges )
2022-02-26 18:24:08 +13:00
{
2023-09-08 18:16:05 -07:00
if ( ! TryGetActionData ( actionId , out var action ) | |
action . Charges = = charges )
{
2022-02-26 18:24:08 +13:00
return ;
2023-09-08 18:16:05 -07:00
}
2022-02-26 18:24:08 +13:00
action . Charges = charges ;
2023-09-23 04:49:39 -04:00
UpdateAction ( actionId , action ) ;
2023-09-08 18:16:05 -07:00
Dirty ( actionId . Value , action ) ;
}
2023-12-15 04:41:44 -05:00
public int? GetCharges ( EntityUid ? actionId )
{
if ( ! TryGetActionData ( actionId , out var action ) )
return null ;
return action . Charges ;
}
public void AddCharges ( EntityUid ? actionId , int addCharges )
{
if ( ! TryGetActionData ( actionId , out var action ) | | action . Charges = = null | | addCharges < 1 )
return ;
action . Charges + = addCharges ;
UpdateAction ( actionId , action ) ;
Dirty ( actionId . Value , action ) ;
}
public void RemoveCharges ( EntityUid ? actionId , int? removeCharges )
{
if ( ! TryGetActionData ( actionId , out var action ) | | action . Charges = = null )
return ;
if ( removeCharges = = null )
action . Charges = removeCharges ;
else
action . Charges - = removeCharges ;
if ( action . Charges is < 0 )
action . Charges = null ;
UpdateAction ( actionId , action ) ;
Dirty ( actionId . Value , action ) ;
}
2024-09-25 10:27:28 -04:00
public void ResetCharges ( EntityUid ? actionId , bool update = false , bool dirty = false )
2023-12-15 04:41:44 -05:00
{
if ( ! TryGetActionData ( actionId , out var action ) )
return ;
action . Charges = action . MaxCharges ;
2024-09-25 10:27:28 -04:00
if ( update )
UpdateAction ( actionId , action ) ;
if ( dirty )
Dirty ( actionId . Value , action ) ;
2023-12-15 04:41:44 -05:00
}
2023-09-08 18:16:05 -07:00
private void OnActionsGetState ( EntityUid uid , ActionsComponent component , ref ComponentGetState args )
{
2023-09-11 09:42:41 +10:00
args . State = new ActionsComponentState ( GetNetEntitySet ( component . Actions ) ) ;
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
2022-04-14 16:17:34 +12:00
/// will raise the relevant <see cref="InstantActionEvent"/>
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 ;
if ( ! TryComp ( user , out ActionsComponent ? component ) )
return ;
2023-09-11 09:42:41 +10:00
var actionEnt = GetEntity ( ev . Action ) ;
if ( ! TryComp ( actionEnt , out MetaDataComponent ? metaData ) )
2023-09-08 18:16:05 -07:00
return ;
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}." ) ;
2022-02-26 18:24:08 +13:00
return ;
}
2023-09-23 04:49:39 -04:00
if ( ! TryGetActionData ( actionEnt , out var action ) )
return ;
DebugTools . Assert ( action . AttachedEntity = = user ) ;
if ( ! action . Enabled )
2022-02-26 18:24:08 +13:00
return ;
2024-03-01 02:48:43 +00:00
// check for action use prevention
// TODO: make code below use this event with a dedicated component
var attemptEv = new ActionAttemptEvent ( user ) ;
RaiseLocalEvent ( actionEnt , ref attemptEv ) ;
if ( attemptEv . Cancelled )
return ;
2022-02-26 18:24:08 +13:00
var curTime = GameTiming . CurTime ;
2024-09-25 10:27:28 -04:00
if ( IsCooldownActive ( action , curTime ) )
2022-02-26 18:24:08 +13:00
return ;
2023-12-15 04:41:44 -05:00
// TODO: Replace with individual charge recovery when we have the visuals to aid it
if ( action is { Charges : < 1 , RenewCharges : true } )
2024-09-25 10:27:28 -04:00
ResetCharges ( actionEnt , true , true ) ;
2023-12-15 04:41:44 -05:00
2022-04-14 16:17:34 +12:00
BaseActionEvent ? performEvent = null ;
2022-02-26 18:24:08 +13:00
2024-03-19 00:35:46 +02:00
if ( action . CheckConsciousness & & ! _actionBlockerSystem . CanConsciouslyPerformAction ( user ) )
return ;
2022-02-26 18:24:08 +13:00
// Validate request by checking action blockers and the like:
2023-09-08 18:16:05 -07:00
switch ( action )
2022-02-26 18:24:08 +13:00
{
2023-09-08 18:16:05 -07:00
case EntityTargetActionComponent entityAction :
2023-09-11 09:42:41 +10:00
if ( ev . EntityTarget is not { Valid : true } netTarget )
2022-02-26 18:24:08 +13:00
{
2023-09-08 18:16:05 -07:00
Log . Error ( $"Attempted to perform an entity-targeted action without a target! Action: {name}" ) ;
2022-02-26 18:24:08 +13:00
return ;
}
2023-09-11 09:42:41 +10:00
var entityTarget = GetEntity ( netTarget ) ;
2023-05-01 04:29:18 -04:00
var targetWorldPos = _transformSystem . GetWorldPosition ( entityTarget ) ;
_rotateToFaceSystem . TryFaceCoordinates ( user , targetWorldPos ) ;
2022-02-26 18:24:08 +13:00
2024-04-22 01:39:50 -07:00
if ( ! ValidateEntityTarget ( user , entityTarget , ( actionEnt , entityAction ) ) )
2022-02-26 18:24:08 +13:00
return ;
2023-09-23 04:49:39 -04:00
_adminLogger . Add ( LogType . Action ,
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Container ?? user):provider}) targeted at {ToPrettyString(entityTarget):target}." ) ;
2022-02-26 18:24:08 +13:00
if ( entityAction . Event ! = null )
{
entityAction . Event . Target = entityTarget ;
2023-09-11 09:42:41 +10:00
Dirty ( actionEnt , entityAction ) ;
2022-02-26 18:24:08 +13:00
performEvent = entityAction . Event ;
}
break ;
2023-09-08 18:16:05 -07:00
case WorldTargetActionComponent worldAction :
2023-09-11 09:42:41 +10:00
if ( ev . EntityCoordinatesTarget is not { } netCoordinatesTarget )
2022-02-26 18:24:08 +13:00
{
2023-09-08 18:16:05 -07:00
Log . Error ( $"Attempted to perform a world-targeted action without a target! Action: {name}" ) ;
2022-02-26 18:24:08 +13:00
return ;
}
2023-09-11 09:42:41 +10:00
var entityCoordinatesTarget = GetCoordinates ( netCoordinatesTarget ) ;
2024-07-13 14:05:22 -07:00
_rotateToFaceSystem . TryFaceCoordinates ( user , _transformSystem . ToMapCoordinates ( entityCoordinatesTarget ) . Position ) ;
2022-02-26 18:24:08 +13:00
2024-04-22 01:39:50 -07:00
if ( ! ValidateWorldTarget ( user , entityCoordinatesTarget , ( actionEnt , worldAction ) ) )
2022-02-26 18:24:08 +13:00
return ;
2023-09-23 04:49:39 -04:00
_adminLogger . Add ( LogType . Action ,
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Container ?? user):provider}) targeted at {entityCoordinatesTarget:target}." ) ;
2022-02-26 18:24:08 +13:00
if ( worldAction . Event ! = null )
{
2022-12-06 18:03:20 -05:00
worldAction . Event . Target = entityCoordinatesTarget ;
2023-09-11 09:42:41 +10:00
Dirty ( actionEnt , worldAction ) ;
2022-02-26 18:24:08 +13:00
performEvent = worldAction . Event ;
}
break ;
2024-08-08 02:47:08 -07:00
case EntityWorldTargetActionComponent entityWorldAction :
{
var actionEntity = GetEntity ( ev . EntityTarget ) ;
var actionCoords = GetCoordinates ( ev . EntityCoordinatesTarget ) ;
if ( actionEntity is null & & actionCoords is null )
{
Log . Error ( $"Attempted to perform an entity-world-targeted action without an entity or world coordinates! Action: {name}" ) ;
return ;
}
var entWorldAction = new Entity < EntityWorldTargetActionComponent > ( actionEnt , entityWorldAction ) ;
if ( ! ValidateEntityWorldTarget ( user , actionEntity , actionCoords , entWorldAction ) )
return ;
_adminLogger . Add ( LogType . Action ,
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Container ?? user):provider}) targeted at {ToPrettyString(actionEntity):target} {actionCoords:target}." ) ;
if ( entityWorldAction . Event ! = null )
{
entityWorldAction . Event . Entity = actionEntity ;
entityWorldAction . Event . Coords = actionCoords ;
Dirty ( actionEnt , entityWorldAction ) ;
performEvent = entityWorldAction . Event ;
}
break ;
}
2023-09-08 18:16:05 -07:00
case InstantActionComponent instantAction :
if ( action . CheckCanInteract & & ! _actionBlockerSystem . CanInteract ( user , null ) )
2022-02-26 18:24:08 +13:00
return ;
2023-09-23 04:49:39 -04:00
_adminLogger . Add ( LogType . Action ,
$"{ToPrettyString(user):user} is performing the {name:action} action provided by {ToPrettyString(action.Container ?? user):provider}." ) ;
2022-02-26 18:24:08 +13:00
performEvent = instantAction . Event ;
break ;
}
// All checks passed. Perform the action!
2023-09-11 09:42:41 +10:00
PerformAction ( user , component , actionEnt , action , performEvent , curTime ) ;
2022-02-26 18:24:08 +13:00
}
2024-04-22 01:39:50 -07:00
public bool ValidateEntityTarget ( EntityUid user , EntityUid target , Entity < EntityTargetActionComponent > actionEnt )
{
2024-08-08 02:47:08 -07:00
var comp = actionEnt . Comp ;
if ( ! ValidateEntityTargetBase ( user ,
target ,
comp . Whitelist ,
comp . CheckCanInteract ,
comp . CanTargetSelf ,
comp . CheckCanAccess ,
comp . Range ) )
2024-04-22 01:39:50 -07:00
return false ;
var ev = new ValidateActionEntityTargetEvent ( user , target ) ;
RaiseLocalEvent ( actionEnt , ref ev ) ;
return ! ev . Cancelled ;
}
2024-08-08 02:47:08 -07:00
private bool ValidateEntityTargetBase ( EntityUid user ,
EntityUid ? targetEntity ,
EntityWhitelist ? whitelist ,
bool checkCanInteract ,
bool canTargetSelf ,
bool checkCanAccess ,
float range )
2022-02-26 18:24:08 +13:00
{
2024-08-08 02:47:08 -07:00
if ( targetEntity is not { } target | | ! target . IsValid ( ) | | Deleted ( target ) )
2022-02-26 18:24:08 +13:00
return false ;
2024-08-08 02:47:08 -07:00
if ( _whitelistSystem . IsWhitelistFail ( whitelist , target ) )
2022-02-26 18:24:08 +13:00
return false ;
2024-08-08 02:47:08 -07:00
if ( checkCanInteract & & ! _actionBlockerSystem . CanInteract ( user , target ) )
2022-02-26 18:24:08 +13:00
return false ;
if ( user = = target )
2024-08-08 02:47:08 -07:00
return canTargetSelf ;
2022-02-26 18:24:08 +13:00
2024-08-08 02:47:08 -07:00
if ( ! checkCanAccess )
2022-02-26 18:24:08 +13:00
{
// even if we don't check for obstructions, we may still need to check the range.
var xform = Transform ( user ) ;
var targetXform = Transform ( target ) ;
if ( xform . MapID ! = targetXform . MapID )
return false ;
2024-08-08 02:47:08 -07:00
if ( range < = 0 )
2022-02-26 18:24:08 +13:00
return true ;
2023-07-08 14:08:32 +10:00
var distance = ( _transformSystem . GetWorldPosition ( xform ) - _transformSystem . GetWorldPosition ( targetXform ) ) . Length ( ) ;
2024-08-08 02:47:08 -07:00
return distance < = range ;
2022-02-26 18:24:08 +13:00
}
2024-08-08 02:47:08 -07:00
return _interactionSystem . InRangeAndAccessible ( user , target , range : range ) ;
2022-02-26 18:24:08 +13:00
}
2024-04-22 01:39:50 -07:00
public bool ValidateWorldTarget ( EntityUid user , EntityCoordinates coords , Entity < WorldTargetActionComponent > action )
{
2024-08-08 02:47:08 -07:00
var comp = action . Comp ;
if ( ! ValidateWorldTargetBase ( user , coords , comp . CheckCanInteract , comp . CheckCanAccess , comp . Range ) )
2024-04-22 01:39:50 -07:00
return false ;
var ev = new ValidateActionWorldTargetEvent ( user , coords ) ;
RaiseLocalEvent ( action , ref ev ) ;
return ! ev . Cancelled ;
}
2024-08-08 02:47:08 -07:00
private bool ValidateWorldTargetBase ( EntityUid user ,
EntityCoordinates ? entityCoordinates ,
bool checkCanInteract ,
bool checkCanAccess ,
float range )
2022-02-26 18:24:08 +13:00
{
2024-08-08 02:47:08 -07:00
if ( entityCoordinates is not { } coords )
return false ;
if ( checkCanInteract & & ! _actionBlockerSystem . CanInteract ( user , null ) )
2022-02-26 18:24:08 +13:00
return false ;
2024-08-08 02:47:08 -07:00
if ( ! checkCanAccess )
2022-02-26 18:24:08 +13:00
{
// even if we don't check for obstructions, we may still need to check the range.
var xform = Transform ( user ) ;
2022-12-06 18:03:20 -05:00
if ( xform . MapID ! = coords . GetMapId ( EntityManager ) )
2022-02-26 18:24:08 +13:00
return false ;
2024-08-08 02:47:08 -07:00
if ( range < = 0 )
2022-02-26 18:24:08 +13:00
return true ;
2024-08-08 02:47:08 -07:00
return coords . InRange ( EntityManager , _transformSystem , Transform ( user ) . Coordinates , range ) ;
2022-02-26 18:24:08 +13:00
}
2024-08-08 02:47:08 -07:00
return _interactionSystem . InRangeUnobstructed ( user , coords , range : range ) ;
}
public bool ValidateEntityWorldTarget ( EntityUid user ,
EntityUid ? entity ,
EntityCoordinates ? coords ,
Entity < EntityWorldTargetActionComponent > action )
{
var comp = action . Comp ;
var entityValidated = ValidateEntityTargetBase ( user ,
entity ,
comp . Whitelist ,
comp . CheckCanInteract ,
comp . CanTargetSelf ,
comp . CheckCanAccess ,
comp . Range ) ;
var worldValidated
= ValidateWorldTargetBase ( user , coords , comp . CheckCanInteract , comp . CheckCanAccess , comp . Range ) ;
if ( ! entityValidated & & ! worldValidated )
return false ;
var ev = new ValidateActionEntityWorldTargetEvent ( user ,
entityValidated ? entity : null ,
worldValidated ? coords : null ) ;
RaiseLocalEvent ( action , ref ev ) ;
return ! ev . Cancelled ;
2022-02-26 18:24:08 +13:00
}
2023-09-08 18:16:05 -07:00
public void PerformAction ( EntityUid performer , ActionsComponent ? component , EntityUid actionId , BaseActionComponent action , BaseActionEvent ? actionEvent , TimeSpan curTime , bool predicted = true )
2022-02-26 18:24:08 +13:00
{
var handled = false ;
var toggledBefore = action . Toggled ;
2023-11-03 19:55:32 -04:00
// Note that attached entity and attached container are allowed to be null here.
2023-09-23 04:49:39 -04:00
if ( action . AttachedEntity ! = null & & action . AttachedEntity ! = performer )
{
Log . Error ( $"{ToPrettyString(performer)} is attempting to perform an action {ToPrettyString(actionId)} that is attached to another entity {ToPrettyString(action.AttachedEntity.Value)}" ) ;
return ;
}
2022-02-26 18:24:08 +13:00
if ( actionEvent ! = null )
{
// This here is required because of client-side prediction (RaisePredictiveEvent results in event re-use).
actionEvent . Handled = false ;
2023-11-03 19:55:32 -04:00
var target = performer ;
2024-08-25 22:43:31 +10:00
actionEvent . Performer = performer ;
actionEvent . Action = ( actionId , action ) ;
2023-11-03 19:55:32 -04:00
if ( ! action . RaiseOnUser & & action . Container ! = null & & ! HasComp < MindComponent > ( action . Container ) )
target = action . Container . Value ;
RaiseLocalEvent ( target , ( object ) actionEvent , broadcast : true ) ;
2022-02-26 18:24:08 +13:00
handled = actionEvent . Handled ;
}
if ( ! handled )
return ; // no interaction occurred.
Magic Refactor + Wizard Grimoire (#22568)
* Brings over changes from the original magic refactor PR
* Adds Master Spellbook, spellbook categories, WizCoin currency, and locale
* Wiz€oin™
* Adds currency whitelist to Spellbook preset, grants contained actions on action added.
* Adds grant contained action and remove provided action.
* adds a way for actions to be upgraded to the store
* Adds Fireball 3 and fixes action upgrade logic so that it checks if the action can level or if the action can upgrade separately
* Fixes upgrade logic in ActionUpgradeSystem to allow for level ups without an actual upgrade. Fixed action upgrade logic in store system as well
* Removes current action entity from the bought entities list and adds new or old action entity
* Removes Current Entity
* Removes old comments, fixes TransferAllActionsWithNewAttached
* Removes TODO
* Removes Product Action Upgrade Event
* reverts changes to immovablerodrule
* Removes stale event reference
* fixes mind action grant logic
* reverts shared gun system change to projectile anomaly system
* forgor to remove the using
* Reverts unintended changes to action container
* Adds refund button to the store
* Refreshes store back to origin.
* Refund with correct currency
* Init refund
* Check for terminating and update interface
* Disables refund button
* Removes preset allow refund
* dont refund if map changed
* adds refunds to stores
* Adds method to check for starting map
* comments, datafields, some requested changes
* turns event into ref event
* Adds datafields
* Switches to entity terminating event
* Changes store entity to be nullable and checks if store is terminating to remove reference.
* Tryadd instead of containskey
* Adds a refund disable method, disables refund on bought ent container changes if not an action
* Removes duplicate refundcomp
* Removes unintended merges
* Removed another unintended change from merge
* removes extra using statement
* readds using statement
* might as well just remove both usings since it won't leave the PR
* Fixes Action upgrades from stores
* Changes to non obsolete method uses
* Shares spawn code between instant and world
* Adds action entity to action event, adds beforecastspellevent, adds spell requirements to magic component
* puts prereq check in spell methods, sets up template code for before cast event
* checks for required wizard clothes
* Networks Magic Comp and Wizard Clothes Comp. Renames MagicSpawnData to MagicInstantSpawnData.
* Removes posdata from projectiles
* Speech > RequiresSpeech
* Fixes ActionOnInteract
* checks for muted
* popup for missing reqs
* Validate click loc for blink spell
* Checks if doors are in range and not obstructed before opening
* Check ents by map coords
* Adds speak event
* Comments spellbooks
* Removes comments
* Unobsoletes smite spell
* Invert if
* Requirements loc
* Fixes spell reqs
* Inverts an if
* Comment updates
* Starts doafter work
* Removes doafter references
* Balances fireball upgrades to be more reasonable
* Enables refund on master spellbooks
* Spells to do
* update spellbook doafter
* knock toggles bolts
* Touch Spell comments
* Comments for pending spells
* more comments
* adds spider polymorph to spellbook
* TODOs for spells
* reorganizes spellbook categories and adds wands
* fixes spacing and adds limited conditions
* commented owner only for future store PR
* reenables owner only for the grimoire
* fixes grimoire sprite
* Adds wizard rod polymorph
* summon ghosts event
* Moves rod form to offensive category
* Adds charge spell and loc for rod polymorph
* Oops forgor the actual chages
* Item Recall comment
* Fixes UI
* removes extra field for wizard rod
* Cleanup
* New Condition (INCOMPLETE)
* Fix linter
* Fix linter (for real)
* fixed some descriptions
* adds regions to magic
* Adds a non-refund wizard grimoire, fixes blink to deselect after teleporting, reduces force wall despawn time to 12 seconds
* removes limited upgrade condition
---------
Co-authored-by: AJCM <AJCM@tutanota.com>
2024-05-11 19:06:49 -04:00
// play sound, reduce charges, start cooldown, and mark as dirty (if required).
2024-08-25 22:43:31 +10:00
if ( actionEvent ? . Toggle = = true )
{
action . Toggled = ! action . Toggled ;
}
Magic Refactor + Wizard Grimoire (#22568)
* Brings over changes from the original magic refactor PR
* Adds Master Spellbook, spellbook categories, WizCoin currency, and locale
* Wiz€oin™
* Adds currency whitelist to Spellbook preset, grants contained actions on action added.
* Adds grant contained action and remove provided action.
* adds a way for actions to be upgraded to the store
* Adds Fireball 3 and fixes action upgrade logic so that it checks if the action can level or if the action can upgrade separately
* Fixes upgrade logic in ActionUpgradeSystem to allow for level ups without an actual upgrade. Fixed action upgrade logic in store system as well
* Removes current action entity from the bought entities list and adds new or old action entity
* Removes Current Entity
* Removes old comments, fixes TransferAllActionsWithNewAttached
* Removes TODO
* Removes Product Action Upgrade Event
* reverts changes to immovablerodrule
* Removes stale event reference
* fixes mind action grant logic
* reverts shared gun system change to projectile anomaly system
* forgor to remove the using
* Reverts unintended changes to action container
* Adds refund button to the store
* Refreshes store back to origin.
* Refund with correct currency
* Init refund
* Check for terminating and update interface
* Disables refund button
* Removes preset allow refund
* dont refund if map changed
* adds refunds to stores
* Adds method to check for starting map
* comments, datafields, some requested changes
* turns event into ref event
* Adds datafields
* Switches to entity terminating event
* Changes store entity to be nullable and checks if store is terminating to remove reference.
* Tryadd instead of containskey
* Adds a refund disable method, disables refund on bought ent container changes if not an action
* Removes duplicate refundcomp
* Removes unintended merges
* Removed another unintended change from merge
* removes extra using statement
* readds using statement
* might as well just remove both usings since it won't leave the PR
* Fixes Action upgrades from stores
* Changes to non obsolete method uses
* Shares spawn code between instant and world
* Adds action entity to action event, adds beforecastspellevent, adds spell requirements to magic component
* puts prereq check in spell methods, sets up template code for before cast event
* checks for required wizard clothes
* Networks Magic Comp and Wizard Clothes Comp. Renames MagicSpawnData to MagicInstantSpawnData.
* Removes posdata from projectiles
* Speech > RequiresSpeech
* Fixes ActionOnInteract
* checks for muted
* popup for missing reqs
* Validate click loc for blink spell
* Checks if doors are in range and not obstructed before opening
* Check ents by map coords
* Adds speak event
* Comments spellbooks
* Removes comments
* Unobsoletes smite spell
* Invert if
* Requirements loc
* Fixes spell reqs
* Inverts an if
* Comment updates
* Starts doafter work
* Removes doafter references
* Balances fireball upgrades to be more reasonable
* Enables refund on master spellbooks
* Spells to do
* update spellbook doafter
* knock toggles bolts
* Touch Spell comments
* Comments for pending spells
* more comments
* adds spider polymorph to spellbook
* TODOs for spells
* reorganizes spellbook categories and adds wands
* fixes spacing and adds limited conditions
* commented owner only for future store PR
* reenables owner only for the grimoire
* fixes grimoire sprite
* Adds wizard rod polymorph
* summon ghosts event
* Moves rod form to offensive category
* Adds charge spell and loc for rod polymorph
* Oops forgor the actual chages
* Item Recall comment
* Fixes UI
* removes extra field for wizard rod
* Cleanup
* New Condition (INCOMPLETE)
* Fix linter
* Fix linter (for real)
* fixed some descriptions
* adds regions to magic
* Adds a non-refund wizard grimoire, fixes blink to deselect after teleporting, reduces force wall despawn time to 12 seconds
* removes limited upgrade condition
---------
Co-authored-by: AJCM <AJCM@tutanota.com>
2024-05-11 19:06:49 -04:00
2024-08-25 22:43:31 +10:00
_audio . PlayPredicted ( action . Sound , performer , predicted ? performer : null ) ;
2022-02-26 18:24:08 +13:00
2024-08-25 22:43:31 +10:00
var dirty = toggledBefore ! = action . Toggled ;
2022-02-26 18:24:08 +13:00
if ( action . Charges ! = null )
{
dirty = true ;
action . Charges - - ;
2023-12-15 04:41:44 -05:00
if ( action is { Charges : 0 , RenewCharges : false } )
2022-02-26 18:24:08 +13:00
action . Enabled = false ;
}
action . Cooldown = null ;
2023-12-15 04:41:44 -05:00
if ( action is { UseDelay : not null , Charges : null or < 1 } )
2022-02-26 18:24:08 +13:00
{
dirty = true ;
action . Cooldown = ( curTime , curTime + action . UseDelay . Value ) ;
}
2024-08-25 22:43:31 +10:00
if ( dirty )
{
Dirty ( actionId , action ) ;
UpdateAction ( actionId , action ) ;
}
2024-05-10 17:04:01 -07:00
var ev = new ActionPerformedEvent ( performer ) ;
RaiseLocalEvent ( actionId , ref ev ) ;
2022-02-26 18:24:08 +13:00
}
#endregion
#region AddRemoveActions
2023-09-23 04:49:39 -04:00
public EntityUid ? AddAction ( EntityUid performer ,
string? actionPrototypeId ,
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 ,
string? actionPrototypeId ,
EntityUid container = default ,
ActionsComponent ? component = null )
{
return AddAction ( performer , ref actionId , out _ , actionPrototypeId , container , component ) ;
}
/// <inheritdoc cref="AddAction(Robust.Shared.GameObjects.EntityUid,ref System.Nullable{Robust.Shared.GameObjects.EntityUid},string?,Robust.Shared.GameObjects.EntityUid,Content.Shared.Actions.ActionsComponent?)"/>
public bool AddAction ( EntityUid performer ,
[NotNullWhen(true)] ref EntityUid ? actionId ,
[NotNullWhen(true)] out BaseActionComponent ? action ,
string? actionPrototypeId ,
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
2023-09-23 04:49:39 -04:00
return AddActionDirect ( performer , actionId . Value , component , 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>
2023-09-23 04:49:39 -04:00
public bool AddAction ( EntityUid performer ,
EntityUid actionId ,
EntityUid container ,
ActionsComponent ? comp = null ,
BaseActionComponent ? action = null ,
ActionsContainerComponent ? containerComp = null
)
{
if ( ! ResolveActionData ( actionId , ref action ) )
return false ;
if ( action . Container ! = container
| | ! Resolve ( container , ref containerComp )
| | ! containerComp . Container . Contains ( actionId ) )
2022-04-14 16:17:34 +12:00
{
2023-09-23 04:49:39 -04:00
Log . Error ( $"Attempted to add an action with an invalid container: {ToPrettyString(actionId)}" ) ;
return false ;
2022-04-14 16:17:34 +12:00
}
2023-09-23 04:49:39 -04:00
return AddActionDirect ( performer , actionId , comp , action ) ;
}
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>
public bool AddActionDirect ( EntityUid performer ,
EntityUid actionId ,
ActionsComponent ? comp = null ,
BaseActionComponent ? action = null )
{
if ( ! ResolveActionData ( actionId , ref action ) )
return false ;
DebugTools . Assert ( action . Container = = null | |
( TryComp ( action . Container , out ActionsContainerComponent ? containerComp )
& & containerComp . Container . Contains ( actionId ) ) ) ;
2022-02-26 18:24:08 +13:00
2023-10-24 11:53:27 +11:00
if ( action . AttachedEntity ! = null )
RemoveAction ( action . AttachedEntity . Value , actionId , action : action ) ;
2023-10-19 12:34:31 -07:00
DebugTools . AssertOwner ( performer , comp ) ;
2023-09-23 04:49:39 -04:00
comp ? ? = EnsureComp < ActionsComponent > ( performer ) ;
action . AttachedEntity = performer ;
comp . Actions . Add ( actionId ) ;
Dirty ( actionId , action ) ;
Dirty ( performer , comp ) ;
ActionAdded ( performer , actionId , comp , action ) ;
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>
protected virtual void ActionAdded ( EntityUid performer , EntityUid actionId , ActionsComponent comp , BaseActionComponent 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>
public void GrantActions ( EntityUid performer , IEnumerable < EntityUid > actions , EntityUid container , ActionsComponent ? comp = null , ActionsContainerComponent ? containerComp = null )
2022-02-26 18:24:08 +13:00
{
2023-09-23 04:49:39 -04:00
if ( ! Resolve ( container , ref containerComp ) )
return ;
2022-02-26 18:24:08 +13:00
2023-10-19 12:34:31 -07:00
DebugTools . AssertOwner ( performer , comp ) ;
2023-09-23 04:49:39 -04:00
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
{
2023-09-23 04:49:39 -04:00
AddAction ( performer , actionId , container , comp , containerComp : containerComp ) ;
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 )
{
if ( TryGetActionData ( actionId , out var action ) )
AddActionDirect ( performer , actionId , performer . Comp , action ) ;
}
}
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 ) ;
if ( TryGetActionData ( actionId , out var action ) )
AddActionDirect ( performer , actionId , performer . Comp , action ) ;
}
2023-09-09 16:14:17 -07:00
public IEnumerable < ( EntityUid Id , BaseActionComponent Comp ) > 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
{
if ( ! TryGetActionData ( actionId , out var action ) )
continue ;
yield return ( actionId , action ) ;
}
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
{
2023-09-23 04:49:39 -04:00
if ( ! TryGetActionData ( actionId , out var action ) )
return ;
2023-09-08 18:16:05 -07:00
2023-09-23 04:49:39 -04:00
if ( action . Container = = container )
RemoveAction ( performer , actionId , comp ) ;
}
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 )
{
if ( ! Resolve ( performer , ref comp , false ) | | ! TryGetActionData ( actionId , out var action ) )
return ;
if ( action . Container = = container )
RemoveAction ( performer , actionId , comp ) ;
}
2023-09-23 04:49:39 -04:00
public void RemoveAction ( EntityUid ? actionId )
2022-02-26 18:24:08 +13:00
{
2023-09-23 04:49:39 -04:00
if ( actionId = = null )
2022-02-26 18:24:08 +13:00
return ;
2023-09-23 04:49:39 -04:00
if ( ! TryGetActionData ( actionId , out var action ) )
return ;
2022-02-26 18:24:08 +13:00
2023-09-23 04:49:39 -04:00
if ( ! TryComp ( action . AttachedEntity , out ActionsComponent ? comp ) )
return ;
2023-09-09 16:14:17 -07:00
2023-09-23 04:49:39 -04:00
RemoveAction ( action . AttachedEntity . Value , actionId , comp , action ) ;
2023-09-08 18:16:05 -07:00
}
2023-09-23 04:49:39 -04:00
public void RemoveAction ( EntityUid performer , EntityUid ? actionId , ActionsComponent ? comp = null , BaseActionComponent ? action = null )
2023-09-08 18:16:05 -07:00
{
2023-09-23 04:49:39 -04:00
if ( actionId = = null )
2023-09-08 18:16:05 -07:00
return ;
2023-09-23 04:49:39 -04:00
if ( ! ResolveActionData ( actionId , ref action ) )
return ;
2023-09-08 18:16:05 -07:00
2023-10-12 04:50:10 +11:00
if ( action . AttachedEntity ! = performer )
{
2023-11-11 17:45:46 +11:00
DebugTools . Assert ( ! Resolve ( performer , ref comp , false )
| | comp . LifeStage > = ComponentLifeStage . Stopping
| | ! comp . Actions . Contains ( actionId . Value ) ) ;
2023-10-29 19:10:30 +11:00
if ( ! GameTiming . ApplyingState )
2024-03-28 14:16:13 +13:00
Log . Error ( $"Attempted to remove an action {ToPrettyString(actionId)} from an entity that it was never attached to: {ToPrettyString(performer)}. Trace: {Environment.StackTrace}" ) ;
2023-10-12 04:50:10 +11:00
return ;
}
2023-09-23 04:49:39 -04:00
if ( ! Resolve ( performer , ref comp , false ) )
{
2023-10-12 04:50:10 +11:00
DebugTools . Assert ( action . AttachedEntity = = null | | TerminatingOrDeleted ( action . AttachedEntity . Value ) ) ;
action . AttachedEntity = null ;
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
2023-09-23 04:49:39 -04:00
if ( action . AttachedEntity = = null )
2023-09-08 18:16:05 -07:00
{
2023-09-23 04:49:39 -04:00
// action was already removed?
DebugTools . Assert ( ! comp . Actions . Contains ( actionId . Value ) | | GameTiming . ApplyingState ) ;
return ;
2023-09-08 18:16:05 -07:00
}
2023-09-23 04:49:39 -04:00
comp . Actions . Remove ( actionId . Value ) ;
action . AttachedEntity = null ;
Dirty ( actionId . Value , action ) ;
Dirty ( performer , comp ) ;
ActionRemoved ( performer , actionId . Value , comp , action ) ;
if ( action . Temporary )
QueueDel ( actionId . Value ) ;
}
/// <summary>
/// This method gets called after an action got removed.
/// </summary>
protected virtual void ActionRemoved ( EntityUid performer , EntityUid actionId , ActionsComponent comp , BaseActionComponent action )
{
// See client-side system for UI code.
2022-02-26 18:24:08 +13:00
}
2024-08-18 12:22:36 -04:00
public bool ValidAction ( BaseActionComponent action , bool canReach = true )
{
if ( ! action . Enabled )
return false ;
if ( action . Charges . HasValue & & action . Charges < = 0 )
return false ;
var curTime = GameTiming . CurTime ;
if ( action . Cooldown . HasValue & & action . Cooldown . Value . End > curTime )
return false ;
return canReach | | action is BaseTargetActionComponent { CheckCanAccess : false } ;
}
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
private void OnDidEquip ( EntityUid uid , ActionsComponent component , DidEquipEvent args )
{
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 ;
2023-09-23 04:49:39 -04:00
GrantActions ( args . Equipee , ev . Actions , args . Equipment , component ) ;
2022-02-26 18:24:08 +13:00
}
private void OnHandEquipped ( EntityUid uid , ActionsComponent component , DidEquipHandEvent args )
{
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 ;
2023-09-23 04:49:39 -04:00
GrantActions ( args . User , ev . Actions , args . Equipped , component ) ;
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
public void SetEntityIcon ( EntityUid uid , EntityUid ? icon , BaseActionComponent ? action = null )
{
if ( ! Resolve ( uid , ref action ) )
return ;
action . EntityIcon = icon ;
Dirty ( uid , action ) ;
}
2024-09-25 10:27:28 -04:00
/// <summary>
/// Checks if the action has a cooldown and if it's still active
/// </summary>
protected bool IsCooldownActive ( BaseActionComponent action , TimeSpan ? curTime = null )
{
curTime ? ? = GameTiming . CurTime ;
// TODO: Check for charge recovery timer
return action . Cooldown . HasValue & & action . Cooldown . Value . End > curTime ;
}
protected bool ShouldResetCharges ( BaseActionComponent action )
{
return action is { Charges : < 1 , RenewCharges : true } ;
}
2022-02-26 18:24:08 +13:00
}