using Content.Client.Administration.Managers; using Content.Shared.Administration; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Robust.Client.AutoGenerated; using Robust.Client.Console; using Robust.Client.Timing; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Timing; namespace Content.Client.Administration.UI.ManageSolutions { /// /// A simple window that displays solutions and their contained reagents. Allows you to edit the reagent quantities and add new reagents. /// [GenerateTypedNameReferences] public sealed partial class EditSolutionsWindow : DefaultWindow { [Dependency] private readonly IClientConsoleHost _consoleHost = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IClientGameTiming _timing = default!; [Dependency] private readonly IClientAdminManager _admin = default!; private NetEntity _target = NetEntity.Invalid; private string? _selectedSolution; private AddReagentWindow? _addReagentWindow; private Dictionary? _solutions; private EditSolutionsEuiState? _nextState; public EditSolutionsWindow() { IoCManager.InjectDependencies(this); RobustXamlLoader.Load(this); SolutionOption.OnItemSelected += SolutionSelected; AddButton.OnPressed += OpenAddReagentWindow; VVButton.OnPressed += OpenVVWindow; SolutionButton.OnPressed += OpenSolutionWindow; VVButton.Disabled = !_admin.CanViewVar(); SolutionButton.Disabled = !_admin.CanViewVar(); } public override void Close() { base.Close(); _addReagentWindow?.Close(); _addReagentWindow?.Dispose(); } public void SetTargetEntity(NetEntity target) { _target = target; var uid = _entityManager.GetEntity(target); var targetName = _entityManager.EntityExists(uid) ? _entityManager.GetComponent(uid).EntityName : string.Empty; Title = Loc.GetString("admin-solutions-window-title", ("targetName", targetName)); } /// /// Update the capacity label and re-create the reagent list /// public void UpdateReagents() { ReagentList.RemoveAllChildren(); if (_selectedSolution == null || _solutions == null) return; if (!_solutions.TryGetValue(_selectedSolution, out var solutionId) || !_entityManager.TryGetComponent(solutionId, out SolutionComponent? solutionComp)) return; var solution = solutionComp.Solution; UpdateVolumeBox(solution); UpdateThermalBox(solution); foreach (var reagent in solution) { AddReagentEntry(reagent); } } /// /// Updates the entry displaying the current and maximum volume of the selected solution. /// /// The selected solution. private void UpdateVolumeBox(Solution solution) { VolumeBox.RemoveAllChildren(); var volumeLabel = new Label(); volumeLabel.HorizontalExpand = true; volumeLabel.Margin = new Thickness(0, 4); volumeLabel.Text = Loc.GetString("admin-solutions-window-volume-label", ("currentVolume", solution.Volume), ("maxVolume", solution.MaxVolume)); var capacityBox = new BoxContainer(); capacityBox.Orientation = BoxContainer.LayoutOrientation.Horizontal; capacityBox.HorizontalExpand = true; capacityBox.Margin = new Thickness(0, 4); var capacityLabel = new Label(); capacityLabel.HorizontalExpand = true; capacityLabel.Margin = new Thickness(0, 1); capacityLabel.Text = Loc.GetString("admin-solutions-window-capacity-label"); var capacitySpin = new FloatSpinBox(1, 2); capacitySpin.HorizontalExpand = true; capacitySpin.Margin = new Thickness(0, 1); capacitySpin.Value = (float) solution.MaxVolume; capacitySpin.OnValueChanged += SetCapacity; capacityBox.AddChild(capacityLabel); capacityBox.AddChild(capacitySpin); VolumeBox.AddChild(volumeLabel); VolumeBox.AddChild(capacityBox); } /// /// Updates the entry displaying the current specific heat, heat capacity, temperature, and thermal energy /// of the selected solution. /// /// The selected solution. private void UpdateThermalBox(Solution solution) { ThermalBox.RemoveAllChildren(); var heatCap = solution.GetHeatCapacity(null); var specificHeatLabel = new Label(); specificHeatLabel.HorizontalExpand = true; specificHeatLabel.Margin = new Thickness(0, 1); specificHeatLabel.Text = Loc.GetString("admin-solutions-window-specific-heat-label", ("specificHeat", heatCap.ToString("G3"))); var heatCapacityLabel = new Label(); heatCapacityLabel.HorizontalExpand = true; heatCapacityLabel.Margin = new Thickness(0, 1); heatCapacityLabel.Text = Loc.GetString("admin-solutions-window-heat-capacity-label", ("heatCapacity", (heatCap/solution.Volume.Float()).ToString("G3"))); // Temperature entry: var temperatureBox = new BoxContainer(); temperatureBox.Orientation = BoxContainer.LayoutOrientation.Horizontal; temperatureBox.HorizontalExpand = true; temperatureBox.Margin = new Thickness(0, 1); var temperatureLabel = new Label(); temperatureLabel.HorizontalExpand = true; temperatureLabel.Margin = new Thickness(0, 1); temperatureLabel.Text = Loc.GetString("admin-solutions-window-temperature-label"); var temperatureSpin = new FloatSpinBox(1, 2); temperatureSpin.HorizontalExpand = true; temperatureSpin.Margin = new Thickness(0, 1); temperatureSpin.Value = solution.Temperature; temperatureSpin.OnValueChanged += SetTemperature; temperatureBox.AddChild(temperatureLabel); temperatureBox.AddChild(temperatureSpin); // Thermal energy entry: var thermalEnergyBox = new BoxContainer(); thermalEnergyBox.Orientation = BoxContainer.LayoutOrientation.Horizontal; thermalEnergyBox.HorizontalExpand = true; thermalEnergyBox.Margin = new Thickness(0, 1); var thermalEnergyLabel = new Label(); thermalEnergyLabel.HorizontalExpand = true; thermalEnergyLabel.Margin = new Thickness(0, 1); thermalEnergyLabel.Text = Loc.GetString("admin-solutions-window-thermal-energy-label"); var thermalEnergySpin = new FloatSpinBox(1, 2); thermalEnergySpin.HorizontalExpand = true; thermalEnergySpin.Margin = new Thickness(0, 1); thermalEnergySpin.Value = solution.Temperature * heatCap; thermalEnergySpin.OnValueChanged += SetThermalEnergy; thermalEnergyBox.AddChild(thermalEnergyLabel); thermalEnergyBox.AddChild(thermalEnergySpin); ThermalBox.AddChild(specificHeatLabel); ThermalBox.AddChild(heatCapacityLabel); ThermalBox.AddChild(temperatureBox); ThermalBox.AddChild(thermalEnergyBox); } /// /// Add a single reagent entry to the list /// private void AddReagentEntry(ReagentQuantity reagentQuantity) { var box = new BoxContainer(); var spin = new FloatSpinBox(1, 2); spin.Value = reagentQuantity.Quantity.Float(); spin.OnValueChanged += (args) => SetReagent(args, reagentQuantity.Reagent.Prototype); spin.HorizontalExpand = true; box.AddChild(new Label() { Text = reagentQuantity.Reagent.Prototype , HorizontalExpand = true}); box.AddChild(spin); ReagentList.AddChild(box); } /// /// Execute a command to modify the reagents in the solution. /// private void SetReagent(FloatSpinBox.FloatSpinBoxEventArgs args, string prototype) { if (_solutions == null || _selectedSolution == null || !_solutions.TryGetValue(_selectedSolution, out var solutionId) || !_entityManager.TryGetComponent(solutionId, out SolutionComponent? solutionComp)) return; var solution = solutionComp.Solution; var current = solution.GetTotalPrototypeQuantity(prototype); var delta = args.Value - current.Float(); if (MathF.Abs(delta) < 0.01) return; var command = $"addreagent {_target} {_selectedSolution} {prototype} {delta}"; _consoleHost.ExecuteCommand(command); } private void SetCapacity(FloatSpinBox.FloatSpinBoxEventArgs args) { if (_solutions == null || _selectedSolution == null) return; var command = $"setsolutioncapacity {_target} {_selectedSolution} {args.Value}"; _consoleHost.ExecuteCommand(command); } /// /// Sets the temperature of the selected solution to a value. /// /// An argument struct containing the value to set the temperature to. private void SetTemperature(FloatSpinBox.FloatSpinBoxEventArgs args) { if (_solutions == null || _selectedSolution == null) return; var command = $"setsolutiontemperature {_target} {_selectedSolution} {args.Value}"; _consoleHost.ExecuteCommand(command); } /// /// Sets the thermal energy of the selected solution to a value. /// /// An argument struct containing the value to set the thermal energy to. private void SetThermalEnergy(FloatSpinBox.FloatSpinBoxEventArgs args) { if (_solutions == null || _selectedSolution == null) return; var command = $"setsolutionthermalenergy {_target} {_selectedSolution} {args.Value}"; _consoleHost.ExecuteCommand(command); } /// /// Open a new window that has options to add new reagents to the solution. /// private void OpenAddReagentWindow(BaseButton.ButtonEventArgs obj) { if (string.IsNullOrEmpty(_selectedSolution)) return; _addReagentWindow?.Close(); _addReagentWindow?.Dispose(); _addReagentWindow = new AddReagentWindow(_target, _selectedSolution); _addReagentWindow.OpenCentered(); } /// /// Open the corresponding solution entity in a ViewVariables window. /// private void OpenVVWindow(BaseButton.ButtonEventArgs obj) { if (_solutions == null || _selectedSolution == null || !_solutions.TryGetValue(_selectedSolution, out var uid) || !_entityManager.TryGetNetEntity(uid, out var netEntity)) return; _consoleHost.ExecuteCommand($"vv {netEntity}"); } /// /// Open the corresponding Solution instance in a ViewVariables window. /// private void OpenSolutionWindow(BaseButton.ButtonEventArgs obj) { if (_solutions == null || _selectedSolution == null || !_solutions.TryGetValue(_selectedSolution, out var uid) || !_entityManager.TryGetNetEntity(uid, out var netEntity)) return; _consoleHost.ExecuteCommand($"vv /entity/{netEntity}/Solution/Solution"); } /// /// When a new solution is selected, set _selectedSolution and update the reagent list. /// private void SolutionSelected(OptionButton.ItemSelectedEventArgs args) { SolutionOption.SelectId(args.Id); _selectedSolution = (string?) SolutionOption.SelectedMetadata; _addReagentWindow?.UpdateSolution(_selectedSolution); UpdateReagents(); } /// /// Update the solution options. /// public void UpdateSolutions(List<(string, NetEntity)>? solutions) { SolutionOption.Clear(); if (solutions is { Count: > 0 }) { if (_solutions is { Count: > 0 }) _solutions.Clear(); else _solutions = new(solutions.Count); foreach (var (name, netSolution) in solutions) { if (_entityManager.TryGetEntity(netSolution, out var solution)) _solutions.Add(name, solution.Value); } } else _solutions = null; if (_solutions == null) return; int i = 0; int selectedIndex = 0; // Default to the first solution if none are found. foreach (var (name, _) in _solutions) { SolutionOption.AddItem(name, i); SolutionOption.SetItemMetadata(i, name); if (name == _selectedSolution) selectedIndex = i; i++; } if (SolutionOption.ItemCount == 0) { // No applicable solutions Close(); Dispose(); return; } SolutionOption.Select(selectedIndex); _selectedSolution = (string?) SolutionOption.SelectedMetadata; } protected override void FrameUpdate(FrameEventArgs args) { // TODO: THIS IS FUCKING TERRIBLE. // Ok so the problem is that this shouldn't be via an EUI at all. Why? // The EUI update notification comes in *before* the game state it updates from. // So the UI doesn't update properly. Heck. // I didn't wanna completely rewrite this thing to work properly so instead you get terrible hacks. if (_nextState != null && _timing.LastRealTick >= _nextState.Tick) { SetTargetEntity(_nextState.Target); UpdateSolutions(_nextState.Solutions); UpdateReagents(); _nextState = null; } } public void SetState(EditSolutionsEuiState state) { _nextState = state; } } }