From 07349570370b94f7712c9be4defd68021539a38d Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Mon, 30 Jul 2018 00:11:02 +0200
Subject: [PATCH] Change weekly stats labels to dynamically determined (closes
#59)
---
src/RealTime/Core/RealTimeCore.cs | 14 +
src/RealTime/Localization/TranslationKeys.cs | 3 +
src/RealTime/RealTime.csproj | 1 +
src/RealTime/Simulation/SimulationHandler.cs | 4 +
src/RealTime/Simulation/Statistics.cs | 260 +++++++++++++++++++
5 files changed, 282 insertions(+)
create mode 100644 src/RealTime/Simulation/Statistics.cs
diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs
index 6648a72..02e28d9 100644
--- a/src/RealTime/Core/RealTimeCore.cs
+++ b/src/RealTime/Core/RealTimeCore.cs
@@ -143,6 +143,17 @@ namespace RealTime.Core
var result = new RealTimeCore(timeAdjustment, customTimeBar, eventManager, patcher);
eventManager.EventsChanged += result.CityEventsChanged;
+
+ var statistics = new Statistics(timeInfo, localizationProvider);
+ if (statistics.Initialize())
+ {
+ statistics.RefreshUnits();
+ }
+ else
+ {
+ statistics = null;
+ }
+
SimulationHandler.NewDay += result.CityEventsChanged;
SimulationHandler.TimeAdjustment = timeAdjustment;
@@ -152,6 +163,7 @@ namespace RealTime.Core
SimulationHandler.Buildings = BuildingAIPatches.RealTimeAI;
SimulationHandler.Buildings.UpdateFrameDuration();
SimulationHandler.Buildings.InitializeLightState();
+ SimulationHandler.Statistics = statistics;
AwakeSleepSimulation.Install(configProvider.Configuration);
@@ -205,6 +217,8 @@ namespace RealTime.Core
SimulationHandler.WeatherInfo = null;
SimulationHandler.Buildings = null;
SimulationHandler.CitizenProcessor = null;
+ SimulationHandler.Statistics?.Close();
+ SimulationHandler.Statistics = null;
isEnabled = false;
}
diff --git a/src/RealTime/Localization/TranslationKeys.cs b/src/RealTime/Localization/TranslationKeys.cs
index b7de428..03c007e 100644
--- a/src/RealTime/Localization/TranslationKeys.cs
+++ b/src/RealTime/Localization/TranslationKeys.cs
@@ -20,5 +20,8 @@ namespace RealTime.Localization
/// The key for a 'incompatible mods found' message.
public const string IncompatibleModsFoundMessage = "IncompatibleModsFoundMessage";
+
+ /// The key for the abbreviated 'minutes' text.
+ public const string Minutes = "Minutes";
}
}
diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj
index e0bba36..27346c6 100644
--- a/src/RealTime/RealTime.csproj
+++ b/src/RealTime/RealTime.csproj
@@ -139,6 +139,7 @@
+
diff --git a/src/RealTime/Simulation/SimulationHandler.cs b/src/RealTime/Simulation/SimulationHandler.cs
index 92dc4b8..8509065 100644
--- a/src/RealTime/Simulation/SimulationHandler.cs
+++ b/src/RealTime/Simulation/SimulationHandler.cs
@@ -46,6 +46,9 @@ namespace RealTime.Simulation
/// Gets or sets the citizen processing class instance.
internal static CitizenProcessor CitizenProcessor { get; set; }
+ /// Gets or sets the statistics processing class instance.
+ internal static Statistics Statistics { get; set; }
+
///
/// Called before each game simulation tick. A tick contains multiple frames.
/// Performs the dispatching for this simulation phase.
@@ -70,6 +73,7 @@ namespace RealTime.Simulation
if (updateFrameLength)
{
Buildings?.UpdateFrameDuration();
+ Statistics?.RefreshUnits();
}
if (DayTimeSimulation == null || CitizenProcessor == null)
diff --git a/src/RealTime/Simulation/Statistics.cs b/src/RealTime/Simulation/Statistics.cs
new file mode 100644
index 0000000..9942d7c
--- /dev/null
+++ b/src/RealTime/Simulation/Statistics.cs
@@ -0,0 +1,260 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.Simulation
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Reflection;
+ using ColossalFramework.Globalization;
+ using ColossalFramework.UI;
+ using RealTime.Localization;
+ using RealTime.Tools;
+ using UnityEngine;
+
+ ///
+ /// Handles the customization of the game's statistics numbers.
+ ///
+ internal sealed class Statistics
+ {
+ private const int VanillaFramesPerWeek = 3840;
+ private const string UnitPlaceholder = "{unit}";
+ private const string OverrddenTranslationType = "Units";
+ private const string CityInfoPanelName = "(Library) CityInfoPanel";
+ private const string DistrictInfoPanelName = "(Library) DistrictWorldInfoPanel";
+ private const string TouristsPanelName = "Tourists";
+ private const string TouristsLabelName = "Label";
+ private const string InfoPanelName = "InfoPanel";
+ private const string CitizensChangeLabelName = "ProjectedChange";
+ private const string IncomeLabelName = "ProjectedIncome";
+ private const string BuildingsButtonsContainer = "TSContainer";
+
+ private readonly ITimeInfo timeInfo;
+ private readonly ILocalizationProvider localizationProvider;
+ private readonly Locale customLocale;
+
+ private Locale mainLocale;
+ private UILabel cityInfoPanelTourists;
+ private UILabel districtInfoPanelTourists;
+ private UILabel labelPopulation;
+ private UILabel labelIncome;
+ private UITabContainer buildingsTabContainer;
+
+ /// Initializes a new instance of the class.
+ /// An object that provides the game's time information.
+ /// The object to get the current locale from.
+ /// Thrown when any argument is null.
+ public Statistics(ITimeInfo timeInfo, ILocalizationProvider localizationProvider)
+ {
+ this.timeInfo = timeInfo ?? throw new ArgumentNullException(nameof(timeInfo));
+ this.localizationProvider = localizationProvider ?? throw new ArgumentNullException(nameof(localizationProvider));
+
+ customLocale = new Locale();
+ }
+
+ /// Initializes this instance by preparing connections to the necessary game parts.
+ /// true on success; otherwise, false.
+ public bool Initialize()
+ {
+ if (mainLocale != null)
+ {
+ return true;
+ }
+
+ try
+ {
+ FieldInfo field = typeof(LocaleManager).GetField("m_Locale", BindingFlags.Instance | BindingFlags.NonPublic);
+ mainLocale = field.GetValue(LocaleManager.instance) as Locale;
+ }
+ catch (Exception ex)
+ {
+ Log.Warning("The 'Real Time' mod could not obtain the locale field of the LocaleManager, error message: " + ex);
+ return false;
+ }
+
+ cityInfoPanelTourists = GameObject
+ .Find(CityInfoPanelName)?
+ .GetComponent()?
+ .Find(TouristsPanelName)?
+ .Find(TouristsLabelName);
+
+ if (cityInfoPanelTourists == null)
+ {
+ Log.Warning("The 'Real Time' mod could not obtain the CityInfoPanel.Tourists.Label object");
+ }
+
+ districtInfoPanelTourists = GameObject
+ .Find(DistrictInfoPanelName)?
+ .GetComponent()?
+ .Find(TouristsPanelName)?
+ .Find(TouristsLabelName);
+
+ if (districtInfoPanelTourists == null)
+ {
+ Log.Warning("The 'Real Time' mod could not obtain the DistrictWorldInfoPanel.Tourists.Label object");
+ }
+
+ UIPanel infoPanel = UIView.Find(InfoPanelName);
+ if (infoPanel == null)
+ {
+ Log.Warning("The 'Real Time' mod could not obtain the InfoPanel object");
+ }
+ else
+ {
+ labelPopulation = infoPanel.Find(CitizensChangeLabelName);
+ if (labelPopulation == null)
+ {
+ Log.Warning("The 'Real Time' mod could not obtain the ProjectedChange object");
+ }
+
+ labelIncome = infoPanel.Find(IncomeLabelName);
+ if (labelIncome == null)
+ {
+ Log.Warning("The 'Real Time' mod could not obtain the ProjectedIncome object");
+ }
+ }
+
+ buildingsTabContainer = UIView.Find(BuildingsButtonsContainer);
+ if (buildingsTabContainer == null)
+ {
+ Log.Warning("The 'Real Time' mod could not obtain the TSContainer object");
+ }
+
+ LocaleManager.eventLocaleChanged += LocaleChanged;
+ return true;
+ }
+
+ /// Shuts down this instance.
+ public void Close()
+ {
+ LocaleManager.eventLocaleChanged -= LocaleChanged;
+ mainLocale = null;
+ cityInfoPanelTourists = null;
+ districtInfoPanelTourists = null;
+ labelIncome = null;
+ labelPopulation = null;
+ buildingsTabContainer = null;
+ }
+
+ /// Refreshes the statistics units for current game speed.
+ public void RefreshUnits()
+ {
+ if (mainLocale == null)
+ {
+ return;
+ }
+
+ var unit = TimeSpan.FromHours(VanillaFramesPerWeek * timeInfo.HoursPerFrame);
+
+ double minutes = Math.Round(unit.TotalMinutes);
+ if (minutes >= 30d)
+ {
+ minutes = Math.Round(minutes / 10d) * 10d;
+ }
+ else if (minutes >= 10d)
+ {
+ minutes = Math.Round(minutes / 5d) * 5d;
+ }
+
+ string displayUnit = $"{minutes:F0} {localizationProvider.Translate(TranslationKeys.Minutes)}";
+ if (RefreshUnits(displayUnit))
+ {
+ RefreshUI();
+ }
+ }
+
+ private static void RefreshEconomyPanel()
+ {
+ IEnumerable components = ToolsModifierControl.economyPanel?
+ .GetComponentsInChildren()?
+ .Where(c => !string.IsNullOrEmpty(c.tooltipLocaleID));
+
+ if (components == null)
+ {
+ return;
+ }
+
+ foreach (UIComponent component in components.Where(c => c is UISprite || c is UITextComponent))
+ {
+ component.tooltip = Locale.Get(component.tooltipLocaleID);
+ }
+ }
+
+ private bool RefreshUnits(string displayUnit)
+ {
+ customLocale.Reset();
+
+ IDictionary overridden = localizationProvider.GetOverriddenTranslations(OverrddenTranslationType);
+ if (overridden == null || overridden.Count == 0)
+ {
+ return false;
+ }
+
+ foreach (KeyValuePair value in overridden)
+ {
+ string translated = value.Value.Replace(UnitPlaceholder, displayUnit);
+ customLocale.AddLocalizedString(new Locale.Key { m_Identifier = value.Key }, translated);
+ }
+
+ mainLocale.Merge(null, customLocale);
+ return true;
+ }
+
+ private void RefreshUI()
+ {
+ if (labelIncome != null)
+ {
+ labelIncome.tooltip = Locale.Get(labelIncome.tooltipLocaleID);
+ }
+
+ if (labelPopulation != null)
+ {
+ labelPopulation.tooltip = Locale.Get(labelPopulation.tooltipLocaleID);
+ }
+
+ if (cityInfoPanelTourists != null)
+ {
+ cityInfoPanelTourists.text = Locale.Get(cityInfoPanelTourists.localeID);
+ }
+
+ if (districtInfoPanelTourists != null)
+ {
+ districtInfoPanelTourists.text = Locale.Get(districtInfoPanelTourists.localeID);
+ }
+
+ RefreshEconomyPanel();
+ RefreshBuildingsButtons();
+ }
+
+ private void RefreshBuildingsButtons()
+ {
+ if (buildingsTabContainer == null)
+ {
+ return;
+ }
+
+ // This creates objects on heap, but it won't cause memory pressure because it's not called
+ // in the simulation loop
+ var items = buildingsTabContainer.GetComponentsInChildren()?
+ .Select(b => new { Info = b.objectUserData as PrefabInfo, Button = b })
+ .Where(i => i.Info is BuildingInfo || i.Info is NetInfo);
+
+ if (items == null)
+ {
+ return;
+ }
+
+ foreach (var item in items)
+ {
+ item.Button.tooltip = item.Info.GetLocalizedTooltip();
+ }
+ }
+
+ private void LocaleChanged()
+ {
+ RefreshUnits();
+ }
+ }
+}
--
GitLab