Action container rejig (#20260)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
17
Content.Shared/Actions/ActionContainerComponent.cs
Normal file
17
Content.Shared/Actions/ActionContainerComponent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
/// <summary>
|
||||
/// This component indicates that this entity contains actions inside of some container.
|
||||
/// </summary>
|
||||
[NetworkedComponent, RegisterComponent]
|
||||
[Access(typeof(ActionContainerSystem), typeof(SharedActionsSystem))]
|
||||
public sealed partial class ActionsContainerComponent : Component
|
||||
{
|
||||
public const string ContainerId = "actions";
|
||||
|
||||
[ViewVariables]
|
||||
public Container Container = default!;
|
||||
}
|
||||
214
Content.Shared/Actions/ActionContainerSystem.cs
Normal file
214
Content.Shared/Actions/ActionContainerSystem.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
/// <summary>
|
||||
/// Handles storing & spawning action entities in a container.
|
||||
/// </summary>
|
||||
public sealed class ActionContainerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ActionsContainerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<ActionsContainerComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<ActionsContainerComponent, EntRemovedFromContainerMessage>(OnEntityRemoved);
|
||||
SubscribeLocalEvent<ActionsContainerComponent, EntInsertedIntoContainerMessage>(OnEntityInserted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a new action entity and adds it to the given container.
|
||||
/// </summary>
|
||||
public EntityUid? AddAction(EntityUid uid, string actionPrototypeId, ActionsContainerComponent? comp = null)
|
||||
{
|
||||
EntityUid? result = default;
|
||||
EnsureAction(uid, ref result, actionPrototypeId, comp);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that a given entityUid refers to a valid entity action contained by the given container.
|
||||
/// If the entity does not exist, it will attempt to spawn a new action.
|
||||
/// Returns false if the given entity exists, but is not in a valid state.
|
||||
/// </summary>
|
||||
public bool EnsureAction(EntityUid uid,
|
||||
[NotNullWhen(true)] ref EntityUid? actionId,
|
||||
string actionPrototypeId,
|
||||
ActionsContainerComponent? comp = null)
|
||||
{
|
||||
return EnsureAction(uid, ref actionId, out _, actionPrototypeId, comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EnsureAction(Robust.Shared.GameObjects.EntityUid,ref System.Nullable{Robust.Shared.GameObjects.EntityUid},string?,Content.Shared.Actions.ActionsContainerComponent?)"/>
|
||||
public bool EnsureAction(EntityUid uid,
|
||||
[NotNullWhen(true)] ref EntityUid? actionId,
|
||||
[NotNullWhen(true)] out BaseActionComponent? action,
|
||||
string? actionPrototypeId,
|
||||
ActionsContainerComponent? comp = null)
|
||||
{
|
||||
action = null;
|
||||
|
||||
DebugTools.Assert(comp == null || comp.Owner == uid);
|
||||
comp ??= EnsureComp<ActionsContainerComponent>(uid);
|
||||
|
||||
if (Exists(actionId))
|
||||
{
|
||||
if (!comp.Container.Contains(actionId.Value))
|
||||
{
|
||||
Log.Error($"Action {ToPrettyString(actionId.Value)} is not contained in the expected container {ToPrettyString(uid)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_actions.TryGetActionData(actionId, out action))
|
||||
return false;
|
||||
|
||||
DebugTools.Assert(Transform(actionId.Value).ParentUid == uid);
|
||||
DebugTools.Assert(_container.IsEntityInContainer(actionId.Value));
|
||||
DebugTools.Assert(action.Container == uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Null prototypes are never valid entities, they mean that someone didn't provide a proper prototype.
|
||||
if (actionPrototypeId == null)
|
||||
return false;
|
||||
|
||||
// Client cannot predict entity spawning.
|
||||
if (_netMan.IsClient && !IsClientSide(uid))
|
||||
return false;
|
||||
|
||||
actionId = Spawn(actionPrototypeId);
|
||||
if (AddAction(uid, actionId.Value, action, comp) && _actions.TryGetActionData(actionId, out action))
|
||||
return true;
|
||||
|
||||
Del(actionId.Value);
|
||||
actionId = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a pre-existing action to an action container.
|
||||
/// </summary>
|
||||
public bool AddAction(EntityUid uid, EntityUid actionId, BaseActionComponent? action = null, ActionsContainerComponent? comp = null)
|
||||
{
|
||||
if (!_actions.ResolveActionData(actionId, ref action))
|
||||
return false;
|
||||
|
||||
if (action.Container != null)
|
||||
{
|
||||
Log.Error($"Attempted to insert an action {ToPrettyString(actionId)} that was already in a container {ToPrettyString(action.Container.Value)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
DebugTools.Assert(comp == null || comp.Owner == uid);
|
||||
comp ??= EnsureComp<ActionsContainerComponent>(uid);
|
||||
if (!comp.Container.Insert(actionId))
|
||||
{
|
||||
Log.Error($"Failed to insert action {ToPrettyString(actionId)} into {ToPrettyString(uid)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Container insert events should have updated the component's fields:
|
||||
DebugTools.Assert(comp.Container.Contains(actionId));
|
||||
DebugTools.Assert(action.Container == uid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, ActionsContainerComponent component, ComponentInit args)
|
||||
{
|
||||
component.Container = _container.EnsureContainer<Container>(uid, ActionsContainerComponent.ContainerId);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, ActionsContainerComponent component, ComponentShutdown args)
|
||||
{
|
||||
component.Container.Shutdown();
|
||||
}
|
||||
|
||||
private void OnEntityInserted(EntityUid uid, ActionsContainerComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != ActionsContainerComponent.ContainerId)
|
||||
return;
|
||||
|
||||
if (!_actions.TryGetActionData(args.Entity, out var data))
|
||||
return;
|
||||
|
||||
DebugTools.Assert(data.AttachedEntity == null || data.Container != EntityUid.Invalid);
|
||||
DebugTools.Assert(data.Container == null || data.Container == uid);
|
||||
|
||||
data.Container = uid;
|
||||
Dirty(uid, component);
|
||||
|
||||
var ev = new ActionAddedEvent(args.Entity, data);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
|
||||
private void OnEntityRemoved(EntityUid uid, ActionsContainerComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != ActionsContainerComponent.ContainerId)
|
||||
return;
|
||||
|
||||
// Actions should only be getting removed while terminating or moving outside of PVS range.
|
||||
DebugTools.Assert(Terminating(args.Entity)
|
||||
|| _netMan.IsServer // I love gibbing code
|
||||
|| _timing.ApplyingState);
|
||||
|
||||
if (!_actions.TryGetActionData(args.Entity, out var data, false))
|
||||
return;
|
||||
|
||||
// No event - the only entity that should care about this is the entity that the action was provided to.
|
||||
if (data.AttachedEntity != null)
|
||||
_actions.RemoveAction(data.AttachedEntity.Value, args.Entity, null, data);
|
||||
|
||||
var ev = new ActionRemovedEvent(args.Entity, data);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
if (_netMan.IsServer)
|
||||
{
|
||||
// TODO Actions
|
||||
// log an error or warning here once gibbing code is fixed.
|
||||
QueueDel(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised directed at an action container when a new action entity gets inserted.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly struct ActionAddedEvent
|
||||
{
|
||||
public readonly EntityUid Action;
|
||||
public readonly BaseActionComponent Component;
|
||||
|
||||
public ActionAddedEvent(EntityUid action, BaseActionComponent component)
|
||||
{
|
||||
Action = action;
|
||||
Component = component;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised directed at an action container when an action entity gets removed.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly struct ActionRemovedEvent
|
||||
{
|
||||
public readonly EntityUid Action;
|
||||
public readonly BaseActionComponent Component;
|
||||
|
||||
public ActionRemovedEvent(EntityUid action, BaseActionComponent component)
|
||||
{
|
||||
Action = action;
|
||||
Component = component;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ using Content.Shared.Hands;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Actions;
|
||||
@@ -18,8 +17,7 @@ namespace Content.Shared.Actions;
|
||||
/// </remarks>
|
||||
public sealed class GetItemActionsEvent : EntityEventArgs
|
||||
{
|
||||
private readonly IEntityManager _entities;
|
||||
private readonly INetManager _net;
|
||||
private readonly ActionContainerSystem _system;
|
||||
public readonly SortedSet<EntityUid> Actions = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -27,6 +25,12 @@ public sealed class GetItemActionsEvent : EntityEventArgs
|
||||
/// </summary>
|
||||
public EntityUid User;
|
||||
|
||||
/// <summary>
|
||||
/// The entity that is being asked to provide the actions. This is used as a default argument to <see cref="AddAction(ref System.Nullable{Robust.Shared.GameObjects.EntityUid},string,Robust.Shared.GameObjects.EntityUid)"/>.
|
||||
/// I.e., if a new action needs to be spawned, then it will be inserted into this entity unless otherwise specified.
|
||||
/// </summary>
|
||||
public EntityUid Provider;
|
||||
|
||||
/// <summary>
|
||||
/// Slot flags for the inventory slot that this item got equipped to. Null if not in a slot (i.e., if equipped to hands).
|
||||
/// </summary>
|
||||
@@ -37,25 +41,36 @@ public sealed class GetItemActionsEvent : EntityEventArgs
|
||||
/// </summary>
|
||||
public bool InHands => SlotFlags == null;
|
||||
|
||||
public GetItemActionsEvent(IEntityManager entities, INetManager net, EntityUid user, SlotFlags? slotFlags = null)
|
||||
public GetItemActionsEvent(ActionContainerSystem system, EntityUid user, EntityUid provider, SlotFlags? slotFlags = null)
|
||||
{
|
||||
_entities = entities;
|
||||
_net = net;
|
||||
_system = system;
|
||||
User = user;
|
||||
Provider = provider;
|
||||
SlotFlags = slotFlags;
|
||||
}
|
||||
|
||||
public void AddAction(ref EntityUid? actionId, string? prototypeId)
|
||||
/// <summary>
|
||||
/// Grant the given action. If the EntityUid does not refer to a valid action entity, it will create a new action and
|
||||
/// store it in <see cref="container"/>.
|
||||
/// </summary>
|
||||
public void AddAction(ref EntityUid? actionId, string prototypeId, EntityUid container)
|
||||
{
|
||||
if (_entities.Deleted(actionId))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prototypeId) || _net.IsClient)
|
||||
return;
|
||||
if (_system.EnsureAction(container, ref actionId, prototypeId))
|
||||
Actions.Add(actionId.Value);
|
||||
}
|
||||
|
||||
actionId = _entities.Spawn(prototypeId);
|
||||
}
|
||||
/// <summary>
|
||||
/// Grant the given action. If the EntityUid does not refer to a valid action entity, it will create a new action and
|
||||
/// store it in <see cref="Provider"/>.
|
||||
/// </summary>
|
||||
public void AddAction(ref EntityUid? actionId, string prototypeId)
|
||||
{
|
||||
AddAction(ref actionId, prototypeId, Provider);
|
||||
}
|
||||
|
||||
Actions.Add(actionId.Value);
|
||||
public void AddAction(EntityUid actionId)
|
||||
{
|
||||
Actions.Add(actionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,10 @@ namespace Content.Shared.Actions;
|
||||
public sealed partial class ActionsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Handled on the client to track added and removed actions.
|
||||
/// List of actions currently granted to this entity.
|
||||
/// On the client, this may contain a mixture of client-side and networked entities.
|
||||
/// </summary>
|
||||
[ViewVariables] public readonly Dictionary<EntityUid, ActionMetaData> OldClientActions = new();
|
||||
|
||||
[ViewVariables] public readonly HashSet<EntityUid> Actions = new();
|
||||
|
||||
public override bool SendOnlyToOwner => true;
|
||||
[DataField] public HashSet<EntityUid> Actions = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
@@ -29,7 +26,7 @@ public sealed class ActionsComponentState : ComponentState
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct ActionMetaData(bool ClientExclusive, bool AutoRemove);
|
||||
public readonly record struct ActionMetaData(bool ClientExclusive);
|
||||
|
||||
/// <summary>
|
||||
/// Determines how the action icon appears in the hotbar for item actions.
|
||||
|
||||
@@ -5,6 +5,9 @@ using Robust.Shared.Utility;
|
||||
namespace Content.Shared.Actions;
|
||||
|
||||
// TODO this should be an IncludeDataFields of each action component type, not use inheritance
|
||||
|
||||
// TODO add access attribute. Need to figure out what to do with decal & mapping actions.
|
||||
// [Access(typeof(SharedActionsSystem))]
|
||||
public abstract partial class BaseActionComponent : Component
|
||||
{
|
||||
public abstract BaseActionEvent? BaseEvent { get; }
|
||||
@@ -46,11 +49,13 @@ public abstract partial class BaseActionComponent : Component
|
||||
/// The toggle can set directly via <see cref="SharedActionsSystem.SetToggled"/>, but it will also be
|
||||
/// automatically toggled for targeted-actions while selecting a target.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public bool Toggled;
|
||||
|
||||
/// <summary>
|
||||
/// The current cooldown on the action.
|
||||
/// </summary>
|
||||
// TODO serialization
|
||||
public (TimeSpan Start, TimeSpan End)? Cooldown;
|
||||
|
||||
/// <summary>
|
||||
@@ -65,21 +70,34 @@ public abstract partial class BaseActionComponent : Component
|
||||
[DataField("charges")] public int? Charges;
|
||||
|
||||
/// <summary>
|
||||
/// The entity that enables / provides this action. If the action is innate, this may be the user themselves. If
|
||||
/// this action has no provider (e.g., mapping tools), the this will result in broadcast events.
|
||||
/// The entity that contains this action. If the action is innate, this may be the user themselves.
|
||||
/// This should almost always be non-null.
|
||||
/// </summary>
|
||||
public EntityUid? Provider;
|
||||
[Access(typeof(ActionContainerSystem), typeof(SharedActionsSystem))]
|
||||
[DataField]
|
||||
public EntityUid? Container;
|
||||
|
||||
/// <summary>
|
||||
/// Entity to use for the action icon. Defaults to using <see cref="Provider"/>.
|
||||
/// Entity to use for the action icon. If no entity is provided and the <see cref="Container"/> differs from
|
||||
/// <see cref="AttachedEntity"/>, then it will default to using <see cref="Container"/>
|
||||
/// </summary>
|
||||
public EntityUid? EntityIcon
|
||||
{
|
||||
get => _entityIcon ?? Provider;
|
||||
set => _entityIcon = value;
|
||||
get
|
||||
{
|
||||
if (EntIcon != null)
|
||||
return EntIcon;
|
||||
|
||||
if (AttachedEntity != Container)
|
||||
return Container;
|
||||
|
||||
return null;
|
||||
}
|
||||
set => EntIcon = value;
|
||||
}
|
||||
|
||||
private EntityUid? _entityIcon;
|
||||
[DataField]
|
||||
public EntityUid? EntIcon;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the action system should block this action if the user cannot currently interact. Some spells or
|
||||
@@ -88,7 +106,7 @@ public abstract partial class BaseActionComponent : Component
|
||||
[DataField("checkCanInteract")] public bool CheckCanInteract = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, will simply execute the action locally without sending to the server.
|
||||
/// If true, this will cause the action to only execute locally without ever notifying the server.
|
||||
/// </summary>
|
||||
[DataField("clientExclusive")] public bool ClientExclusive = false;
|
||||
|
||||
@@ -107,24 +125,10 @@ public abstract partial class BaseActionComponent : Component
|
||||
/// </summary>
|
||||
[DataField("autoPopulate")] public bool AutoPopulate = true;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to automatically remove this action to the action bar when it becomes unavailable.
|
||||
/// Temporary actions are deleted when they get removed a <see cref="ActionsComponent"/>.
|
||||
/// </summary>
|
||||
[DataField("autoRemove")] public bool AutoRemove = true;
|
||||
|
||||
/// <summary>
|
||||
/// Temporary actions are removed from the action component when removed from the action-bar/GUI. Currently,
|
||||
/// should only be used for client-exclusive actions (server is not notified).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Currently there is no way for a player to just voluntarily remove actions. They can hide them from the
|
||||
/// toolbar, but not actually remove them. This is undesirable for things like dynamically added mapping
|
||||
/// entity-selection actions, as the # of actions would just keep increasing.
|
||||
/// </remarks>
|
||||
[DataField("temporary")] public bool Temporary;
|
||||
// TODO re-add support for this
|
||||
// UI refactor seems to have just broken it.
|
||||
|
||||
/// <summary>
|
||||
/// Determines the appearance of the entity-icon for actions that are enabled via some entity.
|
||||
@@ -149,20 +153,22 @@ public abstract class BaseActionComponentState : ComponentState
|
||||
public (TimeSpan Start, TimeSpan End)? Cooldown;
|
||||
public TimeSpan? UseDelay;
|
||||
public int? Charges;
|
||||
public NetEntity? Provider;
|
||||
public NetEntity? Container;
|
||||
public NetEntity? EntityIcon;
|
||||
public bool CheckCanInteract;
|
||||
public bool ClientExclusive;
|
||||
public int Priority;
|
||||
public NetEntity? AttachedEntity;
|
||||
public bool AutoPopulate;
|
||||
public bool AutoRemove;
|
||||
public bool Temporary;
|
||||
public ItemActionIconStyle ItemIconStyle;
|
||||
public SoundSpecifier? Sound;
|
||||
|
||||
protected BaseActionComponentState(BaseActionComponent component, IEntityManager entManager)
|
||||
{
|
||||
Container = entManager.GetNetEntity(component.Container);
|
||||
EntityIcon = entManager.GetNetEntity(component.EntIcon);
|
||||
AttachedEntity = entManager.GetNetEntity(component.AttachedEntity);
|
||||
Icon = component.Icon;
|
||||
IconOn = component.IconOn;
|
||||
IconColor = component.IconColor;
|
||||
@@ -172,20 +178,10 @@ public abstract class BaseActionComponentState : ComponentState
|
||||
Cooldown = component.Cooldown;
|
||||
UseDelay = component.UseDelay;
|
||||
Charges = component.Charges;
|
||||
|
||||
// TODO ACTION REFACTOR fix bugs
|
||||
if (entManager.TryGetNetEntity(component.Provider, out var provider))
|
||||
Provider = provider;
|
||||
if (entManager.TryGetNetEntity(component.EntityIcon, out var icon))
|
||||
EntityIcon = icon;
|
||||
if (entManager.TryGetNetEntity(component.AttachedEntity, out var attached))
|
||||
AttachedEntity = attached;
|
||||
|
||||
CheckCanInteract = component.CheckCanInteract;
|
||||
ClientExclusive = component.ClientExclusive;
|
||||
Priority = component.Priority;
|
||||
AutoPopulate = component.AutoPopulate;
|
||||
AutoRemove = component.AutoRemove;
|
||||
Temporary = component.Temporary;
|
||||
ItemIconStyle = component.ItemIconStyle;
|
||||
Sound = component.Sound;
|
||||
|
||||
@@ -10,7 +10,6 @@ using Content.Shared.Inventory.Events;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -18,18 +17,15 @@ namespace Content.Shared.Actions;
|
||||
|
||||
public abstract class SharedActionsSystem : EntitySystem
|
||||
{
|
||||
private const string ActionContainerId = "ActionContainer";
|
||||
private const string ProvidedActionContainerId = "ProvidedActionContainer";
|
||||
|
||||
[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!;
|
||||
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -40,26 +36,16 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
SubscribeLocalEvent<ActionsComponent, DidUnequipEvent>(OnDidUnequip);
|
||||
SubscribeLocalEvent<ActionsComponent, DidUnequipHandEvent>(OnHandUnequipped);
|
||||
|
||||
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);
|
||||
|
||||
SubscribeLocalEvent<InstantActionComponent, EntGotRemovedFromContainerMessage>(OnEntGotRemovedFromContainer);
|
||||
SubscribeLocalEvent<EntityTargetActionComponent, EntGotRemovedFromContainerMessage>(OnEntGotRemovedFromContainer);
|
||||
SubscribeLocalEvent<WorldTargetActionComponent, EntGotRemovedFromContainerMessage>(OnEntGotRemovedFromContainer);
|
||||
|
||||
SubscribeAllEvent<RequestPerformActionEvent>(OnActionRequest);
|
||||
}
|
||||
|
||||
@@ -78,117 +64,43 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
args.State = new WorldTargetActionComponentState(component, EntityManager);
|
||||
}
|
||||
|
||||
private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
|
||||
{
|
||||
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 = EnsureEntity<T>(state.Provider, uid);
|
||||
component.EntityIcon = EnsureEntity<T>(state.EntityIcon, uid);
|
||||
component.CheckCanInteract = state.CheckCanInteract;
|
||||
component.ClientExclusive = state.ClientExclusive;
|
||||
component.Priority = state.Priority;
|
||||
component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid);
|
||||
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<InstantActionComponent>(uid, component, state);
|
||||
}
|
||||
|
||||
private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not EntityTargetActionComponentState state)
|
||||
return;
|
||||
|
||||
BaseHandleState<EntityTargetActionComponent>(uid, 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<WorldTargetActionComponent>(uid, component, state);
|
||||
}
|
||||
|
||||
private void OnGetActionData<T>(EntityUid uid, T component, ref GetActionDataEvent args) where T : BaseActionComponent
|
||||
{
|
||||
args.Action = component;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
[NotNullWhen(true)] EntityUid? uid,
|
||||
[NotNullWhen(true)] out BaseActionComponent? result,
|
||||
bool logError = true)
|
||||
{
|
||||
action = null;
|
||||
return actionId != null && (action = GetActionData(actionId)) != null;
|
||||
result = null;
|
||||
if (!Exists(uid))
|
||||
return false;
|
||||
|
||||
var ev = new GetActionDataEvent();
|
||||
RaiseLocalEvent(uid.Value, ref ev);
|
||||
result = ev.Action;
|
||||
|
||||
if (result != null)
|
||||
return true;
|
||||
|
||||
Log.Error($"Failed to get action from action entity: {ToPrettyString(uid.Value)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Container EnsureContainer(EntityUid holderId, EntityUid? providerId)
|
||||
public bool ResolveActionData(
|
||||
[NotNullWhen(true)] EntityUid? uid,
|
||||
[NotNullWhen(true)] ref BaseActionComponent? result,
|
||||
bool logError = true)
|
||||
{
|
||||
return providerId == null
|
||||
? _containerSystem.EnsureContainer<Container>(holderId, ActionContainerId)
|
||||
: _containerSystem.EnsureContainer<Container>(providerId.Value, ProvidedActionContainerId);
|
||||
}
|
||||
if (result != null)
|
||||
{
|
||||
DebugTools.Assert(result.Owner == uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool TryGetContainer(
|
||||
EntityUid holderId,
|
||||
[NotNullWhen(true)] out BaseContainer? container,
|
||||
ContainerManagerComponent? containerManager = null)
|
||||
{
|
||||
return _containerSystem.TryGetContainer(holderId, ActionContainerId, out container, containerManager);
|
||||
}
|
||||
|
||||
protected bool TryGetProvidedContainer(
|
||||
EntityUid providerId,
|
||||
[NotNullWhen(true)] out BaseContainer? container,
|
||||
ContainerManagerComponent? containerManager = null)
|
||||
{
|
||||
return _containerSystem.TryGetContainer(providerId, ProvidedActionContainerId, out container, containerManager);
|
||||
return TryGetActionData(uid, out result, logError);
|
||||
}
|
||||
|
||||
public void SetCooldown(EntityUid? actionId, TimeSpan start, TimeSpan end)
|
||||
@@ -196,8 +108,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (actionId == null)
|
||||
return;
|
||||
|
||||
var action = GetActionData(actionId);
|
||||
if (action == null)
|
||||
if (!TryGetActionData(actionId, out var action))
|
||||
return;
|
||||
|
||||
action.Cooldown = (start, end);
|
||||
@@ -218,25 +129,9 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
}
|
||||
|
||||
#region ComponentStateManagement
|
||||
public virtual void Dirty(EntityUid? actionId)
|
||||
protected virtual void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
|
||||
{
|
||||
if (!TryGetActionData(actionId, out var action))
|
||||
return;
|
||||
|
||||
Dirty(actionId.Value, action);
|
||||
|
||||
if (action.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
var ent = action.AttachedEntity;
|
||||
|
||||
if (!TryComp(ent, out ActionsComponent? comp))
|
||||
{
|
||||
action.AttachedEntity = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Dirty(action.AttachedEntity.Value, comp);
|
||||
// See client-side code.
|
||||
}
|
||||
|
||||
public void SetToggled(EntityUid? actionId, bool toggled)
|
||||
@@ -248,6 +143,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
}
|
||||
|
||||
action.Toggled = toggled;
|
||||
UpdateAction(actionId, action);
|
||||
Dirty(actionId.Value, action);
|
||||
}
|
||||
|
||||
@@ -260,6 +156,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
}
|
||||
|
||||
action.Enabled = enabled;
|
||||
UpdateAction(actionId, action);
|
||||
Dirty(actionId.Value, action);
|
||||
}
|
||||
|
||||
@@ -272,25 +169,15 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
}
|
||||
|
||||
action.Charges = charges;
|
||||
UpdateAction(actionId, action);
|
||||
Dirty(actionId.Value, action);
|
||||
}
|
||||
|
||||
private void OnActionsMapInit(EntityUid uid, ActionsComponent component, MapInitEvent args)
|
||||
{
|
||||
EnsureContainer(uid, null);
|
||||
}
|
||||
|
||||
private void OnActionsGetState(EntityUid uid, ActionsComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new ActionsComponentState(GetNetEntitySet(component.Actions));
|
||||
}
|
||||
|
||||
private void OnActionsShutdown(EntityUid uid, ActionsComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (TryGetContainer(uid, out var container))
|
||||
container.Shutdown(EntityManager);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Execution
|
||||
@@ -321,8 +208,12 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
var action = GetActionData(actionEnt);
|
||||
if (action == null || !action.Enabled)
|
||||
if (!TryGetActionData(actionEnt, out var action))
|
||||
return;
|
||||
|
||||
DebugTools.Assert(action.AttachedEntity == user);
|
||||
|
||||
if (!action.Enabled)
|
||||
return;
|
||||
|
||||
var curTime = GameTiming.CurTime;
|
||||
@@ -349,16 +240,8 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (!ValidateEntityTarget(user, entityTarget, entityAction))
|
||||
return;
|
||||
|
||||
if (action.Provider == null)
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action targeted at {ToPrettyString(entityTarget):target}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Provider.Value):provider}) targeted at {ToPrettyString(entityTarget):target}.");
|
||||
}
|
||||
_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}.");
|
||||
|
||||
if (entityAction.Event != null)
|
||||
{
|
||||
@@ -381,16 +264,8 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (!ValidateWorldTarget(user, entityCoordinatesTarget, worldAction))
|
||||
return;
|
||||
|
||||
if (action.Provider == null)
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action targeted at {entityCoordinatesTarget:target}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Provider.Value):provider}) targeted at {entityCoordinatesTarget:target}.");
|
||||
}
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action (provided by {ToPrettyString(action.Container ?? user):provider}) targeted at {entityCoordinatesTarget:target}.");
|
||||
|
||||
if (worldAction.Event != null)
|
||||
{
|
||||
@@ -404,16 +279,8 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
if (action.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, null))
|
||||
return;
|
||||
|
||||
if (action.Provider == null)
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action provided by {ToPrettyString(action.Provider.Value):provider}.");
|
||||
}
|
||||
_adminLogger.Add(LogType.Action,
|
||||
$"{ToPrettyString(user):user} is performing the {name:action} action provided by {ToPrettyString(action.Container ?? user):provider}.");
|
||||
|
||||
performEvent = instantAction.Event;
|
||||
break;
|
||||
@@ -493,17 +360,18 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
|
||||
var toggledBefore = action.Toggled;
|
||||
|
||||
// Note that attached entity is allowed to be null here.
|
||||
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;
|
||||
}
|
||||
|
||||
if (actionEvent != null)
|
||||
{
|
||||
// This here is required because of client-side prediction (RaisePredictiveEvent results in event re-use).
|
||||
actionEvent.Handled = false;
|
||||
var provider = action.Provider;
|
||||
|
||||
if (provider == null)
|
||||
RaiseLocalEvent(performer, (object) actionEvent, broadcast: true);
|
||||
else
|
||||
RaiseLocalEvent(provider.Value, (object) actionEvent, broadcast: true);
|
||||
|
||||
RaiseLocalEvent(action.Container ?? performer, (object) actionEvent, broadcast: true);
|
||||
handled = actionEvent.Handled;
|
||||
}
|
||||
|
||||
@@ -540,91 +408,129 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
#endregion
|
||||
|
||||
#region AddRemoveActions
|
||||
/// <summary>
|
||||
/// Add an action to an action holder.
|
||||
/// If the holder has no actions component, this will give them one.
|
||||
/// </summary>
|
||||
public BaseActionComponent? AddAction(EntityUid holderId, ref EntityUid? actionId, string? actionPrototypeId, EntityUid? provider = null, ActionsComponent? holderComp = null)
|
||||
|
||||
public EntityUid? AddAction(EntityUid performer,
|
||||
string? actionPrototypeId,
|
||||
EntityUid container = default,
|
||||
ActionsComponent? component = 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);
|
||||
EntityUid? actionId = null;
|
||||
AddAction(performer, ref actionId, out _, actionPrototypeId, container, component);
|
||||
return actionId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an action to an action holder.
|
||||
/// Adds an action to an action holder. If the given entity does not exist, it will attempt to spawn one.
|
||||
/// If the holder has no actions component, this will give them one.
|
||||
/// </summary>
|
||||
/// <param name="holderId">Entity to receive the actions</param>
|
||||
/// <param name="performer">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, BaseContainer? actionContainer = null)
|
||||
/// <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>
|
||||
/// <param name="container">The entity that contains/enables this action (e.g., flashlight)..</param>
|
||||
public bool AddAction(EntityUid performer,
|
||||
[NotNullWhen(true)] ref EntityUid? actionId,
|
||||
string? actionPrototypeId,
|
||||
EntityUid container = default,
|
||||
ActionsComponent? component = 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;
|
||||
}
|
||||
|
||||
holder ??= EnsureComp<ActionsComponent>(holderId);
|
||||
action.Provider = provider;
|
||||
action.AttachedEntity = holderId;
|
||||
Dirty(actionId, action);
|
||||
|
||||
actionContainer ??= EnsureContainer(holderId, provider);
|
||||
AddActionInternal(holderId, actionId, actionContainer, holder);
|
||||
|
||||
if (dirty)
|
||||
Dirty(holderId, holder);
|
||||
return AddAction(performer, ref actionId, out _, actionPrototypeId, container, component);
|
||||
}
|
||||
|
||||
protected virtual void AddActionInternal(EntityUid holderId, EntityUid actionId, BaseContainer container, ActionsComponent holder)
|
||||
/// <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)
|
||||
{
|
||||
container.Insert(actionId);
|
||||
holder.Actions.Add(actionId);
|
||||
Dirty(holderId, holder);
|
||||
if (!container.IsValid())
|
||||
container = performer;
|
||||
|
||||
if (!_actionContainer.EnsureAction(container, ref actionId, out action, actionPrototypeId))
|
||||
return false;
|
||||
|
||||
return AddActionDirect(performer, actionId.Value, component, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add actions to an action component. If the entity has no action component, this will give them one.
|
||||
/// Adds a pre-existing action.
|
||||
/// </summary>
|
||||
/// <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 holderId, IEnumerable<EntityUid> actions, EntityUid? provider, ActionsComponent? comp = null, bool dirty = true)
|
||||
public bool AddAction(EntityUid performer,
|
||||
EntityUid actionId,
|
||||
EntityUid container,
|
||||
ActionsComponent? comp = null,
|
||||
BaseActionComponent? action = null,
|
||||
ActionsContainerComponent? containerComp = null
|
||||
)
|
||||
{
|
||||
comp ??= EnsureComp<ActionsComponent>(holderId);
|
||||
if (!ResolveActionData(actionId, ref action))
|
||||
return false;
|
||||
|
||||
var allClientExclusive = true;
|
||||
var container = EnsureContainer(holderId, provider);
|
||||
if (action.Container != container
|
||||
|| !Resolve(container, ref containerComp)
|
||||
|| !containerComp.Container.Contains(actionId))
|
||||
{
|
||||
Log.Error($"Attempted to add an action with an invalid container: {ToPrettyString(actionId)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return AddActionDirect(performer, actionId, comp, action);
|
||||
}
|
||||
|
||||
/// <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)));
|
||||
|
||||
DebugTools.Assert(comp == null || comp.Owner == performer);
|
||||
comp ??= EnsureComp<ActionsComponent>(performer);
|
||||
action.AttachedEntity = performer;
|
||||
comp.Actions.Add(actionId);
|
||||
Dirty(actionId, action);
|
||||
Dirty(performer, comp);
|
||||
ActionAdded(performer, actionId, comp, action);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method gets called after a new action got added.
|
||||
/// </summary>
|
||||
protected virtual void ActionAdded(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
|
||||
{
|
||||
// See client-side system for UI code.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grant pre-existing actions. If the entity has no action component, this will give them one.
|
||||
/// </summary>
|
||||
/// <param name="performer">Entity to receive the actions</param>
|
||||
/// <param name="actions">The actions to add</param>
|
||||
/// <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)
|
||||
{
|
||||
if (!Resolve(container, ref containerComp))
|
||||
return;
|
||||
|
||||
DebugTools.Assert(comp == null || comp.Owner == performer);
|
||||
comp ??= EnsureComp<ActionsComponent>(performer);
|
||||
|
||||
foreach (var actionId in actions)
|
||||
{
|
||||
var action = GetActionData(actionId);
|
||||
if (action == null)
|
||||
continue;
|
||||
|
||||
AddAction(holderId, actionId, provider, comp, action, false, container);
|
||||
allClientExclusive = allClientExclusive && action.ClientExclusive;
|
||||
AddAction(performer, actionId, container, comp, containerComp: containerComp);
|
||||
}
|
||||
|
||||
if (dirty && !allClientExclusive)
|
||||
Dirty(holderId, comp);
|
||||
}
|
||||
|
||||
public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetActions(EntityUid holderId, ActionsComponent? actions = null)
|
||||
@@ -644,74 +550,72 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Remove any actions that were enabled by some other entity. Useful when unequiping items that grant actions.
|
||||
/// </summary>
|
||||
public void RemoveProvidedActions(EntityUid holderId, EntityUid provider, ActionsComponent? comp = null)
|
||||
public void RemoveProvidedActions(EntityUid performer, EntityUid container, ActionsComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(holderId, ref comp, false))
|
||||
if (!Resolve(performer, ref comp, false))
|
||||
return;
|
||||
|
||||
if (!TryGetProvidedContainer(provider, out var container))
|
||||
return;
|
||||
|
||||
foreach (var actionId in container.ContainedEntities.ToArray())
|
||||
foreach (var actionId in comp.Actions.ToArray())
|
||||
{
|
||||
var action = GetActionData(actionId);
|
||||
if (action?.Provider == provider)
|
||||
RemoveAction(holderId, actionId, comp, dirty: false);
|
||||
}
|
||||
if (!TryGetActionData(actionId, out var action))
|
||||
return;
|
||||
|
||||
Dirty(holderId, comp);
|
||||
if (action.Container == container)
|
||||
RemoveAction(performer, actionId, comp);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void RemoveAction(EntityUid holderId, EntityUid? actionId, ActionsComponent? comp = null, BaseActionComponent? action = null, bool dirty = true)
|
||||
public void RemoveAction(EntityUid? actionId)
|
||||
{
|
||||
if (actionId == null ||
|
||||
!Resolve(holderId, ref comp, false) ||
|
||||
TerminatingOrDeleted(actionId.Value))
|
||||
if (actionId == null)
|
||||
return;
|
||||
|
||||
if (!TryGetActionData(actionId, out var action))
|
||||
return;
|
||||
|
||||
if (!TryComp(action.AttachedEntity, out ActionsComponent? comp))
|
||||
return;
|
||||
|
||||
RemoveAction(action.AttachedEntity.Value, actionId, comp, action);
|
||||
}
|
||||
|
||||
public void RemoveAction(EntityUid performer, EntityUid? actionId, ActionsComponent? comp = null, BaseActionComponent? action = null)
|
||||
{
|
||||
if (actionId == null)
|
||||
return;
|
||||
|
||||
if (!ResolveActionData(actionId, ref action))
|
||||
return;
|
||||
|
||||
if (!Resolve(performer, ref comp, false))
|
||||
{
|
||||
DebugTools.AssertNull(action.AttachedEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
action ??= GetActionData(actionId);
|
||||
|
||||
if (TryGetContainer(holderId, out var container) && container.Contains(actionId.Value))
|
||||
QueueDel(actionId.Value);
|
||||
|
||||
comp.Actions.Remove(actionId.Value);
|
||||
|
||||
if (action != null)
|
||||
if (action.AttachedEntity == null)
|
||||
{
|
||||
action.AttachedEntity = null;
|
||||
Dirty(actionId.Value, action);
|
||||
// action was already removed?
|
||||
DebugTools.Assert(!comp.Actions.Contains(actionId.Value) || GameTiming.ApplyingState);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
Dirty(holderId, comp);
|
||||
|
||||
DebugTools.Assert(Transform(actionId.Value).ParentUid.IsValid());
|
||||
DebugTools.Assert(action.AttachedEntity == performer);
|
||||
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>
|
||||
/// Removes all actions with the given prototype id.
|
||||
/// This method gets called after an action got removed.
|
||||
/// </summary>
|
||||
public void RemoveAction(EntityUid holderId, string actionPrototypeId, ActionsComponent? holderComp = null)
|
||||
protected virtual void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
|
||||
{
|
||||
if (!Resolve(holderId, ref holderComp, 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));
|
||||
}
|
||||
|
||||
if (actions.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
RemoveAction(holderId, action.Id, holderComp, action.Comp);
|
||||
}
|
||||
// See client-side system for UI code.
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -719,34 +623,55 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
#region EquipHandlers
|
||||
private void OnDidEquip(EntityUid uid, ActionsComponent component, DidEquipEvent args)
|
||||
{
|
||||
var ev = new GetItemActionsEvent(EntityManager, _net, args.Equipee, args.SlotFlags);
|
||||
if (GameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
var ev = new GetItemActionsEvent(_actionContainer, args.Equipee, args.Equipment, args.SlotFlags);
|
||||
RaiseLocalEvent(args.Equipment, ev);
|
||||
|
||||
if (ev.Actions.Count == 0)
|
||||
return;
|
||||
|
||||
AddActions(args.Equipee, ev.Actions, args.Equipment, component);
|
||||
GrantActions(args.Equipee, ev.Actions, args.Equipment, component);
|
||||
}
|
||||
|
||||
private void OnHandEquipped(EntityUid uid, ActionsComponent component, DidEquipHandEvent args)
|
||||
{
|
||||
var ev = new GetItemActionsEvent(EntityManager, _net, args.User);
|
||||
if (GameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
var ev = new GetItemActionsEvent(_actionContainer, args.User, args.Equipped);
|
||||
RaiseLocalEvent(args.Equipped, ev);
|
||||
|
||||
if (ev.Actions.Count == 0)
|
||||
return;
|
||||
|
||||
AddActions(args.User, ev.Actions, args.Equipped, component);
|
||||
GrantActions(args.User, ev.Actions, args.Equipped, component);
|
||||
}
|
||||
|
||||
private void OnDidUnequip(EntityUid uid, ActionsComponent component, DidUnequipEvent args)
|
||||
{
|
||||
if (GameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
RemoveProvidedActions(uid, args.Equipment, component);
|
||||
}
|
||||
|
||||
private void OnHandUnequipped(EntityUid uid, ActionsComponent component, DidUnequipHandEvent args)
|
||||
{
|
||||
if (GameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
RemoveProvidedActions(uid, args.Unequipped, component);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void SetEntityIcon(EntityUid uid, EntityUid? icon, BaseActionComponent? action = null)
|
||||
{
|
||||
if (!Resolve(uid, ref action))
|
||||
return;
|
||||
|
||||
action.EntityIcon = icon;
|
||||
Dirty(uid, action);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user