ActionContainerSystem.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Linq;
  3. using Content.Shared.Ghost;
  4. using Content.Shared.Mind;
  5. using Content.Shared.Mind.Components;
  6. using Robust.Shared.Containers;
  7. using Robust.Shared.Network;
  8. using Robust.Shared.Timing;
  9. using Robust.Shared.Utility;
  10. namespace Content.Shared.Actions;
  11. /// <summary>
  12. /// Handles storing & spawning action entities in a container.
  13. /// </summary>
  14. public sealed class ActionContainerSystem : EntitySystem
  15. {
  16. [Dependency] private readonly IGameTiming _timing = default!;
  17. [Dependency] private readonly SharedContainerSystem _container = default!;
  18. [Dependency] private readonly SharedActionsSystem _actions = default!;
  19. [Dependency] private readonly INetManager _netMan = default!;
  20. [Dependency] private readonly SharedTransformSystem _transform = default!;
  21. [Dependency] private readonly SharedMindSystem _mind = default!;
  22. public override void Initialize()
  23. {
  24. base.Initialize();
  25. SubscribeLocalEvent<ActionsContainerComponent, ComponentInit>(OnInit);
  26. SubscribeLocalEvent<ActionsContainerComponent, ComponentShutdown>(OnShutdown);
  27. SubscribeLocalEvent<ActionsContainerComponent, EntRemovedFromContainerMessage>(OnEntityRemoved);
  28. SubscribeLocalEvent<ActionsContainerComponent, EntInsertedIntoContainerMessage>(OnEntityInserted);
  29. SubscribeLocalEvent<ActionsContainerComponent, ActionAddedEvent>(OnActionAdded);
  30. SubscribeLocalEvent<ActionsContainerComponent, MindAddedMessage>(OnMindAdded);
  31. SubscribeLocalEvent<ActionsContainerComponent, MindRemovedMessage>(OnMindRemoved);
  32. }
  33. private void OnMindAdded(EntityUid uid, ActionsContainerComponent component, MindAddedMessage args)
  34. {
  35. if (!_mind.TryGetMind(uid, out var mindId, out _))
  36. return;
  37. if (!TryComp<ActionsContainerComponent>(mindId, out var mindActionContainerComp))
  38. return;
  39. if (!HasComp<GhostComponent>(uid) && mindActionContainerComp.Container.ContainedEntities.Count > 0 )
  40. _actions.GrantContainedActions(uid, mindId);
  41. }
  42. private void OnMindRemoved(EntityUid uid, ActionsContainerComponent component, MindRemovedMessage args)
  43. {
  44. _actions.RemoveProvidedActions(uid, args.Mind);
  45. }
  46. /// <summary>
  47. /// Spawns a new action entity and adds it to the given container.
  48. /// </summary>
  49. public EntityUid? AddAction(EntityUid uid, string actionPrototypeId, ActionsContainerComponent? comp = null)
  50. {
  51. EntityUid? result = default;
  52. EnsureAction(uid, ref result, actionPrototypeId, comp);
  53. return result;
  54. }
  55. /// <summary>
  56. /// Ensures that a given entityUid refers to a valid entity action contained by the given container.
  57. /// If the entity does not exist, it will attempt to spawn a new action.
  58. /// Returns false if the given entity exists, but is not in a valid state.
  59. /// </summary>
  60. public bool EnsureAction(EntityUid uid,
  61. [NotNullWhen(true)] ref EntityUid? actionId,
  62. string actionPrototypeId,
  63. ActionsContainerComponent? comp = null)
  64. {
  65. return EnsureAction(uid, ref actionId, out _, actionPrototypeId, comp);
  66. }
  67. /// <inheritdoc cref="EnsureAction(Robust.Shared.GameObjects.EntityUid,ref System.Nullable{Robust.Shared.GameObjects.EntityUid},string?,Content.Shared.Actions.ActionsContainerComponent?)"/>
  68. public bool EnsureAction(EntityUid uid,
  69. [NotNullWhen(true)] ref EntityUid? actionId,
  70. [NotNullWhen(true)] out BaseActionComponent? action,
  71. string? actionPrototypeId,
  72. ActionsContainerComponent? comp = null)
  73. {
  74. action = null;
  75. DebugTools.AssertOwner(uid, comp);
  76. comp ??= EnsureComp<ActionsContainerComponent>(uid);
  77. if (Exists(actionId))
  78. {
  79. if (!comp.Container.Contains(actionId.Value))
  80. {
  81. Log.Error($"Action {ToPrettyString(actionId.Value)} is not contained in the expected container {ToPrettyString(uid)}");
  82. return false;
  83. }
  84. if (!_actions.TryGetActionData(actionId, out action))
  85. return false;
  86. DebugTools.Assert(Transform(actionId.Value).ParentUid == uid);
  87. DebugTools.Assert(_container.IsEntityInContainer(actionId.Value));
  88. DebugTools.Assert(action.Container == uid);
  89. return true;
  90. }
  91. // Null prototypes are never valid entities, they mean that someone didn't provide a proper prototype.
  92. if (actionPrototypeId == null)
  93. return false;
  94. // Client cannot predict entity spawning.
  95. if (_netMan.IsClient && !IsClientSide(uid))
  96. return false;
  97. actionId = Spawn(actionPrototypeId);
  98. if (AddAction(uid, actionId.Value, action, comp) && _actions.TryGetActionData(actionId, out action))
  99. return true;
  100. Del(actionId.Value);
  101. actionId = null;
  102. return false;
  103. }
  104. /// <summary>
  105. /// Transfers an action from one container to another, while keeping the attached entity the same.
  106. /// </summary>
  107. /// <remarks>
  108. /// While the attached entity should be the same at the end, this will actually remove and then re-grant the action.
  109. /// </remarks>
  110. public void TransferAction(
  111. EntityUid actionId,
  112. EntityUid newContainer,
  113. BaseActionComponent? action = null,
  114. ActionsContainerComponent? container = null)
  115. {
  116. if (!_actions.ResolveActionData(actionId, ref action))
  117. return;
  118. if (action.Container == newContainer)
  119. return;
  120. var attached = action.AttachedEntity;
  121. if (!AddAction(newContainer, actionId, action, container))
  122. return;
  123. DebugTools.AssertEqual(action.Container, newContainer);
  124. DebugTools.AssertEqual(action.AttachedEntity, attached);
  125. }
  126. /// <summary>
  127. /// Transfers all actions from one container to another, while keeping the attached entity the same.
  128. /// </summary>
  129. /// <remarks>
  130. /// While the attached entity should be the same at the end, this will actually remove and then re-grant the action.
  131. /// </remarks>
  132. public void TransferAllActions(
  133. EntityUid from,
  134. EntityUid to,
  135. ActionsContainerComponent? oldContainer = null,
  136. ActionsContainerComponent? newContainer = null)
  137. {
  138. if (!Resolve(from, ref oldContainer) || !Resolve(to, ref newContainer))
  139. return;
  140. foreach (var action in oldContainer.Container.ContainedEntities.ToArray())
  141. {
  142. TransferAction(action, to, container: newContainer);
  143. }
  144. DebugTools.AssertEqual(oldContainer.Container.Count, 0);
  145. }
  146. /// <summary>
  147. /// Transfers an actions from one container to another, while changing the attached entity.
  148. /// </summary>
  149. /// <remarks>
  150. /// This will actually remove and then re-grant the action.
  151. /// Useful where you need to transfer from one container to another but also change the attached entity (ie spellbook > mind > user)
  152. /// </remarks>
  153. public void TransferActionWithNewAttached(
  154. EntityUid actionId,
  155. EntityUid newContainer,
  156. EntityUid newAttached,
  157. BaseActionComponent? action = null,
  158. ActionsContainerComponent? container = null)
  159. {
  160. if (!_actions.ResolveActionData(actionId, ref action))
  161. return;
  162. if (action.Container == newContainer)
  163. return;
  164. var attached = newAttached;
  165. if (!AddAction(newContainer, actionId, action, container))
  166. return;
  167. DebugTools.AssertEqual(action.Container, newContainer);
  168. _actions.AddActionDirect(newAttached, actionId, action: action);
  169. DebugTools.AssertEqual(action.AttachedEntity, attached);
  170. }
  171. /// <summary>
  172. /// Transfers all actions from one container to another, while changing the attached entity.
  173. /// </summary>
  174. /// <remarks>
  175. /// This will actually remove and then re-grant the action.
  176. /// Useful where you need to transfer from one container to another but also change the attached entity (ie spellbook > mind > user)
  177. /// </remarks>
  178. public void TransferAllActionsWithNewAttached(
  179. EntityUid from,
  180. EntityUid to,
  181. EntityUid newAttached,
  182. ActionsContainerComponent? oldContainer = null,
  183. ActionsContainerComponent? newContainer = null)
  184. {
  185. if (!Resolve(from, ref oldContainer) || !Resolve(to, ref newContainer))
  186. return;
  187. foreach (var action in oldContainer.Container.ContainedEntities.ToArray())
  188. {
  189. TransferActionWithNewAttached(action, to, newAttached, container: newContainer);
  190. }
  191. DebugTools.AssertEqual(oldContainer.Container.Count, 0);
  192. }
  193. /// <summary>
  194. /// Adds a pre-existing action to an action container. If the action is already in some container it will first remove it.
  195. /// </summary>
  196. public bool AddAction(EntityUid uid, EntityUid actionId, BaseActionComponent? action = null, ActionsContainerComponent? comp = null)
  197. {
  198. if (!_actions.ResolveActionData(actionId, ref action))
  199. return false;
  200. if (action.Container != null)
  201. RemoveAction(actionId, action);
  202. DebugTools.AssertOwner(uid, comp);
  203. comp ??= EnsureComp<ActionsContainerComponent>(uid);
  204. if (!_container.Insert(actionId, comp.Container))
  205. {
  206. Log.Error($"Failed to insert action {ToPrettyString(actionId)} into {ToPrettyString(uid)}");
  207. return false;
  208. }
  209. // Container insert events should have updated the component's fields:
  210. DebugTools.Assert(comp.Container.Contains(actionId));
  211. DebugTools.Assert(action.Container == uid);
  212. return true;
  213. }
  214. /// <summary>
  215. /// Removes an action from its container and any action-performer and moves the action to null-space
  216. /// </summary>
  217. public void RemoveAction(EntityUid actionId, BaseActionComponent? action = null)
  218. {
  219. if (!_actions.ResolveActionData(actionId, ref action))
  220. return;
  221. if (action.Container == null)
  222. return;
  223. _transform.DetachEntity(actionId, Transform(actionId));
  224. // Container removal events should have removed the action from the action container.
  225. // However, just in case the container was already deleted we will still manually clear the container field
  226. if (action.Container != null)
  227. {
  228. if (Exists(action.Container))
  229. Log.Error($"Failed to remove action {ToPrettyString(actionId)} from its container {ToPrettyString(action.Container)}?");
  230. action.Container = null;
  231. }
  232. // If the action was granted to some entity, then the removal from the container should have automatically removed it.
  233. // However, if the action was granted without ever being placed in an action container, it will not have been removed.
  234. // Therefore, to ensure that the behaviour of the method is consistent we will also explicitly remove the action.
  235. if (action.AttachedEntity != null)
  236. _actions.RemoveAction(action.AttachedEntity.Value, actionId, action: action);
  237. }
  238. private void OnInit(EntityUid uid, ActionsContainerComponent component, ComponentInit args)
  239. {
  240. component.Container = _container.EnsureContainer<Container>(uid, ActionsContainerComponent.ContainerId);
  241. }
  242. private void OnShutdown(EntityUid uid, ActionsContainerComponent component, ComponentShutdown args)
  243. {
  244. if (_timing.ApplyingState && component.NetSyncEnabled)
  245. return; // The game state should handle the container removal & action deletion.
  246. _container.ShutdownContainer(component.Container);
  247. }
  248. private void OnEntityInserted(EntityUid uid, ActionsContainerComponent component, EntInsertedIntoContainerMessage args)
  249. {
  250. if (args.Container.ID != ActionsContainerComponent.ContainerId)
  251. return;
  252. if (!_actions.TryGetActionData(args.Entity, out var data))
  253. return;
  254. if (data.Container != uid)
  255. {
  256. data.Container = uid;
  257. Dirty(args.Entity, data);
  258. }
  259. var ev = new ActionAddedEvent(args.Entity, data);
  260. RaiseLocalEvent(uid, ref ev);
  261. }
  262. private void OnEntityRemoved(EntityUid uid, ActionsContainerComponent component, EntRemovedFromContainerMessage args)
  263. {
  264. if (args.Container.ID != ActionsContainerComponent.ContainerId)
  265. return;
  266. if (!_actions.TryGetActionData(args.Entity, out var data, false))
  267. return;
  268. var ev = new ActionRemovedEvent(args.Entity, data);
  269. RaiseLocalEvent(uid, ref ev);
  270. if (data.Container == null)
  271. return;
  272. data.Container = null;
  273. Dirty(args.Entity, data);
  274. }
  275. private void OnActionAdded(EntityUid uid, ActionsContainerComponent component, ActionAddedEvent args)
  276. {
  277. if (TryComp<MindComponent>(uid, out var mindComp) && mindComp.OwnedEntity != null && HasComp<ActionsContainerComponent>(mindComp.OwnedEntity.Value))
  278. _actions.GrantContainedAction(mindComp.OwnedEntity.Value, uid, args.Action);
  279. }
  280. }
  281. /// <summary>
  282. /// Raised directed at an action container when a new action entity gets inserted.
  283. /// </summary>
  284. [ByRefEvent]
  285. public readonly struct ActionAddedEvent
  286. {
  287. public readonly EntityUid Action;
  288. public readonly BaseActionComponent Component;
  289. public ActionAddedEvent(EntityUid action, BaseActionComponent component)
  290. {
  291. Action = action;
  292. Component = component;
  293. }
  294. }
  295. /// <summary>
  296. /// Raised directed at an action container when an action entity gets removed.
  297. /// </summary>
  298. [ByRefEvent]
  299. public readonly struct ActionRemovedEvent
  300. {
  301. public readonly EntityUid Action;
  302. public readonly BaseActionComponent Component;
  303. public ActionRemovedEvent(EntityUid action, BaseActionComponent component)
  304. {
  305. Action = action;
  306. Component = component;
  307. }
  308. }