ItemSlotsComponent.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. using Content.Shared.Whitelist;
  2. using Robust.Shared.Audio;
  3. using Robust.Shared.Containers;
  4. using Robust.Shared.GameStates;
  5. using Robust.Shared.Prototypes;
  6. using Robust.Shared.Serialization;
  7. using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
  8. namespace Content.Shared.Containers.ItemSlots
  9. {
  10. /// <summary>
  11. /// Used for entities that can hold items in different slots. Needed by ItemSlotSystem to support basic
  12. /// insert/eject interactions.
  13. /// </summary>
  14. [RegisterComponent]
  15. [Access(typeof(ItemSlotsSystem))]
  16. [NetworkedComponent]
  17. public sealed partial class ItemSlotsComponent : Component
  18. {
  19. /// <summary>
  20. /// The dictionary that stores all of the item slots whose interactions will be managed by the <see
  21. /// cref="ItemSlotsSystem"/>.
  22. /// </summary>
  23. [DataField(readOnly:true)]
  24. public Dictionary<string, ItemSlot> Slots = new();
  25. // There are two ways to use item slots:
  26. //
  27. // #1 - Give your component an ItemSlot datafield, and add/remove the item slot through the ItemSlotsSystem on
  28. // component init/remove.
  29. //
  30. // #2 - Give your component a key string datafield, and make sure that every entity with that component also has
  31. // an ItemSlots component with a matching key. Then use ItemSlots system to get the slot with this key whenever
  32. // you need it, or just get a reference to the slot on init and store it. This is how generic entity containers
  33. // are usually used.
  34. //
  35. // In order to avoid #1 leading to duplicate slots when saving a map, the Slots dictionary is a read-only
  36. // datafield. This means that if your system/component dynamically changes the item slot (e.g., updating
  37. // whitelist or whatever), you should use #1. Alternatively: split the Slots dictionary here into two: one
  38. // datafield, one that is actually used by the ItemSlotsSystem for keeping track of slots.
  39. }
  40. [Serializable, NetSerializable]
  41. public sealed class ItemSlotsComponentState : ComponentState
  42. {
  43. public readonly Dictionary<string, ItemSlot> Slots;
  44. public ItemSlotsComponentState(Dictionary<string, ItemSlot> slots)
  45. {
  46. Slots = slots;
  47. }
  48. }
  49. /// <summary>
  50. /// This is effectively a wrapper for a ContainerSlot that adds content functionality like entity whitelists and
  51. /// insert/eject sounds.
  52. /// </summary>
  53. [DataDefinition]
  54. [Access(typeof(ItemSlotsSystem))]
  55. [Serializable, NetSerializable]
  56. public sealed partial class ItemSlot
  57. {
  58. public ItemSlot() { }
  59. public ItemSlot(ItemSlot other)
  60. {
  61. CopyFrom(other);
  62. }
  63. [DataField]
  64. [Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
  65. public EntityWhitelist? Whitelist;
  66. [DataField]
  67. public EntityWhitelist? Blacklist;
  68. [DataField]
  69. public SoundSpecifier? InsertSound = new SoundPathSpecifier("/Audio/Weapons/Guns/MagIn/revolver_magin.ogg");
  70. [DataField]
  71. public SoundSpecifier? EjectSound = new SoundPathSpecifier("/Audio/Weapons/Guns/MagOut/revolver_magout.ogg");
  72. /// <summary>
  73. /// The name of this item slot. This will be shown to the user in the verb menu.
  74. /// </summary>
  75. /// <remarks>
  76. /// This will be passed through Loc.GetString. If the name is an empty string, then verbs will use the name
  77. /// of the currently held or currently inserted entity instead.
  78. /// </remarks>
  79. [DataField(readOnly: true)]
  80. [Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
  81. public string Name = string.Empty;
  82. /// <summary>
  83. /// The entity prototype that is spawned into this slot on map init.
  84. /// </summary>
  85. /// <remarks>
  86. /// Marked as readOnly because some components (e.g. PowerCellSlot) set the starting item based on some
  87. /// property of that component (e.g., cell slot size category), and this can lead to unnecessary changes
  88. /// when mapping.
  89. /// </remarks>
  90. [DataField(readOnly: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
  91. [Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
  92. [NonSerialized]
  93. public string? StartingItem;
  94. /// <summary>
  95. /// Whether or not an item can currently be ejected or inserted from this slot.
  96. /// </summary>
  97. /// <remarks>
  98. /// This doesn't have to mean the slot is somehow physically locked. In the case of the item cabinet, the
  99. /// cabinet may simply be closed at the moment and needs to be opened first.
  100. /// </remarks>
  101. [DataField(readOnly: true)]
  102. [ViewVariables(VVAccess.ReadWrite)]
  103. public bool Locked = false;
  104. /// <summary>
  105. /// Prevents adding the eject alt-verb, but still lets you swap items.
  106. /// </summary>
  107. /// <remarks>
  108. /// This does not affect EjectOnInteract, since if you do that you probably want ejecting to work.
  109. /// </remarks>
  110. [DataField, ViewVariables(VVAccess.ReadWrite)]
  111. public bool DisableEject = false;
  112. /// <summary>
  113. /// Whether the item slots system will attempt to insert item from the user's hands into this slot when interacted with.
  114. /// It doesn't block other insertion methods, like verbs.
  115. /// </summary>
  116. [DataField]
  117. public bool InsertOnInteract = true;
  118. /// <summary>
  119. /// Whether the item slots system will attempt to eject this item to the user's hands when interacted with.
  120. /// </summary>
  121. /// <remarks>
  122. /// For most item slots, this is probably not the case (eject is usually an alt-click interaction). But
  123. /// there are some exceptions. For example item cabinets and charging stations should probably eject their
  124. /// contents when clicked on normally.
  125. /// </remarks>
  126. [DataField]
  127. public bool EjectOnInteract = false;
  128. /// <summary>
  129. /// If true, and if this slot is attached to an item, then it will attempt to eject slot when to the slot is
  130. /// used in the user's hands.
  131. /// </summary>
  132. /// <remarks>
  133. /// Desirable for things like ranged weapons ('Z' to eject), but not desirable for others (e.g., PDA uses
  134. /// 'Z' to open UI). Unlike <see cref="EjectOnInteract"/>, this will not make any changes to the context
  135. /// menu, nor will it disable alt-click interactions.
  136. /// </remarks>
  137. [DataField]
  138. public bool EjectOnUse = false;
  139. /// <summary>
  140. /// Override the insert verb text. Defaults to using the slot's name (if specified) or the name of the
  141. /// targeted item. If specified, the verb will not be added to the default insert verb category.
  142. /// </summary>
  143. [DataField]
  144. public string? InsertVerbText;
  145. /// <summary>
  146. /// Override the eject verb text. Defaults to using the slot's name (if specified) or the name of the
  147. /// targeted item. If specified, the verb will not be added to the default eject verb category
  148. /// </summary>
  149. [DataField]
  150. public string? EjectVerbText;
  151. [ViewVariables, NonSerialized]
  152. public ContainerSlot? ContainerSlot = default!;
  153. /// <summary>
  154. /// If this slot belongs to some de-constructible component, should the item inside the slot be ejected upon
  155. /// deconstruction?
  156. /// </summary>
  157. /// <remarks>
  158. /// The actual deconstruction logic is handled by the server-side EmptyOnMachineDeconstructSystem.
  159. /// </remarks>
  160. [DataField]
  161. [Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
  162. [NonSerialized]
  163. public bool EjectOnDeconstruct = true;
  164. /// <summary>
  165. /// If this slot belongs to some breakable or destructible entity, should the item inside the slot be
  166. /// ejected when it is broken or destroyed?
  167. /// </summary>
  168. [DataField]
  169. [Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
  170. [NonSerialized]
  171. public bool EjectOnBreak = false;
  172. /// <summary>
  173. /// When specified, a popup will be generated whenever someone attempts to insert a bad item into this slot.
  174. /// </summary>
  175. [DataField]
  176. public LocId? WhitelistFailPopup;
  177. /// <summary>
  178. /// When specified, a popup will be generated whenever someone attempts to insert a valid item, or eject an item
  179. /// from the slot while that slot is locked.
  180. /// </summary>
  181. [DataField]
  182. public LocId? LockedFailPopup;
  183. /// <summary>
  184. /// When specified, a popup will be generated whenever someone successfully inserts a valid item into this slot.
  185. /// This is also used for insertions resulting from swapping.
  186. /// </summary>
  187. [DataField]
  188. public LocId? InsertSuccessPopup;
  189. /// <summary>
  190. /// If the user interacts with an entity with an already-filled item slot, should they attempt to swap out the item?
  191. /// </summary>
  192. /// <remarks>
  193. /// Useful for things like chem dispensers, but undesirable for things like the ID card console, where you
  194. /// want to insert more than one item that matches the same whitelist.
  195. /// </remarks>
  196. [DataField]
  197. [Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
  198. public bool Swap = true;
  199. public string? ID => ContainerSlot?.ID;
  200. // Convenience properties
  201. public bool HasItem => ContainerSlot?.ContainedEntity != null;
  202. public EntityUid? Item => ContainerSlot?.ContainedEntity;
  203. /// <summary>
  204. /// Priority for use with the eject & insert verbs for this slot.
  205. /// </summary>
  206. [DataField]
  207. public int Priority = 0;
  208. /// <summary>
  209. /// If false, errors when adding an item slot with a duplicate key are suppressed. Local==true implies that
  210. /// the slot was added via client component state handling.
  211. /// </summary>
  212. [NonSerialized]
  213. public bool Local = true;
  214. public void CopyFrom(ItemSlot other)
  215. {
  216. // These fields are mutable reference types. But they generally don't get modified, so this should be fine.
  217. Whitelist = other.Whitelist;
  218. InsertSound = other.InsertSound;
  219. EjectSound = other.EjectSound;
  220. Name = other.Name;
  221. Locked = other.Locked;
  222. InsertOnInteract = other.InsertOnInteract;
  223. EjectOnInteract = other.EjectOnInteract;
  224. EjectOnUse = other.EjectOnUse;
  225. InsertVerbText = other.InsertVerbText;
  226. EjectVerbText = other.EjectVerbText;
  227. WhitelistFailPopup = other.WhitelistFailPopup;
  228. LockedFailPopup = other.LockedFailPopup;
  229. InsertSuccessPopup = other.InsertSuccessPopup;
  230. Swap = other.Swap;
  231. Priority = other.Priority;
  232. }
  233. }
  234. /// <summary>
  235. /// Event raised on the slot entity and the item being inserted to determine if an item can be inserted into an item slot.
  236. /// </summary>
  237. [ByRefEvent]
  238. public record struct ItemSlotInsertAttemptEvent(EntityUid SlotEntity, EntityUid Item, EntityUid? User, ItemSlot Slot, bool Cancelled = false);
  239. /// <summary>
  240. /// Event raised on the slot entity and the item being inserted to determine if an item can be ejected from an item slot.
  241. /// </summary>
  242. [ByRefEvent]
  243. public record struct ItemSlotEjectAttemptEvent(EntityUid SlotEntity, EntityUid Item, EntityUid? User, ItemSlot Slot, bool Cancelled = false);
  244. }