InventorySystem.Slots.cs 11 KB

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