1
0

StorageUIController.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. using System.Numerics;
  2. using Content.Client.Examine;
  3. using Content.Client.Hands.Systems;
  4. using Content.Client.Interaction;
  5. using Content.Client.Storage;
  6. using Content.Client.Storage.Systems;
  7. using Content.Client.UserInterface.Systems.Hotbar.Widgets;
  8. using Content.Client.UserInterface.Systems.Info;
  9. using Content.Client.UserInterface.Systems.Storage.Controls;
  10. using Content.Client.Verbs.UI;
  11. using Content.Shared.CCVar;
  12. using Content.Shared.Input;
  13. using Content.Shared.Interaction;
  14. using Content.Shared.Storage;
  15. using Robust.Client.GameObjects;
  16. using Robust.Client.Input;
  17. using Robust.Client.Player;
  18. using Robust.Client.UserInterface;
  19. using Robust.Client.UserInterface.Controllers;
  20. using Robust.Client.UserInterface.Controls;
  21. using Robust.Shared.Configuration;
  22. using Robust.Shared.Input;
  23. using Robust.Shared.Timing;
  24. namespace Content.Client.UserInterface.Systems.Storage;
  25. public sealed class StorageUIController : UIController, IOnSystemChanged<StorageSystem>
  26. {
  27. /*
  28. * Things are a bit over the shop but essentially
  29. * - Clicking into storagewindow is handled via storagewindow
  30. * - Clicking out of it is via ItemGridPiece
  31. * - Dragging around is handled here
  32. * - Drawing is handled via ItemGridPiece
  33. * - StorageSystem handles any sim stuff around open windows.
  34. */
  35. [Dependency] private readonly IConfigurationManager _configuration = default!;
  36. [Dependency] private readonly IInputManager _input = default!;
  37. [Dependency] private readonly IPlayerManager _player = default!;
  38. [Dependency] private readonly CloseRecentWindowUIController _closeRecentWindowUIController = default!;
  39. [UISystemDependency] private readonly StorageSystem _storage = default!;
  40. [UISystemDependency] private readonly UserInterfaceSystem _ui = default!;
  41. private readonly DragDropHelper<ItemGridPiece> _menuDragHelper;
  42. public ItemGridPiece? DraggingGhost => _menuDragHelper.Dragged;
  43. public Angle DraggingRotation = Angle.Zero;
  44. public bool StaticStorageUIEnabled;
  45. public bool OpaqueStorageWindow;
  46. public bool IsDragging => _menuDragHelper.IsDragging;
  47. public ItemGridPiece? CurrentlyDragging => _menuDragHelper.Dragged;
  48. public bool WindowTitle { get; private set; } = false;
  49. public StorageUIController()
  50. {
  51. _menuDragHelper = new DragDropHelper<ItemGridPiece>(OnMenuBeginDrag, OnMenuContinueDrag, OnMenuEndDrag);
  52. }
  53. public override void Initialize()
  54. {
  55. base.Initialize();
  56. _configuration.OnValueChanged(CCVars.StaticStorageUI, OnStaticStorageChanged, true);
  57. _configuration.OnValueChanged(CCVars.OpaqueStorageWindow, OnOpaqueWindowChanged, true);
  58. _configuration.OnValueChanged(CCVars.StorageWindowTitle, OnStorageWindowTitle, true);
  59. }
  60. private void OnStorageWindowTitle(bool obj)
  61. {
  62. WindowTitle = obj;
  63. }
  64. private void OnOpaqueWindowChanged(bool obj)
  65. {
  66. OpaqueStorageWindow = obj;
  67. }
  68. private void OnStaticStorageChanged(bool obj)
  69. {
  70. StaticStorageUIEnabled = obj;
  71. }
  72. public StorageWindow CreateStorageWindow(StorageBoundUserInterface sBui)
  73. {
  74. var window = new StorageWindow();
  75. window.MouseFilter = Control.MouseFilterMode.Pass;
  76. window.OnPiecePressed += (args, piece) =>
  77. {
  78. OnPiecePressed(args, window, piece);
  79. };
  80. window.OnPieceUnpressed += (args, piece) =>
  81. {
  82. OnPieceUnpressed(args, window, piece);
  83. };
  84. if (StaticStorageUIEnabled)
  85. {
  86. UIManager.GetActiveUIWidgetOrNull<HotbarGui>()?.StorageContainer.AddChild(window);
  87. _closeRecentWindowUIController.SetMostRecentlyInteractedWindow(window);
  88. }
  89. else
  90. {
  91. // Open at parent position if it's open.
  92. if (_ui.TryGetOpenUi<StorageBoundUserInterface>(EntityManager.GetComponent<TransformComponent>(sBui.Owner).ParentUid,
  93. StorageComponent.StorageUiKey.Key, out var bui) && bui.Position != null)
  94. {
  95. window.Open(bui.Position.Value);
  96. }
  97. // Open at the saved position if it exists.
  98. else if (_ui.TryGetPosition(sBui.Owner, StorageComponent.StorageUiKey.Key, out var pos))
  99. {
  100. window.Open(pos);
  101. }
  102. // Open at the default position.
  103. else
  104. {
  105. window.OpenCenteredLeft();
  106. }
  107. }
  108. _ui.RegisterControl(sBui, window);
  109. return window;
  110. }
  111. public void OnSystemLoaded(StorageSystem system)
  112. {
  113. _input.FirstChanceOnKeyEvent += OnMiddleMouse;
  114. }
  115. public void OnSystemUnloaded(StorageSystem system)
  116. {
  117. _input.FirstChanceOnKeyEvent -= OnMiddleMouse;
  118. }
  119. /// One might ask, Hey Emo, why are you parsing raw keyboard input just to rotate a rectangle?
  120. /// The answer is, that input bindings regarding mouse inputs are always intercepted by the UI,
  121. /// thus, if i want to be able to rotate my damn piece anywhere on the screen,
  122. /// I have to side-step all of the input handling. Cheers.
  123. private void OnMiddleMouse(KeyEventArgs keyEvent, KeyEventType type)
  124. {
  125. if (keyEvent.Handled)
  126. return;
  127. if (type != KeyEventType.Down)
  128. return;
  129. //todo there's gotta be a method for this in InputManager just expose it to content I BEG.
  130. if (!_input.TryGetKeyBinding(ContentKeyFunctions.RotateStoredItem, out var binding))
  131. return;
  132. if (binding.BaseKey != keyEvent.Key)
  133. return;
  134. if (keyEvent.Shift &&
  135. !(binding.Mod1 == Keyboard.Key.Shift ||
  136. binding.Mod2 == Keyboard.Key.Shift ||
  137. binding.Mod3 == Keyboard.Key.Shift))
  138. return;
  139. if (keyEvent.Alt &&
  140. !(binding.Mod1 == Keyboard.Key.Alt ||
  141. binding.Mod2 == Keyboard.Key.Alt ||
  142. binding.Mod3 == Keyboard.Key.Alt))
  143. return;
  144. if (keyEvent.Control &&
  145. !(binding.Mod1 == Keyboard.Key.Control ||
  146. binding.Mod2 == Keyboard.Key.Control ||
  147. binding.Mod3 == Keyboard.Key.Control))
  148. return;
  149. if (!IsDragging && EntityManager.System<HandsSystem>().GetActiveHandEntity() == null)
  150. return;
  151. //clamp it to a cardinal.
  152. DraggingRotation = (DraggingRotation + Math.PI / 2f).GetCardinalDir().ToAngle();
  153. if (DraggingGhost != null)
  154. DraggingGhost.Location.Rotation = DraggingRotation;
  155. if (IsDragging || UIManager.CurrentlyHovered is StorageWindow)
  156. keyEvent.Handle();
  157. }
  158. private void OnPiecePressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
  159. {
  160. if (IsDragging || !window.IsOpen)
  161. return;
  162. if (args.Function == ContentKeyFunctions.MoveStoredItem)
  163. {
  164. DraggingRotation = control.Location.Rotation;
  165. _menuDragHelper.MouseDown(control);
  166. _menuDragHelper.Update(0f);
  167. args.Handle();
  168. }
  169. else if (args.Function == ContentKeyFunctions.SaveItemLocation)
  170. {
  171. if (window.StorageEntity is not {} storage)
  172. return;
  173. EntityManager.RaisePredictiveEvent(new StorageSaveItemLocationEvent(
  174. EntityManager.GetNetEntity(control.Entity),
  175. EntityManager.GetNetEntity(storage)));
  176. args.Handle();
  177. }
  178. else if (args.Function == ContentKeyFunctions.ExamineEntity)
  179. {
  180. EntityManager.System<ExamineSystem>().DoExamine(control.Entity);
  181. args.Handle();
  182. }
  183. else if (args.Function == EngineKeyFunctions.UseSecondary)
  184. {
  185. UIManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(control.Entity);
  186. args.Handle();
  187. }
  188. else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
  189. {
  190. EntityManager.RaisePredictiveEvent(
  191. new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: false));
  192. args.Handle();
  193. }
  194. else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
  195. {
  196. EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: true));
  197. args.Handle();
  198. }
  199. window.FlagDirty();
  200. }
  201. private void OnPieceUnpressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
  202. {
  203. if (args.Function != ContentKeyFunctions.MoveStoredItem)
  204. return;
  205. // Want to get the control under the dragged control.
  206. // This means we can drag the original control around (and not hide the original).
  207. control.MouseFilter = Control.MouseFilterMode.Ignore;
  208. var targetControl = UIManager.MouseGetControl(args.PointerLocation);
  209. var targetStorage = targetControl as StorageWindow;
  210. control.MouseFilter = Control.MouseFilterMode.Pass;
  211. var localPlayer = _player.LocalEntity;
  212. window.RemoveGrid(control);
  213. window.FlagDirty();
  214. // If we tried to drag it on top of another grid piece then cancel out.
  215. if (targetControl is ItemGridPiece || window.StorageEntity is not { } sourceStorage || localPlayer == null)
  216. {
  217. window.Reclaim(control.Location, control);
  218. args.Handle();
  219. _menuDragHelper.EndDrag();
  220. return;
  221. }
  222. if (_menuDragHelper.IsDragging && DraggingGhost is { } draggingGhost)
  223. {
  224. var dragEnt = draggingGhost.Entity;
  225. var dragLoc = draggingGhost.Location;
  226. // Dragging in the same storage
  227. // The existing ItemGridPiece just stops rendering but still exists so check if it's hovered.
  228. if (targetStorage == window)
  229. {
  230. var position = targetStorage.GetMouseGridPieceLocation(dragEnt, dragLoc);
  231. var newLocation = new ItemStorageLocation(DraggingRotation, position);
  232. EntityManager.RaisePredictiveEvent(new StorageSetItemLocationEvent(
  233. EntityManager.GetNetEntity(draggingGhost.Entity),
  234. EntityManager.GetNetEntity(sourceStorage),
  235. newLocation));
  236. window.Reclaim(newLocation, control);
  237. }
  238. // Dragging to new storage
  239. else if (targetStorage?.StorageEntity != null && targetStorage != window)
  240. {
  241. var position = targetStorage.GetMouseGridPieceLocation(dragEnt, dragLoc);
  242. var newLocation = new ItemStorageLocation(DraggingRotation, position);
  243. // Check it fits and we can move to hand (no free transfers).
  244. if (_storage.ItemFitsInGridLocation(
  245. (dragEnt, null),
  246. (targetStorage.StorageEntity.Value, null),
  247. newLocation))
  248. {
  249. // Can drop and move.
  250. EntityManager.RaisePredictiveEvent(new StorageTransferItemEvent(
  251. EntityManager.GetNetEntity(dragEnt),
  252. EntityManager.GetNetEntity(targetStorage.StorageEntity.Value),
  253. newLocation));
  254. targetStorage.Reclaim(newLocation, control);
  255. DraggingRotation = Angle.Zero;
  256. }
  257. else
  258. {
  259. // Cancel it (rather than dropping).
  260. window.Reclaim(dragLoc, control);
  261. }
  262. }
  263. targetStorage?.FlagDirty();
  264. }
  265. // If we just clicked, then take it out of the bag.
  266. else
  267. {
  268. EntityManager.RaisePredictiveEvent(new StorageInteractWithItemEvent(
  269. EntityManager.GetNetEntity(control.Entity),
  270. EntityManager.GetNetEntity(sourceStorage)));
  271. }
  272. _menuDragHelper.EndDrag();
  273. args.Handle();
  274. }
  275. private bool OnMenuBeginDrag()
  276. {
  277. if (_menuDragHelper.Dragged is not { } dragged)
  278. return false;
  279. DraggingGhost!.Orphan();
  280. DraggingRotation = dragged.Location.Rotation;
  281. UIManager.PopupRoot.AddChild(DraggingGhost);
  282. SetDraggingRotation();
  283. return true;
  284. }
  285. private bool OnMenuContinueDrag(float frameTime)
  286. {
  287. if (DraggingGhost == null)
  288. return false;
  289. SetDraggingRotation();
  290. return true;
  291. }
  292. private void SetDraggingRotation()
  293. {
  294. if (DraggingGhost == null)
  295. return;
  296. var offset = ItemGridPiece.GetCenterOffset(
  297. (DraggingGhost.Entity, null),
  298. new ItemStorageLocation(DraggingRotation, Vector2i.Zero),
  299. EntityManager);
  300. // I don't know why it divides the position by 2. Hope this helps! -emo
  301. LayoutContainer.SetPosition(DraggingGhost, UIManager.MousePositionScaled.Position / 2 - offset );
  302. }
  303. private void OnMenuEndDrag()
  304. {
  305. if (DraggingGhost == null)
  306. return;
  307. DraggingRotation = Angle.Zero;
  308. }
  309. public override void FrameUpdate(FrameEventArgs args)
  310. {
  311. base.FrameUpdate(args);
  312. _menuDragHelper.Update(args.DeltaSeconds);
  313. }
  314. }