Skip to content

Commit

Permalink
Merge branch 'feature-1.10'
Browse files Browse the repository at this point in the history
  • Loading branch information
dymanoid committed Aug 1, 2018
2 parents db65178 + 2788d27 commit 603180a
Show file tree
Hide file tree
Showing 67 changed files with 1,166 additions and 406 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# RealTime
A mod for the Cities: Skylines game. Adjusts the time flow in the game to make it more real.

The Steam Workshop item for this mod: [click here](https://steamcommunity.com/sharedfiles/filedetails/?id=1420955187)
28 changes: 23 additions & 5 deletions src/RealTime/Config/RealTimeConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ public RealTimeConfig(bool latestVersion)
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "DayTime", Justification = "Reviewed")]
[ConfigItem("1General", "0Time", 2)]
[ConfigItemSlider(1, 7, ValueType = SliderValueType.Default)]
[ConfigItemSlider(1, 6, ValueType = SliderValueType.Default)]
public uint DayTimeSpeed { get; set; }

/// <summary>
/// Gets or sets the speed of the time flow on night time. Valid values are 1..7.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "NightTime", Justification = "Reviewed")]
[ConfigItem("1General", "0Time", 3)]
[ConfigItemSlider(1, 7, ValueType = SliderValueType.Default)]
[ConfigItemSlider(1, 6, ValueType = SliderValueType.Default)]
public uint NightTimeSpeed { get; set; }

/// <summary>
Expand Down Expand Up @@ -261,6 +261,20 @@ public RealTimeConfig(bool latestVersion)
[ConfigItemSlider(11, 16, 0.25f, SliderValueType.Time)]
public float SchoolEnd { get; set; }

/// <summary>
/// Gets or sets the maximum vacation length in days.
/// </summary>
[ConfigItem("4Time", 7)]
[ConfigItemSlider(0, 7, ValueType = SliderValueType.Default)]
public uint MaxVacationLength { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the mod should show the incompatibility notifications.
/// </summary>
[ConfigItem("Tools", 0)]
[ConfigItemCheckBox]
public bool ShowIncompatibilityNotifications { get; set; }

/// <summary>Checks the version of the deserialized object and migrates it to the latest version when necessary.</summary>
/// <returns>This instance.</returns>
public RealTimeConfig MigrateWhenNecessary()
Expand Down Expand Up @@ -290,8 +304,8 @@ public RealTimeConfig Validate()
WakeUpHour = RealTimeMath.Clamp(WakeUpHour, 4f, 8f);
GoToSleepHour = RealTimeMath.Clamp(GoToSleepHour, 20f, 23.75f);

DayTimeSpeed = RealTimeMath.Clamp(DayTimeSpeed, 1u, 7u);
NightTimeSpeed = RealTimeMath.Clamp(NightTimeSpeed, 1u, 7u);
DayTimeSpeed = RealTimeMath.Clamp(DayTimeSpeed, 1u, 6u);
NightTimeSpeed = RealTimeMath.Clamp(NightTimeSpeed, 1u, 6u);

VirtualCitizens = (VirtualCitizensLevel)RealTimeMath.Clamp((int)VirtualCitizens, (int)VirtualCitizensLevel.None, (int)VirtualCitizensLevel.Many);
ConstructionSpeed = RealTimeMath.Clamp(ConstructionSpeed, 0u, 100u);
Expand Down Expand Up @@ -324,6 +338,7 @@ public RealTimeConfig Validate()
SchoolBegin = RealTimeMath.Clamp(SchoolBegin, 4f, 10f);
SchoolEnd = RealTimeMath.Clamp(SchoolEnd, 11f, 16f);
MaxOvertime = RealTimeMath.Clamp(MaxOvertime, 0f, 4f);
MaxVacationLength = RealTimeMath.Clamp(MaxVacationLength, 0u, 7u);
return this;
}

Expand All @@ -339,7 +354,7 @@ public void ResetToDefaults()
GoToSleepHour = 22f;

IsDynamicDayLengthEnabled = true;
DayTimeSpeed = 5;
DayTimeSpeed = 4;
NightTimeSpeed = 5;

