InventorySystem.Slots.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Linq;
  3. using Content.Shared.Inventory.Events;
  4. using Content.Shared.Storage;
  5. using Robust.Shared.Containers;
  6. using Robust.Shared.Prototypes;
  7. using Robust.Shared.Utility;
  8. namespace Content.Shared.Inventory;
  9. public partial class InventorySystem : EntitySystem
  10. {
  11. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  12. [Dependency] private readonly IViewVariablesManager _vvm = default!;
  13. private void InitializeSlots()
  14. {
  15. SubscribeLocalEvent<InventoryComponent, ComponentInit>(OnInit);
  16. SubscribeAllEvent<OpenSlotStorageNetworkMessage>(OnOpenSlotStorage);
  17. _vvm.GetTypeHandler<InventoryComponent>()
  18. .AddHandler(HandleViewVariablesSlots, ListViewVariablesSlots);
  19. SubscribeLocalEvent<InventoryComponent, AfterAutoHandleStateEvent>(AfterAutoState);
  20. }
  21. private void ShutdownSlots()
  22. {
  23. _vvm.GetTypeHandler<InventoryComponent>()
  24. .RemoveHandler(HandleViewVariablesSlots, ListViewVariablesSlots);
  25. }
  26. /// <summary>
  27. /// Tries to find an entity in the specified slot with the specified component.
  28. /// </summary>
  29. public bool TryGetInventoryEntity<T>(Entity<InventoryComponent?> entity, out Entity<T?> target)
  30. where T : IComponent, IClothingSlots
  31. {
  32. if (TryGetContainerSlotEnumerator(entity.Owner, out var containerSlotEnumerator))
  33. {
  34. while (containerSlotEnumerator.NextItem(out var item, out var slot))
  35. {
  36. if (!TryComp<T>(item, out var required))
  37. continue;
  38. if ((((IClothingSlots) required).Slots & slot.SlotFlags) == 0x0)
  39. continue;
  40. target = (item, required);
  41. return true;
  42. }
  43. }
  44. target = EntityUid.Invalid;
  45. return false;
  46. }
  47. protected virtual void OnInit(EntityUid uid, InventoryComponent component, ComponentInit args)
  48. {
  49. if (!_prototypeManager.TryIndex(component.TemplateId, out InventoryTemplatePrototype? invTemplate))
  50. return;
  51. component.Slots = invTemplate.Slots;
  52. component.Containers = new ContainerSlot[component.Slots.Length];
  53. for (var i = 0; i < component.Containers.Length; i++)
  54. {
  55. var slot = component.Slots[i];
  56. var container = _containerSystem.EnsureContainer<ContainerSlot>(uid, slot.Name);
  57. container.OccludesLight = false;
  58. component.Containers[i] = container;
  59. }
  60. }
  61. private void AfterAutoState(Entity<InventoryComponent> ent, ref AfterAutoHandleStateEvent args)
  62. {
  63. UpdateInventoryTemplate(ent);
  64. }
  65. protected virtual void UpdateInventoryTemplate(Entity<InventoryComponent> ent)
  66. {
  67. if (ent.Comp.LifeStage < ComponentLifeStage.Initialized)
  68. return;
  69. if (!_prototypeManager.TryIndex(ent.Comp.TemplateId, out InventoryTemplatePrototype? invTemplate))
  70. return;
  71. DebugTools.Assert(ent.Comp.Slots.Length == invTemplate.Slots.Length);
  72. ent.Comp.Slots = invTemplate.Slots;
  73. var ev = new InventoryTemplateUpdated();
  74. RaiseLocalEvent(ent, ref ev);
  75. }
  76. private void OnOpenSlotStorage(OpenSlotStorageNetworkMessage ev, EntitySessionEventArgs args)
  77. {
  78. if (args.SenderSession.AttachedEntity is not { Valid: true } uid)
  79. return;
  80. if (TryGetSlotEntity(uid, ev.Slot, out var entityUid) && TryComp<StorageComponent>(entityUid, out var storageComponent))
  81. {
  82. _storageSystem.OpenStorageUI(entityUid.Value, uid, storageComponent, false);
  83. }
  84. }
  85. public bool TryGetSlotContainer(EntityUid uid, string slot, [NotNullWhen(true)] out ContainerSlot? containerSlot, [NotNullWhen(true)] out SlotDefinition? slotDefinition,
  86. InventoryComponent? inventory = null, ContainerManagerComponent? containerComp = null)
  87. {
  88. containerSlot = null;
  89. slotDefinition = null;
  90. if (!Resolve(uid, ref inventory, ref containerComp, false))
  91. return false;
  92. if (!TryGetSlot(uid, slot, out slotDefinition, inventory: inventory))
  93. return false;
  94. if (!_containerSystem.TryGetContainer(uid, slotDefinition.Name, out var container, containerComp))
  95. {
  96. if (inventory.LifeStage >= ComponentLifeStage.Initialized)
  97. Log.Error($"Missing inventory container {slot} on entity {ToPrettyString(uid)}");
  98. return false;
  99. }
  100. if (container is not ContainerSlot containerSlotChecked)
  101. return false;
  102. containerSlot = containerSlotChecked;
  103. return true;
  104. }
  105. public bool HasSlot(EntityUid uid, string slot, InventoryComponent? component = null) =>
  106. TryGetSlot(uid, slot, out _, component);
  107. public bool TryGetSlot(EntityUid uid, string slot, [NotNullWhen(true)] out SlotDefinition? slotDefinition, InventoryComponent? inventory = null)
  108. {
  109. slotDefinition = null;
  110. if (!Resolve(uid, ref inventory, false))
  111. return false;
  112. foreach (var slotDef in inventory.Slots)
  113. {
  114. if (!slotDef.Name.Equals(slot))
  115. continue;
  116. slotDefinition = slotDef;
  117. return true;
  118. }
  119. return false;
  120. }
  121. public bool TryGetContainerSlotEnumerator(Entity<InventoryComponent?> entity, out InventorySlotEnumerator containerSlotEnumerator, SlotFlags flags = SlotFlags.All)
  122. {
  123. if (!Resolve(entity.Owner, ref entity.Comp, false))
  124. {
  125. containerSlotEnumerator = default;
  126. return false;
  127. }
  128. containerSlotEnumerator = new InventorySlotEnumerator(entity.Comp, flags);
  129. return true;
  130. }
  131. public InventorySlotEnumerator GetSlotEnumerator(Entity<InventoryComponent?> entity, SlotFlags flags = SlotFlags.All)
  132. {
  133. if (!Resolve(entity.Owner, ref entity.Comp, false))
  134. return InventorySlotEnumerator.Empty;
  135. return new InventorySlotEnumerator(entity.Comp, flags);
  136. }
  137. public bool TryGetSlots(EntityUid uid, [NotNullWhen(true)] out SlotDefinition[]? slotDefinitions)
  138. {
  139. if (!TryComp(uid, out InventoryComponent? inv))
  140. {
  141. slotDefinitions = null;
  142. return false;
  143. }
  144. slotDefinitions = inv.Slots;
  145. return true;
  146. }
  147. private ViewVariablesPath? HandleViewVariablesSlots(EntityUid uid, InventoryComponent comp, string relativePath)
  148. {
  149. return TryGetSlotEntity(uid, relativePath, out var entity, comp)
  150. ? ViewVariablesPath.FromObject(entity)
  151. : null;
  152. }
  153. private IEnumerable<string> ListViewVariablesSlots(EntityUid uid, InventoryComponent comp)
  154. {
  155. foreach (var slotDef in comp.Slots)
  156. {
  157. yield return slotDef.Name;
  158. }
  159. }
  160. /// <summary>
  161. /// Change the inventory template ID an entity is using. The new template must be compatible.
  162. /// </summary>
  163. /// <remarks>
  164. /// <para>
  165. /// For an inventory template to be compatible with another, it must have exactly the same slot names.
  166. /// All other changes are rejected.
  167. /// </para>
  168. /// </remarks>
  169. /// <param name="ent">The entity to update.</param>
  170. /// <param name="newTemplate">The ID of the new inventory template prototype.</param>
  171. /// <exception cref="ArgumentException">
  172. /// Thrown if the new template is not compatible with the existing one.
  173. /// </exception>
  174. public void SetTemplateId(Entity<InventoryComponent> ent, ProtoId<InventoryTemplatePrototype> newTemplate)
  175. {
  176. var newPrototype = _prototypeManager.Index(newTemplate);
  177. if (!newPrototype.Slots.Select(x => x.Name).SequenceEqual(ent.Comp.Slots.Select(x => x.Name)))
  178. throw new ArgumentException("Incompatible inventory template!");
  179. ent.Comp.TemplateId = newTemplate;
  180. Dirty(ent);
  181. }
  182. /// <summary>
  183. /// Enumerator for iterating over an inventory's slot containers. Also has methods that skip empty containers.
  184. /// It should be safe to add or remove items while enumerating.
  185. /// </summary>
  186. public struct InventorySlotEnumerator
  187. {
  188. private readonly SlotDefinition[] _slots;
  189. private readonly ContainerSlot[] _containers;
  190. private readonly SlotFlags _flags;
  191. private int _nextIdx = 0;
  192. public static InventorySlotEnumerator Empty = new(Array.Empty<SlotDefinition>(), Array.Empty<ContainerSlot>());
  193. public InventorySlotEnumerator(InventoryComponent inventory, SlotFlags flags = SlotFlags.All)
  194. : this(inventory.Slots, inventory.Containers, flags)
  195. {
  196. }
  197. public InventorySlotEnumerator(SlotDefinition[] slots, ContainerSlot[] containers, SlotFlags flags = SlotFlags.All)
  198. {
  199. DebugTools.Assert(flags != SlotFlags.NONE);
  200. DebugTools.AssertEqual(slots.Length, containers.Length);
  201. _flags = flags;
  202. _slots = slots;
  203. _containers = containers;
  204. }
  205. public bool MoveNext([NotNullWhen(true)] out ContainerSlot? container)
  206. {
  207. while (_nextIdx < _slots.Length)
  208. {
  209. var i = _nextIdx++;
  210. var slot = _slots[i];
  211. if ((slot.SlotFlags & _flags) == 0)
  212. continue;
  213. container = _containers[i];
  214. return true;
  215. }
  216. container = null;
  217. return false;
  218. }
  219. public bool NextItem(out EntityUid item)
  220. {
  221. while (_nextIdx < _slots.Length)
  222. {
  223. var i = _nextIdx++;
  224. var slot = _slots[i];
  225. if ((slot.SlotFlags & _flags) == 0)
  226. continue;
  227. var container = _containers[i];
  228. if (container.ContainedEntity is { } uid)
  229. {
  230. item = uid;
  231. return true;
  232. }
  233. }
  234. item = default;
  235. return false;
  236. }
  237. public bool NextItem(out EntityUid item, [NotNullWhen(true)] out SlotDefinition? slot)
  238. {
  239. while (_nextIdx < _slots.Length)
  240. {
  241. var i = _nextIdx++;
  242. slot = _slots[i];
  243. if ((slot.SlotFlags & _flags) == 0)
  244. continue;
  245. var container = _containers[i];
  246. if (container.ContainedEntity is { } uid)
  247. {
  248. item = uid;
  249. return true;
  250. }
  251. }
  252. item = default;
  253. slot = null;
  254. return false;
  255. }
  256. }
  257. }