1
0

ClientInventorySystem.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. using Content.Client.Clothing;
  2. using Content.Client.Examine;
  3. using Content.Client.Verbs.UI;
  4. using Content.Shared.Interaction;
  5. using Content.Shared.Inventory;
  6. using Content.Shared.Inventory.Events;
  7. using Content.Shared.Storage;
  8. using JetBrains.Annotations;
  9. using Robust.Client.Player;
  10. using Robust.Client.UserInterface;
  11. using Robust.Shared.Containers;
  12. using Robust.Shared.Input.Binding;
  13. using Robust.Shared.Player;
  14. namespace Content.Client.Inventory
  15. {
  16. [UsedImplicitly]
  17. public sealed class ClientInventorySystem : InventorySystem
  18. {
  19. [Dependency] private readonly IPlayerManager _playerManager = default!;
  20. [Dependency] private readonly IUserInterfaceManager _ui = default!;
  21. [Dependency] private readonly ClientClothingSystem _clothingVisualsSystem = default!;
  22. [Dependency] private readonly ExamineSystem _examine = default!;
  23. public Action<SlotData>? EntitySlotUpdate = null;
  24. public Action<SlotData>? OnSlotAdded = null;
  25. public Action<SlotData>? OnSlotRemoved = null;
  26. public Action<EntityUid, InventorySlotsComponent>? OnLinkInventorySlots = null;
  27. public Action? OnUnlinkInventory = null;
  28. public Action<SlotSpriteUpdate>? OnSpriteUpdate = null;
  29. private readonly Queue<(InventorySlotsComponent comp, EntityEventArgs args)> _equipEventsQueue = new();
  30. public override void Initialize()
  31. {
  32. UpdatesOutsidePrediction = true;
  33. base.Initialize();
  34. SubscribeLocalEvent<InventorySlotsComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
  35. SubscribeLocalEvent<InventorySlotsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
  36. SubscribeLocalEvent<InventoryComponent, ComponentShutdown>(OnShutdown);
  37. SubscribeLocalEvent<InventorySlotsComponent, DidEquipEvent>((_, comp, args) =>
  38. _equipEventsQueue.Enqueue((comp, args)));
  39. SubscribeLocalEvent<InventorySlotsComponent, DidUnequipEvent>((_, comp, args) =>
  40. _equipEventsQueue.Enqueue((comp, args)));
  41. }
  42. public override void Update(float frameTime)
  43. {
  44. base.Update(frameTime);
  45. while (_equipEventsQueue.TryDequeue(out var tuple))
  46. {
  47. var (component, args) = tuple;
  48. switch (args)
  49. {
  50. case DidEquipEvent equipped:
  51. OnDidEquip(component, equipped);
  52. break;
  53. case DidUnequipEvent unequipped:
  54. OnDidUnequip(component, unequipped);
  55. break;
  56. default:
  57. throw new InvalidOperationException($"Received queued event of unknown type: {args.GetType()}");
  58. }
  59. }
  60. }
  61. private void OnDidUnequip(InventorySlotsComponent component, DidUnequipEvent args)
  62. {
  63. UpdateSlot(args.Equipee, component, args.Slot);
  64. if (args.Equipee != _playerManager.LocalEntity)
  65. return;
  66. var update = new SlotSpriteUpdate(null, args.SlotGroup, args.Slot, false);
  67. OnSpriteUpdate?.Invoke(update);
  68. }
  69. private void OnDidEquip(InventorySlotsComponent component, DidEquipEvent args)
  70. {
  71. UpdateSlot(args.Equipee, component, args.Slot);
  72. if (args.Equipee != _playerManager.LocalEntity)
  73. return;
  74. var update = new SlotSpriteUpdate(args.Equipment, args.SlotGroup, args.Slot,
  75. HasComp<StorageComponent>(args.Equipment));
  76. OnSpriteUpdate?.Invoke(update);
  77. }
  78. private void OnShutdown(EntityUid uid, InventoryComponent component, ComponentShutdown args)
  79. {
  80. if (uid == _playerManager.LocalEntity)
  81. OnUnlinkInventory?.Invoke();
  82. }
  83. private void OnPlayerDetached(EntityUid uid, InventorySlotsComponent component, LocalPlayerDetachedEvent args)
  84. {
  85. OnUnlinkInventory?.Invoke();
  86. }
  87. private void OnPlayerAttached(EntityUid uid, InventorySlotsComponent component, LocalPlayerAttachedEvent args)
  88. {
  89. if (TryGetSlots(uid, out var definitions))
  90. {
  91. foreach (var definition in definitions)
  92. {
  93. if (!TryGetSlotContainer(uid, definition.Name, out var container, out _))
  94. continue;
  95. if (!component.SlotData.TryGetValue(definition.Name, out var data))
  96. {
  97. data = new SlotData(definition);
  98. component.SlotData[definition.Name] = data;
  99. }
  100. data.Container = container;
  101. }
  102. }
  103. OnLinkInventorySlots?.Invoke(uid, component);
  104. }
  105. public override void Shutdown()
  106. {
  107. CommandBinds.Unregister<ClientInventorySystem>();
  108. base.Shutdown();
  109. }
  110. protected override void OnInit(EntityUid uid, InventoryComponent component, ComponentInit args)
  111. {
  112. base.OnInit(uid, component, args);
  113. _clothingVisualsSystem.InitClothing(uid, component);
  114. if (!TryComp(uid, out InventorySlotsComponent? inventorySlots))
  115. return;
  116. foreach (var slot in component.Slots)
  117. {
  118. TryAddSlotDef(uid, inventorySlots, slot);
  119. }
  120. }
  121. public void ReloadInventory(InventorySlotsComponent? component = null)
  122. {
  123. var player = _playerManager.LocalEntity;
  124. if (player == null || !Resolve(player.Value, ref component, false))
  125. {
  126. return;
  127. }
  128. OnUnlinkInventory?.Invoke();
  129. OnLinkInventorySlots?.Invoke(player.Value, component);
  130. }
  131. public void SetSlotHighlight(EntityUid owner, InventorySlotsComponent component, string slotName, bool state)
  132. {
  133. var oldData = component.SlotData[slotName];
  134. var newData = component.SlotData[slotName] = new SlotData(oldData, state);
  135. if (owner == _playerManager.LocalEntity)
  136. EntitySlotUpdate?.Invoke(newData);
  137. }
  138. public void UpdateSlot(EntityUid owner, InventorySlotsComponent component, string slotName,
  139. bool? blocked = null, bool? highlight = null)
  140. {
  141. var oldData = component.SlotData[slotName];
  142. var newHighlight = oldData.Highlighted;
  143. var newBlocked = oldData.Blocked;
  144. if (blocked != null)
  145. newBlocked = blocked.Value;
  146. if (highlight != null)
  147. newHighlight = highlight.Value;
  148. var newData = component.SlotData[slotName] =
  149. new SlotData(component.SlotData[slotName], newHighlight, newBlocked);
  150. if (owner == _playerManager.LocalEntity)
  151. EntitySlotUpdate?.Invoke(newData);
  152. }
  153. public bool TryAddSlotDef(EntityUid owner, InventorySlotsComponent component, SlotDefinition newSlotDef)
  154. {
  155. SlotData newSlotData = newSlotDef; //convert to slotData
  156. if (!component.SlotData.TryAdd(newSlotDef.Name, newSlotData))
  157. return false;
  158. if (owner == _playerManager.LocalEntity)
  159. OnSlotAdded?.Invoke(newSlotData);
  160. return true;
  161. }
  162. public void UIInventoryActivate(string slot)
  163. {
  164. EntityManager.RaisePredictiveEvent(new UseSlotNetworkMessage(slot));
  165. }
  166. public void UIInventoryStorageActivate(string slot)
  167. {
  168. EntityManager.RaisePredictiveEvent(new OpenSlotStorageNetworkMessage(slot));
  169. }
  170. public void UIInventoryExamine(string slot, EntityUid uid)
  171. {
  172. if (!TryGetSlotEntity(uid, slot, out var item))
  173. return;
  174. _examine.DoExamine(item.Value);
  175. }
  176. public void UIInventoryOpenContextMenu(string slot, EntityUid uid)
  177. {
  178. if (!TryGetSlotEntity(uid, slot, out var item))
  179. return;
  180. _ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(item.Value);
  181. }
  182. public void UIInventoryActivateItem(string slot, EntityUid uid)
  183. {
  184. if (!TryGetSlotEntity(uid, slot, out var item))
  185. return;
  186. EntityManager.RaisePredictiveEvent(
  187. new InteractInventorySlotEvent(GetNetEntity(item.Value), altInteract: false));
  188. }
  189. public void UIInventoryAltActivateItem(string slot, EntityUid uid)
  190. {
  191. if (!TryGetSlotEntity(uid, slot, out var item))
  192. return;
  193. EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(GetNetEntity(item.Value), altInteract: true));
  194. }
  195. protected override void UpdateInventoryTemplate(Entity<InventoryComponent> ent)
  196. {
  197. base.UpdateInventoryTemplate(ent);
  198. if (TryComp(ent, out InventorySlotsComponent? inventorySlots))
  199. {
  200. foreach (var slot in ent.Comp.Slots)
  201. {
  202. if (inventorySlots.SlotData.TryGetValue(slot.Name, out var slotData))
  203. slotData.SlotDef = slot;
  204. }
  205. }
  206. }
  207. public sealed class SlotData
  208. {
  209. public SlotDefinition SlotDef;
  210. public EntityUid? HeldEntity => Container?.ContainedEntity;
  211. public bool Blocked;
  212. public bool Highlighted;
  213. [ViewVariables]
  214. public ContainerSlot? Container;
  215. public bool HasSlotGroup => SlotDef.SlotGroup != "Default";
  216. public Vector2i ButtonOffset => SlotDef.UIWindowPosition;
  217. public string SlotName => SlotDef.Name;
  218. public bool ShowInWindow => SlotDef.ShowInWindow;
  219. public string SlotGroup => SlotDef.SlotGroup;
  220. public string SlotDisplayName => SlotDef.DisplayName;
  221. public string TextureName => "Slots/" + SlotDef.TextureName;
  222. public string FullTextureName => SlotDef.FullTextureName;
  223. public SlotData(SlotDefinition slotDef, ContainerSlot? container = null, bool highlighted = false,
  224. bool blocked = false)
  225. {
  226. SlotDef = slotDef;
  227. Highlighted = highlighted;
  228. Blocked = blocked;
  229. Container = container;
  230. }
  231. public SlotData(SlotData oldData, bool highlighted = false, bool blocked = false)
  232. {
  233. SlotDef = oldData.SlotDef;
  234. Highlighted = highlighted;
  235. Container = oldData.Container;
  236. Blocked = blocked;
  237. }
  238. public static implicit operator SlotData(SlotDefinition s)
  239. {
  240. return new SlotData(s);
  241. }
  242. public static implicit operator SlotDefinition(SlotData s)
  243. {
  244. return s.SlotDef;
  245. }
  246. }
  247. public readonly record struct SlotSpriteUpdate(
  248. EntityUid? Entity,
  249. string Group,
  250. string Name,
  251. bool ShowStorage
  252. );
  253. }
  254. }