VirtualCitizens = VirtualCitizensLevel.Few;
Expand Down Expand Up @@ -372,6 +387,9 @@ public void ResetToDefaults()
MaxOvertime = 2f;
SchoolBegin = 8f;
SchoolEnd = 14f;
MaxVacationLength = 3u;

ShowIncompatibilityNotifications = true;
}
}
}
13 changes: 13 additions & 0 deletions src/RealTime/Core/Compatibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ namespace RealTime.Core
/// </summary>
internal static class Compatibility
{
/// <summary>The Workshop ID of the 'CitizenLifecycleRebalance' mod.</summary>
public const ulong CitizenLifecycleRebalanceId = 654707599;

private const string UIInfoPanel = "InfoPanel";
private const string UIPanelTime = "PanelTime";

Expand Down Expand Up @@ -62,6 +65,16 @@ public static void CheckAndNotify(string modName, ILocalizationProvider localiza
}
}

/// <summary>
/// Determines whether a mod with specified Workshop ID is currently installed and enabled.
/// </summary>
/// <param name="modId">The mod ID to check.</param>
/// <returns><c>true</c> if a mod with specified Workshop ID is currently installed and enabled; otherwise, <c>false</c>.</returns>
public static bool IsModActive(ulong modId)
{
return PluginManager.instance.GetPluginsInfo().Any(m => m.isEnabled && m.publishedFileID.AsUInt64 == modId);
}

