diff --git a/src/RealTime/Config/ConfigurationProvider.cs b/src/RealTime/Config/ConfigurationProvider.cs
index 374fe534..b01ae258 100644
--- a/src/RealTime/Config/ConfigurationProvider.cs
+++ b/src/RealTime/Config/ConfigurationProvider.cs
@@ -7,80 +7,146 @@ namespace RealTime.Config
using System;
using System.IO;
using System.Xml.Serialization;
+ using RealTime.Core;
using RealTime.Tools;
///
/// A static class that loads and stores the objects.
/// An XML file 'RealTime.xml' in the default (game) directory is used as a storage.
///
- internal static class ConfigurationProvider
+ internal sealed class ConfigurationProvider : IStorageData
{
+ private const string StorageId = "RealTimeConfiguration";
private static readonly string SettingsFileName = typeof(ConfigurationProvider).Assembly.GetName().Name + ".xml";
+ /// Occurs when the instance changes.
+ public event EventHandler Changed;
+
+ /// Gets the current configuration.
+ public RealTimeConfig Configuration { get; private set; }
+
+ /// Gets a value indicating whether the instance is a default configuration.
+ public bool IsDefault { get; private set; }
+
+ /// Gets an unique ID of this storage data set.
+ string IStorageData.StorageDataId => StorageId;
+
///
- /// Loads the configuration object from the serialized storage. If no storage is available,
+ /// Loads the default configuration object from the serialized storage. If no storage is available,
/// returns a new object with its values set to defaults.
///
- ///
- /// A object containing the configuration.
- public static RealTimeConfig LoadConfiguration()
+ public void LoadDefaultConfiguration()
{
try
{
- if (!File.Exists(SettingsFileName))
+ if (File.Exists(SettingsFileName))
{
- return new RealTimeConfig(true);
+ using (var stream = new FileStream(SettingsFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ {
+ Configuration = Deserialize(stream);
+ }
}
-
- return Deserialize();
}
catch (Exception ex)
{
- Log.Warning($"The 'Real Time' mod has encountered an error while trying to load the configuration, error message: " + ex);
- return new RealTimeConfig(true);
+ Log.Error("The 'Real Time' mod cannot load its default configuration, error message: " + ex);
}
+
+ IsDefault = true;
+ if (Configuration == null)
+ {
+ Configuration = new RealTimeConfig(true);
+ }
+
+ OnChanged();
}
///
- /// Stores the specified object to the storage.
+ /// Stores the current configuration object to the storage as a default configuration.
///
- ///
- /// Thrown when the argument is null.
- ///
- /// A object to store.
- public static void SaveConfiguration(RealTimeConfig config)
+ public void SaveDefaultConfiguration()
{
- if (config == null)
+ if (Configuration == null)
{
- throw new ArgumentNullException(nameof(config));
+ Configuration = new RealTimeConfig(true);
}
try
{
- Serialize(config);
+ using (var stream = new FileStream(SettingsFileName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
+ {
+ Serialize(Configuration, stream);
+ }
}
catch (Exception ex)
{
- Log.Error("The 'Real Time' mod cannot save its configuration, error message: " + ex);
+ Log.Error("The 'Real Time' mod cannot save its default configuration, error message: " + ex);
}
}
- private static RealTimeConfig Deserialize()
+ /// Reads the data set from the specified .
+ /// A to read the data set from.
+ void IStorageData.ReadData(Stream source)
{
- var serializer = new XmlSerializer(typeof(RealTimeConfig));
- using (var sr = new StreamReader(SettingsFileName))
+ Configuration = Deserialize(source);
+ if (Configuration == null)
+ {
+ LoadDefaultConfiguration();
+ }
+ else
{
- return ((RealTimeConfig)serializer.Deserialize(sr)).MigrateWhenNecessary().Validate();
+ OnChanged();
}
+
+ IsDefault = false;
}
- private static void Serialize(RealTimeConfig config)
+ /// Stores the data set to the specified .
+ /// A to write the data set to.
+ void IStorageData.StoreData(Stream target)
{
- var serializer = new XmlSerializer(typeof(RealTimeConfig), SerializationTools.IgnoreObsoleteProperties(config));
- using (var sw = new StreamWriter(SettingsFileName))
+ if (Configuration != null)
{
- serializer.Serialize(sw, config);
+ Serialize(Configuration, target);
}
}
+
+ private static RealTimeConfig Deserialize(Stream source)
+ {
+ try
+ {
+ var serializer = new XmlSerializer(typeof(RealTimeConfig));
+ using (var sr = new StreamReader(source))
+ {
+ return ((RealTimeConfig)serializer.Deserialize(sr)).MigrateWhenNecessary().Validate();
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Warning($"The 'Real Time' mod has encountered an error while trying to load the configuration, error message: " + ex);
+ return null;
+ }
+ }
+
+ private static void Serialize(RealTimeConfig config, Stream target)
+ {
+ try
+ {
+ var serializer = new XmlSerializer(typeof(RealTimeConfig), SerializationTools.IgnoreObsoleteProperties(config));
+ using (var sw = new StreamWriter(target))
+ {
+ serializer.Serialize(sw, config);
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error("The 'Real Time' mod cannot save its configuration, error message: " + ex);
+ }
+ }
+
+ private void OnChanged()
+ {
+ Changed?.Invoke(this, EventArgs.Empty);
+ }
}
}
diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs
index 83697432..77a4639d 100644
--- a/src/RealTime/Core/RealTimeCore.cs
+++ b/src/RealTime/Core/RealTimeCore.cs
@@ -45,20 +45,20 @@ private RealTimeCore(TimeAdjustment timeAdjustment, CustomTimeBar timeBar, RealT
/// Runs the mod by activating its parts.
///
///
- /// Thrown when or is null.
+ /// Thrown when or is null.
/// Thrown when is null or an empty string.
///
- /// The configuration to run with.
+ /// The configuration provider that provides the mod's configuration.
/// The path to the mod's assembly. Additional files are stored here too.
/// The to use for text translation.
///
/// A instance that can be used to stop the mod.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This is the entry point and needs to instantiate all parts")]
- public static RealTimeCore Run(RealTimeConfig config, string rootPath, ILocalizationProvider localizationProvider)
+ public static RealTimeCore Run(ConfigurationProvider configProvider, string rootPath, ILocalizationProvider localizationProvider)
{
- if (config == null)
+ if (configProvider == null)
{
- throw new ArgumentNullException(nameof(config));
+ throw new ArgumentNullException(nameof(configProvider));
}
if (string.IsNullOrEmpty(rootPath))
@@ -96,7 +96,12 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, ILocaliza
return null;
}
- var timeInfo = new TimeInfo(config);
+ if (RealTimeStorage.CurrentLevelStorage != null)
+ {
+ LoadStorageData(new[] { configProvider }, RealTimeStorage.CurrentLevelStorage);
+ }
+
+ var timeInfo = new TimeInfo(configProvider.Configuration);
var buildingManager = new BuildingManagerConnection();
var randomizer = new GameRandomizer();
@@ -112,21 +117,21 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, ILocaliza
weatherInfo);
var eventManager = new RealTimeEventManager(
- config,
+ configProvider.Configuration,
CityEventsLoader.Instance,
new EventManagerConnection(),
buildingManager,
randomizer,
timeInfo);
- if (!SetupCustomAI(timeInfo, config, gameConnections, eventManager))
+ if (!SetupCustomAI(timeInfo, configProvider.Configuration, gameConnections, eventManager))
{
Log.Error("The 'Real Time' mod failed to setup the customized AI and will now be deactivated.");
patcher.Revert();
return null;
}
- var timeAdjustment = new TimeAdjustment(config);
+ var timeAdjustment = new TimeAdjustment(configProvider.Configuration);
DateTime gameDate = timeAdjustment.Enable();
SimulationHandler.CitizenProcessor.UpdateFrameDuration();
@@ -141,19 +146,24 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, ILocaliza
SimulationHandler.NewDay += result.CityEventsChanged;
SimulationHandler.TimeAdjustment = timeAdjustment;
- SimulationHandler.DayTimeSimulation = new DayTimeSimulation(config);
+ SimulationHandler.DayTimeSimulation = new DayTimeSimulation(configProvider.Configuration);
SimulationHandler.EventManager = eventManager;
SimulationHandler.WeatherInfo = weatherInfo;
SimulationHandler.Buildings = BuildingAIPatches.RealTimeAI;
SimulationHandler.Buildings.UpdateFrameDuration();
SimulationHandler.Buildings.InitializeLightState();
- AwakeSleepSimulation.Install(config);
+ AwakeSleepSimulation.Install(configProvider.Configuration);
RealTimeStorage.CurrentLevelStorage.GameSaving += result.GameSaving;
result.storageData.Add(eventManager);
result.storageData.Add(ResidentAIPatch.RealTimeAI.GetStorageService());
- result.LoadStorageData(RealTimeStorage.CurrentLevelStorage);
+ if (RealTimeStorage.CurrentLevelStorage != null)
+ {
+ LoadStorageData(result.storageData, RealTimeStorage.CurrentLevelStorage);
+ }
+
+ result.storageData.Add(configProvider);
result.Translate(localizationProvider);
@@ -278,19 +288,18 @@ private static void CustomTimeBarCityEventClick(object sender, CustomTimeBarClic
CameraHelper.NavigateToBuilding(e.CityEventBuildingId);
}
- private void CityEventsChanged(object sender, EventArgs e)
- {
- timeBar.UpdateEventsDisplay(eventManager.CityEvents);
- }
-
- private void LoadStorageData(RealTimeStorage storage)
+ private static void LoadStorageData(IEnumerable storageData, RealTimeStorage storage)
{
foreach (IStorageData item in storageData)
{
storage.Deserialize(item);
+ Log.Debug("The 'Real Time' mod loaded its data from container " + item.StorageDataId);
}
+ }
- Log.Info("The 'Real Time' mod successfully loaded its data for the current game.");
+ private void CityEventsChanged(object sender, EventArgs e)
+ {
+ timeBar.UpdateEventsDisplay(eventManager.CityEvents);
}
private void GameSaving(object sender, EventArgs e)
@@ -299,9 +308,8 @@ private void GameSaving(object sender, EventArgs e)
foreach (IStorageData item in storageData)
{
storage.Serialize(item);
+ Log.Debug("The 'Real Time' mod stored its data in the current game for container " + item.StorageDataId);
}
-
- Log.Info("The 'Real Time' mod successfully stored its data in the current game.");
}
}
}
diff --git a/src/RealTime/Core/RealTimeMod.cs b/src/RealTime/Core/RealTimeMod.cs
index 467e862c..642d2c0c 100644
--- a/src/RealTime/Core/RealTimeMod.cs
+++ b/src/RealTime/Core/RealTimeMod.cs
@@ -25,7 +25,7 @@ public sealed class RealTimeMod : LoadingExtensionBase, IUserMod
private readonly string modVersion = GitVersion.GetAssemblyVersion(typeof(RealTimeMod).Assembly);
private readonly string modPath = GetModPath();
- private RealTimeConfig config;
+ private ConfigurationProvider configProvider;
private RealTimeCore core;
private ConfigUI configUI;
private LocalizationProvider localizationProvider;
@@ -46,7 +46,8 @@ public sealed class RealTimeMod : LoadingExtensionBase, IUserMod
public void OnEnabled()
{
Log.Info("The 'Real Time' mod has been enabled, version: " + modVersion);
- config = ConfigurationProvider.LoadConfiguration();
+ configProvider = new ConfigurationProvider();
+ configProvider.LoadDefaultConfiguration();
localizationProvider = new LocalizationProvider(modPath);
}
@@ -57,9 +58,13 @@ public void OnEnabled()
public void OnDisabled()
{
Log.Info("The 'Real Time' mod has been disabled.");
- ConfigurationProvider.SaveConfiguration(config);
- config = null;
- configUI = null;
+
+ CloseConfigUI();
+
+ if (configProvider != null && configProvider.IsDefault)
+ {
+ configProvider.SaveDefaultConfiguration();
+ }
}
///
@@ -71,19 +76,20 @@ public void OnDisabled()
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Must be instance method due to C:S API")]
public void OnSettingsUI(UIHelperBase helper)
{
- if (helper == null)
+ if (helper == null || configProvider == null)
{
return;
}
- if (config == null)
+ if (configProvider.Configuration == null)
{
Log.Warning("The 'Real Time' mod wants to display the configuration page, but the configuration is unexpectedly missing.");
- config = ConfigurationProvider.LoadConfiguration();
+ configProvider.LoadDefaultConfiguration();
}
IViewItemFactory itemFactory = new CitiesViewItemFactory(helper);
- configUI = ConfigUI.Create(config, itemFactory);
+ CloseConfigUI();
+ configUI = ConfigUI.Create(configProvider, itemFactory);
ApplyLanguage();
}
@@ -113,7 +119,7 @@ public override void OnLevelLoaded(LoadMode mode)
core.Stop();
}
- core = RealTimeCore.Run(config, modPath, localizationProvider);
+ core = RealTimeCore.Run(configProvider, modPath, localizationProvider);
if (core == null)
{
Log.Warning("Showing a warning message to user because the mod isn't working");
@@ -136,7 +142,8 @@ public override void OnLevelUnloading()
core = null;
}
- ConfigurationProvider.SaveConfiguration(config);
+ configProvider.LoadDefaultConfiguration();
+ CloseConfigUI();
}
private static string GetModPath()
@@ -165,5 +172,14 @@ private void ApplyLanguage()
configUI?.Translate(localizationProvider);
}
+
+ private void CloseConfigUI()
+ {
+ if (configUI != null)
+ {
+ configUI.Close();
+ configUI = null;
+ }
+ }
}
}
diff --git a/src/RealTime/Localization/Translations/de.xml b/src/RealTime/Localization/Translations/de.xml
index d3985ab9..1801075b 100644
--- a/src/RealTime/Localization/Translations/de.xml
+++ b/src/RealTime/Localization/Translations/de.xml
@@ -6,6 +6,7 @@
+
diff --git a/src/RealTime/Localization/Translations/en.xml b/src/RealTime/Localization/Translations/en.xml
index 5bc38002..bf6c4127 100644
--- a/src/RealTime/Localization/Translations/en.xml
+++ b/src/RealTime/Localization/Translations/en.xml
@@ -6,6 +6,7 @@
+
diff --git a/src/RealTime/Localization/Translations/es.xml b/src/RealTime/Localization/Translations/es.xml
index dad548c1..cece0a9e 100644
--- a/src/RealTime/Localization/Translations/es.xml
+++ b/src/RealTime/Localization/Translations/es.xml
@@ -6,6 +6,7 @@
+
diff --git a/src/RealTime/Localization/Translations/fr.xml b/src/RealTime/Localization/Translations/fr.xml
index 1863d84a..a1313571 100644
--- a/src/RealTime/Localization/Translations/fr.xml
+++ b/src/RealTime/Localization/Translations/fr.xml
@@ -6,6 +6,7 @@
+
diff --git a/src/RealTime/Localization/Translations/ko.xml b/src/RealTime/Localization/Translations/ko.xml
index 3a28fbd4..507eca96 100644
--- a/src/RealTime/Localization/Translations/ko.xml
+++ b/src/RealTime/Localization/Translations/ko.xml
@@ -6,6 +6,7 @@
+
diff --git a/src/RealTime/Localization/Translations/pl.xml b/src/RealTime/Localization/Translations/pl.xml
index 55ad00ea..c9d10307 100644
--- a/src/RealTime/Localization/Translations/pl.xml
+++ b/src/RealTime/Localization/Translations/pl.xml
@@ -6,6 +6,7 @@
+
diff --git a/src/RealTime/Localization/Translations/pt.xml b/src/RealTime/Localization/Translations/pt.xml
index c2021afd..289a7588 100644
--- a/src/RealTime/Localization/Translations/pt.xml
+++ b/src/RealTime/Localization/Translations/pt.xml
@@ -6,6 +6,7 @@
+
diff --git a/src/RealTime/Localization/Translations/ru.xml b/src/RealTime/Localization/Translations/ru.xml
index 3e8d97f2..d0493386 100644
--- a/src/RealTime/Localization/Translations/ru.xml
+++ b/src/RealTime/Localization/Translations/ru.xml
@@ -6,6 +6,7 @@
+
diff --git a/src/RealTime/Localization/Translations/zh.xml b/src/RealTime/Localization/Translations/zh.xml
index 51242026..30265ba6 100644
--- a/src/RealTime/Localization/Translations/zh.xml
+++ b/src/RealTime/Localization/Translations/zh.xml
@@ -6,6 +6,7 @@
+
diff --git a/src/RealTime/UI/CitiesCheckBoxItem.cs b/src/RealTime/UI/CitiesCheckBoxItem.cs
index 55a22270..2197a5a6 100644
--- a/src/RealTime/UI/CitiesCheckBoxItem.cs
+++ b/src/RealTime/UI/CitiesCheckBoxItem.cs
@@ -4,6 +4,7 @@
namespace RealTime.UI
{
+ using System;
using System.Reflection;
using ColossalFramework.UI;
using ICities;
@@ -18,13 +19,13 @@ internal sealed class CitiesCheckBoxItem : CitiesViewItem
///
/// The property description that specifies the target property where to store the value.
///
- /// The configuration storage object for the value.
+ /// A method that provides the configuration storage object for the value.
/// Thrown when any argument is null.
///
/// thrown when the is an empty string.
///
- public CitiesCheckBoxItem(UIHelperBase uiHelper, string id, PropertyInfo property, object config)
- : base(uiHelper, id, property, config)
+ public CitiesCheckBoxItem(UIHelperBase uiHelper, string id, PropertyInfo property, Func