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 ;
using Robust.Shared.Containers ;
using Robust.Shared.GameStates ;
using Robust.Shared.Map ;
2023-09-08 18:16:05 -07:00
using Robust.Shared.Network ;
2022-02-26 18:24:08 +13:00
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
{
2023-09-08 18:16:05 -07:00
private const string ActionContainerId = "ActionContainer" ;
2023-09-09 16:14:17 -07:00
private const string ProvidedActionContainerId = "ProvidedActionContainer" ;
2023-09-08 18:16:05 -07:00
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 ! ;
2023-09-08 18:16:05 -07:00
[Dependency] private readonly INetManager _net = default ! ;
2022-02-26 18:24:08 +13:00
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default ! ;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default ! ;
[Dependency] private readonly SharedContainerSystem _containerSystem = 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 ! ;
2022-02-26 18:24:08 +13:00
public override void Initialize ( )
{
base . Initialize ( ) ;
SubscribeLocalEvent < ActionsComponent , DidEquipEvent > ( OnDidEquip ) ;
SubscribeLocalEvent < ActionsComponent , DidEquipHandEvent > ( OnHandEquipped ) ;
SubscribeLocalEvent < ActionsComponent , DidUnequipEvent > ( OnDidUnequip ) ;
SubscribeLocalEvent < ActionsComponent , DidUnequipHandEvent > ( OnHandUnequipped ) ;
2023-09-08 18:16:05 -07:00
SubscribeLocalEvent < ActionsComponent , MapInitEvent > ( OnActionsMapInit ) ;
SubscribeLocalEvent < ActionsComponent , ComponentGetState > ( OnActionsGetState ) ;
SubscribeLocalEvent < ActionsComponent , ComponentShutdown > ( OnActionsShutdown ) ;
SubscribeLocalEvent < InstantActionComponent , ComponentGetState > ( OnInstantGetState ) ;
SubscribeLocalEvent < EntityTargetActionComponent , ComponentGetState > ( OnEntityTargetGetState ) ;
SubscribeLocalEvent < WorldTargetActionComponent , ComponentGetState > ( OnWorldTargetGetState ) ;
SubscribeLocalEvent < InstantActionComponent , ComponentHandleState > ( OnInstantHandleState ) ;
SubscribeLocalEvent < EntityTargetActionComponent , ComponentHandleState > ( OnEntityTargetHandleState ) ;
SubscribeLocalEvent < WorldTargetActionComponent , ComponentHandleState > ( OnWorldTargetHandleState ) ;
SubscribeLocalEvent < InstantActionComponent , GetActionDataEvent > ( OnGetActionData ) ;
SubscribeLocalEvent < EntityTargetActionComponent , GetActionDataEvent > ( OnGetActionData ) ;
SubscribeLocalEvent < WorldTargetActionComponent , GetActionDataEvent > ( OnGetActionData ) ;
2022-02-26 18:24:08 +13:00
2023-09-09 16:14:17 -07:00
SubscribeLocalEvent < InstantActionComponent , EntGotRemovedFromContainerMessage > ( OnEntGotRemovedFromContainer ) ;
SubscribeLocalEvent < EntityTargetActionComponent , EntGotRemovedFromContainerMessage > ( OnEntGotRemovedFromContainer ) ;
SubscribeLocalEvent < WorldTargetActionComponent , EntGotRemovedFromContainerMessage > ( OnEntGotRemovedFromContainer ) ;
2022-02-26 18:24:08 +13:00
SubscribeAllEvent < RequestPerformActionEvent > ( OnActionRequest ) ;
}
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
}
2023-09-11 09:42:41 +10:00
private void BaseHandleState < T > ( EntityUid uid , BaseActionComponent component , BaseActionComponentState state ) where T : BaseActionComponent
2023-09-08 18:16:05 -07:00
{
component . Icon = state . Icon ;
component . IconOn = state . IconOn ;
component . IconColor = state . IconColor ;
component . Keywords = new HashSet < string > ( state . Keywords ) ;
component . Enabled = state . Enabled ;
component . Toggled = state . Toggled ;
component . Cooldown = state . Cooldown ;
component . UseDelay = state . UseDelay ;
component . Charges = state . Charges ;
2023-09-11 09:42:41 +10:00
component . Provider = EnsureEntity < T > ( state . Provider , uid ) ;
component . EntityIcon = EnsureEntity < T > ( state . EntityIcon , uid ) ;
2023-09-08 18:16:05 -07:00
component . CheckCanInteract = state . CheckCanInteract ;
component . ClientExclusive = state . ClientExclusive ;
component . Priority = state . Priority ;
2023-09-11 09:42:41 +10:00
component . AttachedEntity = EnsureEntity < T > ( state . AttachedEntity , uid ) ;
2023-09-08 18:16:05 -07:00
component . AutoPopulate = state . AutoPopulate ;
component . AutoRemove = state . AutoRemove ;
component . Temporary = state . Temporary ;
component . ItemIconStyle = state . ItemIconStyle ;
component . Sound = state . Sound ;
}
private void OnInstantHandleState ( EntityUid uid , InstantActionComponent component , ref ComponentHandleState args )
{
if ( args . Current is not InstantActionComponentState state )
return ;
2023-09-11 09:42:41 +10:00
BaseHandleState < InstantActionComponent > ( uid , component , state ) ;
2023-09-08 18:16:05 -07:00
}
private void OnEntityTargetHandleState ( EntityUid uid , EntityTargetActionComponent component , ref ComponentHandleState args )
{
if ( args . Current is not EntityTargetActionComponentState state )
return ;
2023-09-11 09:42:41 +10:00
BaseHandleState < EntityTargetActionComponent > ( uid , component , state ) ;
2023-09-08 18:16:05 -07:00
component . Whitelist = state . Whitelist ;
component . CanTargetSelf = state . CanTargetSelf ;
}
private void OnWorldTargetHandleState ( EntityUid uid , WorldTargetActionComponent component , ref ComponentHandleState args )
{
if ( args . Current is not WorldTargetActionComponentState state )
return ;
2023-09-11 09:42:41 +10:00
BaseHandleState < WorldTargetActionComponent > ( uid , component , state ) ;
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-09 16:14:17 -07:00
private void OnEntGotRemovedFromContainer < T > ( EntityUid uid , T component , EntGotRemovedFromContainerMessage args ) where T : BaseActionComponent
{
if ( args . Container . ID ! = ProvidedActionContainerId )
return ;
if ( TryComp ( component . AttachedEntity , out ActionsComponent ? actions ) )
{
actions . Actions . Remove ( uid ) ;
Dirty ( component . AttachedEntity . Value , actions ) ;
if ( TryGetActionData ( uid , out var action ) )
action . AttachedEntity = null ;
}
}
2023-09-08 18:16:05 -07:00
public BaseActionComponent ? GetActionData ( EntityUid ? actionId )
{
if ( actionId = = null )
return null ;
// TODO split up logic between each action component with different subscriptions
// good luck future coder
var ev = new GetActionDataEvent ( ) ;
RaiseLocalEvent ( actionId . Value , ref ev ) ;
return ev . Action ;
}
public bool TryGetActionData (
[NotNullWhen(true)] EntityUid ? actionId ,
[NotNullWhen(true)] out BaseActionComponent ? action )
{
action = null ;
return actionId ! = null & & ( action = GetActionData ( actionId ) ) ! = null ;
}
2023-09-09 16:14:17 -07:00
protected Container EnsureContainer ( EntityUid holderId , EntityUid ? providerId )
2023-09-08 18:16:05 -07:00
{
2023-09-09 16:14:17 -07:00
return providerId = = null
? _containerSystem . EnsureContainer < Container > ( holderId , ActionContainerId )
: _containerSystem . EnsureContainer < Container > ( providerId . Value , ProvidedActionContainerId ) ;
2023-09-08 18:16:05 -07:00
}
protected bool TryGetContainer (
EntityUid holderId ,
2023-09-11 09:42:41 +10:00
[NotNullWhen(true)] out BaseContainer ? container ,
2023-09-08 18:16:05 -07:00
ContainerManagerComponent ? containerManager = null )
{
return _containerSystem . TryGetContainer ( holderId , ActionContainerId , out container , containerManager ) ;
}
2023-09-09 16:14:17 -07:00
protected bool TryGetProvidedContainer (
EntityUid providerId ,
2023-09-11 09:42:41 +10:00
[NotNullWhen(true)] out BaseContainer ? container ,
2023-09-09 16:14:17 -07:00
ContainerManagerComponent ? containerManager = null )
{
return _containerSystem . TryGetContainer ( providerId , ProvidedActionContainerId , out container , containerManager ) ;
}
2023-09-08 18:16:05 -07:00
public void SetCooldown ( EntityUid ? actionId , TimeSpan start , TimeSpan end )
{
if ( actionId = = null )
return ;
var action = GetActionData ( actionId ) ;
if ( action = = null )
return ;
action . Cooldown = ( start , end ) ;
Dirty ( actionId . Value , action ) ;
}
2022-02-26 18:24:08 +13:00
#region ComponentStateManagement
2023-09-08 18:16:05 -07:00
public virtual void Dirty ( EntityUid ? actionId )
2022-02-26 18:24:08 +13:00
{
2023-09-08 18:16:05 -07:00
if ( ! TryGetActionData ( actionId , out var action ) )
return ;
Dirty ( actionId . Value , action ) ;
2022-02-26 18:24:08 +13:00
if ( action . AttachedEntity = = null )
return ;
2023-09-11 09:42:41 +10:00
var ent = action . AttachedEntity ;
if ( ! TryComp ( ent , out ActionsComponent ? comp ) )
2022-02-26 18:24:08 +13:00
{
action . AttachedEntity = null ;
return ;
}
2023-09-08 18:16:05 -07:00
Dirty ( action . AttachedEntity . Value , comp ) ;
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-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-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-08 18:16:05 -07:00
Dirty ( actionId . Value , action ) ;
}
private void OnActionsMapInit ( EntityUid uid , ActionsComponent component , MapInitEvent args )
{
2023-09-09 16:14:17 -07:00
EnsureContainer ( uid , null ) ;
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
}
2023-09-08 18:16:05 -07:00
private void OnActionsShutdown ( EntityUid uid , ActionsComponent component , ComponentShutdown args )
2022-02-26 18:24:08 +13:00
{
2023-09-08 18:16:05 -07:00
if ( TryGetContainer ( uid , out var container ) )
container . Shutdown ( EntityManager ) ;
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-11 09:42:41 +10:00
var action = GetActionData ( actionEnt ) ;
2023-09-08 18:16:05 -07:00
if ( action = = null | | ! action . Enabled )
2022-02-26 18:24:08 +13:00
return ;
var curTime = GameTiming . CurTime ;
2023-09-08 18:16:05 -07:00
if ( action . Cooldown . HasValue & & action . Cooldown . Value . End > curTime )
2022-02-26 18:24:08 +13:00
return ;
2022-04-14 16:17:34 +12:00
BaseActionEvent ? performEvent = null ;
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
if ( ! ValidateEntityTarget ( user , entityTarget , entityAction ) )
return ;
2023-09-08 18:16:05 -07:00
if ( action . Provider = = null )
2023-06-27 23:56:52 +10:00
{
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . Action ,
2023-06-27 23:56:52 +10:00
$"{ToPrettyString(user):user} is performing the {name:action} action targeted at {ToPrettyString(entityTarget):target}." ) ;
}
2022-02-26 18:24:08 +13:00
else
2023-06-27 23:56:52 +10:00
{
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . Action ,
2023-09-08 18:16:05 -07:00
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Provider.Value):provider}) targeted at {ToPrettyString(entityTarget):target}." ) ;
2023-06-27 23:56:52 +10:00
}
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 ) ;
2022-12-06 18:03:20 -05:00
_rotateToFaceSystem . TryFaceCoordinates ( user , entityCoordinatesTarget . Position ) ;
2022-02-26 18:24:08 +13:00
2022-12-06 18:03:20 -05:00
if ( ! ValidateWorldTarget ( user , entityCoordinatesTarget , worldAction ) )
2022-02-26 18:24:08 +13:00
return ;
2023-09-08 18:16:05 -07:00
if ( action . Provider = = null )
2023-06-27 23:56:52 +10:00
{
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . Action ,
2022-12-06 18:03:20 -05:00
$"{ToPrettyString(user):user} is performing the {name:action} action targeted at {entityCoordinatesTarget:target}." ) ;
2023-06-27 23:56:52 +10:00
}
2022-02-26 18:24:08 +13:00
else
2023-06-27 23:56:52 +10:00
{
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . Action ,
2023-09-08 18:16:05 -07:00
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Provider.Value):provider}) targeted at {entityCoordinatesTarget:target}." ) ;
2023-06-27 23:56:52 +10:00
}
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 ;
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-08 18:16:05 -07:00
if ( action . Provider = = null )
2023-06-27 23:56:52 +10:00
{
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . Action ,
2022-02-26 18:24:08 +13:00
$"{ToPrettyString(user):user} is performing the {name:action} action." ) ;
2023-06-27 23:56:52 +10:00
}
2022-02-26 18:24:08 +13:00
else
2023-06-27 23:56:52 +10:00
{
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . Action ,
2023-09-08 18:16:05 -07:00
$"{ToPrettyString(user):user} is performing the {name:action} action provided by {ToPrettyString(action.Provider.Value):provider}." ) ;
2023-06-27 23:56:52 +10:00
}
2022-02-26 18:24:08 +13:00
performEvent = instantAction . Event ;
break ;
}
if ( performEvent ! = null )
performEvent . Performer = user ;
// 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
}
2023-09-08 18:16:05 -07:00
public bool ValidateEntityTarget ( EntityUid user , EntityUid target , EntityTargetActionComponent action )
2022-02-26 18:24:08 +13:00
{
if ( ! target . IsValid ( ) | | Deleted ( target ) )
return false ;
if ( action . Whitelist ! = null & & ! action . Whitelist . IsValid ( target , EntityManager ) )
return false ;
if ( action . CheckCanInteract & & ! _actionBlockerSystem . CanInteract ( user , target ) )
return false ;
if ( user = = target )
return action . CanTargetSelf ;
if ( ! action . CheckCanAccess )
{
// 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 ;
if ( action . Range < = 0 )
return true ;
2023-07-08 14:08:32 +10:00
var distance = ( _transformSystem . GetWorldPosition ( xform ) - _transformSystem . GetWorldPosition ( targetXform ) ) . Length ( ) ;
2023-05-01 04:29:18 -04:00
return distance < = action . Range ;
2022-02-26 18:24:08 +13:00
}
if ( _interactionSystem . InRangeUnobstructed ( user , target , range : action . Range )
& & _containerSystem . IsInSameOrParentContainer ( user , target ) )
{
return true ;
}
return _interactionSystem . CanAccessViaStorage ( user , target ) ;
}
2023-09-08 18:16:05 -07:00
public bool ValidateWorldTarget ( EntityUid user , EntityCoordinates coords , WorldTargetActionComponent action )
2022-02-26 18:24:08 +13:00
{
if ( action . CheckCanInteract & & ! _actionBlockerSystem . CanInteract ( user , null ) )
return false ;
if ( ! action . CheckCanAccess )
{
// 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 ;
if ( action . Range < = 0 )
return true ;
2023-05-01 04:29:18 -04:00
return coords . InRange ( EntityManager , _transformSystem , Transform ( user ) . Coordinates , action . Range ) ;
2022-02-26 18:24:08 +13:00
}
return _interactionSystem . InRangeUnobstructed ( user , coords , range : action . Range ) ;
}
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 ;
if ( actionEvent ! = null )
{
// This here is required because of client-side prediction (RaisePredictiveEvent results in event re-use).
actionEvent . Handled = false ;
2023-09-11 09:42:41 +10:00
var provider = action . Provider ;
2022-02-26 18:24:08 +13:00
2023-09-11 09:42:41 +10:00
if ( provider = = null )
2023-01-02 13:01:40 +13:00
RaiseLocalEvent ( performer , ( object ) actionEvent , broadcast : true ) ;
2022-02-26 18:24:08 +13:00
else
2023-09-11 09:42:41 +10:00
RaiseLocalEvent ( provider . Value , ( object ) actionEvent , broadcast : true ) ;
2022-02-26 18:24:08 +13:00
handled = actionEvent . Handled ;
}
2023-04-26 16:04:44 +12:00
_audio . PlayPredicted ( action . Sound , performer , predicted ? performer : null ) ;
handled | = action . Sound ! = null ;
2022-02-26 18:24:08 +13:00
if ( ! handled )
return ; // no interaction occurred.
// reduce charges, start cooldown, and mark as dirty (if required).
var dirty = toggledBefore = = action . Toggled ;
if ( action . Charges ! = null )
{
dirty = true ;
action . Charges - - ;
if ( action . Charges = = 0 )
action . Enabled = false ;
}
action . Cooldown = null ;
if ( action . UseDelay ! = null )
{
dirty = true ;
action . Cooldown = ( curTime , curTime + action . UseDelay . Value ) ;
}
2023-09-08 18:16:05 -07:00
Dirty ( actionId , action ) ;
2023-01-02 13:01:40 +13:00
if ( dirty & & component ! = null )
2023-09-08 18:16:05 -07:00
Dirty ( performer , component ) ;
2022-02-26 18:24:08 +13:00
}
#endregion
#region AddRemoveActions
/// <summary>
2023-09-08 18:16:05 -07:00
/// Add an action to an action holder.
/// If the holder has no actions component, this will give them one.
2022-02-26 18:24:08 +13:00
/// </summary>
2023-09-08 18:16:05 -07:00
public BaseActionComponent ? AddAction ( EntityUid holderId , ref EntityUid ? actionId , string? actionPrototypeId , EntityUid ? provider = null , ActionsComponent ? holderComp = null )
{
if ( Deleted ( actionId ) )
{
if ( _net . IsClient )
return null ;
if ( string . IsNullOrWhiteSpace ( actionPrototypeId ) )
return null ;
actionId = Spawn ( actionPrototypeId ) ;
}
AddAction ( holderId , actionId . Value , provider , holderComp ) ;
return GetActionData ( actionId ) ;
}
/// <summary>
/// Add an action to an action holder.
/// If the holder has no actions component, this will give them one.
/// </summary>
/// <param name="holderId">Entity to receive the actions</param>
/// <param name="actionId">Action entity to add</param>
2022-02-26 18:24:08 +13:00
/// <param name="provider">The entity that enables these actions (e.g., flashlight). May be null (innate actions).</param>
2023-09-08 18:16:05 -07:00
/// <param name="holder">Component of <see cref="holderId"/></param>
/// <param name="action">Component of <see cref="actionId"/></param>
/// <param name="actionContainer">Action container of <see cref="holderId"/></param>
2023-09-11 09:42:41 +10:00
public virtual void AddAction ( EntityUid holderId , EntityUid actionId , EntityUid ? provider , ActionsComponent ? holder = null , BaseActionComponent ? action = null , bool dirty = true , BaseContainer ? actionContainer = null )
2022-02-26 18:24:08 +13:00
{
2023-09-08 18:16:05 -07:00
action ? ? = GetActionData ( actionId ) ;
// TODO remove when action subscriptions are split up
if ( action = = null )
2022-04-14 16:17:34 +12:00
{
2023-09-08 18:16:05 -07:00
Log . Warning ( $"No {nameof(BaseActionComponent)} found on entity {actionId}" ) ;
2022-04-14 16:17:34 +12:00
return ;
}
2023-09-08 18:16:05 -07:00
holder ? ? = EnsureComp < ActionsComponent > ( holderId ) ;
2022-02-26 18:24:08 +13:00
action . Provider = provider ;
2023-09-08 18:16:05 -07:00
action . AttachedEntity = holderId ;
Dirty ( actionId , action ) ;
2023-09-09 16:14:17 -07:00
actionContainer ? ? = EnsureContainer ( holderId , provider ) ;
AddActionInternal ( holderId , actionId , actionContainer , holder ) ;
2022-02-26 18:24:08 +13:00
2023-04-25 07:29:47 +12:00
if ( dirty )
2023-09-08 18:16:05 -07:00
Dirty ( holderId , holder ) ;
2022-02-26 18:24:08 +13:00
}
2023-09-11 09:42:41 +10:00
protected virtual void AddActionInternal ( EntityUid holderId , EntityUid actionId , BaseContainer container , ActionsComponent holder )
2022-02-26 18:24:08 +13:00
{
2023-09-08 18:16:05 -07:00
container . Insert ( actionId ) ;
2023-09-09 16:14:17 -07:00
holder . Actions . Add ( actionId ) ;
Dirty ( holderId , holder ) ;
2022-02-26 18:24:08 +13:00
}
/// <summary>
/// Add actions to an action component. If the entity has no action component, this will give them one.
/// </summary>
2023-09-08 18:16:05 -07:00
/// <param name="holderId">Entity to receive the actions</param>
2022-02-26 18:24:08 +13:00
/// <param name="actions">The actions to add</param>
/// <param name="provider">The entity that enables these actions (e.g., flashlight). May be null (innate actions).</param>
2023-09-08 18:16:05 -07:00
public void AddActions ( EntityUid holderId , IEnumerable < EntityUid > actions , EntityUid ? provider , ActionsComponent ? comp = null , bool dirty = true )
2022-02-26 18:24:08 +13:00
{
2023-09-08 18:16:05 -07:00
comp ? ? = EnsureComp < ActionsComponent > ( holderId ) ;
2022-02-26 18:24:08 +13:00
2023-05-01 04:29:18 -04:00
var allClientExclusive = true ;
2023-09-09 16:14:17 -07:00
var container = EnsureContainer ( holderId , provider ) ;
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-08 18:16:05 -07:00
var action = GetActionData ( actionId ) ;
if ( action = = null )
continue ;
AddAction ( holderId , actionId , provider , comp , action , false , container ) ;
2023-04-23 21:38:52 +02:00
allClientExclusive = allClientExclusive & & action . ClientExclusive ;
2022-02-26 18:24:08 +13:00
}
2023-04-23 21:38:52 +02:00
if ( dirty & & ! allClientExclusive )
2023-09-08 18:16:05 -07:00
Dirty ( holderId , comp ) ;
}
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-09 16:14:17 -07:00
public void RemoveProvidedActions ( EntityUid holderId , EntityUid provider , ActionsComponent ? comp = null )
2022-02-26 18:24:08 +13:00
{
2023-09-09 16:14:17 -07:00
if ( ! Resolve ( holderId , ref comp , false ) )
2023-09-08 18:16:05 -07:00
return ;
2023-09-09 16:14:17 -07:00
if ( ! TryGetProvidedContainer ( provider , out var container ) )
2022-02-26 18:24:08 +13:00
return ;
2023-09-08 18:16:05 -07:00
foreach ( var actionId in container . ContainedEntities . ToArray ( ) )
2023-04-25 07:29:47 +12:00
{
2023-09-08 18:16:05 -07:00
var action = GetActionData ( actionId ) ;
if ( action ? . Provider = = provider )
2023-09-09 16:14:17 -07:00
RemoveAction ( holderId , actionId , comp , dirty : false ) ;
2023-04-25 07:29:47 +12:00
}
2023-09-08 18:16:05 -07:00
Dirty ( holderId , comp ) ;
2022-02-26 18:24:08 +13:00
}
2023-09-09 16:14:17 -07:00
public virtual void RemoveAction ( EntityUid holderId , EntityUid ? actionId , ActionsComponent ? comp = null , BaseActionComponent ? action = null , bool dirty = true )
2022-02-26 18:24:08 +13:00
{
2023-09-08 18:16:05 -07:00
if ( actionId = = null | |
2023-09-09 16:14:17 -07:00
! Resolve ( holderId , ref comp , false ) | |
2023-09-08 18:16:05 -07:00
TerminatingOrDeleted ( actionId . Value ) )
{
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
2023-09-08 18:16:05 -07:00
action ? ? = GetActionData ( actionId ) ;
2023-09-09 16:14:17 -07:00
if ( TryGetContainer ( holderId , out var container ) & & container . Contains ( actionId . Value ) )
QueueDel ( actionId . Value ) ;
comp . Actions . Remove ( actionId . Value ) ;
2023-09-08 18:16:05 -07:00
if ( action ! = null )
{
action . AttachedEntity = null ;
Dirty ( actionId . Value , action ) ;
}
2022-02-26 18:24:08 +13:00
if ( dirty )
2023-09-08 18:16:05 -07:00
Dirty ( holderId , comp ) ;
2023-09-09 16:14:17 -07:00
DebugTools . Assert ( Transform ( actionId . Value ) . ParentUid . IsValid ( ) ) ;
2023-09-08 18:16:05 -07:00
}
/// <summary>
/// Removes all actions with the given prototype id.
/// </summary>
2023-09-09 16:14:17 -07:00
public void RemoveAction ( EntityUid holderId , string actionPrototypeId , ActionsComponent ? holderComp = null )
2023-09-08 18:16:05 -07:00
{
2023-09-09 16:14:17 -07:00
if ( ! Resolve ( holderId , ref holderComp , false ) )
2023-09-08 18:16:05 -07:00
return ;
var actions = new List < ( EntityUid Id , BaseActionComponent Comp ) > ( ) ;
foreach ( var ( id , comp ) in GetActions ( holderId ) )
{
if ( Prototype ( id ) ? . ID = = actionPrototypeId )
actions . Add ( ( id , comp ) ) ;
}
2023-09-09 16:14:17 -07:00
if ( actions . Count = = 0 )
return ;
2023-09-08 18:16:05 -07:00
foreach ( var action in actions )
{
2023-09-09 16:14:17 -07:00
RemoveAction ( holderId , action . Id , holderComp , action . Comp ) ;
2023-09-08 18:16:05 -07:00
}
2022-02-26 18:24:08 +13:00
}
#endregion
#region EquipHandlers
private void OnDidEquip ( EntityUid uid , ActionsComponent component , DidEquipEvent args )
{
2023-09-08 18:16:05 -07:00
var ev = new GetItemActionsEvent ( EntityManager , _net , args . Equipee , 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 ;
AddActions ( args . Equipee , ev . Actions , args . Equipment , component ) ;
}
private void OnHandEquipped ( EntityUid uid , ActionsComponent component , DidEquipHandEvent args )
{
2023-09-08 18:16:05 -07:00
var ev = new GetItemActionsEvent ( EntityManager , _net , args . User ) ;
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 ;
AddActions ( args . User , ev . Actions , args . Equipped , component ) ;
}
private void OnDidUnequip ( EntityUid uid , ActionsComponent component , DidUnequipEvent args )
{
RemoveProvidedActions ( uid , args . Equipment , component ) ;
}
private void OnHandUnequipped ( EntityUid uid , ActionsComponent component , DidUnequipHandEvent args )
{
RemoveProvidedActions ( uid , args . Unequipped , component ) ;
}
#endregion
}