private static void NotifyWithDialog(string caption, string text)
{
MessageBox.Show(caption, text);
Expand Down
53 changes: 36 additions & 17 deletions src/RealTime/Core/RealTimeCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,8 @@ public static RealTimeCore Run(ConfigurationProvider configProvider, string root
throw new ArgumentNullException(nameof(localizationProvider));
}

var patcher = new MethodPatcher(
BuildingAIPatches.GetConstructionTime,
BuildingAIPatches.HandleWorkers,
BuildingAIPatches.CommercialSimulation,
BuildingAIPatches.PrivateShowConsumption,
BuildingAIPatches.PlayerShowConsumption,
ResidentAIPatch.Location,
ResidentAIPatch.ArriveAtDestination,
TouristAIPatch.Location,
TransferManagerPatch.AddOutgoingOffer,
UIGraphPatches.MinDataPoints,
UIGraphPatches.VisibleEndTime,
UIGraphPatches.BuildLabels);
IEnumerable<IPatch> patches = GetMethodPatches();
var patcher = new MethodPatcher(patches);

try
{
Expand Down Expand Up @@ -167,11 +156,11 @@ public static RealTimeCore Run(ConfigurationProvider configProvider, string root

AwakeSleepSimulation.Install(configProvider.Configuration);

RealTimeStorage.CurrentLevelStorage.GameSaving += result.GameSaving;
result.storageData.Add(eventManager);
result.storageData.Add(ResidentAIPatch.RealTimeAI.GetStorageService());
if (RealTimeStorage.CurrentLevelStorage != null)
{
RealTimeStorage.CurrentLevelStorage.GameSaving += result.GameSaving;
LoadStorageData(result.storageData, RealTimeStorage.CurrentLevelStorage);
}

Expand Down Expand Up @@ -242,6 +231,36 @@ public void Translate(ILocalizationProvider localizationProvider)
UIGraphPatches.Translate(localizationProvider.CurrentCulture);
}

private static IEnumerable<IPatch> GetMethodPatches()
{
var patches = new List<IPatch>
{
BuildingAIPatches.GetConstructionTime,
BuildingAIPatches.HandleWorkers,
BuildingAIPatches.CommercialSimulation,
BuildingAIPatches.PrivateShowConsumption,
BuildingAIPatches.PlayerShowConsumption,
ResidentAIPatch.Location,
ResidentAIPatch.ArriveAtTarget,
TouristAIPatch.Location,
TransferManagerPatch.AddOutgoingOffer,
UIGraphPatches.MinDataPoints,
UIGraphPatches.VisibleEndTime,
UIGraphPatches.BuildLabels
};

if (Compatibility.IsModActive(Compatibility.CitizenLifecycleRebalanceId))
{
Log.Info("The 'Real Time' mod will not change the citizens aging because the 'Citizen Lifecycle Rebalance' mod is active.");
}
else
{
patches.Add(ResidentAIPatch.UpdateAge);
}

return patches;
}

private static bool SetupCustomAI(
TimeInfo timeInfo,
RealTimeConfig config,
Expand Down Expand Up @@ -280,7 +299,7 @@ private static bool SetupCustomAI(
travelBehavior);

ResidentAIPatch.RealTimeAI = realTimeResidentAI;
SimulationHandler.CitizenProcessor = new CitizenProcessor(realTimeResidentAI, spareTimeBehavior, timeInfo);
SimulationHandler.CitizenProcessor = new CitizenProcessor<ResidentAI, Citizen>(realTimeResidentAI, spareTimeBehavior, timeInfo);

TouristAIConnection<TouristAI, Citizen> touristAIConnection = TouristAIPatch.GetTouristAIConnection();
if (touristAIConnection == null)
Expand Down Expand Up @@ -309,7 +328,7 @@ private static void LoadStorageData(IEnumerable<IStorageData> storageData, RealT
foreach (IStorageData item in storageData)
{
storage.Deserialize(item);
Log.Debug("The 'Real Time' mod loaded its data from container " + item.StorageDataId);
Log.Debug(LogCategories.Generic, "The 'Real Time' mod loaded its data from container " + item.StorageDataId);
}
}

Expand All @@ -324,7 +343,7 @@ 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.Debug(LogCategories.Generic, "The 'Real Time' mod stored its data in the current game for container " + item.StorageDataId);
}
}
}
Expand Down
43 changes: 33 additions & 10 deletions src/RealTime/Core/RealTimeMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace RealTime.Core
{
using System;
using System.Linq;
using ColossalFramework;
using ColossalFramework.Globalization;
Expand All @@ -21,6 +20,7 @@ namespace RealTime.Core
public sealed class RealTimeMod : LoadingExtensionBase, IUserMod
{
private const long WorkshopId = 1420955187;
private const string NoWorkshopMessage = "Real Time can only run when subscribed to in Steam Workshop";

private readonly string modVersion = GitVersion.GetAssemblyVersion(typeof(RealTimeMod).Assembly);
private readonly string modPath = GetModPath();
Expand All @@ -45,6 +45,12 @@ public sealed class RealTimeMod : LoadingExtensionBase, IUserMod
/// </summary>
public void OnEnabled()
{
if (string.IsNullOrEmpty(modPath))
{
Log.Info($"The 'Real Time' mod version {modVersion} cannot be started because of no Steam Workshop");
return;
}

Log.Info("The 'Real Time' mod has been enabled, version: " + modVersion);
configProvider = new ConfigurationProvider();
configProvider.LoadDefaultConfiguration();
Expand All @@ -57,14 +63,18 @@ public void OnEnabled()
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Must be instance method due to C:S API")]
public void OnDisabled()
{
Log.Info("The 'Real Time' mod has been disabled.");
if (string.IsNullOrEmpty(modPath))
{
return;
}

CloseConfigUI();

if (configProvider != null && configProvider.IsDefault)
{
configProvider.SaveDefaultConfiguration();
}

Log.Info("The 'Real Time' mod has been disabled.");
}

/// <summary>
Expand All @@ -76,6 +86,12 @@ 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 (string.IsNullOrEmpty(modPath))
{
helper?.AddGroup(NoWorkshopMessage);
return;
}

if (helper == null || configProvider == null)
{
return;
Expand All @@ -101,6 +117,12 @@ public void OnSettingsUI(UIHelperBase helper)
/// <param name="mode">The <see cref="LoadMode"/> a game level is loaded in.</param>
public override void OnLevelLoaded(LoadMode mode)
{
if (string.IsNullOrEmpty(modPath))
{
MessageBox.Show("Sorry", NoWorkshopMessage);
return;
}

switch (mode)
{
case LoadMode.LoadGame:
Expand All @@ -127,7 +149,7 @@ public override void OnLevelLoaded(LoadMode mode)
localizationProvider.Translate(TranslationKeys.Warning),
localizationProvider.Translate(TranslationKeys.ModNotWorkingMessage));
}
else
else if (configProvider.Configuration.ShowIncompatibilityNotifications)
{
Compatibility.CheckAndNotify(Name, localizationProvider);
}
Expand All @@ -139,6 +161,11 @@ public override void OnLevelLoaded(LoadMode mode)
/// </summary>
public override void OnLevelUnloading()
{
if (string.IsNullOrEmpty(modPath))
{
return;
}

if (core != null)
{
Log.Info($"The 'Real Time' mod stops.");
Expand All @@ -151,14 +178,10 @@ public override void OnLevelUnloading()

private static string GetModPath()
{
string assemblyName = typeof(RealTimeMod).Assembly.GetName().Name;

PluginManager.PluginInfo pluginInfo = PluginManager.instance.GetPluginsInfo()
.FirstOrDefault(pi => pi.name == assemblyName || pi.publishedFileID.AsUInt64 == WorkshopId);
.FirstOrDefault(pi => pi.publishedFileID.AsUInt64 == WorkshopId);

return pluginInfo == null
? Environment.CurrentDirectory
: pluginInfo.modPath;
return pluginInfo?.modPath;
}

private void ApplyLanguage()
Expand Down
24 changes: 21 additions & 3 deletions src/RealTime/CustomAI/CitizenSchedule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ internal struct CitizenSchedule
/// <summary>The citizen's work status.</summary>
public WorkStatus WorkStatus;

/// <summary>The number of days the citizen will be on vacation (including the current day).</summary>
public byte VacationDaysLeft;

/// <summary>The ID of the citizen's work building. If it doesn't equal the game's value, the work shift data needs to be updated.</summary>
public ushort WorkBuilding;

Expand All @@ -37,6 +40,9 @@ internal struct CitizenSchedule
/// <summary>Gets the citizen's next scheduled state.</summary>
public ResidentState ScheduledState { get; private set; }

/// <summary>Gets the citizen's previous scheduled state.</summary>
public ResidentState LastScheduledState { get; private set; }

/// <summary>Gets the time when the citizen will perform the next state change.</summary>
public DateTime ScheduledStateTime { get; private set; }

Expand Down Expand Up @@ -93,22 +99,33 @@ public void UpdateWorkShift(WorkShift workShift, float startHour, float endHour,
WorksOnWeekends = worksOnWeekends;
}

/// <summary>Schedules next actions for the citizen.</summary>
/// <summary>Schedules next actions for the citizen with a specified action time.</summary>
/// <param name="nextState">The next scheduled citizen's state.</param>
/// <param name="nextStateTime">The time when the scheduled state must change.</param>
public void Schedule(ResidentState nextState, DateTime nextStateTime)
{
LastScheduledState = ScheduledState;
ScheduledState = nextState;
ScheduledStateTime = nextStateTime;
}

/// <summary>Schedules next actions for the citizen with no action time (ASAP).</summary>
/// <param name="nextState">The next scheduled citizen's state.</param>
public void Schedule(ResidentState nextState)
{
// Note: not calling the overload to avoid additional method call - this method will be called frequently
LastScheduledState = ScheduledState;
ScheduledState = nextState;
ScheduledStateTime = default;
}

/// <summary>Writes this instance to the specified target buffer.</summary>
/// <param name="target">The target buffer. Must have length of <see cref="DataRecordSize"/> elements.</param>
/// <param name="referenceTime">The reference time (in ticks) to use for time serialization.</param>
public void Write(byte[] target, long referenceTime)
{
target[0] = (byte)(((int)WorkShift & 0xF) + ((int)WorkStatus << 4));
target[1] = (byte)ScheduledState;
target[1] = (byte)(((int)ScheduledState & 0xF) + (VacationDaysLeft << 4));

ushort minutes = ScheduledStateTime == default
? (ushort)0
Expand All @@ -129,7 +146,8 @@ public void Read(byte[] source, long referenceTime)
{
WorkShift = (WorkShift)(source[0] & 0xF);
WorkStatus = (WorkStatus)(source[0] >> 4);
ScheduledState = (ResidentState)source[1];
ScheduledState = (ResidentState)(source[1] & 0xF);
VacationDaysLeft = (byte)(source[1] >> 4);

int minutes = source[2] + (source[3] << 8);
ScheduledStateTime = minutes == 0
Expand Down
Loading

0 comments on commit 603180a

Please sign in to comment.