Refactor actions to be entities with components (#19900)
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Actions.Events;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands;
|
||||
@@ -8,16 +10,18 @@ using Content.Shared.Inventory.Events;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
public abstract class SharedActionsSystem : EntitySystem
|
||||
{
|
||||
private const string ActionContainerId = "ActionContainer";
|
||||
|
||||
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
@@ -34,14 +38,149 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
SubscribeLocalEvent<ActionsComponent, DidUnequipEvent>(OnDidUnequip);
|
||||
SubscribeLocalEvent<ActionsComponent, DidUnequipHandEvent>(OnHandUnequipped);
|
||||
|
||||
SubscribeLocalEvent<ActionsComponent, ComponentGetState>(GetState);
|
||||
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);
|
||||
|
||||
SubscribeAllEvent<RequestPerformActionEvent>(OnActionRequest);
|
||||
}
|
||||
|
||||
#region ComponentStateManagement
|
||||
public virtual void Dirty(ActionType action)
|
||||
private void OnInstantGetState(EntityUid uid, InstantActionComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new InstantActionComponentState(component);
|
||||
}
|
||||
|
||||
private void OnEntityTargetGetState(EntityUid uid, EntityTargetActionComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new EntityTargetActionComponentState(component);
|
||||
}
|
||||
|
||||
private void OnWorldTargetGetState(EntityUid uid, WorldTargetActionComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new WorldTargetActionComponentState(component);
|
||||
}
|
||||
|
||||
private void BaseHandleState(BaseActionComponent component, BaseActionComponentState state)
|
||||
{
|
||||
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;
|
||||
component.Provider = state.Provider;
|
||||
component.EntityIcon = state.EntityIcon;
|
||||
component.CheckCanInteract = state.CheckCanInteract;
|
||||
component.ClientExclusive = state.ClientExclusive;
|
||||
component.Priority = state.Priority;
|
||||
component.AttachedEntity = state.AttachedEntity;
|
||||
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;
|
||||
|
||||
BaseHandleState(component, state);
|
||||
}
|
||||
|
||||
private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not EntityTargetActionComponentState state)
|
||||
return;
|
||||
|
||||
BaseHandleState(component, state);
|
||||
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;
|
||||
|
||||
BaseHandleState(component, state);
|
||||
}
|
||||
|
||||
private void OnGetActionData<T>(EntityUid uid, T component, ref GetActionDataEvent args) where T : BaseActionComponent
|
||||
{
|
||||
args.Action = component;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
protected Container EnsureContainer(EntityUid holderId)
|
||||
{
|
||||
return _containerSystem.EnsureContainer<Container>(holderId, ActionContainerId);
|
||||
}
|
||||
|
||||
protected bool TryGetContainer(
|
||||
EntityUid holderId,
|
||||
[NotNullWhen(true)] out IContainer? container,
|
||||
ContainerManagerComponent? containerManager = null)
|
||||
{
|
||||
return _containerSystem.TryGetContainer(holderId, ActionContainerId, out container, containerManager);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#region ComponentStateManagement
|
||||
public virtual void Dirty(EntityUid? actionId)
|
||||
{
|
||||
if (!TryGetActionData(actionId, out var action))
|
||||
return;
|
||||
|
||||
Dirty(actionId.Value, action);
|
||||
|
||||
if (action.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
@@ -51,39 +190,63 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
Dirty(comp);
|
||||
Dirty(action.AttachedEntity.Value, comp);
|
||||
}
|
||||
|
||||
public void SetToggled(ActionType action, bool toggled)
|
||||
public void SetToggled(EntityUid? actionId, bool toggled)
|
||||
{
|
||||
if (action.Toggled == toggled)
|
||||
if (!TryGetActionData(actionId, out var action) ||
|
||||
action.Toggled == toggled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.Toggled = toggled;
|
||||
Dirty(action);
|
||||
Dirty(actionId.Value, action);
|
||||
}
|
||||
|
||||
public void SetEnabled(ActionType action, bool enabled)
|
||||
public void SetEnabled(EntityUid? actionId, bool enabled)
|
||||
{
|
||||
if (action.Enabled == enabled)
|
||||
if (!TryGetActionData(actionId, out var action) ||
|
||||
action.Enabled == enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.Enabled = enabled;
|
||||
Dirty(action);
|
||||
Dirty(actionId.Value, action);
|
||||
}
|
||||
|
||||
public void SetCharges(ActionType action, int? charges)
|
||||
public void SetCharges(EntityUid? actionId, int? charges)
|
||||
{
|
||||
if (action.Charges == charges)
|
||||
if (!TryGetActionData(actionId, out var action) ||
|
||||
action.Charges == charges)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action.Charges = charges;
|
||||
Dirty(action);
|
||||
Dirty(actionId.Value, action);
|
||||
}
|
||||
|
||||
private void GetState(EntityUid uid, ActionsComponent component, ref ComponentGetState args)
|
||||
private void OnActionsMapInit(EntityUid uid, ActionsComponent component, MapInitEvent args)
|
||||
{
|
||||
args.State = new ActionsComponentState(component.Actions.ToList());
|
||||
EnsureContainer(uid);
|
||||
}
|
||||
|
||||
private void OnActionsGetState(EntityUid uid, ActionsComponent component, ref ComponentGetState args)
|
||||
{
|
||||
var actions = new List<EntityUid>();
|
||||
if (TryGetContainer(uid, out var container))
|
||||
actions.AddRange(container.ContainedEntities);
|
||||
|
||||
args.State = new ActionsComponentState(actions);
|
||||
}
|
||||
|
||||
private void OnActionsShutdown(EntityUid uid, ActionsComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (TryGetContainer(uid, out var container))
|
||||
container.Shutdown(EntityManager);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -101,33 +264,36 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (!TryComp(user, out ActionsComponent? component))
|
||||
return;
|
||||
|
||||
if (!TryComp(ev.Action, out MetaDataComponent? metaData))
|
||||
return;
|
||||
|
||||
var name = Name(ev.Action, metaData);
|
||||
|
||||
// Does the user actually have the requested action?
|
||||
if (!component.Actions.TryGetValue(ev.Action, out var act))
|
||||
if (!TryGetContainer(user, out var container) || !container.Contains(ev.Action))
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} attempted to perform an action that they do not have: {ev.Action.DisplayName}.");
|
||||
$"{ToPrettyString(user):user} attempted to perform an action that they do not have: {name}.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!act.Enabled)
|
||||
var action = GetActionData(ev.Action);
|
||||
if (action == null || !action.Enabled)
|
||||
return;
|
||||
|
||||
var curTime = GameTiming.CurTime;
|
||||
if (act.Cooldown.HasValue && act.Cooldown.Value.End > curTime)
|
||||
if (action.Cooldown.HasValue && action.Cooldown.Value.End > curTime)
|
||||
return;
|
||||
|
||||
BaseActionEvent? performEvent = null;
|
||||
|
||||
// Validate request by checking action blockers and the like:
|
||||
var name = Loc.GetString(act.DisplayName);
|
||||
|
||||
switch (act)
|
||||
switch (action)
|
||||
{
|
||||
case EntityTargetAction entityAction:
|
||||
|
||||
case EntityTargetActionComponent entityAction:
|
||||
if (ev.EntityTarget is not { Valid: true } entityTarget)
|
||||
{
|
||||
Log.Error($"Attempted to perform an entity-targeted action without a target! Action: {entityAction.DisplayName}");
|
||||
Log.Error($"Attempted to perform an entity-targeted action without a target! Action: {name}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -137,7 +303,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (!ValidateEntityTarget(user, entityTarget, entityAction))
|
||||
return;
|
||||
|
||||
if (act.Provider == null)
|
||||
if (action.Provider == null)
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action targeted at {ToPrettyString(entityTarget):target}.");
|
||||
@@ -145,22 +311,21 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(act.Provider.Value):provider}) targeted at {ToPrettyString(entityTarget):target}.");
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Provider.Value):provider}) targeted at {ToPrettyString(entityTarget):target}.");
|
||||
}
|
||||
|
||||
if (entityAction.Event != null)
|
||||
{
|
||||
entityAction.Event.Target = entityTarget;
|
||||
Dirty(ev.Action, entityAction);
|
||||
performEvent = entityAction.Event;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case WorldTargetAction worldAction:
|
||||
|
||||
case WorldTargetActionComponent worldAction:
|
||||
if (ev.EntityCoordinatesTarget is not { } entityCoordinatesTarget)
|
||||
{
|
||||
Log.Error($"Attempted to perform a world-targeted action without a target! Action: {worldAction.DisplayName}");
|
||||
Log.Error($"Attempted to perform a world-targeted action without a target! Action: {name}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,7 +334,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (!ValidateWorldTarget(user, entityCoordinatesTarget, worldAction))
|
||||
return;
|
||||
|
||||
if (act.Provider == null)
|
||||
if (action.Provider == null)
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action targeted at {entityCoordinatesTarget:target}.");
|
||||
@@ -177,23 +342,22 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(act.Provider.Value):provider}) targeted at {entityCoordinatesTarget:target}.");
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Provider.Value):provider}) targeted at {entityCoordinatesTarget:target}.");
|
||||
}
|
||||
|
||||
if (worldAction.Event != null)
|
||||
{
|
||||
worldAction.Event.Target = entityCoordinatesTarget;
|
||||
Dirty(ev.Action, worldAction);
|
||||
performEvent = worldAction.Event;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case InstantAction instantAction:
|
||||
|
||||
if (act.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, null))
|
||||
case InstantActionComponent instantAction:
|
||||
if (action.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, null))
|
||||
return;
|
||||
|
||||
if (act.Provider == null)
|
||||
if (action.Provider == null)
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action.");
|
||||
@@ -201,7 +365,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action provided by {ToPrettyString(act.Provider.Value):provider}.");
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action provided by {ToPrettyString(action.Provider.Value):provider}.");
|
||||
}
|
||||
|
||||
performEvent = instantAction.Event;
|
||||
@@ -212,10 +376,10 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
performEvent.Performer = user;
|
||||
|
||||
// All checks passed. Perform the action!
|
||||
PerformAction(user, component, act, performEvent, curTime);
|
||||
PerformAction(user, component, ev.Action, action, performEvent, curTime);
|
||||
}
|
||||
|
||||
public bool ValidateEntityTarget(EntityUid user, EntityUid target, EntityTargetAction action)
|
||||
public bool ValidateEntityTarget(EntityUid user, EntityUid target, EntityTargetActionComponent action)
|
||||
{
|
||||
if (!target.IsValid() || Deleted(target))
|
||||
return false;
|
||||
@@ -254,7 +418,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
return _interactionSystem.CanAccessViaStorage(user, target);
|
||||
}
|
||||
|
||||
public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, WorldTargetAction action)
|
||||
public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, WorldTargetActionComponent action)
|
||||
{
|
||||
if (action.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, null))
|
||||
return false;
|
||||
@@ -276,7 +440,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
return _interactionSystem.InRangeUnobstructed(user, coords, range: action.Range);
|
||||
}
|
||||
|
||||
public void PerformAction(EntityUid performer, ActionsComponent? component, ActionType action, BaseActionEvent? actionEvent, TimeSpan curTime, bool predicted = true)
|
||||
public void PerformAction(EntityUid performer, ActionsComponent? component, EntityUid actionId, BaseActionComponent action, BaseActionEvent? actionEvent, TimeSpan curTime, bool predicted = true)
|
||||
{
|
||||
var handled = false;
|
||||
|
||||
@@ -320,89 +484,180 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
action.Cooldown = (curTime, curTime + action.UseDelay.Value);
|
||||
}
|
||||
|
||||
Dirty(actionId, action);
|
||||
|
||||
if (dirty && component != null)
|
||||
Dirty(component);
|
||||
Dirty(performer, component);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AddRemoveActions
|
||||
/// <summary>
|
||||
/// Add an action to an action component. If the entity has no action component, this will give them one.
|
||||
/// Add an action to an action holder.
|
||||
/// If the holder has no actions component, this will give them one.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity to receive the actions</param>
|
||||
/// <param name="action">The action to add</param>
|
||||
/// <param name="provider">The entity that enables these actions (e.g., flashlight). May be null (innate actions).</param>
|
||||
public virtual void AddAction(EntityUid uid, ActionType action, EntityUid? provider, ActionsComponent? comp = null, bool dirty = true)
|
||||
public BaseActionComponent? AddAction(EntityUid holderId, ref EntityUid? actionId, string? actionPrototypeId, EntityUid? provider = null, ActionsComponent? holderComp = null)
|
||||
{
|
||||
// Because action classes have state data, e.g. cooldowns and uses-remaining, people should not be adding prototypes directly
|
||||
if (action is IPrototype)
|
||||
if (Deleted(actionId))
|
||||
{
|
||||
Log.Error("Attempted to directly add a prototype action. You need to clone a prototype in order to use it.");
|
||||
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>
|
||||
/// <param name="provider">The entity that enables these actions (e.g., flashlight). May be null (innate actions).</param>
|
||||
/// <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>
|
||||
public virtual void AddAction(EntityUid holderId, EntityUid actionId, EntityUid? provider, ActionsComponent? holder = null, BaseActionComponent? action = null, bool dirty = true, IContainer? actionContainer = null)
|
||||
{
|
||||
action ??= GetActionData(actionId);
|
||||
// TODO remove when action subscriptions are split up
|
||||
if (action == null)
|
||||
{
|
||||
Log.Warning($"No {nameof(BaseActionComponent)} found on entity {actionId}");
|
||||
return;
|
||||
}
|
||||
|
||||
comp ??= EnsureComp<ActionsComponent>(uid);
|
||||
holder ??= EnsureComp<ActionsComponent>(holderId);
|
||||
action.Provider = provider;
|
||||
action.AttachedEntity = uid;
|
||||
AddActionInternal(comp, action);
|
||||
action.AttachedEntity = holderId;
|
||||
Dirty(actionId, action);
|
||||
|
||||
actionContainer ??= EnsureContainer(holderId);
|
||||
AddActionInternal(actionId, actionContainer);
|
||||
|
||||
if (dirty)
|
||||
Dirty(comp);
|
||||
Dirty(holderId, holder);
|
||||
}
|
||||
|
||||
protected virtual void AddActionInternal(ActionsComponent comp, ActionType action)
|
||||
protected virtual void AddActionInternal(EntityUid actionId, IContainer container)
|
||||
{
|
||||
comp.Actions.Add(action);
|
||||
container.Insert(actionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add actions to an action component. If the entity has no action component, this will give them one.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity to receive the actions</param>
|
||||
/// <param name="holderId">Entity to receive the actions</param>
|
||||
/// <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>
|
||||
public void AddActions(EntityUid uid, IEnumerable<ActionType> actions, EntityUid? provider, ActionsComponent? comp = null, bool dirty = true)
|
||||
public void AddActions(EntityUid holderId, IEnumerable<EntityUid> actions, EntityUid? provider, ActionsComponent? comp = null, bool dirty = true)
|
||||
{
|
||||
comp ??= EnsureComp<ActionsComponent>(uid);
|
||||
comp ??= EnsureComp<ActionsComponent>(holderId);
|
||||
|
||||
var allClientExclusive = true;
|
||||
var container = EnsureContainer(holderId);
|
||||
|
||||
foreach (var action in actions)
|
||||
foreach (var actionId in actions)
|
||||
{
|
||||
AddAction(uid, action, provider, comp, false);
|
||||
var action = GetActionData(actionId);
|
||||
if (action == null)
|
||||
continue;
|
||||
|
||||
AddAction(holderId, actionId, provider, comp, action, false, container);
|
||||
allClientExclusive = allClientExclusive && action.ClientExclusive;
|
||||
}
|
||||
|
||||
if (dirty && !allClientExclusive)
|
||||
Dirty(comp);
|
||||
Dirty(holderId, comp);
|
||||
}
|
||||
|
||||
public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetActions(EntityUid holderId, IContainer? container = null)
|
||||
{
|
||||
if (container == null &&
|
||||
!TryGetContainer(holderId, out container))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var actionId in container.ContainedEntities)
|
||||
{
|
||||
if (!TryGetActionData(actionId, out var action))
|
||||
continue;
|
||||
|
||||
yield return (actionId, action);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove any actions that were enabled by some other entity. Useful when unequiping items that grant actions.
|
||||
/// </summary>
|
||||
public void RemoveProvidedActions(EntityUid uid, EntityUid provider, ActionsComponent? comp = null)
|
||||
public void RemoveProvidedActions(EntityUid holderId, EntityUid provider, ActionsComponent? comp = null, ContainerManagerComponent? actionContainer = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
if (!Resolve(holderId, ref comp, ref actionContainer, false))
|
||||
return;
|
||||
|
||||
foreach (var act in comp.Actions.ToArray())
|
||||
if (!TryGetContainer(holderId, out var container, actionContainer))
|
||||
return;
|
||||
|
||||
foreach (var actionId in container.ContainedEntities.ToArray())
|
||||
{
|
||||
if (act.Provider == provider)
|
||||
RemoveAction(uid, act, comp, dirty: false);
|
||||
var action = GetActionData(actionId);
|
||||
if (action?.Provider == provider)
|
||||
RemoveAction(holderId, actionId, comp, dirty: false, actionContainer: actionContainer);
|
||||
}
|
||||
Dirty(comp);
|
||||
|
||||
Dirty(holderId, comp);
|
||||
}
|
||||
|
||||
public virtual void RemoveAction(EntityUid uid, ActionType action, ActionsComponent? comp = null, bool dirty = true)
|
||||
public virtual void RemoveAction(EntityUid holderId, EntityUid? actionId, ActionsComponent? comp = null, BaseActionComponent? action = null, bool dirty = true, ContainerManagerComponent? actionContainer = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
if (actionId == null ||
|
||||
!Resolve(holderId, ref comp, ref actionContainer, false) ||
|
||||
!TryGetContainer(holderId, out var container, actionContainer) ||
|
||||
!container.Contains(actionId.Value) ||
|
||||
TerminatingOrDeleted(actionId.Value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
comp.Actions.Remove(action);
|
||||
action.AttachedEntity = null;
|
||||
action ??= GetActionData(actionId);
|
||||
container.Remove(actionId.Value);
|
||||
|
||||
if (action != null)
|
||||
{
|
||||
action.AttachedEntity = null;
|
||||
Dirty(actionId.Value, action);
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
Dirty(comp);
|
||||
Dirty(holderId, comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all actions with the given prototype id.
|
||||
/// </summary>
|
||||
public void RemoveAction(EntityUid holderId, string actionPrototypeId, ActionsComponent? holderComp = null, ContainerManagerComponent? actionContainer = null)
|
||||
{
|
||||
if (!Resolve(holderId, ref holderComp, ref actionContainer, false))
|
||||
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));
|
||||
}
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
RemoveAction(holderId, action.Id, holderComp, action.Comp, actionContainer: actionContainer);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -410,7 +665,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
#region EquipHandlers
|
||||
private void OnDidEquip(EntityUid uid, ActionsComponent component, DidEquipEvent args)
|
||||
{
|
||||
var ev = new GetItemActionsEvent(args.Equipee, args.SlotFlags);
|
||||
var ev = new GetItemActionsEvent(EntityManager, _net, args.Equipee, args.SlotFlags);
|
||||
RaiseLocalEvent(args.Equipment, ev);
|
||||
|
||||
if (ev.Actions.Count == 0)
|
||||
@@ -421,7 +676,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
|
||||
private void OnHandEquipped(EntityUid uid, ActionsComponent component, DidEquipHandEvent args)
|
||||
{
|
||||
var ev = new GetItemActionsEvent(args.User);
|
||||
var ev = new GetItemActionsEvent(EntityManager, _net, args.User);
|
||||
RaiseLocalEvent(args.Equipped, ev);
|
||||
|
||||
if (ev.Actions.Count == 0)
|
||||
|
||||
Reference in New Issue
Block a user