| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using Content.Shared.Ghost;
- using Content.Shared.Mind;
- using Content.Shared.Mind.Components;
- 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!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly SharedMindSystem _mind = default!;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<ActionsContainerComponent, ComponentInit>(OnInit);
- SubscribeLocalEvent<ActionsContainerComponent, ComponentShutdown>(OnShutdown);
- SubscribeLocalEvent<ActionsContainerComponent, EntRemovedFromContainerMessage>(OnEntityRemoved);
- SubscribeLocalEvent<ActionsContainerComponent, EntInsertedIntoContainerMessage>(OnEntityInserted);
- SubscribeLocalEvent<ActionsContainerComponent, ActionAddedEvent>(OnActionAdded);
- SubscribeLocalEvent<ActionsContainerComponent, MindAddedMessage>(OnMindAdded);
- SubscribeLocalEvent<ActionsContainerComponent, MindRemovedMessage>(OnMindRemoved);
- }
- private void OnMindAdded(EntityUid uid, ActionsContainerComponent component, MindAddedMessage args)
- {
- if (!_mind.TryGetMind(uid, out var mindId, out _))
- return;
- if (!TryComp<ActionsContainerComponent>(mindId, out var mindActionContainerComp))
- return;
- if (!HasComp<GhostComponent>(uid) && mindActionContainerComp.Container.ContainedEntities.Count > 0 )
- _actions.GrantContainedActions(uid, mindId);
- }
- private void OnMindRemoved(EntityUid uid, ActionsContainerComponent component, MindRemovedMessage args)
- {
- _actions.RemoveProvidedActions(uid, args.Mind);
- }
- /// <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.AssertOwner(uid, comp);
- 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>
- /// Transfers an action from one container to another, while keeping the attached entity the same.
- /// </summary>
- /// <remarks>
- /// While the attached entity should be the same at the end, this will actually remove and then re-grant the action.
- /// </remarks>
- public void TransferAction(
- EntityUid actionId,
- EntityUid newContainer,
- BaseActionComponent? action = null,
- ActionsContainerComponent? container = null)
- {
- if (!_actions.ResolveActionData(actionId, ref action))
- return;
- if (action.Container == newContainer)
- return;
- var attached = action.AttachedEntity;
- if (!AddAction(newContainer, actionId, action, container))
- return;
- DebugTools.AssertEqual(action.Container, newContainer);
- DebugTools.AssertEqual(action.AttachedEntity, attached);
- }
- /// <summary>
- /// Transfers all actions from one container to another, while keeping the attached entity the same.
- /// </summary>
- /// <remarks>
- /// While the attached entity should be the same at the end, this will actually remove and then re-grant the action.
- /// </remarks>
- public void TransferAllActions(
- EntityUid from,
- EntityUid to,
- ActionsContainerComponent? oldContainer = null,
- ActionsContainerComponent? newContainer = null)
- {
- if (!Resolve(from, ref oldContainer) || !Resolve(to, ref newContainer))
- return;
- foreach (var action in oldContainer.Container.ContainedEntities.ToArray())
- {
- TransferAction(action, to, container: newContainer);
- }
- DebugTools.AssertEqual(oldContainer.Container.Count, 0);
- }
- /// <summary>
- /// Transfers an actions from one container to another, while changing the attached entity.
- /// </summary>
- /// <remarks>
- /// This will actually remove and then re-grant the action.
- /// Useful where you need to transfer from one container to another but also change the attached entity (ie spellbook > mind > user)
- /// </remarks>
- public void TransferActionWithNewAttached(
- EntityUid actionId,
- EntityUid newContainer,
- EntityUid newAttached,
- BaseActionComponent? action = null,
- ActionsContainerComponent? container = null)
- {
- if (!_actions.ResolveActionData(actionId, ref action))
- return;
- if (action.Container == newContainer)
- return;
- var attached = newAttached;
- if (!AddAction(newContainer, actionId, action, container))
- return;
- DebugTools.AssertEqual(action.Container, newContainer);
- _actions.AddActionDirect(newAttached, actionId, action: action);
- DebugTools.AssertEqual(action.AttachedEntity, attached);
- }
- /// <summary>
- /// Transfers all actions from one container to another, while changing the attached entity.
- /// </summary>
- /// <remarks>
- /// This will actually remove and then re-grant the action.
- /// Useful where you need to transfer from one container to another but also change the attached entity (ie spellbook > mind > user)
- /// </remarks>
- public void TransferAllActionsWithNewAttached(
- EntityUid from,
- EntityUid to,
- EntityUid newAttached,
- ActionsContainerComponent? oldContainer = null,
- ActionsContainerComponent? newContainer = null)
- {
- if (!Resolve(from, ref oldContainer) || !Resolve(to, ref newContainer))
- return;
- foreach (var action in oldContainer.Container.ContainedEntities.ToArray())
- {
- TransferActionWithNewAttached(action, to, newAttached, container: newContainer);
- }
- DebugTools.AssertEqual(oldContainer.Container.Count, 0);
- }
- /// <summary>
- /// Adds a pre-existing action to an action container. If the action is already in some container it will first remove it.
- /// </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)
- RemoveAction(actionId, action);
- DebugTools.AssertOwner(uid, comp);
- comp ??= EnsureComp<ActionsContainerComponent>(uid);
- if (!_container.Insert(actionId, comp.Container))
- {
- 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;
- }
- /// <summary>
- /// Removes an action from its container and any action-performer and moves the action to null-space
- /// </summary>
- public void RemoveAction(EntityUid actionId, BaseActionComponent? action = null)
- {
- if (!_actions.ResolveActionData(actionId, ref action))
- return;
- if (action.Container == null)
- return;
- _transform.DetachEntity(actionId, Transform(actionId));
- // Container removal events should have removed the action from the action container.
- // However, just in case the container was already deleted we will still manually clear the container field
- if (action.Container != null)
- {
- if (Exists(action.Container))
- Log.Error($"Failed to remove action {ToPrettyString(actionId)} from its container {ToPrettyString(action.Container)}?");
- action.Container = null;
- }
- // If the action was granted to some entity, then the removal from the container should have automatically removed it.
- // However, if the action was granted without ever being placed in an action container, it will not have been removed.
- // Therefore, to ensure that the behaviour of the method is consistent we will also explicitly remove the action.
- if (action.AttachedEntity != null)
- _actions.RemoveAction(action.AttachedEntity.Value, actionId, action: action);
- }
- 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)
- {
- if (_timing.ApplyingState && component.NetSyncEnabled)
- return; // The game state should handle the container removal & action deletion.
- _container.ShutdownContainer(component.Container);
- }
- 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;
- if (data.Container != uid)
- {
- data.Container = uid;
- Dirty(args.Entity, data);
- }
- 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;
- if (!_actions.TryGetActionData(args.Entity, out var data, false))
- return;
- var ev = new ActionRemovedEvent(args.Entity, data);
- RaiseLocalEvent(uid, ref ev);
- if (data.Container == null)
- return;
- data.Container = null;
- Dirty(args.Entity, data);
- }
- private void OnActionAdded(EntityUid uid, ActionsContainerComponent component, ActionAddedEvent args)
- {
- if (TryComp<MindComponent>(uid, out var mindComp) && mindComp.OwnedEntity != null && HasComp<ActionsContainerComponent>(mindComp.OwnedEntity.Value))
- _actions.GrantContainedAction(mindComp.OwnedEntity.Value, uid, args.Action);
- }
- }
- /// <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;
- }
- }
|