EditSolutionsWindow.xaml.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. using Content.Shared.Administration;
  2. using Content.Shared.Chemistry.Components;
  3. using Content.Shared.Chemistry.Reagent;
  4. using Robust.Client.AutoGenerated;
  5. using Robust.Client.Console;
  6. using Robust.Client.Timing;
  7. using Robust.Client.UserInterface.Controls;
  8. using Robust.Client.UserInterface.CustomControls;
  9. using Robust.Client.UserInterface.XAML;
  10. using Robust.Shared.Timing;
  11. namespace Content.Client.Administration.UI.ManageSolutions
  12. {
  13. /// <summary>
  14. /// A simple window that displays solutions and their contained reagents. Allows you to edit the reagent quantities and add new reagents.
  15. /// </summary>
  16. [GenerateTypedNameReferences]
  17. public sealed partial class EditSolutionsWindow : DefaultWindow
  18. {
  19. [Dependency] private readonly IClientConsoleHost _consoleHost = default!;
  20. [Dependency] private readonly IEntityManager _entityManager = default!;
  21. [Dependency] private readonly IClientGameTiming _timing = default!;
  22. private NetEntity _target = NetEntity.Invalid;
  23. private string? _selectedSolution;
  24. private AddReagentWindow? _addReagentWindow;
  25. private Dictionary<string, EntityUid>? _solutions;
  26. private EditSolutionsEuiState? _nextState;
  27. public EditSolutionsWindow()
  28. {
  29. IoCManager.InjectDependencies(this);
  30. RobustXamlLoader.Load(this);
  31. SolutionOption.OnItemSelected += SolutionSelected;
  32. AddButton.OnPressed += OpenAddReagentWindow;
  33. }
  34. public override void Close()
  35. {
  36. base.Close();
  37. _addReagentWindow?.Close();
  38. _addReagentWindow?.Dispose();
  39. }
  40. public void SetTargetEntity(NetEntity target)
  41. {
  42. _target = target;
  43. var uid = _entityManager.GetEntity(target);
  44. var targetName = _entityManager.EntityExists(uid)
  45. ? _entityManager.GetComponent<MetaDataComponent>(uid).EntityName
  46. : string.Empty;
  47. Title = Loc.GetString("admin-solutions-window-title", ("targetName", targetName));
  48. }
  49. /// <summary>
  50. /// Update the capacity label and re-create the reagent list
  51. /// </summary>
  52. public void UpdateReagents()
  53. {
  54. ReagentList.DisposeAllChildren();
  55. if (_selectedSolution == null || _solutions == null)
  56. return;
  57. if (!_solutions.TryGetValue(_selectedSolution, out var solutionId) ||
  58. !_entityManager.TryGetComponent(solutionId, out SolutionComponent? solutionComp))
  59. return;
  60. var solution = solutionComp.Solution;
  61. UpdateVolumeBox(solution);
  62. UpdateThermalBox(solution);
  63. foreach (var reagent in solution)
  64. {
  65. AddReagentEntry(reagent);
  66. }
  67. }
  68. /// <summary>
  69. /// Updates the entry displaying the current and maximum volume of the selected solution.
  70. /// </summary>
  71. /// <param name="solution">The selected solution.</param>
  72. private void UpdateVolumeBox(Solution solution)
  73. {
  74. VolumeBox.DisposeAllChildren();
  75. var volumeLabel = new Label();
  76. volumeLabel.HorizontalExpand = true;
  77. volumeLabel.Margin = new Thickness(0, 4);
  78. volumeLabel.Text = Loc.GetString("admin-solutions-window-volume-label",
  79. ("currentVolume", solution.Volume),
  80. ("maxVolume", solution.MaxVolume));
  81. var capacityBox = new BoxContainer();
  82. capacityBox.Orientation = BoxContainer.LayoutOrientation.Horizontal;
  83. capacityBox.HorizontalExpand = true;
  84. capacityBox.Margin = new Thickness(0, 4);
  85. var capacityLabel = new Label();
  86. capacityLabel.HorizontalExpand = true;
  87. capacityLabel.Margin = new Thickness(0, 1);
  88. capacityLabel.Text = Loc.GetString("admin-solutions-window-capacity-label");
  89. var capacitySpin = new FloatSpinBox(1, 2);
  90. capacitySpin.HorizontalExpand = true;
  91. capacitySpin.Margin = new Thickness(0, 1);
  92. capacitySpin.Value = (float) solution.MaxVolume;
  93. capacitySpin.OnValueChanged += SetCapacity;
  94. capacityBox.AddChild(capacityLabel);
  95. capacityBox.AddChild(capacitySpin);
  96. VolumeBox.AddChild(volumeLabel);
  97. VolumeBox.AddChild(capacityBox);
  98. }
  99. /// <summary>
  100. /// Updates the entry displaying the current specific heat, heat capacity, temperature, and thermal energy
  101. /// of the selected solution.
  102. /// </summary>
  103. /// <param name="solution">The selected solution.</param>
  104. private void UpdateThermalBox(Solution solution)
  105. {
  106. ThermalBox.DisposeAllChildren();
  107. var heatCap = solution.GetHeatCapacity(null);
  108. var specificHeatLabel = new Label();
  109. specificHeatLabel.HorizontalExpand = true;
  110. specificHeatLabel.Margin = new Thickness(0, 1);
  111. specificHeatLabel.Text = Loc.GetString("admin-solutions-window-specific-heat-label", ("specificHeat", heatCap.ToString("G3")));
  112. var heatCapacityLabel = new Label();
  113. heatCapacityLabel.HorizontalExpand = true;
  114. heatCapacityLabel.Margin = new Thickness(0, 1);
  115. heatCapacityLabel.Text = Loc.GetString("admin-solutions-window-heat-capacity-label", ("heatCapacity", (heatCap/solution.Volume.Float()).ToString("G3")));
  116. // Temperature entry:
  117. var temperatureBox = new BoxContainer();
  118. temperatureBox.Orientation = BoxContainer.LayoutOrientation.Horizontal;
  119. temperatureBox.HorizontalExpand = true;
  120. temperatureBox.Margin = new Thickness(0, 1);
  121. var temperatureLabel = new Label();
  122. temperatureLabel.HorizontalExpand = true;
  123. temperatureLabel.Margin = new Thickness(0, 1);
  124. temperatureLabel.Text = Loc.GetString("admin-solutions-window-temperature-label");
  125. var temperatureSpin = new FloatSpinBox(1, 2);
  126. temperatureSpin.HorizontalExpand = true;
  127. temperatureSpin.Margin = new Thickness(0, 1);
  128. temperatureSpin.Value = solution.Temperature;
  129. temperatureSpin.OnValueChanged += SetTemperature;
  130. temperatureBox.AddChild(temperatureLabel);
  131. temperatureBox.AddChild(temperatureSpin);
  132. // Thermal energy entry:
  133. var thermalEnergyBox = new BoxContainer();
  134. thermalEnergyBox.Orientation = BoxContainer.LayoutOrientation.Horizontal;
  135. thermalEnergyBox.HorizontalExpand = true;
  136. thermalEnergyBox.Margin = new Thickness(0, 1);
  137. var thermalEnergyLabel = new Label();
  138. thermalEnergyLabel.HorizontalExpand = true;
  139. thermalEnergyLabel.Margin = new Thickness(0, 1);
  140. thermalEnergyLabel.Text = Loc.GetString("admin-solutions-window-thermal-energy-label");
  141. var thermalEnergySpin = new FloatSpinBox(1, 2);
  142. thermalEnergySpin.HorizontalExpand = true;
  143. thermalEnergySpin.Margin = new Thickness(0, 1);
  144. thermalEnergySpin.Value = solution.Temperature * heatCap;
  145. thermalEnergySpin.OnValueChanged += SetThermalEnergy;
  146. thermalEnergyBox.AddChild(thermalEnergyLabel);
  147. thermalEnergyBox.AddChild(thermalEnergySpin);
  148. ThermalBox.AddChild(specificHeatLabel);
  149. ThermalBox.AddChild(heatCapacityLabel);
  150. ThermalBox.AddChild(temperatureBox);
  151. ThermalBox.AddChild(thermalEnergyBox);
  152. }
  153. /// <summary>
  154. /// Add a single reagent entry to the list
  155. /// </summary>
  156. private void AddReagentEntry(ReagentQuantity reagentQuantity)
  157. {
  158. var box = new BoxContainer();
  159. var spin = new FloatSpinBox(1, 2);
  160. spin.Value = reagentQuantity.Quantity.Float();
  161. spin.OnValueChanged += (args) => SetReagent(args, reagentQuantity.Reagent.Prototype);
  162. spin.HorizontalExpand = true;
  163. box.AddChild(new Label() { Text = reagentQuantity.Reagent.Prototype , HorizontalExpand = true});
  164. box.AddChild(spin);
  165. ReagentList.AddChild(box);
  166. }
  167. /// <summary>
  168. /// Execute a command to modify the reagents in the solution.
  169. /// </summary>
  170. private void SetReagent(FloatSpinBox.FloatSpinBoxEventArgs args, string prototype)
  171. {
  172. if (_solutions == null || _selectedSolution == null ||
  173. !_solutions.TryGetValue(_selectedSolution, out var solutionId) ||
  174. !_entityManager.TryGetComponent(solutionId, out SolutionComponent? solutionComp))
  175. return;
  176. var solution = solutionComp.Solution;
  177. var current = solution.GetTotalPrototypeQuantity(prototype);
  178. var delta = args.Value - current.Float();
  179. if (MathF.Abs(delta) < 0.01)
  180. return;
  181. var command = $"addreagent {_target} {_selectedSolution} {prototype} {delta}";
  182. _consoleHost.ExecuteCommand(command);
  183. }
  184. private void SetCapacity(FloatSpinBox.FloatSpinBoxEventArgs args)
  185. {
  186. if (_solutions == null || _selectedSolution == null)
  187. return;
  188. var command = $"setsolutioncapacity {_target} {_selectedSolution} {args.Value}";
  189. _consoleHost.ExecuteCommand(command);
  190. }
  191. /// <summary>
  192. /// Sets the temperature of the selected solution to a value.
  193. /// </summary>
  194. /// <param name="args">An argument struct containing the value to set the temperature to.</param>
  195. private void SetTemperature(FloatSpinBox.FloatSpinBoxEventArgs args)
  196. {
  197. if (_solutions == null || _selectedSolution == null)
  198. return;
  199. var command = $"setsolutiontemperature {_target} {_selectedSolution} {args.Value}";
  200. _consoleHost.ExecuteCommand(command);
  201. }
  202. /// <summary>
  203. /// Sets the thermal energy of the selected solution to a value.
  204. /// </summary>
  205. /// <param name="args">An argument struct containing the value to set the thermal energy to.</param>
  206. private void SetThermalEnergy(FloatSpinBox.FloatSpinBoxEventArgs args)
  207. {
  208. if (_solutions == null || _selectedSolution == null)
  209. return;
  210. var command = $"setsolutionthermalenergy {_target} {_selectedSolution} {args.Value}";
  211. _consoleHost.ExecuteCommand(command);
  212. }
  213. /// <summary>
  214. /// Open a new window that has options to add new reagents to the solution.
  215. /// </summary>
  216. private void OpenAddReagentWindow(BaseButton.ButtonEventArgs obj)
  217. {
  218. if (string.IsNullOrEmpty(_selectedSolution))
  219. return;
  220. _addReagentWindow?.Close();
  221. _addReagentWindow?.Dispose();
  222. _addReagentWindow = new AddReagentWindow(_target, _selectedSolution);
  223. _addReagentWindow.OpenCentered();
  224. }
  225. /// <summary>
  226. /// When a new solution is selected, set _selectedSolution and update the reagent list.
  227. /// </summary>
  228. private void SolutionSelected(OptionButton.ItemSelectedEventArgs args)
  229. {
  230. SolutionOption.SelectId(args.Id);
  231. _selectedSolution = (string?) SolutionOption.SelectedMetadata;
  232. _addReagentWindow?.UpdateSolution(_selectedSolution);
  233. UpdateReagents();
  234. }
  235. /// <summary>
  236. /// Update the solution options.
  237. /// </summary>
  238. public void UpdateSolutions(List<(string, NetEntity)>? solutions)
  239. {
  240. SolutionOption.Clear();
  241. if (solutions is { Count: > 0 })
  242. {
  243. if (_solutions is { Count: > 0 })
  244. _solutions.Clear();
  245. else
  246. _solutions = new(solutions.Count);
  247. foreach (var (name, netSolution) in solutions)
  248. {
  249. if (_entityManager.TryGetEntity(netSolution, out var solution))
  250. _solutions.Add(name, solution.Value);
  251. }
  252. }
  253. else
  254. _solutions = null;
  255. if (_solutions == null)
  256. return;
  257. int i = 0;
  258. int selectedIndex = 0; // Default to the first solution if none are found.
  259. foreach (var (name, _) in _solutions)
  260. {
  261. SolutionOption.AddItem(name, i);
  262. SolutionOption.SetItemMetadata(i, name);
  263. if (name == _selectedSolution)
  264. selectedIndex = i;
  265. i++;
  266. }
  267. if (SolutionOption.ItemCount == 0)
  268. {
  269. // No applicable solutions
  270. Close();
  271. Dispose();
  272. return;
  273. }
  274. SolutionOption.Select(selectedIndex);
  275. _selectedSolution = (string?) SolutionOption.SelectedMetadata;
  276. }
  277. protected override void FrameUpdate(FrameEventArgs args)
  278. {
  279. // TODO: THIS IS FUCKING TERRIBLE.
  280. // Ok so the problem is that this shouldn't be via an EUI at all. Why?
  281. // The EUI update notification comes in *before* the game state it updates from.
  282. // So the UI doesn't update properly. Heck.
  283. // I didn't wanna completely rewrite this thing to work properly so instead you get terrible hacks.
  284. if (_nextState != null && _timing.LastRealTick >= _nextState.Tick)
  285. {
  286. SetTargetEntity(_nextState.Target);
  287. UpdateSolutions(_nextState.Solutions);
  288. UpdateReagents();
  289. _nextState = null;
  290. }
  291. }
  292. public void SetState(EditSolutionsEuiState state)
  293. {
  294. _nextState = state;
  295. }
  296. }
  297. }