diff --git a/src/auto-evo/simulation/SimulationCache.cs b/src/auto-evo/simulation/SimulationCache.cs index e3f386bfde7..c67c2c2c3e5 100644 --- a/src/auto-evo/simulation/SimulationCache.cs +++ b/src/auto-evo/simulation/SimulationCache.cs @@ -82,10 +82,12 @@ public EnergyBalanceInfo GetEnergyBalanceForSpecies(MicrobeSpecies species, IBio var maximumMovementDirection = MicrobeInternalCalculations.MaximumSpeedDirection(species.Organelles); + cached = new EnergyBalanceInfo(); + // Auto-evo uses the average values of compound during the course of a simulated day - cached = ProcessSystem.ComputeEnergyBalance(species.Organelles, biomeConditions, species.MembraneType, - maximumMovementDirection, true, species.PlayerSpecies, worldSettings, CompoundAmountType.Average, false, - this); + ProcessSystem.ComputeEnergyBalance(species.Organelles, biomeConditions, species.MembraneType, + maximumMovementDirection, true, species.PlayerSpecies, worldSettings, CompoundAmountType.Average, this, + cached); cachedEnergyBalances.Add(key, cached); return cached; diff --git a/src/general/base_stage/HexEditorComponentBase.cs b/src/general/base_stage/HexEditorComponentBase.cs index 19030792410..256c966bbc5 100644 --- a/src/general/base_stage/HexEditorComponentBase.cs +++ b/src/general/base_stage/HexEditorComponentBase.cs @@ -706,6 +706,27 @@ public void UpdateArrow(bool animateMovement = true) } } + protected void UpdateVisualLightLevel(float dayLightFraction, Patch currentPatch) + { + var maxLightLevel = currentPatch.Biome.GetCompound(Compound.Sunlight, CompoundAmountType.Biome).Ambient; + var templateMaxLightLevel = + currentPatch.GetCompoundAmountForDisplay(Compound.Sunlight, CompoundAmountType.Template); + + // Currently, patches whose templates have zero sunlight can be given non-zero sunlight as an instance. But + // nighttime shaders haven't been created for these patches (specifically the sea floor) so for now we can't + // reduce light level in such patches without things looking bad. So we have to check the template light level + // is non-zero too. + if (maxLightLevel > 0.0f && templateMaxLightLevel > 0.0f) + { + camera!.LightLevel = dayLightFraction; + } + else + { + // Don't change lighting for patches without day/night effects + camera!.LightLevel = 1.0f; + } + } + protected MeshInstance3D CreateEditorHex() { var hex = (MeshInstance3D)hexScene.Instantiate(); diff --git a/src/microbe_stage/MicrobeInternalCalculations.cs b/src/microbe_stage/MicrobeInternalCalculations.cs index 870d8c36cac..2aee84a800e 100644 --- a/src/microbe_stage/MicrobeInternalCalculations.cs +++ b/src/microbe_stage/MicrobeInternalCalculations.cs @@ -54,17 +54,20 @@ public static float GetTotalNominalCapacity(IEnumerable organ public static Dictionary GetTotalSpecificCapacity(IReadOnlyList organelles, out float nominalCapacity) { - var totalNominalCap = 0.0f; + var totalNominalCap = GetTotalNominalCapacity(organelles); + nominalCapacity = totalNominalCap; - int count = organelles.Count; + var capacities = new Dictionary(); - for (int i = 0; i < count; ++i) - { - var organelle = organelles[i]; - totalNominalCap += GetNominalCapacityForOrganelle(organelle.Definition, organelle.Upgrades); - } + AddSpecificCapacity(organelles, capacities); - var capacities = new Dictionary(); + return capacities; + } + + public static void AddSpecificCapacity(IReadOnlyList organelles, + Dictionary capacities) + { + var count = organelles.Count; // Update the variant of this logic in UpdateSpecificCapacities if changes are made for (int i = 0; i < count; ++i) @@ -77,23 +80,15 @@ public static Dictionary GetTotalSpecificCapacity(IReadOnlyList continue; // If this is updated the code in CompoundBag.AddSpecificCapacityForCompound must also be updated - if (capacities.TryGetValue(specificCapacity.Compound, out var currentCapacity)) - { - capacities[specificCapacity.Compound] = currentCapacity + specificCapacity.Capacity; - } - else - { - capacities.Add(specificCapacity.Compound, specificCapacity.Capacity + totalNominalCap); - } + capacities.TryGetValue(specificCapacity.Compound, out var currentCapacity); + capacities[specificCapacity.Compound] = currentCapacity + specificCapacity.Capacity; } - - nominalCapacity = totalNominalCap; - return capacities; } /// - /// Variant of to update spawned microbe stats. The used - /// must already have the correct nominal capacity set for this to work correctly. + /// Variant of to update + /// spawned microbe stats. The used must already have the correct nominal capacity set + /// for this to work correctly. /// /// Target compound bag to set info in (this doesn't update nominal capacity) /// Organelles to find specific capacity from @@ -467,15 +462,21 @@ public static void GiveNearNightInitialCompoundBuff(CompoundBag compoundReceiver IReadOnlyList organelles, MembraneType membraneType, bool moving, bool playerSpecies, BiomeConditions biomeConditions, WorldGenerationSettings worldSettings) { - var energyBalance = ProcessSystem.ComputeEnergyBalance(organelles, biomeConditions, membraneType, - moving, playerSpecies, worldSettings, CompoundAmountType.Biome, false); + var energyBalance = new EnergyBalanceInfo(); + + ProcessSystem.ComputeEnergyBalance(organelles, biomeConditions, membraneType, + moving, playerSpecies, worldSettings, CompoundAmountType.Biome, energyBalance); + + var compoundBalances = new Dictionary(); - var compoundBalances = ProcessSystem.ComputeCompoundBalanceAtEquilibrium(organelles, - biomeConditions, CompoundAmountType.Biome, energyBalance); + ProcessSystem.ComputeCompoundBalanceAtEquilibrium(organelles, + biomeConditions, CompoundAmountType.Biome, energyBalance, compoundBalances); // TODO: is it fine to use energy balance calculated with the biome numbers here? - var minimums = ProcessSystem.ComputeCompoundBalanceAtEquilibrium(organelles, - biomeConditions, CompoundAmountType.Minimum, energyBalance); + var minimums = new Dictionary(); + + ProcessSystem.ComputeCompoundBalanceAtEquilibrium(organelles, + biomeConditions, CompoundAmountType.Minimum, energyBalance, minimums); var cachedCapacities = GetTotalSpecificCapacity(organelles, out var cachedCapacity); @@ -558,16 +559,26 @@ public static (bool CanSurvive, Dictionary RequiredStorage) Cal { if (dayCompoundBalances == null) { - var energyBalance = ProcessSystem.ComputeEnergyBalance(organelles, biomeConditions, membraneType, - moving, playerSpecies, worldSettings, CompoundAmountType.Biome, false); + var energyBalance = new EnergyBalanceInfo(); + + ProcessSystem.ComputeEnergyBalance(organelles, biomeConditions, membraneType, + moving, playerSpecies, worldSettings, CompoundAmountType.Biome, energyBalance); + + dayCompoundBalances = new Dictionary(); - dayCompoundBalances = ProcessSystem.ComputeCompoundBalanceAtEquilibrium(organelles, - biomeConditions, CompoundAmountType.Biome, energyBalance); + ProcessSystem.ComputeCompoundBalanceAtEquilibrium(organelles, + biomeConditions, CompoundAmountType.Biome, energyBalance, dayCompoundBalances); } - var minimums = ProcessSystem.ComputeCompoundBalanceAtEquilibrium(organelles, - biomeConditions, CompoundAmountType.Minimum, ProcessSystem.ComputeEnergyBalance(organelles, biomeConditions, - membraneType, moving, playerSpecies, worldSettings, CompoundAmountType.Minimum, false)); + var energyBalanceAtMinimum = new EnergyBalanceInfo(); + + ProcessSystem.ComputeEnergyBalance(organelles, biomeConditions, membraneType, moving, playerSpecies, + worldSettings, CompoundAmountType.Minimum, energyBalanceAtMinimum); + + var minimums = new Dictionary(); + + ProcessSystem.ComputeCompoundBalanceAtEquilibrium(organelles, biomeConditions, CompoundAmountType.Minimum, + energyBalanceAtMinimum, minimums); var cachedCapacities = GetTotalSpecificCapacity(organelles, out var cachedCapacity); diff --git a/src/microbe_stage/MicrobeSpecies.cs b/src/microbe_stage/MicrobeSpecies.cs index 4bfff6bdbf1..2326ff38d0d 100644 --- a/src/microbe_stage/MicrobeSpecies.cs +++ b/src/microbe_stage/MicrobeSpecies.cs @@ -169,9 +169,11 @@ public override void UpdateInitialCompounds() // TODO: improve this depending on a hardcoded patch: https://github.com/Revolutionary-Games/Thrive/issues/5446 var biomeConditions = simulationParameters.GetBiome("speciesInitialCompoundsBiome").Conditions; + var compoundBalances = new Dictionary(); + // False is passed here until we can make the initial compounds patch specific - var compoundBalances = ProcessSystem.ComputeCompoundBalance(Organelles, - biomeConditions, CompoundAmountType.Biome, false); + ProcessSystem.ComputeCompoundBalance(Organelles, biomeConditions, CompoundAmountType.Biome, false, + compoundBalances); bool giveBonusGlucose = Organelles.Count <= Constants.FULL_INITIAL_GLUCOSE_SMALL_SIZE_LIMIT && IsBacteria; diff --git a/src/microbe_stage/OrganismStatisticsPanel.cs b/src/microbe_stage/OrganismStatisticsPanel.cs new file mode 100644 index 00000000000..9fceb2d2560 --- /dev/null +++ b/src/microbe_stage/OrganismStatisticsPanel.cs @@ -0,0 +1,606 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Godot; + +/// +/// Displays organism statistics calculated by an editor component +/// +public partial class OrganismStatisticsPanel : PanelContainer +{ + [Export] + public bool ShowHealthStat; + + [Export] + public bool ShowSizeStat; + + [Export] + public bool ShowStorageStat; + + [Export] + public bool ShowSpeedStat; + + [Export] + public bool ShowRotationSpeedStat; + + [Export] + public bool ShowDigestionSpeedStat; + + [Export] + public bool ShowDigestionEfficiencyStat; + +#pragma warning disable CA2213 + + [Export] + public LabelSettings ATPBalanceNormalText = null!; + + [Export] + public LabelSettings ATPBalanceNotEnoughText = null!; + +#pragma warning restore CA2213 + + private readonly StringBuilder atpToolTipTextBuilder = new(); + + private readonly ATPComparer atpComparer = new(); + +#pragma warning disable CA2213 + + [Export] + private CellStatsIndicator sizeLabel = null!; + + [Export] + private CellStatsIndicator speedLabel = null!; + + [Export] + private CellStatsIndicator rotationSpeedLabel = null!; + + [Export] + private CellStatsIndicator hpLabel = null!; + + [Export] + private CellStatsIndicator storageLabel = null!; + + [Export] + private CellStatsIndicator digestionSpeedLabel = null!; + + [Export] + private CellStatsIndicator digestionEfficiencyLabel = null!; + + [Export] + private Control basicStatsSeparator = null!; + + [Export] + private Control movementStatsSeparator = null!; + + [Export] + private Control digestionStatsSeparator = null!; + + [Export] + private Label generationLabel = null!; + + [Export] + private Control atpBalancePanel = null!; + + [Export] + private Label atpBalanceLabel = null!; + + [Export] + private Label atpProductionLabel = null!; + + [Export] + private Label atpConsumptionLabel = null!; + + [Export] + private SegmentedBar atpProductionBar = null!; + + [Export] + private SegmentedBar atpConsumptionBar = null!; + + [Export] + private CompoundBalanceDisplay compoundBalance = null!; + + [Export] + private CompoundStorageStatistics compoundStorageLastingTimes = null!; + + [Export] + private CustomRichTextLabel notEnoughStorageWarning = null!; + + [Export] + private CheckBox calculateBalancesAsIfDay = null!; + + [Export] + private CheckBox calculateBalancesWhenMoving = null!; + + [Export] + private Button processListButton = null!; + + [Export] + private ProcessList processList = null!; + + [Export] + private CustomWindow processListWindow = null!; + + [Export] + private LightConfigurationPanel lightConfigurationPanel = null!; + +#pragma warning restore CA2213 + + private LightLevelOption selectedLightLevelOption = LightLevelOption.Current; + + private EnergyBalanceInfo? energyBalanceInfo; + + [Signal] + public delegate void OnLightLevelChangedEventHandler(int option); + + [Signal] + public delegate void OnEnergyBalanceOptionsChangedEventHandler(); + + [Signal] + public delegate void OnResourceLimitingModeChangedEventHandler(); + + public TutorialState? TutorialState { get; set; } + + public BalanceDisplayType BalanceDisplayType => compoundBalance.CurrentDisplayType; + + public CompoundAmountType CompoundAmountType => calculateBalancesAsIfDay.ButtonPressed ? + CompoundAmountType.Biome : + CompoundAmountType.Current; + + public ResourceLimitingMode ResourceLimitingMode { get; set; } + + public bool CalculateBalancesWhenMoving => calculateBalancesWhenMoving.ButtonPressed; + + public override void _Ready() + { + base._Ready(); + + atpProductionBar.SelectedType = SegmentedBar.Type.ATP; + atpProductionBar.IsProduction = true; + atpConsumptionBar.SelectedType = SegmentedBar.Type.ATP; + + UpdateStatVisibility(); + } + + public void OnTranslationsChanged() + { + if (energyBalanceInfo != null) + { + UpdateEnergyBalance(energyBalanceInfo); + } + } + + public void UpdateStatVisibility() + { + hpLabel.Visible = ShowHealthStat; + sizeLabel.Visible = ShowSizeStat; + storageLabel.Visible = ShowStorageStat; + basicStatsSeparator.Visible = ShowHealthStat || ShowSizeStat || ShowStorageStat; + + speedLabel.Visible = ShowSpeedStat; + rotationSpeedLabel.Visible = ShowRotationSpeedStat; + movementStatsSeparator.Visible = ShowSpeedStat || ShowRotationSpeedStat; + + digestionSpeedLabel.Visible = ShowDigestionSpeedStat; + digestionEfficiencyLabel.Visible = ShowDigestionEfficiencyStat; + digestionStatsSeparator.Visible = ShowDigestionSpeedStat || ShowDigestionEfficiencyStat; + } + + public void SendObjectsToTutorials(TutorialState tutorial, MicrobeEditorTutorialGUI gui) + { + tutorial.AtpBalanceIntroduction.ATPBalanceBarControl = atpBalancePanel; + } + + public void UpdateEnergyBalance(EnergyBalanceInfo energyBalance) + { + energyBalanceInfo = energyBalance; + + if (energyBalance.FinalBalance > 0) + { + atpBalanceLabel.Text = Localization.Translate("ATP_PRODUCTION"); + atpBalanceLabel.LabelSettings = ATPBalanceNormalText; + } + else + { + atpBalanceLabel.Text = Localization.Translate("ATP_PRODUCTION") + " - " + + Localization.Translate("ATP_PRODUCTION_TOO_LOW"); + atpBalanceLabel.LabelSettings = ATPBalanceNotEnoughText; + } + + atpProductionLabel.Text = string.Format(CultureInfo.CurrentCulture, "{0:F1}", energyBalance.TotalProduction); + atpConsumptionLabel.Text = string.Format(CultureInfo.CurrentCulture, "{0:F1}", energyBalance.TotalConsumption); + + float maxValue = Math.Max(energyBalance.TotalConsumption, energyBalance.TotalProduction); + atpProductionBar.MaxValue = maxValue; + atpConsumptionBar.MaxValue = maxValue; + + atpProductionBar.UpdateAndMoveBars(SortBarData(energyBalance.Production)); + atpConsumptionBar.UpdateAndMoveBars(SortBarData(energyBalance.Consumption)); + + UpdateEnergyBalanceToolTips(energyBalance); + } + + public void UpdateEnergyBalanceToolTips(EnergyBalanceInfo energyBalance) + { + var simulationParameters = SimulationParameters.Instance; + + foreach (var subBar in atpProductionBar.SubBars) + { + var tooltip = ToolTipManager.Instance.GetToolTip(subBar.Name, "processesProduction"); + + if (tooltip == null) + throw new InvalidOperationException("Could not find process production tooltip"); + + subBar.RegisterToolTipForControl(tooltip, true); + + // Show required compounds for this process + Dictionary? requiredCompounds = null; + + if (energyBalance.ProductionRequiresCompounds != null) + { + energyBalance.ProductionRequiresCompounds.TryGetValue(subBar.Name, out requiredCompounds); + } + else + { + GD.PrintErr("Tracking for used compounds for energy not set up"); + } + + bool includedRequirement = false; + + if (requiredCompounds is { Count: > 0 }) + { + atpToolTipTextBuilder.Clear(); + + var translationFormat = Localization.Translate("ENERGY_BALANCE_REQUIRED_COMPOUND_LINE"); + + foreach (var requiredCompound in requiredCompounds) + { + var compound = simulationParameters.GetCompoundDefinition(requiredCompound.Key); + + // Don't show environmental compounds as the player doesn't need to worry about having those to be + // able to generate ATP + if (compound.IsEnvironmental) + continue; + + if (atpToolTipTextBuilder.Length > 0) + atpToolTipTextBuilder.Append('\n'); + + atpToolTipTextBuilder.Append(translationFormat.FormatSafe(compound.Name, + Math.Round(requiredCompound.Value, 2))); + } + + // As we don't check for environmental compounds before starting the loop, we might not find any valid + // data in the end in which case this needs to be skipped + if (atpToolTipTextBuilder.Length > 0) + { + tooltip.Description = Localization.Translate("ENERGY_BALANCE_TOOLTIP_PRODUCTION_WITH_REQUIREMENT") + .FormatSafe(SimulationParameters.Instance.GetOrganelleType(subBar.Name).Name, + Math.Round(energyBalance.Production[subBar.Name], 3), atpToolTipTextBuilder.ToString()); + includedRequirement = true; + } + } + + if (!includedRequirement) + { + // Normal display if didn't show with a requirement + tooltip.Description = Localization.Translate("ENERGY_BALANCE_TOOLTIP_PRODUCTION").FormatSafe( + SimulationParameters.Instance.GetOrganelleType(subBar.Name).Name, + Math.Round(energyBalance.Production[subBar.Name], 3)); + } + } + + foreach (var subBar in atpConsumptionBar.SubBars) + { + var tooltip = ToolTipManager.Instance.GetToolTip(subBar.Name, "processesConsumption"); + + if (tooltip == null) + throw new InvalidOperationException("Could not find process consumption tooltip"); + + subBar.RegisterToolTipForControl(tooltip, true); + + string displayName; + + switch (subBar.Name) + { + case "osmoregulation": + { + displayName = Localization.Translate("OSMOREGULATION"); + break; + } + + case "baseMovement": + { + displayName = Localization.Translate("BASE_MOVEMENT"); + break; + } + + default: + { + displayName = SimulationParameters.Instance.GetOrganelleType(subBar.Name).Name; + break; + } + } + + tooltip.Description = Localization.Translate("ENERGY_BALANCE_TOOLTIP_CONSUMPTION") + .FormatSafe(displayName, Math.Round(energyBalance.Consumption[subBar.Name])); + } + } + + public void UpdateCompoundBalances(Dictionary balances, float warningTime) + { + compoundBalance.UpdateBalances(balances, warningTime); + } + + public void UpdateCompoundLastingTimes(Dictionary normalBalance, + Dictionary nightBalance, float nominalStorage, + Dictionary specificStorages, float warningTime, float fillingUpTime) + { + compoundStorageLastingTimes.UpdateStorage(normalBalance, nightBalance, nominalStorage, specificStorages, + warningTime, fillingUpTime, notEnoughStorageWarning); + } + + public void RegisterTooltips() + { + digestionEfficiencyLabel.RegisterToolTipForControl("digestionEfficiencyDetails", "editor"); + storageLabel.RegisterToolTipForControl("storageDetails", "editor"); + } + + public void UpdateSize(int size) + { + sizeLabel.Value = size; + } + + public void UpdateGeneration(int generation) + { + generationLabel.Text = generation.ToString(CultureInfo.CurrentCulture); + } + + public void UpdateSpeed(float speed) + { + speedLabel.Value = (float)Math.Round(MicrobeInternalCalculations.SpeedToUserReadableNumber(speed), 1); + } + + public void UpdateRotationSpeed(float speed) + { + rotationSpeedLabel.Value = (float)Math.Round( + MicrobeInternalCalculations.RotationSpeedToUserReadableNumber(speed), 1); + } + + public void UpdateHitpoints(float hp) + { + hpLabel.Value = hp; + } + + public void UpdateStorage(Dictionary storage, float nominalStorage) + { + // Storage values can be as low as 0.25 so 2 decimals are needed + storageLabel.Value = MathF.Round(nominalStorage, 2); + + if (storage.Count == 0) + { + storageLabel.UnRegisterFirstToolTipForControl(); + return; + } + + var tooltip = ToolTipManager.Instance.GetToolTip("storageDetails", "editor"); + if (tooltip == null) + { + GD.PrintErr("Can't update storage tooltip"); + return; + } + + if (!storageLabel.IsToolTipRegistered(tooltip)) + storageLabel.RegisterToolTipForControl(tooltip, true); + + var description = new LocalizedStringBuilder(100); + + bool first = true; + + var simulationParameters = SimulationParameters.Instance; + + foreach (var entry in storage) + { + if (!first) + description.Append("\n"); + + first = false; + + description.Append(simulationParameters.GetCompoundDefinition(entry.Key).Name); + description.Append(": "); + description.Append(entry.Value); + } + + tooltip.Description = description.ToString(); + } + + public void UpdateTotalDigestionSpeed(float speed) + { + digestionSpeedLabel.Format = Localization.Translate("DIGESTION_SPEED_VALUE"); + digestionSpeedLabel.Value = (float)Math.Round(speed, 2); + } + + public void UpdateDigestionEfficiencies(Dictionary efficiencies) + { + if (efficiencies.Count == 1) + { + digestionEfficiencyLabel.Format = Localization.Translate("PERCENTAGE_VALUE"); + digestionEfficiencyLabel.Value = (float)Math.Round(efficiencies.First().Value * 100, 2); + } + else + { + digestionEfficiencyLabel.Format = Localization.Translate("MIXED_DOT_DOT_DOT"); + + // Set this to a value hero to fix the up/down arrow + // Using sum makes the arrow almost always go up, using average makes the arrow almost always point down... + // digestionEfficiencyLabel.Value = efficiencies.Select(e => e.Value).Average() * 100; + digestionEfficiencyLabel.Value = efficiencies.Select(e => e.Value).Sum() * 100; + } + + var description = new LocalizedStringBuilder(100); + + bool first = true; + + foreach (var enzyme in efficiencies) + { + if (!first) + description.Append("\n"); + + first = false; + + description.Append(enzyme.Key.Name); + description.Append(": "); + description.Append(new LocalizedString("PERCENTAGE_VALUE", (float)Math.Round(enzyme.Value * 100, 2))); + } + + var tooltip = ToolTipManager.Instance.GetToolTip("digestionEfficiencyDetails", "editor"); + if (tooltip != null) + { + tooltip.Description = description.ToString(); + } + else + { + GD.PrintErr("Can't update digestion efficiency tooltip"); + } + } + + public void UpdateProcessList(List processInfo) + { + processList.ProcessesToShow = processInfo; + } + + public void UpdateLightSelectionPanelVisibility(bool hasDayAndNight) + { + lightConfigurationPanel.Visible = hasDayAndNight; + + // When not in a patch with light, hide the useless always day selector + if (!hasDayAndNight) + { + calculateBalancesAsIfDay.ButtonPressed = false; + calculateBalancesAsIfDay.Visible = false; + } + else + { + calculateBalancesAsIfDay.Visible = true; + } + } + + public void ApplyLightLevelSelection() + { + calculateBalancesAsIfDay.Disabled = false; + + lightConfigurationPanel.ApplyLightLevelSelection(selectedLightLevelOption); + + // Show selected light level + switch (selectedLightLevelOption) + { + case LightLevelOption.Day: + { + calculateBalancesAsIfDay.ButtonPressed = true; + calculateBalancesAsIfDay.Disabled = true; + break; + } + + case LightLevelOption.Night: + { + calculateBalancesAsIfDay.ButtonPressed = false; + calculateBalancesAsIfDay.Disabled = true; + break; + } + + case LightLevelOption.Average: + { + calculateBalancesAsIfDay.ButtonPressed = false; + break; + } + + case LightLevelOption.Current: + { + break; + } + + default: + throw new Exception("Invalid light level option"); + } + + EmitSignal(SignalName.OnLightLevelChanged, (int)selectedLightLevelOption); + } + + private void OnCompoundBalanceTypeChanged(BalanceDisplayType newType) + { + _ = newType; + + EmitSignal(SignalName.OnEnergyBalanceOptionsChanged); + } + + private void OnBalanceShowOptionsChanged(bool pressed) + { + _ = pressed; + + EmitSignal(SignalName.OnEnergyBalanceOptionsChanged); + } + + private void SelectATPBalanceMode(int index) + { + ResourceLimitingMode = (ResourceLimitingMode)index; + + EmitSignal(SignalName.OnResourceLimitingModeChanged); + } + + private void OnProcessListButtonClicked() + { + processListWindow.Visible = !processListWindow.Visible; + } + + private void OnLightLevelButtonPressed(int option) + { + GUICommon.Instance.PlayButtonPressSound(); + + var selection = (LightLevelOption)option; + + selectedLightLevelOption = selection; + + ApplyLightLevelSelection(); + } + + private List> SortBarData(Dictionary bar) + { + return bar.OrderBy(i => i.Key, atpComparer).ToList(); + } + + private class ATPComparer : IComparer + { + /// + /// Compares ATP production / consumption items + /// + /// + /// + /// Only works if there aren't duplicate entries of osmoregulation or baseMovement. + /// + /// + public int Compare(string? stringA, string? stringB) + { + if (stringA == "osmoregulation") + { + return -1; + } + + if (stringB == "osmoregulation") + { + return 1; + } + + if (stringA == "baseMovement") + { + return -1; + } + + if (stringB == "baseMovement") + { + return 1; + } + + return string.Compare(stringA, stringB, StringComparison.InvariantCulture); + } + } +} diff --git a/src/microbe_stage/OrganismStatisticsPanel.tscn b/src/microbe_stage/OrganismStatisticsPanel.tscn new file mode 100644 index 00000000000..e74ff29cace --- /dev/null +++ b/src/microbe_stage/OrganismStatisticsPanel.tscn @@ -0,0 +1,288 @@ +[gd_scene load_steps=20 format=3 uid="uid://olpir0vkwcsi"] + +[ext_resource type="Theme" uid="uid://b4cx0o110g4b6" path="res://src/gui_common/thrive_theme.tres" id="1_ca4to"] +[ext_resource type="Script" path="res://src/microbe_stage/OrganismStatisticsPanel.cs" id="1_hd1ty"] +[ext_resource type="LabelSettings" uid="uid://w2g8aleycqpl" path="res://src/gui_common/fonts/Title-Regular-Bigger.tres" id="2_pxb83"] +[ext_resource type="PackedScene" uid="uid://c027xyogk08vx" path="res://src/microbe_stage/editor/CellStatsIndicator.tscn" id="3_mvl7o"] +[ext_resource type="LabelSettings" uid="uid://o0tip7etc0x2" path="res://src/gui_common/fonts/Body-Bold-Small-Red.tres" id="3_u6vqe"] +[ext_resource type="Texture2D" uid="uid://cq25ojg73mpr" path="res://assets/textures/gui/bevel/HealthIcon.png" id="4_6j8dv"] +[ext_resource type="Texture2D" uid="uid://b6s4ekosu7unk" path="res://assets/textures/gui/bevel/SizeIcon.png" id="5_wtbqs"] +[ext_resource type="Texture2D" uid="uid://bmu3av0d807dw" path="res://assets/textures/gui/bevel/StorageIcon.png" id="6_6daqi"] +[ext_resource type="Texture2D" uid="uid://c4w7v5wtq2d1b" path="res://assets/textures/gui/bevel/SpeedIcon.png" id="7_qfkhg"] +[ext_resource type="Texture2D" uid="uid://5ivblyrfrfc0" path="res://assets/textures/gui/bevel/RotationIcon.png" id="8_52j0g"] +[ext_resource type="Texture2D" uid="uid://ctdcp7ts77dhd" path="res://assets/textures/gui/bevel/DigestionSpeedIcon.png" id="9_yl0er"] +[ext_resource type="Texture2D" uid="uid://bqq2yi8f6l4lt" path="res://assets/textures/gui/bevel/DigestionIcon.png" id="10_l6acm"] +[ext_resource type="LabelSettings" uid="uid://chasppwihgufu" path="res://src/gui_common/fonts/Body-Bold-Small.tres" id="11_bfneb"] +[ext_resource type="LabelSettings" uid="uid://terwl7t76t4h" path="res://src/gui_common/fonts/Body-Regular-AlmostSmall.tres" id="12_mwh2k"] +[ext_resource type="PackedScene" path="res://src/gui_common/SegmentedBar.tscn" id="13_rekbr"] +[ext_resource type="LabelSettings" uid="uid://dvqx73nhtr0y2" path="res://src/gui_common/fonts/Body-Regular-Small.tres" id="14_0mgfc"] +[ext_resource type="PackedScene" path="res://src/gui_common/CustomRichTextLabel.tscn" id="15_ogl68"] +[ext_resource type="PackedScene" uid="uid://sxqf3o1pkl0n" path="res://src/microbe_stage/editor/CompoundBalanceDisplay.tscn" id="16_wlbdy"] +[ext_resource type="PackedScene" uid="uid://doveokjve1v3t" path="res://src/microbe_stage/editor/CompoundStorageStatistics.tscn" id="17_pca5e"] + +[node name="OrganismStatisticsPanel" type="PanelContainer" node_paths=PackedStringArray("sizeLabel", "speedLabel", "rotationSpeedLabel", "hpLabel", "storageLabel", "digestionSpeedLabel", "digestionEfficiencyLabel", "basicStatsSeparator", "movementStatsSeparator", "digestionStatsSeparator", "generationLabel", "atpBalancePanel", "atpBalanceLabel", "atpProductionLabel", "atpConsumptionLabel", "atpProductionBar", "atpConsumptionBar", "compoundBalance", "compoundStorageLastingTimes", "notEnoughStorageWarning", "calculateBalancesAsIfDay", "calculateBalancesWhenMoving", "processListButton")] +size_flags_vertical = 3 +mouse_filter = 1 +theme = ExtResource("1_ca4to") +script = ExtResource("1_hd1ty") +ATPBalanceNormalText = ExtResource("11_bfneb") +ATPBalanceNotEnoughText = ExtResource("3_u6vqe") +sizeLabel = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/Size") +speedLabel = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/Speed") +rotationSpeedLabel = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/Rotation") +hpLabel = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/Hp") +storageLabel = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/Storage") +digestionSpeedLabel = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/DigestionSpeed") +digestionEfficiencyLabel = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/DigestionEfficiency") +basicStatsSeparator = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/HSeparator") +movementStatsSeparator = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/HSeparator2") +digestionStatsSeparator = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/HSeparator3") +generationLabel = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/Generation/Value") +atpBalancePanel = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel") +atpBalanceLabel = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer/ATPBalanceTitle") +atpProductionLabel = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer/ATPBarContainer/HBoxContainer/Label") +atpConsumptionLabel = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer/ATPBarContainer/HBoxContainer2/Label") +atpProductionBar = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer/ATPBarContainer/HBoxContainer/ATPProductionBar") +atpConsumptionBar = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer/ATPBarContainer/HBoxContainer2/ATPConsumptionBar") +compoundBalance = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/CompoundBalanceDisplay") +compoundStorageLastingTimes = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/CompoundStorageStatistics") +notEnoughStorageWarning = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/StorageNotEnough") +calculateBalancesAsIfDay = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/NumbersAtDay") +calculateBalancesWhenMoving = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/NumbersWhenMoving") +processListButton = NodePath("VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ProcessListButton") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="Header" type="MarginContainer" parent="VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 10 + +[node name="Title" type="Label" parent="VBoxContainer/Header"] +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +text = "ORGANISM_STATISTICS" +label_settings = ExtResource("2_pxb83") +autowrap_mode = 3 + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer"] +layout_mode = 2 +mouse_filter = 1 + +[node name="Body" type="MarginContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/Body"] +layout_mode = 2 +follow_focus = true + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/Body/ScrollContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 3 +theme_override_constants/margin_top = 0 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/Body/ScrollContainer/MarginContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Hp" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_mvl7o")] +layout_mode = 2 +Icon = ExtResource("4_6j8dv") +Description = "HP_COLON" + +[node name="Size" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_mvl7o")] +layout_mode = 2 +Icon = ExtResource("5_wtbqs") +Description = "SIZE_COLON" + +[node name="Storage" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_mvl7o")] +layout_mode = 2 +Icon = ExtResource("6_6daqi") +Description = "STORAGE_COLON" + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Speed" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_mvl7o")] +layout_mode = 2 +tooltip_text = "CELL_STAT_SPEED_TOOLTIP" +Icon = ExtResource("7_qfkhg") +Description = "SPEED_COLON" + +[node name="Rotation" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_mvl7o")] +layout_mode = 2 +tooltip_text = "CELL_STAT_ROTATION_TOOLTIP" +Icon = ExtResource("8_52j0g") +Description = "ROTATION_COLON" + +[node name="HSeparator2" type="HSeparator" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="DigestionSpeed" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_mvl7o")] +layout_mode = 2 +Icon = ExtResource("9_yl0er") +Description = "DIGESTION_SPEED_COLON" + +[node name="DigestionEfficiency" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_mvl7o")] +layout_mode = 2 +Icon = ExtResource("10_l6acm") +Description = "DIGESTION_EFFICIENCY_COLON" + +[node name="HSeparator3" type="HSeparator" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Generation" type="HBoxContainer" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Description" type="Label" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/Generation"] +layout_mode = 2 +text = "GENERATION_COLON" +label_settings = ExtResource("11_bfneb") + +[node name="Value" type="Label" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/Generation"] +editor_description = "PLACEHOLDER" +layout_mode = 2 +text = "n/a" +label_settings = ExtResource("12_mwh2k") + +[node name="ATPBalancePanel" type="MarginContainer" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 3 +theme_override_constants/margin_top = 3 +theme_override_constants/margin_right = 3 +theme_override_constants/margin_bottom = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel"] +layout_mode = 2 +theme_override_constants/separation = 0 + +[node name="ATPBalanceTitle" type="Label" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer"] +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +size_flags_horizontal = 3 +tooltip_text = "ATP_BALANCE_TOOLTIP" +mouse_filter = 1 +text = "ATP_BALANCE" +label_settings = ExtResource("11_bfneb") +autowrap_mode = 3 + +[node name="Separator" type="Control" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer"] +custom_minimum_size = Vector2(0, 5) +layout_mode = 2 +mouse_filter = 2 + +[node name="ATPBarContainer" type="VBoxContainer" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer"] +layout_mode = 2 +alignment = 1 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer/ATPBarContainer"] +custom_minimum_size = Vector2(240, 0) +layout_mode = 2 + +[node name="ATPProductionBar" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer/ATPBarContainer/HBoxContainer" instance=ExtResource("13_rekbr")] +custom_minimum_size = Vector2(190, 30) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 + +[node name="Label" type="Label" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer/ATPBarContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "00000" +label_settings = ExtResource("14_0mgfc") + +[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer/ATPBarContainer"] +custom_minimum_size = Vector2(240, 0) +layout_mode = 2 + +[node name="ATPConsumptionBar" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer/ATPBarContainer/HBoxContainer2" instance=ExtResource("13_rekbr")] +custom_minimum_size = Vector2(190, 30) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 + +[node name="Label" type="Label" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer/ATPBarContainer/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "00000" +label_settings = ExtResource("14_0mgfc") + +[node name="Separator2" type="Control" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer"] +custom_minimum_size = Vector2(0, 5) +layout_mode = 2 +mouse_filter = 2 + +[node name="ATPBalanceMode" type="OptionButton" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer"] +layout_mode = 2 +text_overrun_behavior = 3 +clip_text = true +selected = 0 +fit_to_longest_item = false +item_count = 5 +popup/item_0/text = "ATP_BALANCE_WITH_ALL_COMPOUNDS" +popup/item_1/text = "ATP_BALANCE_WITHOUT_GLUCOSE" +popup/item_1/id = 1 +popup/item_2/text = "ATP_BALANCE_WITHOUT_IRON" +popup/item_2/id = 2 +popup/item_3/text = "ATP_BALANCE_WITHOUT_HYDROGEN_SULFIDE" +popup/item_3/id = 3 +popup/item_4/text = "ATP_BALANCE_WITHOUT_EXTERNAL_RESOURCES" +popup/item_4/id = 4 + +[node name="StorageNotEnough" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer" instance=ExtResource("15_ogl68")] +visible = false +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +theme_override_colors/default_color = Color(1, 0.301961, 0.301961, 1) +fit_content = true + +[node name="NumbersAtDay" type="CheckBox" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +tooltip_text = "BALANCE_DISPLAY_AT_DAY_ALWAYS_TOOLTIP" +theme_override_font_sizes/font_size = 12 +text = "BALANCE_DISPLAY_AT_DAY_ALWAYS" +text_overrun_behavior = 4 +clip_text = true + +[node name="NumbersWhenMoving" type="CheckBox" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +tooltip_text = "BALANCE_DISPLAY_WHILE_MOVING_TOOLTIP" +theme_override_font_sizes/font_size = 12 +button_pressed = true +text = "BALANCE_DISPLAY_WHILE_MOVING" +text_overrun_behavior = 4 +clip_text = true + +[node name="Separator2" type="Control" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(0, 10) +layout_mode = 2 +mouse_filter = 1 + +[node name="CompoundBalanceDisplay" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer" instance=ExtResource("16_wlbdy")] +layout_mode = 2 + +[node name="CompoundStorageStatistics" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer" instance=ExtResource("17_pca5e")] +layout_mode = 2 + +[node name="ProcessListButton" type="Button" parent="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 14 +text = "VIEW_CELL_PROCESSES" +text_overrun_behavior = 3 +clip_text = true + +[connection signal="item_selected" from="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ATPBalancePanel/VBoxContainer/ATPBalanceMode" to="." method="SelectATPBalanceMode"] +[connection signal="toggled" from="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/NumbersAtDay" to="." method="OnBalanceShowOptionsChanged"] +[connection signal="toggled" from="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/NumbersWhenMoving" to="." method="OnBalanceShowOptionsChanged"] +[connection signal="BalanceTypeChanged" from="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/CompoundBalanceDisplay" to="." method="OnCompoundBalanceTypeChanged"] +[connection signal="pressed" from="VBoxContainer/Body/ScrollContainer/MarginContainer/VBoxContainer/ProcessListButton" to="." method="OnProcessListButtonClicked"] diff --git a/src/microbe_stage/components/MicrobeColony.cs b/src/microbe_stage/components/MicrobeColony.cs index 62b8c6a39b0..013ef42d871 100644 --- a/src/microbe_stage/components/MicrobeColony.cs +++ b/src/microbe_stage/components/MicrobeColony.cs @@ -834,6 +834,7 @@ public static void CalculateRotationSpeed(this ref MicrobeColony colony) // shape.TestYRotationInertiaFactor() how to make this take the colony shape into account in rotation to // be more physically accurate + // When changing this method's logic also update the corresponding method in CellBodyPlanInternalCalculations float colonyRotation = MicrobeInternalCalculations .CalculateRotationSpeed(colony.Leader.Get().Organelles!.Organelles); diff --git a/src/microbe_stage/editor/CellEditorComponent.Callbacks.cs b/src/microbe_stage/editor/CellEditorComponent.Callbacks.cs index af1a50db432..63826758666 100644 --- a/src/microbe_stage/editor/CellEditorComponent.Callbacks.cs +++ b/src/microbe_stage/editor/CellEditorComponent.Callbacks.cs @@ -221,9 +221,10 @@ private void DoMembraneChangeAction(MembraneActionData data) // TODO: dynamic MP PR had this line: // OnMembraneChanged(); + organismStatisticsPanel.UpdateSpeed(CalculateSpeed()); + organismStatisticsPanel.UpdateHitpoints(CalculateHitpoints()); + UpdateMembraneButtons(Membrane.InternalName); - UpdateSpeed(CalculateSpeed()); - UpdateHitpoints(CalculateHitpoints()); CalculateEnergyAndCompoundBalance(editedMicrobeOrganelles.Organelles, Membrane); SetMembraneTooltips(Membrane); @@ -245,11 +246,12 @@ private void UndoMembraneChangeAction(MembraneActionData data) Membrane = data.OldMembrane; GD.Print("Changing membrane back to '", Membrane.InternalName, "'"); UpdateMembraneButtons(Membrane.InternalName); - UpdateSpeed(CalculateSpeed()); - UpdateHitpoints(CalculateHitpoints()); CalculateEnergyAndCompoundBalance(editedMicrobeOrganelles.Organelles, Membrane); SetMembraneTooltips(Membrane); + organismStatisticsPanel.UpdateSpeed(CalculateSpeed()); + organismStatisticsPanel.UpdateHitpoints(CalculateHitpoints()); + StartAutoEvoPrediction(); suggestionDirty = true; diff --git a/src/microbe_stage/editor/CellEditorComponent.GUI.cs b/src/microbe_stage/editor/CellEditorComponent.GUI.cs index 8a24f19c404..c464ba71a4c 100644 --- a/src/microbe_stage/editor/CellEditorComponent.GUI.cs +++ b/src/microbe_stage/editor/CellEditorComponent.GUI.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text; using System.Threading.Tasks; using Godot; using UnlockConstraints; @@ -20,8 +19,6 @@ public partial class CellEditorComponent { private readonly Dictionary createdGrowthOrderLabels = new(); - private StringBuilder atpToolTipTextBuilder = new(); - private bool inProgressSuggestionCheckRunning; [Signal] @@ -52,9 +49,9 @@ public void SendObjectsToTutorials(TutorialState tutorial, MicrobeEditorTutorial tutorial.AutoEvoPrediction.EditorAutoEvoPredictionPanel = autoEvoPredictionPanel; - tutorial.AtpBalanceIntroduction.ATPBalanceBarControl = atpBalancePanel; - gui.RightPanelScrollContainer = rightPanelScrollContainer; + + organismStatisticsPanel.SendObjectsToTutorials(tutorial, gui); } public override void OnActionBlockedWhileAnotherIsInProgress() @@ -78,8 +75,8 @@ protected override void RegisterTooltips() base.RegisterTooltips(); rigiditySlider.RegisterToolTipForControl("rigiditySlider", "editor"); - digestionEfficiencyLabel.RegisterToolTipForControl("digestionEfficiencyDetails", "editor"); - storageLabel.RegisterToolTipForControl("storageDetails", "editor"); + + organismStatisticsPanel.RegisterTooltips(); } protected override void OnTranslationsChanged() @@ -93,10 +90,9 @@ protected override void OnTranslationsChanged() UpdateMicrobePartSelections(); UpdateMutationPointsBar(); - UpdateDigestionEfficiencies(CalculateDigestionEfficiencies()); - UpdateTotalDigestionSpeed(CalculateTotalDigestionSpeed()); - - CalculateEnergyAndCompoundBalance(editedMicrobeOrganelles.Organelles, Membrane); + organismStatisticsPanel.OnTranslationsChanged(); + organismStatisticsPanel.UpdateDigestionEfficiencies(CalculateDigestionEfficiencies()); + organismStatisticsPanel.UpdateTotalDigestionSpeed(CalculateTotalDigestionSpeed()); UpdateOsmoregulationTooltips(); UpdateMPCost(); @@ -330,124 +326,6 @@ private void SetRigiditySliderTooltip(int rigidity) } } - private void UpdateSize(int size) - { - sizeLabel.Value = size; - } - - private void UpdateGeneration(int generation) - { - generationLabel.Text = generation.ToString(CultureInfo.CurrentCulture); - } - - private void UpdateSpeed(float speed) - { - speedLabel.Value = (float)Math.Round(MicrobeInternalCalculations.SpeedToUserReadableNumber(speed), 1); - } - - private void UpdateRotationSpeed(float speed) - { - rotationSpeedLabel.Value = (float)Math.Round( - MicrobeInternalCalculations.RotationSpeedToUserReadableNumber(speed), 1); - } - - private void UpdateHitpoints(float hp) - { - hpLabel.Value = hp; - } - - private void UpdateStorage(float nominalStorage, Dictionary storage) - { - // Storage values can be as low as 0.25 so 2 decimals are needed - storageLabel.Value = MathF.Round(nominalStorage, 2); - - if (storage.Count == 0) - { - storageLabel.UnRegisterFirstToolTipForControl(); - return; - } - - var tooltip = ToolTipManager.Instance.GetToolTip("storageDetails", "editor"); - if (tooltip == null) - { - GD.PrintErr("Can't update storage tooltip"); - return; - } - - if (!storageLabel.IsToolTipRegistered(tooltip)) - storageLabel.RegisterToolTipForControl(tooltip, true); - - var description = new LocalizedStringBuilder(100); - - bool first = true; - - var simulationParameters = SimulationParameters.Instance; - - foreach (var entry in storage) - { - if (!first) - description.Append("\n"); - - first = false; - - description.Append(simulationParameters.GetCompoundDefinition(entry.Key).Name); - description.Append(": "); - description.Append(entry.Value); - } - - tooltip.Description = description.ToString(); - } - - private void UpdateTotalDigestionSpeed(float speed) - { - digestionSpeedLabel.Format = Localization.Translate("DIGESTION_SPEED_VALUE"); - digestionSpeedLabel.Value = (float)Math.Round(speed, 2); - } - - private void UpdateDigestionEfficiencies(Dictionary efficiencies) - { - if (efficiencies.Count == 1) - { - digestionEfficiencyLabel.Format = Localization.Translate("PERCENTAGE_VALUE"); - digestionEfficiencyLabel.Value = (float)Math.Round(efficiencies.First().Value * 100, 2); - } - else - { - digestionEfficiencyLabel.Format = Localization.Translate("MIXED_DOT_DOT_DOT"); - - // Set this to a value hero to fix the up/down arrow - // Using sum makes the arrow almost always go up, using average makes the arrow almost always point down... - // digestionEfficiencyLabel.Value = efficiencies.Select(e => e.Value).Average() * 100; - digestionEfficiencyLabel.Value = efficiencies.Select(e => e.Value).Sum() * 100; - } - - var description = new LocalizedStringBuilder(100); - - bool first = true; - - foreach (var enzyme in efficiencies) - { - if (!first) - description.Append("\n"); - - first = false; - - description.Append(enzyme.Key.Name); - description.Append(": "); - description.Append(new LocalizedString("PERCENTAGE_VALUE", (float)Math.Round(enzyme.Value * 100, 2))); - } - - var tooltip = ToolTipManager.Instance.GetToolTip("digestionEfficiencyDetails", "editor"); - if (tooltip != null) - { - tooltip.Description = description.ToString(); - } - else - { - GD.PrintErr("Can't update digestion efficiency tooltip"); - } - } - /// /// Updates the organelle efficiencies in tooltips. /// @@ -662,23 +540,6 @@ private void UpdateMPCost() } } - private void UpdateLightSelectionPanelVisibility() - { - topPanel.Visible = Editor.CurrentGame.GameWorld.WorldSettings.DayNightCycleEnabled && - Editor.CurrentPatch.HasDayAndNight; - - // When not in a patch with light, hide the useless always day selector - if (!topPanel.Visible) - { - calculateBalancesAsIfDay.ButtonPressed = false; - calculateBalancesAsIfDay.Visible = false; - } - else - { - calculateBalancesAsIfDay.Visible = true; - } - } - private void UpdateCompoundBalances(Dictionary balances) { var warningTime = Editor.CurrentGame.GameWorld.LightCycle.DayLengthRealtimeSeconds * @@ -688,7 +549,7 @@ private void UpdateCompoundBalances(Dictionary balanc if (!Editor.CurrentGame.GameWorld.WorldSettings.DayNightCycleEnabled) warningTime = 10000000; - compoundBalance.UpdateBalances(balances, warningTime); + organismStatisticsPanel.UpdateCompoundBalances(balances, warningTime); } private void UpdateCompoundLastingTimes(Dictionary normalBalance, @@ -708,149 +569,8 @@ private void UpdateCompoundLastingTimes(Dictionary no fillingUpTime = warningTime; } - compoundStorageLastingTimes.UpdateStorage(normalBalance, nightBalance, nominalStorage, specificStorages, - warningTime, fillingUpTime, notEnoughStorageWarning); - } - - private void UpdateEnergyBalance(EnergyBalanceInfo energyBalance) - { - energyBalanceInfo = energyBalance; - - if (energyBalance.FinalBalance > 0) - { - atpBalanceLabel.Text = Localization.Translate("ATP_PRODUCTION"); - atpBalanceLabel.LabelSettings = ATPBalanceNormalText; - } - else - { - atpBalanceLabel.Text = Localization.Translate("ATP_PRODUCTION") + " - " + - Localization.Translate("ATP_PRODUCTION_TOO_LOW"); - atpBalanceLabel.LabelSettings = ATPBalanceNotEnoughText; - } - - atpProductionLabel.Text = string.Format(CultureInfo.CurrentCulture, "{0:F1}", energyBalance.TotalProduction); - atpConsumptionLabel.Text = string.Format(CultureInfo.CurrentCulture, "{0:F1}", energyBalance.TotalConsumption); - - float maxValue = Math.Max(energyBalance.TotalConsumption, energyBalance.TotalProduction); - atpProductionBar.MaxValue = maxValue; - atpConsumptionBar.MaxValue = maxValue; - - atpProductionBar.UpdateAndMoveBars(SortBarData(energyBalance.Production)); - atpConsumptionBar.UpdateAndMoveBars(SortBarData(energyBalance.Consumption)); - - if (Visible) - { - TutorialState?.SendEvent(TutorialEventType.MicrobeEditorPlayerEnergyBalanceChanged, - new EnergyBalanceEventArgs(energyBalance), this); - } - - UpdateEnergyBalanceToolTips(energyBalance); - } - - private void UpdateEnergyBalanceToolTips(EnergyBalanceInfo energyBalance) - { - var simulationParameters = SimulationParameters.Instance; - - foreach (var subBar in atpProductionBar.SubBars) - { - var tooltip = ToolTipManager.Instance.GetToolTip(subBar.Name, "processesProduction"); - - if (tooltip == null) - throw new InvalidOperationException("Could not find process production tooltip"); - - subBar.RegisterToolTipForControl(tooltip, true); - - // Show required compounds for this process - Dictionary? requiredCompounds = null; - - if (energyBalance.ProductionRequiresCompounds != null) - { - energyBalance.ProductionRequiresCompounds.TryGetValue(subBar.Name, out requiredCompounds); - } - else - { - GD.PrintErr("Tracking for used compounds for energy not set up"); - } - - bool includedRequirement = false; - - if (requiredCompounds is { Count: > 0 }) - { - atpToolTipTextBuilder.Clear(); - - var translationFormat = Localization.Translate("ENERGY_BALANCE_REQUIRED_COMPOUND_LINE"); - - foreach (var requiredCompound in requiredCompounds) - { - var compound = simulationParameters.GetCompoundDefinition(requiredCompound.Key); - - // Don't show environmental compounds as the player doesn't need to worry about having those to be - // able to generate ATP - if (compound.IsEnvironmental) - continue; - - if (atpToolTipTextBuilder.Length > 0) - atpToolTipTextBuilder.Append('\n'); - - atpToolTipTextBuilder.Append(translationFormat.FormatSafe(compound.Name, - Math.Round(requiredCompound.Value, 2))); - } - - // As we don't check for environmental compounds before starting the loop, we might not find any valid - // data in the end in which case this needs to be skipped - if (atpToolTipTextBuilder.Length > 0) - { - tooltip.Description = Localization.Translate("ENERGY_BALANCE_TOOLTIP_PRODUCTION_WITH_REQUIREMENT") - .FormatSafe(SimulationParameters.Instance.GetOrganelleType(subBar.Name).Name, - Math.Round(energyBalance.Production[subBar.Name], 3), atpToolTipTextBuilder.ToString()); - includedRequirement = true; - } - } - - if (!includedRequirement) - { - // Normal display if didn't show with a requirement - tooltip.Description = Localization.Translate("ENERGY_BALANCE_TOOLTIP_PRODUCTION").FormatSafe( - SimulationParameters.Instance.GetOrganelleType(subBar.Name).Name, - Math.Round(energyBalance.Production[subBar.Name], 3)); - } - } - - foreach (var subBar in atpConsumptionBar.SubBars) - { - var tooltip = ToolTipManager.Instance.GetToolTip(subBar.Name, "processesConsumption"); - - if (tooltip == null) - throw new InvalidOperationException("Could not find process consumption tooltip"); - - subBar.RegisterToolTipForControl(tooltip, true); - - string displayName; - - switch (subBar.Name) - { - case "osmoregulation": - { - displayName = Localization.Translate("OSMOREGULATION"); - break; - } - - case "baseMovement": - { - displayName = Localization.Translate("BASE_MOVEMENT"); - break; - } - - default: - { - displayName = SimulationParameters.Instance.GetOrganelleType(subBar.Name).Name; - break; - } - } - - tooltip.Description = Localization.Translate("ENERGY_BALANCE_TOOLTIP_CONSUMPTION") - .FormatSafe(displayName, Math.Round(energyBalance.Consumption[subBar.Name])); - } + organismStatisticsPanel.UpdateCompoundLastingTimes(normalBalance, nightBalance, nominalStorage, + specificStorages, warningTime, fillingUpTime); } private void UpdateAutoEvoPrediction(EditorAutoEvoRun startedRun, Species playerSpeciesOriginal, @@ -992,36 +712,6 @@ private void OnEndosymbiosisFinished(int targetSpecies) OnActionStatusChanged(); } - private void OnCompoundBalanceTypeChanged(BalanceDisplayType newType) - { - // Called by 2 different things so ignore the parameter and read the new values directly from the relevant - // objects - _ = newType; - - CalculateEnergyAndCompoundBalance(editedMicrobeOrganelles.Organelles, Membrane); - } - - private void OnBalanceShowOptionsChanged(bool pressed) - { - _ = pressed; - - CalculateEnergyAndCompoundBalance(editedMicrobeOrganelles.Organelles, Membrane); - } - - private List> SortBarData(Dictionary bar) - { - var comparer = new ATPComparer(); - - return bar.OrderBy(i => i.Key, comparer).ToList(); - } - - private void SelectATPBalanceMode(int index) - { - balanceMode = (ResourceLimitingMode)index; - - CalculateEnergyAndCompoundBalance(editedMicrobeOrganelles.Organelles, Membrane); - } - private void ConfirmFinishEditingWithNegativeATPPressed() { if (OnFinish == null) @@ -1063,11 +753,11 @@ private void UpdateGUIAfterLoadingSpecies(Species species) ApplySymmetryForCurrentOrganelle(); SetSpeciesInfo(newName, Membrane, Colour, Rigidity, behaviourEditor.Behaviour); - UpdateGeneration(species.Generation); - UpdateHitpoints(CalculateHitpoints()); - UpdateStorage(GetNominalCapacity(), GetAdditionalCapacities()); + organismStatisticsPanel.UpdateGeneration(species.Generation); + organismStatisticsPanel.UpdateHitpoints(CalculateHitpoints()); + organismStatisticsPanel.UpdateStorage(GetAdditionalCapacities(out var nominalCapacity), nominalCapacity); - ApplyLightLevelOption(); + organismStatisticsPanel.ApplyLightLevelSelection(); UpdateCancelButtonVisibility(); } @@ -1175,40 +865,4 @@ public static GrowthOrderLabel Create(int number) }; } } - - private class ATPComparer : IComparer - { - /// - /// Compares ATP production / consumption items - /// - /// - /// - /// Only works if there aren't duplicate entries of osmoregulation or baseMovement. - /// - /// - public int Compare(string? stringA, string? stringB) - { - if (stringA == "osmoregulation") - { - return -1; - } - - if (stringB == "osmoregulation") - { - return 1; - } - - if (stringA == "baseMovement") - { - return -1; - } - - if (stringB == "baseMovement") - { - return 1; - } - - return string.Compare(stringA, stringB, StringComparison.InvariantCulture); - } - } } diff --git a/src/microbe_stage/editor/CellEditorComponent.cs b/src/microbe_stage/editor/CellEditorComponent.cs index 153964da37d..f6c6a0c6d50 100644 --- a/src/microbe_stage/editor/CellEditorComponent.cs +++ b/src/microbe_stage/editor/CellEditorComponent.cs @@ -25,18 +25,6 @@ public partial class CellEditorComponent : [Export] public NodePath? TopPanelPath; - [Export] - public NodePath DayButtonPath = null!; - - [Export] - public NodePath NightButtonPath = null!; - - [Export] - public NodePath AverageLightButtonPath = null!; - - [Export] - public NodePath CurrentLightButtonPath = null!; - [Export] public NodePath TabButtonsPath = null!; @@ -64,33 +52,6 @@ public partial class CellEditorComponent : [Export] public NodePath MembraneTypeSelectionPath = null!; - [Export] - public NodePath SizeLabelPath = null!; - - [Export] - public NodePath OrganismStatisticsPath = null!; - - [Export] - public NodePath SpeedLabelPath = null!; - - [Export] - public NodePath RotationSpeedLabelPath = null!; - - [Export] - public NodePath HpLabelPath = null!; - - [Export] - public NodePath StorageLabelPath = null!; - - [Export] - public NodePath DigestionSpeedLabelPath = null!; - - [Export] - public NodePath DigestionEfficiencyLabelPath = null!; - - [Export] - public NodePath GenerationLabelPath = null!; - [Export] public NodePath AutoEvoPredictionPanelPath = null!; @@ -109,21 +70,6 @@ public partial class CellEditorComponent : [Export] public NodePath MembraneColorPickerPath = null!; - [Export] - public NodePath ATPBalancePanelPath = null!; - - [Export] - public NodePath ATPProductionLabelPath = null!; - - [Export] - public NodePath ATPConsumptionLabelPath = null!; - - [Export] - public NodePath ATPProductionBarPath = null!; - - [Export] - public NodePath ATPConsumptionBarPath = null!; - [Export] public NodePath RigiditySliderPath = null!; @@ -145,14 +91,6 @@ public partial class CellEditorComponent : [Export] public NodePath RightPanelScrollContainerPath = null!; -#pragma warning disable CA2213 - [Export] - public LabelSettings ATPBalanceNormalText = null!; - - [Export] - public LabelSettings ATPBalanceNotEnoughText = null!; -#pragma warning restore CA2213 - /// /// Temporary hex memory for use by the main thread in this component /// @@ -173,13 +111,6 @@ public partial class CellEditorComponent : #pragma warning disable CA2213 - // Light level controls - private Control topPanel = null!; - private Button dayButton = null!; - private Button nightButton = null!; - private Button averageLightButton = null!; - private Button currentLightButton = null!; - // Selection menu tab selector buttons private Button structureTabButton = null!; private Button appearanceTabButton = null!; @@ -212,36 +143,6 @@ public partial class CellEditorComponent : private VBoxContainer partsSelectionContainer = null!; private CollapsibleList membraneTypeSelection = null!; - [JsonProperty] - [AssignOnlyChildItemsOnDeserialize] - private CellStatsIndicator sizeLabel = null!; - - [JsonProperty] - [AssignOnlyChildItemsOnDeserialize] - private CellStatsIndicator speedLabel = null!; - - [JsonProperty] - [AssignOnlyChildItemsOnDeserialize] - private CellStatsIndicator rotationSpeedLabel = null!; - - [JsonProperty] - [AssignOnlyChildItemsOnDeserialize] - private CellStatsIndicator hpLabel = null!; - - [JsonProperty] - [AssignOnlyChildItemsOnDeserialize] - private CellStatsIndicator storageLabel = null!; - - [JsonProperty] - [AssignOnlyChildItemsOnDeserialize] - private CellStatsIndicator digestionSpeedLabel = null!; - - [JsonProperty] - [AssignOnlyChildItemsOnDeserialize] - private CellStatsIndicator digestionEfficiencyLabel = null!; - - private Label generationLabel = null!; - private CellStatsIndicator totalEnergyLabel = null!; private Label autoEvoPredictionFailedLabel = null!; private Label bestPatchLabel = null!; @@ -258,16 +159,6 @@ public partial class CellEditorComponent : private Slider rigiditySlider = null!; private TweakedColourPicker membraneColorPicker = null!; - private Control atpBalancePanel = null!; - - [Export] - private Label atpBalanceLabel = null!; - - private Label atpProductionLabel = null!; - private Label atpConsumptionLabel = null!; - private SegmentedBar atpProductionBar = null!; - private SegmentedBar atpConsumptionBar = null!; - private CustomConfirmationDialog negativeAtpPopup = null!; [Export] @@ -282,27 +173,6 @@ public partial class CellEditorComponent : private OrganellePopupMenu organelleMenu = null!; private OrganelleUpgradeGUI organelleUpgradeGUI = null!; - [Export] - private CheckBox calculateBalancesAsIfDay = null!; - - [Export] - private CheckBox calculateBalancesWhenMoving = null!; - - [Export] - private CompoundBalanceDisplay compoundBalance = null!; - - [Export] - private CompoundStorageStatistics compoundStorageLastingTimes = null!; - - [Export] - private CustomRichTextLabel notEnoughStorageWarning = null!; - - [Export] - private Button processListButton = null!; - - [Export] - private ProcessList processList = null!; - [Export] private CheckBox showGrowthOrderCoordinates = null!; @@ -310,10 +180,10 @@ public partial class CellEditorComponent : private Control growthOrderNumberContainer = null!; [Export] - private CustomWindow processListWindow = null!; + private PopupMicheViewer micheViewer = null!; [Export] - private PopupMicheViewer micheViewer = null!; + private OrganismStatisticsPanel organismStatisticsPanel = null!; private CustomWindow autoEvoPredictionExplanationPopup = null!; private CustomRichTextLabel autoEvoPredictionExplanationLabel = null!; @@ -336,8 +206,6 @@ public partial class CellEditorComponent : private EnergyBalanceInfo? energyBalanceInfo; - private ResourceLimitingMode balanceMode; - private string? bestPatchName; // This and worstPatchPopulation used to be displayed but are now kept for potential future use @@ -360,9 +228,6 @@ public partial class CellEditorComponent : [JsonProperty] private SelectionMenuTab selectedSelectionMenuTab = SelectionMenuTab.Structure; - [JsonProperty] - private LightLevelOption selectedLightLevelOption = LightLevelOption.Current; - private bool? autoEvoPredictionRunSuccessful; private PendingAutoEvoPrediction? waitingForPrediction; private LocalizedStringBuilder? predictionDetailsText; @@ -434,6 +299,8 @@ public partial class CellEditorComponent : private bool showGrowthOrderNumbers; + private TutorialState? tutorialState; + public enum SelectionMenuTab { Structure, @@ -443,14 +310,6 @@ public enum SelectionMenuTab Tolerance, } - public enum LightLevelOption - { - Day, - Night, - Average, - Current, - } - /// /// The selected membrane rigidity /// @@ -597,7 +456,17 @@ public int MicrobeHexSize } [JsonIgnore] - public TutorialState? TutorialState { get; set; } + public TutorialState? TutorialState + { + get => tutorialState; + set + { + tutorialState = value; + + if (tutorialState != null) + organismStatisticsPanel.TutorialState = tutorialState; + } + } /// /// Needed for auto-evo prediction to be able to compare the new energy to the old energy @@ -704,10 +573,6 @@ public override void _Ready() // Hidden in the Godot editor to make selecting other things easier organelleUpgradeGUI.Visible = true; - atpProductionBar.SelectedType = SegmentedBar.Type.ATP; - atpProductionBar.IsProduction = true; - atpConsumptionBar.SelectedType = SegmentedBar.Type.ATP; - // TODO: make this setting persistent from somewhere // showGrowthOrderCoordinates.ButtonPressed = true; growthOrderGUI.ShowCoordinates = showGrowthOrderCoordinates.ButtonPressed; @@ -740,12 +605,6 @@ public override void ResolveNodeReferences() NodeReferencesResolved = true; - topPanel = GetNode(TopPanelPath); - dayButton = GetNode