From f822229e54c996be6286f1a5c0b660bd431b00de Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 01:31:11 +0200 Subject: [PATCH 001/102] Add BuildEnvironment folder for storing the general project environment --- .../{ => BuildEnvironment}/RealTime.ruleset | 1 + .../{ => BuildEnvironment}/packages.config | 0 RealTime/{ => BuildEnvironment}/stylecop.json | 0 RealTime/RealTime.csproj | 20 +++++++++---------- 4 files changed, 10 insertions(+), 11 deletions(-) rename RealTime/{ => BuildEnvironment}/RealTime.ruleset (98%) rename RealTime/{ => BuildEnvironment}/packages.config (100%) rename RealTime/{ => BuildEnvironment}/stylecop.json (100%) diff --git a/RealTime/RealTime.ruleset b/RealTime/BuildEnvironment/RealTime.ruleset similarity index 98% rename from RealTime/RealTime.ruleset rename to RealTime/BuildEnvironment/RealTime.ruleset index 4d362ff7..aee3a307 100644 --- a/RealTime/RealTime.ruleset +++ b/RealTime/BuildEnvironment/RealTime.ruleset @@ -70,5 +70,6 @@ + \ No newline at end of file diff --git a/RealTime/packages.config b/RealTime/BuildEnvironment/packages.config similarity index 100% rename from RealTime/packages.config rename to RealTime/BuildEnvironment/packages.config diff --git a/RealTime/stylecop.json b/RealTime/BuildEnvironment/stylecop.json similarity index 100% rename from RealTime/stylecop.json rename to RealTime/BuildEnvironment/stylecop.json diff --git a/RealTime/RealTime.csproj b/RealTime/RealTime.csproj index 41a9c532..25c0bd5b 100644 --- a/RealTime/RealTime.csproj +++ b/RealTime/RealTime.csproj @@ -23,9 +23,10 @@ DEBUG;TRACE prompt 4 - bin\Debug\RealTime.xml + + true - RealTime.ruleset + BuildEnvironment\RealTime.ruleset pdbonly @@ -34,21 +35,19 @@ TRACE prompt 4 - bin\Release\RealTime.xml + + true - RealTime.ruleset + BuildEnvironment\RealTime.ruleset - ..\..\..\..\..\Data\SteamLibrary\steamapps\common\Cities_Skylines\Cities_Data\Managed\Assembly-CSharp.dll False - ..\..\..\..\..\Data\SteamLibrary\steamapps\common\Cities_Skylines\Cities_Data\Managed\ColossalManaged.dll False - ..\..\..\..\..\Data\SteamLibrary\steamapps\common\Cities_Skylines\Cities_Data\Managed\ICities.dll False @@ -57,7 +56,6 @@ - ..\..\..\..\..\Data\SteamLibrary\steamapps\common\Cities_Skylines\Cities_Data\Managed\UnityEngine.dll False @@ -65,9 +63,9 @@ - - - + + + From 744e22baf0274c6bfe39a367491dc55a8c3884d5 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 01:33:04 +0200 Subject: [PATCH 002/102] Implement the basic time adjustment - synchronize day/night time with the simulation time - change the time bar display format - add the time bar tool tip containing the game date --- RealTime/Core/LoadingExtension.cs | 39 +++++++ RealTime/Core/RealTimeCore.cs | 49 ++++++++ RealTime/Core/RealTimeMod.cs | 34 ++++++ RealTime/RealTime.csproj | 9 ++ RealTime/Simulation/TimeAdjustment.cs | 56 +++++++++ RealTime/Tools/GitVersion.cs | 46 ++++++++ RealTime/Tools/Log.cs | 133 +++++++++++++++++++++ RealTime/UI/CustomTimeBar.cs | 143 +++++++++++++++++++++++ RealTime/UI/DateTooltipBehavior.cs | 41 +++++++ RealTime/UI/RealTimeUIDateTimeWrapper.cs | 36 ++++++ 10 files changed, 586 insertions(+) create mode 100644 RealTime/Core/LoadingExtension.cs create mode 100644 RealTime/Core/RealTimeCore.cs create mode 100644 RealTime/Core/RealTimeMod.cs create mode 100644 RealTime/Simulation/TimeAdjustment.cs create mode 100644 RealTime/Tools/GitVersion.cs create mode 100644 RealTime/Tools/Log.cs create mode 100644 RealTime/UI/CustomTimeBar.cs create mode 100644 RealTime/UI/DateTooltipBehavior.cs create mode 100644 RealTime/UI/RealTimeUIDateTimeWrapper.cs diff --git a/RealTime/Core/LoadingExtension.cs b/RealTime/Core/LoadingExtension.cs new file mode 100644 index 00000000..c5290b63 --- /dev/null +++ b/RealTime/Core/LoadingExtension.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Core +{ + using ICities; + + public sealed class LoadingExtension : LoadingExtensionBase + { + private RealTimeCore core; + + public override void OnLevelLoaded(LoadMode mode) + { + switch (mode) + { + case LoadMode.LoadGame: + case LoadMode.NewGame: + case LoadMode.LoadScenario: + case LoadMode.NewGameFromScenario: + break; + + default: + return; + } + + core = RealTimeCore.Enable(); + } + + public override void OnLevelUnloading() + { + if (core != null) + { + core.Disable(); + core = null; + } + } + } +} \ No newline at end of file diff --git a/RealTime/Core/RealTimeCore.cs b/RealTime/Core/RealTimeCore.cs new file mode 100644 index 00000000..5789c2bd --- /dev/null +++ b/RealTime/Core/RealTimeCore.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Core +{ + using System; + using RealTime.Simulation; + using RealTime.UI; + + internal sealed class RealTimeCore + { + private readonly TimeAdjustment timeAdjustment; + private readonly CustomTimeBar timeBar; + + private bool isEnabled; + + private RealTimeCore(TimeAdjustment timeAdjustment, CustomTimeBar timeBar) + { + this.timeAdjustment = timeAdjustment; + this.timeBar = timeBar; + } + + public static RealTimeCore Enable() + { + var timeAdjustment = new TimeAdjustment(); + DateTime gameDate = timeAdjustment.Enable(); + + var customTimeBar = new CustomTimeBar(); + customTimeBar.Enable(gameDate); + + var core = new RealTimeCore(timeAdjustment, customTimeBar); + core.isEnabled = true; + return core; + } + + public void Disable() + { + if (!isEnabled) + { + return; + } + + timeAdjustment.Disable(); + timeBar.Disable(); + isEnabled = false; + } + } +} diff --git a/RealTime/Core/RealTimeMod.cs b/RealTime/Core/RealTimeMod.cs new file mode 100644 index 00000000..7f957f2c --- /dev/null +++ b/RealTime/Core/RealTimeMod.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Core +{ + using ICities; + using RealTime.Tools; + + public sealed class RealTimeMod : IUserMod + { + private readonly string modVersion = GitVersion.GetAssemblyVersion(typeof(RealTimeMod).Assembly); + + public string Name => "Real Time v" + modVersion; + + // TODO: add localization + public string Description => "Adjusts the time flow in the game to make it more real"; + + public void OnEnabled() + { + Log.Info("The 'Real Time' mod has been enabled, version: " + modVersion); + } + + public void OnDisabled() + { + Log.Info("The 'Real Time' mod has been disabled."); + } + + public void OnSettingsUI(UIHelperBase helper) + { + // TODO: imlement the options page + } + } +} diff --git a/RealTime/RealTime.csproj b/RealTime/RealTime.csproj index 25c0bd5b..051f919c 100644 --- a/RealTime/RealTime.csproj +++ b/RealTime/RealTime.csproj @@ -60,7 +60,16 @@ + + + + + + + + + diff --git a/RealTime/Simulation/TimeAdjustment.cs b/RealTime/Simulation/TimeAdjustment.cs new file mode 100644 index 00000000..6d3fa951 --- /dev/null +++ b/RealTime/Simulation/TimeAdjustment.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Simulation +{ + using System; + using RealTime.Tools; + + internal sealed class TimeAdjustment + { + private const int CustomFramesPerDay = 1 << 16; + private static readonly TimeSpan CustomTimePerFrame = new TimeSpan(24L * 3600L * 10_000_000L / CustomFramesPerDay); + + private readonly uint vanillaFramesPerDay; + private readonly TimeSpan vanillaTimePerFrame; + + public TimeAdjustment() + { + vanillaFramesPerDay = SimulationManager.DAYTIME_FRAMES; + vanillaTimePerFrame = SimulationManager.instance.m_timePerFrame; + } + + public DateTime Enable() + { + if (vanillaTimePerFrame == CustomTimePerFrame) + { + Log.Warning("The 'Real Time' mod has not been properly deactivated! Check the TimeAdjustment.Disable() calls."); + } + + return UpdateTimeSimulationValues(CustomFramesPerDay, CustomTimePerFrame); + } + + public void Disable() + { + UpdateTimeSimulationValues(vanillaFramesPerDay, vanillaTimePerFrame); + } + + private static DateTime UpdateTimeSimulationValues(uint framesPerDay, TimeSpan timePerFrame) + { + SimulationManager.DAYTIME_FRAMES = framesPerDay; + SimulationManager.DAYTIME_FRAME_TO_HOUR = 24f / SimulationManager.DAYTIME_FRAMES; + SimulationManager.DAYTIME_HOUR_TO_FRAME = SimulationManager.DAYTIME_FRAMES / 24f; + + SimulationManager sm = SimulationManager.instance; + sm.m_timePerFrame = timePerFrame; + sm.m_timeOffsetTicks = sm.m_currentGameTime.Ticks - (sm.m_currentFrameIndex * sm.m_timePerFrame.Ticks); + + sm.m_currentDayTimeHour = (float)sm.m_currentGameTime.TimeOfDay.TotalHours; + sm.m_dayTimeFrame = (uint)(SimulationManager.DAYTIME_FRAMES * sm.m_currentDayTimeHour / 24f); + sm.m_dayTimeOffsetFrames = sm.m_dayTimeFrame - sm.m_currentFrameIndex & SimulationManager.DAYTIME_FRAMES - 1; + + return sm.m_currentGameTime; + } + } +} diff --git a/RealTime/Tools/GitVersion.cs b/RealTime/Tools/GitVersion.cs new file mode 100644 index 00000000..77a4a623 --- /dev/null +++ b/RealTime/Tools/GitVersion.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Tools +{ + using System; + using System.Reflection; + + internal static class GitVersion + { + private const string GitVersionTypeName = ".GitVersionInformation"; + private const string VersionFieldName = "SemVer"; + + public static string GetAssemblyVersion(Assembly assembly) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + Type gitVersionInformationType = assembly.GetType(assembly.GetName().Name + GitVersionTypeName); + if (gitVersionInformationType == null) + { + Log.Error("Attempting to retrieve the assembly version of an assembly that is built without GitVersion support."); + return "?"; + } + + FieldInfo versionField = gitVersionInformationType.GetField(VersionFieldName); + if (versionField == null) + { + Log.Error($"Internal error: the '{GitVersionTypeName}' type has no field '{VersionFieldName}'."); + return "?"; + } + + string version = versionField.GetValue(null) as string; + if (string.IsNullOrEmpty(version)) + { + Log.Warning($"The '{GitVersionTypeName}.{VersionFieldName}' value is empty."); + return "?"; + } + + return version; + } + } +} diff --git a/RealTime/Tools/Log.cs b/RealTime/Tools/Log.cs new file mode 100644 index 00000000..369e79c0 --- /dev/null +++ b/RealTime/Tools/Log.cs @@ -0,0 +1,133 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Tools +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Text; + using System.Timers; + + internal static class Log + { +#if DEBUG + private const int FileWriteInterval = 1000; // ms + private const string LogFileName = "RealTime.log"; + private const string TypeDebug = "DBG"; + private const string TypeInfo = "INF"; + private const string TypeWarning = "WRN"; + private const string TypeError = "ERR"; + + private static readonly object SyncObject = new object(); + private static readonly Queue Storage = new Queue(); + + private static readonly Timer FlushTimer = new Timer(FileWriteInterval) { AutoReset = false }; + private static readonly string LogFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), LogFileName); + + // Note: the official Unity 5 docs state that the ThreadStaticAttribute will cause the engine to crash. + // However, this doesn't occur on my system. Anyway, this is only compiled in debug builds and won't affect the mod users. + [ThreadStatic] + private static StringBuilder messageBuilder; + + static Log() + { + FlushTimer.Elapsed += FlushTimer_Elapsed; + FlushTimer.Start(); + } +#endif + + [Conditional("DEBUG")] + public static void Debug(string text) + { +#if DEBUG + DebugLog(text, TypeDebug); +#endif + } + + public static void Info(string text) + { + UnityEngine.Debug.Log(text); + +#if DEBUG + DebugLog(text, TypeInfo); +#endif + } + + public static void Warning(string text) + { + UnityEngine.Debug.LogWarning(text); + +#if DEBUG + DebugLog(text, TypeWarning); +#endif + } + + public static void Error(string text) + { + UnityEngine.Debug.LogError(text); + +#if DEBUG + DebugLog(text, TypeError); +#endif + } + +#if DEBUG + private static void DebugLog(string text, string type) + { + if (messageBuilder == null) + { + messageBuilder = new StringBuilder(1024); + } + + messageBuilder.Length = 0; + messageBuilder.Append(DateTime.Now.ToString("HH:mm:ss.ffff")); + messageBuilder.Append('\t'); + messageBuilder.Append(type); + messageBuilder.Append('\t'); + messageBuilder.Append(text); + string message = messageBuilder.ToString(); + lock (SyncObject) + { + Storage.Enqueue(message); + } + } + + private static void FlushTimer_Elapsed(object sender, ElapsedEventArgs e) + { + List storageCopy; + lock (SyncObject) + { + if (Storage.Count == 0) + { + FlushTimer.Start(); + return; + } + + storageCopy = Storage.ToList(); + Storage.Clear(); + } + + try + { + using (StreamWriter writer = File.AppendText(LogFilePath)) + { + foreach (string line in storageCopy) + { + writer.WriteLine(line); + } + } + } + catch (Exception ex) + { + UnityEngine.Debug.LogError("Error writing to the log file: " + ex.Message); + } + + FlushTimer.Start(); + } +#endif + } +} diff --git a/RealTime/UI/CustomTimeBar.cs b/RealTime/UI/CustomTimeBar.cs new file mode 100644 index 00000000..5ebf07d4 --- /dev/null +++ b/RealTime/UI/CustomTimeBar.cs @@ -0,0 +1,143 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.UI +{ + using System; + using System.Reflection; + using ColossalFramework.UI; + using RealTime.Tools; + using UnityEngine; + + internal sealed class CustomTimeBar + { + private const string UIInfoPanel = "InfoPanel"; + private const string UIWrapperField = "m_GameTime"; + private const string UIPanelTime = "PanelTime"; + private const string UISprite = "Sprite"; + private const string UILabelTime = "Time"; + + private UIDateTimeWrapper originalWrapper; + + public void Enable(DateTime currentDate) + { + if (originalWrapper != null) + { + Log.Warning("Trying to enable the CustomTimeBar multiple times."); + return; + } + + var customWrapper = new RealTimeUIDateTimeWrapper(currentDate); + originalWrapper = SetUIDateTimeWrapper(customWrapper, true); + } + + public void Disable() + { + if (originalWrapper == null) + { + return; + } + + SetUIDateTimeWrapper(originalWrapper, false); + originalWrapper = null; + } + + private static UIDateTimeWrapper SetUIDateTimeWrapper(UIDateTimeWrapper wrapper, bool customize) + { + UIPanel infoPanel = UIView.Find(UIInfoPanel); + if (infoPanel == null) + { + Log.Warning("No UIPanel found: " + UIInfoPanel); + return null; + } + + UISprite progressSprite = GetProgressSprite(infoPanel); + if (progressSprite != null) + { + SetTooltip(progressSprite, customize); + + if (customize) + { + CustomizeTimePanel(progressSprite); + } + } + + return ReplaceUIDateTimeWrapperInPanel(infoPanel, wrapper); + } + + private static UIDateTimeWrapper ReplaceUIDateTimeWrapperInPanel(UIPanel infoPanel, UIDateTimeWrapper wrapper) + { + Bindings bindings = infoPanel.GetUIView().GetComponent(); + if (bindings == null) + { + Log.Warning($"The UIPanel '{UIInfoPanel}' contains no '{nameof(Bindings)}' component"); + return null; + } + + FieldInfo field = typeof(Bindings).GetField(UIWrapperField, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) + { + Log.Warning($"The UIPanel {UIInfoPanel} has no field '{UIWrapperField}'"); + return null; + } + + var originalWrapper = field.GetValue(bindings) as UIDateTimeWrapper; + if (originalWrapper == null) + { + Log.Warning($"The '{nameof(Bindings)}' component has no '{nameof(UIDateTimeWrapper)}'"); + return null; + } + + field.SetValue(bindings, wrapper); + return originalWrapper; + } + + private static UISprite GetProgressSprite(UIPanel infoPanel) + { + UIPanel panelTime = infoPanel.Find(UIPanelTime); + if (panelTime == null) + { + Log.Warning("No UIPanel found: " + UIPanelTime); + return null; + } + + UISprite progressSprite = panelTime.Find(UISprite); + if (progressSprite == null) + { + Log.Warning("No UISprite found: " + UISprite); + return null; + } + + return progressSprite; + } + + private static void CustomizeTimePanel(UISprite progressSprite) + { + UILabel dateLabel = progressSprite.Find(UILabelTime); + if (dateLabel == null) + { + Log.Warning("No UILabel found: " + UILabelTime); + return; + } + + dateLabel.autoSize = false; + dateLabel.size = progressSprite.size; + dateLabel.textAlignment = UIHorizontalAlignment.Center; + dateLabel.relativePosition = new Vector3(0, 0, 0); + } + + private static void SetTooltip(UIComponent component, bool customize) + { + DateTooltipBehavior tooltipBehavior = component.gameObject.GetComponent(); + if (tooltipBehavior == null && customize) + { + tooltipBehavior = component.gameObject.AddComponent(); + } + else if (tooltipBehavior != null && !customize) + { + UnityEngine.Object.Destroy(tooltipBehavior); + } + } + } +} diff --git a/RealTime/UI/DateTooltipBehavior.cs b/RealTime/UI/DateTooltipBehavior.cs new file mode 100644 index 00000000..971dc316 --- /dev/null +++ b/RealTime/UI/DateTooltipBehavior.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Tools +{ + using System; + using ColossalFramework.Globalization; + using ColossalFramework.UI; + using UnityEngine; + + internal sealed class DateTooltipBehavior : MonoBehaviour + { + private UIComponent target; + private DateTime lastValue; + private string tooltip; + + public void Start() + { + target = gameObject.GetComponent(); + } + + public void Update() + { + if (target == null) + { + return; + } + + DateTime newValue = SimulationManager.instance.m_currentGameTime; + if (lastValue.Date != newValue.Date) + { + tooltip = newValue.ToString("d", LocaleManager.cultureInfo); + } + + lastValue = newValue; + target.tooltip = tooltip; + target.RefreshTooltip(); + } + } +} diff --git a/RealTime/UI/RealTimeUIDateTimeWrapper.cs b/RealTime/UI/RealTimeUIDateTimeWrapper.cs new file mode 100644 index 00000000..90992c6d --- /dev/null +++ b/RealTime/UI/RealTimeUIDateTimeWrapper.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.UI +{ + using System; + using System.Globalization; + using ColossalFramework.Globalization; + + public sealed class RealTimeUIDateTimeWrapper : UIDateTimeWrapper + { + internal RealTimeUIDateTimeWrapper(DateTime def) + : base(def) + { + Convert(); + } + + public override void Check(DateTime newVal) + { + if (m_Value.Minute == newVal.Minute && m_Value.Hour == newVal.Hour && m_Value.DayOfWeek == newVal.DayOfWeek) + { + return; + } + + m_Value = newVal; + Convert(); + } + + private void Convert() + { + CultureInfo cultureInfo = LocaleManager.cultureInfo; + m_String = m_Value.ToString("t", cultureInfo) + ", " + m_Value.ToString("dddd", cultureInfo); + } + } +} From 1a158785dd290c04cf2c99e1e595d8a1fc4624d4 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 02:45:45 +0200 Subject: [PATCH 003/102] Add code documentation --- RealTime/Core/LoadingExtension.cs | 17 +++++++++++++-- RealTime/Core/RealTimeCore.cs | 16 ++++++++++++-- RealTime/Core/RealTimeMod.cs | 21 ++++++++++++++++++ RealTime/Simulation/TimeAdjustment.cs | 14 ++++++++++++ RealTime/Tools/GitVersion.cs | 15 +++++++++++++ RealTime/Tools/Log.cs | 27 +++++++++++++++++++++++- RealTime/UI/CustomTimeBar.cs | 13 ++++++++++++ RealTime/UI/DateTooltipBehavior.cs | 13 ++++++++++++ RealTime/UI/RealTimeUIDateTimeWrapper.cs | 20 ++++++++++++++++-- 9 files changed, 149 insertions(+), 7 deletions(-) diff --git a/RealTime/Core/LoadingExtension.cs b/RealTime/Core/LoadingExtension.cs index c5290b63..30aab0ea 100644 --- a/RealTime/Core/LoadingExtension.cs +++ b/RealTime/Core/LoadingExtension.cs @@ -6,10 +6,19 @@ namespace RealTime.Core { using ICities; + /// + /// Activates this mod for a loaded game level. This is the main entry point of the mod's logic. + /// public sealed class LoadingExtension : LoadingExtensionBase { private RealTimeCore core; + /// + /// Calles when a game level is loaded. If applicable, activates the Real Time mod + /// for the loaded level. + /// + /// + /// The a game level is loaded in. public override void OnLevelLoaded(LoadMode mode) { switch (mode) @@ -24,14 +33,18 @@ public override void OnLevelLoaded(LoadMode mode) return; } - core = RealTimeCore.Enable(); + core = RealTimeCore.Run(); } + /// + /// Calles when a game level is about to be unloaded. If the Real Time mod was activated + /// for this level, deactivates the mod for this level. + /// public override void OnLevelUnloading() { if (core != null) { - core.Disable(); + core.Stop(); core = null; } } diff --git a/RealTime/Core/RealTimeCore.cs b/RealTime/Core/RealTimeCore.cs index 5789c2bd..5ad4b05f 100644 --- a/RealTime/Core/RealTimeCore.cs +++ b/RealTime/Core/RealTimeCore.cs @@ -8,6 +8,10 @@ namespace RealTime.Core using RealTime.Simulation; using RealTime.UI; + /// + /// The core component of the Real Time mod. Activates and deactivates + /// the different parts of the mod's logic. + /// internal sealed class RealTimeCore { private readonly TimeAdjustment timeAdjustment; @@ -21,7 +25,12 @@ private RealTimeCore(TimeAdjustment timeAdjustment, CustomTimeBar timeBar) this.timeBar = timeBar; } - public static RealTimeCore Enable() + /// + /// Runs the mod by activating its parts. + /// + /// + /// A instance that can be used to stop the mod. + public static RealTimeCore Run() { var timeAdjustment = new TimeAdjustment(); DateTime gameDate = timeAdjustment.Enable(); @@ -34,7 +43,10 @@ public static RealTimeCore Enable() return core; } - public void Disable() + /// + /// Stops the mod by deactivating all its parts. + /// + public void Stop() { if (!isEnabled) { diff --git a/RealTime/Core/RealTimeMod.cs b/RealTime/Core/RealTimeMod.cs index 7f957f2c..4b0e65d7 100644 --- a/RealTime/Core/RealTimeMod.cs +++ b/RealTime/Core/RealTimeMod.cs @@ -7,25 +7,46 @@ namespace RealTime.Core using ICities; using RealTime.Tools; + /// + /// The main class of the Real Time mod. + /// public sealed class RealTimeMod : IUserMod { private readonly string modVersion = GitVersion.GetAssemblyVersion(typeof(RealTimeMod).Assembly); + /// + /// Gets the name of this mod. + /// public string Name => "Real Time v" + modVersion; + /// + /// Gets the description string of this mod. + /// // TODO: add localization public string Description => "Adjusts the time flow in the game to make it more real"; + /// + /// Called when this mod is enabled. + /// public void OnEnabled() { Log.Info("The 'Real Time' mod has been enabled, version: " + modVersion); } + /// + /// Called when this mod is disabled. + /// public void OnDisabled() { Log.Info("The 'Real Time' mod has been disabled."); } + /// + /// Called when this mod's settings page needs to be displayed. + /// + /// + /// An reference that can be used + /// to construct the mod's settings page. public void OnSettingsUI(UIHelperBase helper) { // TODO: imlement the options page diff --git a/RealTime/Simulation/TimeAdjustment.cs b/RealTime/Simulation/TimeAdjustment.cs index 6d3fa951..75f8cebb 100644 --- a/RealTime/Simulation/TimeAdjustment.cs +++ b/RealTime/Simulation/TimeAdjustment.cs @@ -7,6 +7,9 @@ namespace RealTime.Simulation using System; using RealTime.Tools; + /// + /// Manages the customized time adjustment. This class depends on the class. + /// internal sealed class TimeAdjustment { private const int CustomFramesPerDay = 1 << 16; @@ -15,12 +18,20 @@ internal sealed class TimeAdjustment private readonly uint vanillaFramesPerDay; private readonly TimeSpan vanillaTimePerFrame; + /// + /// Initializes a new instance of the class. + /// public TimeAdjustment() { vanillaFramesPerDay = SimulationManager.DAYTIME_FRAMES; vanillaTimePerFrame = SimulationManager.instance.m_timePerFrame; } + /// + /// Enables the customized time adjustment. + /// + /// + /// The current game date and time. public DateTime Enable() { if (vanillaTimePerFrame == CustomTimePerFrame) @@ -31,6 +42,9 @@ public DateTime Enable() return UpdateTimeSimulationValues(CustomFramesPerDay, CustomTimePerFrame); } + /// + /// Disables the customized time adjustment restoring the default vanilla values. + /// public void Disable() { UpdateTimeSimulationValues(vanillaFramesPerDay, vanillaTimePerFrame); diff --git a/RealTime/Tools/GitVersion.cs b/RealTime/Tools/GitVersion.cs index 77a4a623..eccf1614 100644 --- a/RealTime/Tools/GitVersion.cs +++ b/RealTime/Tools/GitVersion.cs @@ -7,11 +7,26 @@ namespace RealTime.Tools using System; using System.Reflection; + /// + /// A helper class that interacts with the special types injected by the GitVersion toolset. + /// internal static class GitVersion { private const string GitVersionTypeName = ".GitVersionInformation"; private const string VersionFieldName = "SemVer"; + /// + /// Gets a string representation of the full semantic assembly version of the provided . + /// This assembly should be built using the GitVersion toolset; otherwise, a "?" version string will + /// be returned. + /// + /// + /// Thrown when the argument is null. + /// + /// An to get the version of. Should be built using the GitVersion toolset. + /// + /// A string representation of the full semantic version of the provided , + /// or "?" if the version could not be determined. public static string GetAssemblyVersion(Assembly assembly) { if (assembly == null) diff --git a/RealTime/Tools/Log.cs b/RealTime/Tools/Log.cs index 369e79c0..57d13f96 100644 --- a/RealTime/Tools/Log.cs +++ b/RealTime/Tools/Log.cs @@ -12,6 +12,11 @@ namespace RealTime.Tools using System.Text; using System.Timers; + /// + /// Manages the logging. In 'Release' mode, only logs to the Unity's debug log. + /// Also, the method calls will be eliminated in this mode. + /// In 'Debug' mode logs additionaly to a text file that is located on the Desktop. + /// internal static class Log { #if DEBUG @@ -40,6 +45,11 @@ static Log() } #endif + /// + /// Logs a debug information. This method won't be compiled in the 'Release' mode. + /// + /// + /// The text to log. [Conditional("DEBUG")] public static void Debug(string text) { @@ -48,6 +58,11 @@ public static void Debug(string text) #endif } + /// + /// Logs an information text. + /// + /// + /// The text to log. public static void Info(string text) { UnityEngine.Debug.Log(text); @@ -57,6 +72,11 @@ public static void Info(string text) #endif } + /// + /// Logs a warning text. + /// + /// + /// The text to log. public static void Warning(string text) { UnityEngine.Debug.LogWarning(text); @@ -66,6 +86,11 @@ public static void Warning(string text) #endif } + /// + /// Logs an error text. + /// + /// + /// The text to log. public static void Error(string text) { UnityEngine.Debug.LogError(text); @@ -87,7 +112,7 @@ private static void DebugLog(string text, string type) messageBuilder.Append(DateTime.Now.ToString("HH:mm:ss.ffff")); messageBuilder.Append('\t'); messageBuilder.Append(type); - messageBuilder.Append('\t'); + messageBuilder.Append("\t\t"); messageBuilder.Append(text); string message = messageBuilder.ToString(); lock (SyncObject) diff --git a/RealTime/UI/CustomTimeBar.cs b/RealTime/UI/CustomTimeBar.cs index 5ebf07d4..553c34bf 100644 --- a/RealTime/UI/CustomTimeBar.cs +++ b/RealTime/UI/CustomTimeBar.cs @@ -10,6 +10,10 @@ namespace RealTime.UI using RealTime.Tools; using UnityEngine; + /// + /// Manages the time bar customization. The customized time bar will show the day of the week + /// and the current time instead of the date. The date will be displayed in the time bar's tooltip. + /// internal sealed class CustomTimeBar { private const string UIInfoPanel = "InfoPanel"; @@ -20,6 +24,11 @@ internal sealed class CustomTimeBar private UIDateTimeWrapper originalWrapper; + /// + /// Enables the time bar customization. If the customization is already enabled, has no effect. + /// + /// + /// The current game date to set as the time bar's initial value. public void Enable(DateTime currentDate) { if (originalWrapper != null) @@ -32,6 +41,10 @@ public void Enable(DateTime currentDate) originalWrapper = SetUIDateTimeWrapper(customWrapper, true); } + /// + /// Disables the time bar configuration. If the customization is already disabled or was not enabled, + /// has no effect. + /// public void Disable() { if (originalWrapper == null) diff --git a/RealTime/UI/DateTooltipBehavior.cs b/RealTime/UI/DateTooltipBehavior.cs index 971dc316..f09652b4 100644 --- a/RealTime/UI/DateTooltipBehavior.cs +++ b/RealTime/UI/DateTooltipBehavior.cs @@ -9,17 +9,30 @@ namespace RealTime.Tools using ColossalFramework.UI; using UnityEngine; + /// + /// A script that can be attached to any . + /// Observes the value and sets the tooltip + /// of the to the date part of that value. The current + /// is used for string conversion. + /// internal sealed class DateTooltipBehavior : MonoBehaviour { private UIComponent target; private DateTime lastValue; private string tooltip; + /// + /// is called on the frame when a script is enabled + /// just before any of the methods are called the first time. + /// public void Start() { target = gameObject.GetComponent(); } + /// + /// is called every frame, if the is enabled. + /// public void Update() { if (target == null) diff --git a/RealTime/UI/RealTimeUIDateTimeWrapper.cs b/RealTime/UI/RealTimeUIDateTimeWrapper.cs index 90992c6d..f10f26bd 100644 --- a/RealTime/UI/RealTimeUIDateTimeWrapper.cs +++ b/RealTime/UI/RealTimeUIDateTimeWrapper.cs @@ -8,14 +8,30 @@ namespace RealTime.UI using System.Globalization; using ColossalFramework.Globalization; + /// + /// A wrapper that converts a value to a string representation + /// containing the day of week and the time parts. The current + /// is used for string conversion. + /// public sealed class RealTimeUIDateTimeWrapper : UIDateTimeWrapper { - internal RealTimeUIDateTimeWrapper(DateTime def) - : base(def) + /// + /// Initializes a new instance of the class. + /// + /// + /// The initial value to use for conversion. + internal RealTimeUIDateTimeWrapper(DateTime initial) + : base(initial) { Convert(); } + /// + /// Checks the provided value whether it should be converted to a + /// string representation. Converts the value when necessary. + /// + /// + /// The value to process. public override void Check(DateTime newVal) { if (m_Value.Minute == newVal.Minute && m_Value.Hour == newVal.Hour && m_Value.DayOfWeek == newVal.DayOfWeek) From 7e46a6dd384cf08a8a4a6f6364584fe97570aee8 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 03:10:33 +0200 Subject: [PATCH 004/102] Add the basic mod's Configuration, no UI yet --- RealTime/Configuration/Configuration.cs | 123 ++++++++++++++++++++++++ RealTime/RealTime.csproj | 1 + 2 files changed, 124 insertions(+) create mode 100644 RealTime/Configuration/Configuration.cs diff --git a/RealTime/Configuration/Configuration.cs b/RealTime/Configuration/Configuration.cs new file mode 100644 index 00000000..2ab7e4b6 --- /dev/null +++ b/RealTime/Configuration/Configuration.cs @@ -0,0 +1,123 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Config +{ + /// + /// The mod's configuration. + /// + internal sealed class Configuration + { + /// + /// Gets or sets the current mod configuration. + /// + public static Configuration Current { get; set; } = new Configuration(); + + /// + /// Gets or sets a value indicating whether the Weekends are enabled. Cims don't go to work on Weekends. + /// + public bool EnableWeekends { get; set; } = true; + + /// + /// Gets or sets a value indicating whether Cims should go out at lunch for food. + /// + public bool SimulateLunchTime { get; set; } = true; + + /// + /// Gets or sets the percentage of the Cims that will go out for lunch. + /// + public float LunchQuota { get; set; } = 0.6f; + + /// + /// Gets or sets a value indicating whether Cims will search locally for buildings to visit, + /// rather than heading to a random building. + /// + public bool AllowLocalBuildingSearch { get; set; } = true; + + /// + /// Gets or sets the percentage of the population that will search locally for buildings. + /// Valid values are 0.0 to 1.0. + /// + public float LocalBuildingSearchQuota { get; set; } = 0.5f; + + /// + /// Gets or sets the percentage of the Cims that will attend their way to work or to school + /// as soon as possible. + /// + public float EarlyStartQuota { get; set; } = 0.4f; + + /// + /// Gets or sets the percentage of the Cims that will leave their work or school + /// on time (no overtime!). + /// + public float LeaveOnTimeQuota { get; set; } = 0.8f; + + /// + /// Gets or sets the earliest daytime hour when the young Cims head to school or university. + /// + public float MinSchoolHour { get; set; } = 7.5f; + + /// + /// Gets or sets the latest daytime hour when the young Cims head to school or university. + /// + public float StartSchoolHour { get; set; } = 8f; + + /// + /// Gets or sets the earliest daytime hour when the young Cims return from school or university. + /// + public float EndSchoolHour { get; set; } = 13.5f; + + /// + /// Gets or sets the latest daytime hour when the young Cims return from school or university. + /// + public float MaxSchoolHour { get; set; } = 14f; + + /// + /// Gets or sets the earliest daytime hour when the adult Cims head to work. + /// + public float MinWorkHour { get; set; } = 6f; + + /// + /// Gets or sets the latest daytime hour when the adult Cims head to work. + /// + public float StartWorkHour { get; set; } = 9f; + + /// + /// Gets or sets the earliest daytime hour when the adult Cims return from work. + /// + public float EndWorkHour { get; set; } = 15f; + + /// + /// Gets or sets the latest daytime hour when the adult Cims return from work. + /// + public float MaxWorkHour { get; set; } = 18f; + + /// + /// Gets or sets the daytime hour when the Cims go out for lunch. + /// + public float LunchBegin { get; set; } = 12f; + + /// + /// Gets or sets the daytime hour when the Cims return for lunch. + /// + public float LunchEnd { get; set; } = 12.75f; + + /// + /// Gets or sets the minimum school duration (in hours). + /// Cims will not travel to school or university if the time spent there will not reach at least this value. + /// + public float MinSchoolDuration { get; set; } = 1f; + + /// + /// Gets or sets the minimum work duration (in hours). + /// Cims will not travel to work if the time spent there will not reach at least this value. + /// + public float MinWorkDuration { get; set; } = 1f; + + /// + /// Gets or sets the assumed maximum travel time (in hours) to work or to school or to university. + /// + public float WorkOrSchoolTravelTime { get; set; } = 2f; + } +} diff --git a/RealTime/RealTime.csproj b/RealTime/RealTime.csproj index 051f919c..bea70ba1 100644 --- a/RealTime/RealTime.csproj +++ b/RealTime/RealTime.csproj @@ -60,6 +60,7 @@ + From 64655767108659bed916584ceb7d4d1fc4a5df9b Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 03:11:07 +0200 Subject: [PATCH 005/102] Add the first implementation of the customized Cims logic --- RealTime/AI/ILogic.cs | 88 +++++++ RealTime/AI/Logic.cs | 346 +++++++++++++++++++++++++++ RealTime/RealTime.csproj | 4 + RealTime/Tools/DateTimeExtensions.cs | 55 +++++ 4 files changed, 493 insertions(+) create mode 100644 RealTime/AI/ILogic.cs create mode 100644 RealTime/AI/Logic.cs create mode 100644 RealTime/Tools/DateTimeExtensions.cs diff --git a/RealTime/AI/ILogic.cs b/RealTime/AI/ILogic.cs new file mode 100644 index 00000000..000fffa1 --- /dev/null +++ b/RealTime/AI/ILogic.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.AI +{ + /// + /// Describes the basic Real Time customized logic for the Cims. + /// + internal interface ILogic + { + /// + /// Gets a value indicating whether the current game time represents a weekend (no work on those days). + /// Cims have free time. + /// + bool IsWeekend { get; } + + /// + /// Gets a value indicating whether the current game time represents a work day. + /// Cims go to work. + /// + bool IsWorkDay { get; } + + /// + /// Gets a value indicating whether the current game time represents a work hour. + /// Cims are working. + /// + bool IsWorkHour { get; } + + /// + /// Gets a value indicating whether the current game time represents a school hour. + /// Students are studying. + /// + bool IsSchoolHour { get; } + + /// + /// Gets a value indicating whether the current game time represents a lunch hour. + /// Cims are going out for lunch. + /// + bool IsLunchHour { get; } + + /// + /// Determines whether the provided should go to work. + /// + /// + /// The to check. + /// True to ignore the time constraints for the minimum work duration. + /// + /// True if the should go go to work; otherwise, false. + bool ShouldGoToWork(ref Citizen citizen, bool ignoreMinimumDuration = false); + + /// + /// Determines whether the provided should return back from work. + /// + /// + /// The to check. + /// + /// True if the should return back from work; otherwise, false. + bool ShouldReturnFromWork(ref Citizen citizen); + + /// + /// Determines whether the provided should go out for lunch. + /// + /// + /// The to check. + /// + /// True if the should go out for lunch; otherwise, false. + bool ShouldGoToLunch(ref Citizen citizen); + + /// + /// Determines whether the provided should find some entertainment. + /// + /// + /// The to check. + /// + /// True if the should find entertainment; otherwise, false. + bool ShouldFindEntertainment(ref Citizen citizen); + + /// + /// Determines whether the provided can stay out. + /// + /// + /// The to check. + /// + /// True if the can stay out; otherwise, false. + bool CanStayOut(ref Citizen citizen); + } +} \ No newline at end of file diff --git a/RealTime/AI/Logic.cs b/RealTime/AI/Logic.cs new file mode 100644 index 00000000..8e41d9da --- /dev/null +++ b/RealTime/AI/Logic.cs @@ -0,0 +1,346 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.AI +{ + using System; + using ColossalFramework.Math; + using RealTime.Config; + using RealTime.Tools; + using UnityEngine; + + /// + /// The implementation. Describes the basic Real Time customized logic for the Cims. + /// + internal sealed class Logic : ILogic + { + private readonly Configuration config; + private readonly Func timeProvider; + private readonly Randomizer randomizer; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Thrown when any argument is null. + /// + /// A instance containing the mod's configuration. + /// A method that can provide the current game date and time. + /// A instance to use for randomization. + public Logic(Configuration config, Func timeProvider, Randomizer randomizer) + { + this.config = config ?? throw new ArgumentNullException(nameof(config)); + this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider)); + this.randomizer = randomizer; + } + + /// + /// Gets a value indicating whether the current game time represents a weekend (no work on those days). + /// Cims have free time. + /// + public bool IsWeekend => config.EnableWeekends && timeProvider().IsWeekend(); + + /// + /// Gets a value indicating whether the current game time represents a work day. + /// Cims go to work. + /// + public bool IsWorkDay => !config.EnableWeekends || !timeProvider().IsWeekend(); + + /// + /// Gets a value indicating whether the current game time represents a work hour. + /// Cims are working. + /// + public bool IsWorkHour => IsWorkDayAndBetweenHours(config.MinWorkHour, config.EndWorkHour); + + /// + /// Gets a value indicating whether the current game time represents a school hour. + /// Students are studying. + /// + public bool IsSchoolHour => IsWorkDayAndBetweenHours(config.MinSchoolHour, config.EndSchoolHour); + + /// + /// Gets a value indicating whether the current game time represents a lunch hour. + /// Cims are going out for lunch. + /// + public bool IsLunchHour => IsWorkDayAndBetweenHours(config.LunchBegin, config.LunchEnd); + + // Hours to attempt to go to school, if not already at school. Don't want them travelling only to go home straight away + private float MaxSchoolAttemptHour => config.EndSchoolHour - config.MinSchoolDuration; + + // Hours to attempt to go to work, if not already at work. Don't want them travelling only to go home straight away + private float MaxWorkAttemptHour => config.EndWorkHour - config.MinWorkDuration; + + /// + /// Determines whether the provided should go to work. + /// + /// + /// The to check. + /// True to ignore the time constraints for the minimum work duration. + /// + /// True if the should go go to work; otherwise, false. + public bool ShouldGoToWork(ref Citizen citizen, bool ignoreMinimumDuration = false) + { + if (!CanWorkOrStudyToday(ref citizen)) + { + return false; + } + + float currentHour = timeProvider().HourOfDay(); + + switch (Citizen.GetAgeGroup(citizen.Age)) + { + case Citizen.AgeGroup.Child: + case Citizen.AgeGroup.Teen: + if (currentHour > config.MinSchoolHour - config.WorkOrSchoolTravelTime + && currentHour < config.StartSchoolHour - config.WorkOrSchoolTravelTime) + { + return randomizer.Int32(100) < config.EarlyStartQuota * 100; + } + else if (currentHour >= config.StartSchoolHour - config.WorkOrSchoolTravelTime + && currentHour < (ignoreMinimumDuration ? config.EndSchoolHour : MaxSchoolAttemptHour)) + { + return true; + } + + break; + + case Citizen.AgeGroup.Young: + case Citizen.AgeGroup.Adult: + if (currentHour > config.MinWorkHour - config.WorkOrSchoolTravelTime + && currentHour < config.StartWorkHour - config.WorkOrSchoolTravelTime) + { + return randomizer.Int32(100) < config.EarlyStartQuota * 100; + } + else if (currentHour >= config.StartWorkHour - config.WorkOrSchoolTravelTime + && currentHour < (ignoreMinimumDuration ? config.EndWorkHour : MaxWorkAttemptHour)) + { + return true; + } + + break; + } + + return false; + } + + /// + /// Determines whether the provided should return back from work. + /// + /// + /// The to check. + /// + /// True if the should return back from work; otherwise, false. + public bool ShouldReturnFromWork(ref Citizen citizen) + { + if (IsWeekend) + { + return true; + } + + float currentHour = timeProvider().HourOfDay(); + + switch (Citizen.GetAgeGroup(citizen.Age)) + { + case Citizen.AgeGroup.Child: + case Citizen.AgeGroup.Teen: + if (currentHour >= config.EndSchoolHour && currentHour < config.MaxSchoolHour) + { + return randomizer.Int32(100) < config.LeaveOnTimeQuota * 100; + } + else if (currentHour > config.MaxSchoolHour || currentHour < config.MinSchoolHour) + { + return true; + } + + break; + + case Citizen.AgeGroup.Young: + case Citizen.AgeGroup.Adult: + if (currentHour >= config.EndWorkHour && currentHour < config.MaxWorkHour) + { + return randomizer.Int32(100) < config.LeaveOnTimeQuota * 100; + } + else if (currentHour > config.MaxWorkHour || currentHour < config.MinWorkHour) + { + return true; + } + + break; + + default: + return true; + } + + return false; + } + + /// + /// Determines whether the provided should go out for lunch. + /// + /// + /// The to check. + /// + /// True if the should go out for lunch; otherwise, false. + public bool ShouldGoToLunch(ref Citizen citizen) + { + if (!config.SimulateLunchTime) + { + return false; + } + + switch (Citizen.GetAgeGroup(citizen.Age)) + { + case Citizen.AgeGroup.Child: + return false; + + case Citizen.AgeGroup.Teen: + case Citizen.AgeGroup.Young: + case Citizen.AgeGroup.Adult: + case Citizen.AgeGroup.Senior: + break; + } + + float currentHour = timeProvider().HourOfDay(); + if (currentHour > config.LunchBegin && currentHour < config.LunchEnd) + { + return randomizer.Int32(100) < config.LunchQuota * 100; + } + + return false; + } + + /// + /// Determines whether the provided should find some entertainment. + /// + /// + /// The to check. + /// + /// True if the should find entertainment; otherwise, false. + public bool ShouldFindEntertainment(ref Citizen citizen) + { + float dayHourStart = 7f; + float dayHourEnd = 20f; + + switch (Citizen.GetAgeGroup(citizen.Age)) + { + case Citizen.AgeGroup.Child: + dayHourStart = 8f; + dayHourEnd = 18f; + break; + + case Citizen.AgeGroup.Teen: + dayHourStart = 10f; + dayHourEnd = 20f; + break; + + case Citizen.AgeGroup.Young: + case Citizen.AgeGroup.Adult: + dayHourStart = 7f; + dayHourEnd = 20f; + break; + + case Citizen.AgeGroup.Senior: + dayHourStart = 6f; + dayHourEnd = 18f; + break; + } + + float currentHour = timeProvider().HourOfDay(); + if (currentHour > dayHourStart && currentHour < dayHourEnd) + { + return randomizer.Int32(100) < GetGoOutThroughDayChance(citizen.Age) * 100; + } + else + { + return randomizer.Int32(100) < GetGoOutAtNightChance(citizen.Age) * 100; + } + } + + /// + /// Determines whether the provided can stay out. + /// + /// + /// The to check. + /// + /// True if the can stay out; otherwise, false. + public bool CanStayOut(ref Citizen citizen) + { + float currentHour = timeProvider().HourOfDay(); + return (currentHour >= 7f && currentHour < 20f) || GetGoOutAtNightChance(citizen.Age) > 0; + } + + private bool IsWorkDayAndBetweenHours(float fromInclusive, float toExclusive) + { + float currentHour = timeProvider().HourOfDay(); + return IsWorkDay && (currentHour >= fromInclusive && currentHour < toExclusive); + } + + private bool CanWorkOrStudyToday(ref Citizen citizen) + { + return citizen.m_workBuilding != 0 && IsWorkDay; + } + + private bool IsAfterLunchTime(float afterLunchHours) + { + return IsWorkDay && timeProvider().HourOfDay() < (config.LunchEnd + afterLunchHours); + } + + private float GetGoOutAtNightChance(int age) + { + float currentHour = timeProvider().HourOfDay(); + int weekendMultiplier = IsWeekend && timeProvider().IsWeekendAfter(TimeSpan.FromHours(12)) + ? currentHour > 12f + ? 6 + : Mathf.RoundToInt(Mathf.Clamp(-((currentHour * 1.5f) - 6f), 0f, 6f)) + : 1; + + switch (Citizen.GetAgeGroup(age)) + { + case Citizen.AgeGroup.Child: + case Citizen.AgeGroup.Senior: + return 0; + + case Citizen.AgeGroup.Teen: + return 0.1f * weekendMultiplier; + + case Citizen.AgeGroup.Young: + return 0.08f * weekendMultiplier; + + case Citizen.AgeGroup.Adult: + return 0.02f * weekendMultiplier; + + default: + return 0; + } + } + + private float GetGoOutThroughDayChance(int age) + { + int weekendMultiplier = IsWeekend && timeProvider().IsWeekendAfter(TimeSpan.FromHours(12)) + ? 4 + : 1; + + switch (Citizen.GetAgeGroup(age)) + { + case Citizen.AgeGroup.Child: + return 0.6f; + + case Citizen.AgeGroup.Teen: + return 0.13f * weekendMultiplier; + + case Citizen.AgeGroup.Young: + return 0.12f * weekendMultiplier; + + case Citizen.AgeGroup.Adult: + return 0.1f * weekendMultiplier; + + case Citizen.AgeGroup.Senior: + return 0.06f; + + default: + return 0; + } + } + } +} diff --git a/RealTime/RealTime.csproj b/RealTime/RealTime.csproj index bea70ba1..75ed2e73 100644 --- a/RealTime/RealTime.csproj +++ b/RealTime/RealTime.csproj @@ -60,12 +60,15 @@ + + + @@ -81,6 +84,7 @@ + mkdir "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)" diff --git a/RealTime/Tools/DateTimeExtensions.cs b/RealTime/Tools/DateTimeExtensions.cs new file mode 100644 index 00000000..e63556c6 --- /dev/null +++ b/RealTime/Tools/DateTimeExtensions.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Tools +{ + using System; + + /// + /// A class containing various extension methods for the struct. + /// + internal static class DateTimeExtensions + { + /// + /// Determines whether the 's day of week is Saturday or Sunday. + /// + /// + /// The to check. + /// + /// True if the 's day of week value is Saturday or Sunday; + /// otherwise, false. + public static bool IsWeekend(this DateTime dateTime) + { + return dateTime.DayOfWeek == DayOfWeek.Saturday || dateTime.DayOfWeek == DayOfWeek.Sunday; + } + + /// + /// Determines whether the 's day of week will still be Saturday or Sunday + /// after the provided time is elapsed. + /// + /// + /// The base to check. + /// A that represents an interval relative to the + /// to check. + /// + /// True if the 's day of week value is Saturday or Sunday after + /// the provided time is elapsed; otherwise, false. + public static bool IsWeekendAfter(this DateTime dateTime, TimeSpan interval) + { + return (dateTime + interval).IsWeekend(); + } + + /// + /// Gets a value representing the current day's hour. + /// + /// + /// The to act on. + /// + /// A value that represents the cuurent day's hour. + public static float HourOfDay(this DateTime dateTime) + { + return (float)dateTime.TimeOfDay.TotalHours; + } + } +} From 36f868fadefce6770a28c3de7cff3c253b948dee Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 03:46:15 +0200 Subject: [PATCH 006/102] Move the solution into the src directory --- {RealTime => src}/RealTime.sln | 2 +- {RealTime => src/RealTime}/AI/ILogic.cs | 0 {RealTime => src/RealTime}/AI/Logic.cs | 0 {RealTime => src/RealTime}/BuildEnvironment/RealTime.ruleset | 0 {RealTime => src/RealTime}/BuildEnvironment/packages.config | 0 {RealTime => src/RealTime}/BuildEnvironment/stylecop.json | 0 {RealTime => src/RealTime}/Configuration/Configuration.cs | 0 {RealTime => src/RealTime}/Core/LoadingExtension.cs | 0 {RealTime => src/RealTime}/Core/RealTimeCore.cs | 0 {RealTime => src/RealTime}/Core/RealTimeMod.cs | 0 {RealTime => src/RealTime}/Properties/AssemblyInfo.cs | 0 {RealTime => src/RealTime}/RealTime.csproj | 4 ++-- {RealTime => src/RealTime}/Simulation/TimeAdjustment.cs | 0 {RealTime => src/RealTime}/Tools/DateTimeExtensions.cs | 0 {RealTime => src/RealTime}/Tools/GitVersion.cs | 0 {RealTime => src/RealTime}/Tools/Log.cs | 0 {RealTime => src/RealTime}/UI/CustomTimeBar.cs | 0 {RealTime => src/RealTime}/UI/DateTooltipBehavior.cs | 0 {RealTime => src/RealTime}/UI/RealTimeUIDateTimeWrapper.cs | 0 19 files changed, 3 insertions(+), 3 deletions(-) rename {RealTime => src}/RealTime.sln (93%) rename {RealTime => src/RealTime}/AI/ILogic.cs (100%) rename {RealTime => src/RealTime}/AI/Logic.cs (100%) rename {RealTime => src/RealTime}/BuildEnvironment/RealTime.ruleset (100%) rename {RealTime => src/RealTime}/BuildEnvironment/packages.config (100%) rename {RealTime => src/RealTime}/BuildEnvironment/stylecop.json (100%) rename {RealTime => src/RealTime}/Configuration/Configuration.cs (100%) rename {RealTime => src/RealTime}/Core/LoadingExtension.cs (100%) rename {RealTime => src/RealTime}/Core/RealTimeCore.cs (100%) rename {RealTime => src/RealTime}/Core/RealTimeMod.cs (100%) rename {RealTime => src/RealTime}/Properties/AssemblyInfo.cs (100%) rename {RealTime => src/RealTime}/RealTime.csproj (98%) rename {RealTime => src/RealTime}/Simulation/TimeAdjustment.cs (100%) rename {RealTime => src/RealTime}/Tools/DateTimeExtensions.cs (100%) rename {RealTime => src/RealTime}/Tools/GitVersion.cs (100%) rename {RealTime => src/RealTime}/Tools/Log.cs (100%) rename {RealTime => src/RealTime}/UI/CustomTimeBar.cs (100%) rename {RealTime => src/RealTime}/UI/DateTooltipBehavior.cs (100%) rename {RealTime => src/RealTime}/UI/RealTimeUIDateTimeWrapper.cs (100%) diff --git a/RealTime/RealTime.sln b/src/RealTime.sln similarity index 93% rename from RealTime/RealTime.sln rename to src/RealTime.sln index f9e91c4b..19991b8e 100644 --- a/RealTime/RealTime.sln +++ b/src/RealTime.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealTime", "RealTime.csproj", "{7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealTime", "RealTime\RealTime.csproj", "{7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/RealTime/AI/ILogic.cs b/src/RealTime/AI/ILogic.cs similarity index 100% rename from RealTime/AI/ILogic.cs rename to src/RealTime/AI/ILogic.cs diff --git a/RealTime/AI/Logic.cs b/src/RealTime/AI/Logic.cs similarity index 100% rename from RealTime/AI/Logic.cs rename to src/RealTime/AI/Logic.cs diff --git a/RealTime/BuildEnvironment/RealTime.ruleset b/src/RealTime/BuildEnvironment/RealTime.ruleset similarity index 100% rename from RealTime/BuildEnvironment/RealTime.ruleset rename to src/RealTime/BuildEnvironment/RealTime.ruleset diff --git a/RealTime/BuildEnvironment/packages.config b/src/RealTime/BuildEnvironment/packages.config similarity index 100% rename from RealTime/BuildEnvironment/packages.config rename to src/RealTime/BuildEnvironment/packages.config diff --git a/RealTime/BuildEnvironment/stylecop.json b/src/RealTime/BuildEnvironment/stylecop.json similarity index 100% rename from RealTime/BuildEnvironment/stylecop.json rename to src/RealTime/BuildEnvironment/stylecop.json diff --git a/RealTime/Configuration/Configuration.cs b/src/RealTime/Configuration/Configuration.cs similarity index 100% rename from RealTime/Configuration/Configuration.cs rename to src/RealTime/Configuration/Configuration.cs diff --git a/RealTime/Core/LoadingExtension.cs b/src/RealTime/Core/LoadingExtension.cs similarity index 100% rename from RealTime/Core/LoadingExtension.cs rename to src/RealTime/Core/LoadingExtension.cs diff --git a/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs similarity index 100% rename from RealTime/Core/RealTimeCore.cs rename to src/RealTime/Core/RealTimeCore.cs diff --git a/RealTime/Core/RealTimeMod.cs b/src/RealTime/Core/RealTimeMod.cs similarity index 100% rename from RealTime/Core/RealTimeMod.cs rename to src/RealTime/Core/RealTimeMod.cs diff --git a/RealTime/Properties/AssemblyInfo.cs b/src/RealTime/Properties/AssemblyInfo.cs similarity index 100% rename from RealTime/Properties/AssemblyInfo.cs rename to src/RealTime/Properties/AssemblyInfo.cs diff --git a/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj similarity index 98% rename from RealTime/RealTime.csproj rename to src/RealTime/RealTime.csproj index 75ed2e73..b8d2751b 100644 --- a/RealTime/RealTime.csproj +++ b/src/RealTime/RealTime.csproj @@ -19,7 +19,7 @@ true full false - bin\Debug\ + ..\bin\Debug\ DEBUG;TRACE prompt 4 @@ -31,7 +31,7 @@ pdbonly true - bin\Release\ + ..\bin\Release\ TRACE prompt 4 diff --git a/RealTime/Simulation/TimeAdjustment.cs b/src/RealTime/Simulation/TimeAdjustment.cs similarity index 100% rename from RealTime/Simulation/TimeAdjustment.cs rename to src/RealTime/Simulation/TimeAdjustment.cs diff --git a/RealTime/Tools/DateTimeExtensions.cs b/src/RealTime/Tools/DateTimeExtensions.cs similarity index 100% rename from RealTime/Tools/DateTimeExtensions.cs rename to src/RealTime/Tools/DateTimeExtensions.cs diff --git a/RealTime/Tools/GitVersion.cs b/src/RealTime/Tools/GitVersion.cs similarity index 100% rename from RealTime/Tools/GitVersion.cs rename to src/RealTime/Tools/GitVersion.cs diff --git a/RealTime/Tools/Log.cs b/src/RealTime/Tools/Log.cs similarity index 100% rename from RealTime/Tools/Log.cs rename to src/RealTime/Tools/Log.cs diff --git a/RealTime/UI/CustomTimeBar.cs b/src/RealTime/UI/CustomTimeBar.cs similarity index 100% rename from RealTime/UI/CustomTimeBar.cs rename to src/RealTime/UI/CustomTimeBar.cs diff --git a/RealTime/UI/DateTooltipBehavior.cs b/src/RealTime/UI/DateTooltipBehavior.cs similarity index 100% rename from RealTime/UI/DateTooltipBehavior.cs rename to src/RealTime/UI/DateTooltipBehavior.cs diff --git a/RealTime/UI/RealTimeUIDateTimeWrapper.cs b/src/RealTime/UI/RealTimeUIDateTimeWrapper.cs similarity index 100% rename from RealTime/UI/RealTimeUIDateTimeWrapper.cs rename to src/RealTime/UI/RealTimeUIDateTimeWrapper.cs From ccdff7b79bef860021d21256565242ddde5b7e1d Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 06:14:49 +0200 Subject: [PATCH 007/102] Move the build environment files --- src/BuildEnvironment/RealTime.ruleset | 663 ++++++++++++++++++ .../BuildEnvironment/stylecop.json | 0 .../BuildEnvironment/RealTime.ruleset | 75 -- src/RealTime/RealTime.csproj | 28 +- .../{BuildEnvironment => }/packages.config | 0 5 files changed, 679 insertions(+), 87 deletions(-) create mode 100644 src/BuildEnvironment/RealTime.ruleset rename src/{RealTime => }/BuildEnvironment/stylecop.json (100%) delete mode 100644 src/RealTime/BuildEnvironment/RealTime.ruleset rename src/RealTime/{BuildEnvironment => }/packages.config (100%) diff --git a/src/BuildEnvironment/RealTime.ruleset b/src/BuildEnvironment/RealTime.ruleset new file mode 100644 index 00000000..732223fa --- /dev/null +++ b/src/BuildEnvironment/RealTime.ruleset @@ -0,0 +1,663 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RealTime/BuildEnvironment/stylecop.json b/src/BuildEnvironment/stylecop.json similarity index 100% rename from src/RealTime/BuildEnvironment/stylecop.json rename to src/BuildEnvironment/stylecop.json diff --git a/src/RealTime/BuildEnvironment/RealTime.ruleset b/src/RealTime/BuildEnvironment/RealTime.ruleset deleted file mode 100644 index aee3a307..00000000 --- a/src/RealTime/BuildEnvironment/RealTime.ruleset +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj index b8d2751b..bbe8b561 100644 --- a/src/RealTime/RealTime.csproj +++ b/src/RealTime/RealTime.csproj @@ -25,8 +25,9 @@ 4 - true - BuildEnvironment\RealTime.ruleset + false + ..\BuildEnvironment\RealTime.ruleset + 7.3 pdbonly @@ -37,8 +38,9 @@ 4 - true - BuildEnvironment\RealTime.ruleset + false + ..\BuildEnvironment\RealTime.ruleset + 7.3 @@ -76,26 +78,28 @@ - - - - - + + stylecop.json + + + + + + - mkdir "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)" del "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)\$(TargetFileName)" xcopy /y "$(TargetDir)*.dll" "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)" - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/src/RealTime/BuildEnvironment/packages.config b/src/RealTime/packages.config similarity index 100% rename from src/RealTime/BuildEnvironment/packages.config rename to src/RealTime/packages.config From 90a55b610018f49e9bd6db8c67234530a49fa21a Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 06:16:21 +0200 Subject: [PATCH 008/102] Add Redirection project --- src/RealTime.sln | 6 + src/RealTime/RealTime.csproj | 4 + src/Redirection/MethodInfoExtensions.cs | 117 +++++++++++++++ src/Redirection/MethodRedirection.cs | 68 +++++++++ src/Redirection/Properties/AssemblyInfo.cs | 17 +++ src/Redirection/RedirectAttribute.cs | 59 ++++++++ src/Redirection/RedirectCallsState.cs | 41 ++++++ src/Redirection/RedirectFromAttribute.cs | 82 +++++++++++ src/Redirection/RedirectToAttribute.cs | 81 +++++++++++ src/Redirection/Redirection.csproj | 86 +++++++++++ src/Redirection/RedirectionHelper.cs | 160 +++++++++++++++++++++ src/Redirection/Redirector.cs | 112 +++++++++++++++ src/Redirection/packages.config | 5 + 13 files changed, 838 insertions(+) create mode 100644 src/Redirection/MethodInfoExtensions.cs create mode 100644 src/Redirection/MethodRedirection.cs create mode 100644 src/Redirection/Properties/AssemblyInfo.cs create mode 100644 src/Redirection/RedirectAttribute.cs create mode 100644 src/Redirection/RedirectCallsState.cs create mode 100644 src/Redirection/RedirectFromAttribute.cs create mode 100644 src/Redirection/RedirectToAttribute.cs create mode 100644 src/Redirection/Redirection.csproj create mode 100644 src/Redirection/RedirectionHelper.cs create mode 100644 src/Redirection/Redirector.cs create mode 100644 src/Redirection/packages.config diff --git a/src/RealTime.sln b/src/RealTime.sln index 19991b8e..8a91c32f 100644 --- a/src/RealTime.sln +++ b/src/RealTime.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealTime", "RealTime\RealTime.csproj", "{7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redirection", "Redirection\Redirection.csproj", "{7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}.Debug|Any CPU.Build.0 = Debug|Any CPU {7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}.Release|Any CPU.ActiveCfg = Release|Any CPU {7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}.Release|Any CPU.Build.0 = Release|Any CPU + {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj index bbe8b561..63042eb3 100644 --- a/src/RealTime/RealTime.csproj +++ b/src/RealTime/RealTime.csproj @@ -78,6 +78,10 @@ + + {7dcc08ef-dc85-47a4-bd6f-79fc52c7ef13} + Redirection + diff --git a/src/Redirection/MethodInfoExtensions.cs b/src/Redirection/MethodInfoExtensions.cs new file mode 100644 index 00000000..8e04825c --- /dev/null +++ b/src/Redirection/MethodInfoExtensions.cs @@ -0,0 +1,117 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +/* +The MIT License (MIT) +Copyright (c) 2015 Sebastian Schöner +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +namespace Redirection +{ + using System; + using System.Reflection; + using System.Security.Permissions; + + /// + /// Contains various extension methods for the class. + /// + internal static class MethodInfoExtensions + { + /// + /// Creates a instance for the + /// using the target and the + /// assembly and redirects the calls from to the . + /// + /// + /// Thrown when any argument is null. + /// + /// A method to create the redirection for. + /// The target method for the redirection. + /// An that is the redirection source. + /// + /// A instance containing the redirected method description. + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + internal static MethodRedirection CreateRedirectionTo(this MethodInfo method, MethodInfo target, Assembly redirectionSource) + { + if (method == null) + { + throw new ArgumentNullException(nameof(method)); + } + + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (redirectionSource == null) + { + throw new ArgumentNullException(nameof(redirectionSource)); + } + + return new MethodRedirection(method, target, redirectionSource); + } + + /// + /// Compares the provided methods to determine whether a redirection from + /// to is possible. + /// + /// + /// Thrown when any argument is null. + /// + /// The method to compare. + /// The method to compare with. + /// + /// True if the methods are compatible; otherwise, false. + internal static bool IsCompatibleWith(this MethodInfo method, MethodInfo otherMethod) + { + if (method == null) + { + throw new ArgumentNullException(nameof(method)); + } + + if (otherMethod == null) + { + throw new ArgumentNullException(nameof(otherMethod)); + } + + if (method.ReturnType != otherMethod.ReturnType) + { + return false; + } + + ParameterInfo[] thisParameters = method.GetParameters(); + ParameterInfo[] otherParameters = otherMethod.GetParameters(); + + if (thisParameters.Length != otherParameters.Length) + { + return false; + } + + for (int i = 0; i < thisParameters.Length; i++) + { + if (!otherParameters[i].ParameterType.IsAssignableFrom(thisParameters[i].ParameterType)) + { + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Redirection/MethodRedirection.cs b/src/Redirection/MethodRedirection.cs new file mode 100644 index 00000000..31b740b6 --- /dev/null +++ b/src/Redirection/MethodRedirection.cs @@ -0,0 +1,68 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +/* +The MIT License (MIT) +Copyright (c) 2015 Sebastian Schöner +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +namespace Redirection +{ + using System; + using System.Reflection; + using System.Security.Permissions; + + internal sealed class MethodRedirection : IDisposable + { + private readonly RedirectCallsState callsState; + + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + public MethodRedirection(MethodInfo method, MethodInfo target, Assembly redirectionSource) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + Method = method ?? throw new ArgumentNullException(nameof(method)); + callsState = RedirectionHelper.RedirectCalls(method, target); + RedirectionSource = redirectionSource ?? throw new ArgumentNullException(nameof(redirectionSource)); + } + + public MethodInfo Method { get; private set; } + + public Assembly RedirectionSource { get; private set; } + + public bool IsDisposed { get; set; } + + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + public void Dispose() + { + if (IsDisposed) + { + return; + } + + RedirectionHelper.RevertRedirect(Method, callsState); + Method = null; + RedirectionSource = null; + IsDisposed = true; + } + } +} diff --git a/src/Redirection/Properties/AssemblyInfo.cs b/src/Redirection/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..16efb181 --- /dev/null +++ b/src/Redirection/Properties/AssemblyInfo.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Redirection")] +[assembly: AssemblyDescription("Provides a toolset for method calls redirection")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RealTime")] +[assembly: AssemblyCopyright("Copyright © dymanoid 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] diff --git a/src/Redirection/RedirectAttribute.cs b/src/Redirection/RedirectAttribute.cs new file mode 100644 index 00000000..cf8c95ac --- /dev/null +++ b/src/Redirection/RedirectAttribute.cs @@ -0,0 +1,59 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +/* +The MIT License (MIT) +Copyright (c) 2015 Sebastian Schöner +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +namespace Redirection +{ + using System; + + public abstract class RedirectAttribute : Attribute + { + protected RedirectAttribute(Type classType, string methodName, ulong bitSetRequiredOption) + { + ClassType = classType; + MethodName = methodName; + BitSetRequiredOption = bitSetRequiredOption; + } + + protected RedirectAttribute(Type classType, string methodName) + : this(classType, methodName, 0) + { + } + + protected RedirectAttribute(Type classType, ulong bitSetOption) + : this(classType, null, bitSetOption) + { + } + + protected RedirectAttribute(Type classType) + : this(classType, null, 0) + { + } + + public Type ClassType { get; set; } + + public string MethodName { get; set; } + + public ulong BitSetRequiredOption { get; set; } + } +} diff --git a/src/Redirection/RedirectCallsState.cs b/src/Redirection/RedirectCallsState.cs new file mode 100644 index 00000000..e6dece42 --- /dev/null +++ b/src/Redirection/RedirectCallsState.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +/* +The MIT License (MIT) +Copyright (c) 2015 Sebastian Schöner +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +namespace Redirection +{ + public sealed class RedirectCallsState + { + internal byte CallSite { get; set; } + + internal byte Offset1 { get; set; } + + internal byte Offset10 { get; set; } + + internal byte Offset11 { get; set; } + + internal byte Offset12 { get; set; } + + internal ulong Addr { get; set; } + } +} \ No newline at end of file diff --git a/src/Redirection/RedirectFromAttribute.cs b/src/Redirection/RedirectFromAttribute.cs new file mode 100644 index 00000000..ae3eecff --- /dev/null +++ b/src/Redirection/RedirectFromAttribute.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +/* +The MIT License (MIT) +Copyright (c) 2015 Sebastian Schöner +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +namespace Redirection +{ + using System; + + /// + /// Marks a method for redirection. All marked methods are redirected by calling + /// and reverted by + /// + /// + /// + /// NOTE: only the methods belonging to the same assembly that calls Perform/RevertRedirections are redirected. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class RedirectFromAttribute : RedirectAttribute + { + /// + /// Initializes a new instance of the class. + /// + /// The class of the method that will be redirected + /// The name of the method that will be redirected. If null, + /// the name of the attribute's target method will be used. + /// The required bit set option. + public RedirectFromAttribute(Type classType, string methodName, ulong bitSetRequiredOption) + : base(classType, methodName, bitSetRequiredOption) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The class of the method that will be redirected + /// The name of the method that will be redirected. If null, + /// the name of the attribute's target method will be used. + public RedirectFromAttribute(Type classType, string methodName) + : base(classType, methodName) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The class of the method that will be redirected + /// The required bit set option. + public RedirectFromAttribute(Type classType, ulong bitSetRequiredOption) + : base(classType, bitSetRequiredOption) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The class of the method that will be redirected + public RedirectFromAttribute(Type classType) + : base(classType) + { + } + } +} diff --git a/src/Redirection/RedirectToAttribute.cs b/src/Redirection/RedirectToAttribute.cs new file mode 100644 index 00000000..bc1b76f8 --- /dev/null +++ b/src/Redirection/RedirectToAttribute.cs @@ -0,0 +1,81 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +/* +The MIT License (MIT) +Copyright (c) 2015 Sebastian Schöner +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +namespace Redirection +{ + using System; + + /// + /// Marks a method for redirection. All marked methods are redirected by calling + /// and reverted by + /// + /// + /// NOTE: only the methods belonging to the same assembly that calls Perform/RevertRedirections are redirected. + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public sealed class RedirectToAttribute : RedirectAttribute + { + /// + /// Initializes a new instance of the class. + /// + /// The class of the target method + /// The name of the target method. If null, + /// the name of the attribute's target method will be used. + /// The required bit set option. + public RedirectToAttribute(Type classType, string methodName, ulong bitSetRequiredOption) + : base(classType, methodName, bitSetRequiredOption) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The class of the target method + /// The name of the target method. If null, + /// the name of the attribute's target method will be used. + public RedirectToAttribute(Type classType, string methodName) + : base(classType, methodName) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The class of the target method + /// The required bit set option. + public RedirectToAttribute(Type classType, ulong bitSetRequiredOption) + : base(classType, bitSetRequiredOption) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The class of the target method + /// The required bit set option. + public RedirectToAttribute(Type classType) + : base(classType) + { + } + } +} diff --git a/src/Redirection/Redirection.csproj b/src/Redirection/Redirection.csproj new file mode 100644 index 00000000..1fdb4a1c --- /dev/null +++ b/src/Redirection/Redirection.csproj @@ -0,0 +1,86 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13} + Library + Properties + Redirection + Redirection + v3.5 + 512 + + + + + + true + full + false + ..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + ..\BuildEnvironment\RealTime.ruleset + 7.3 + + + pdbonly + true + ..\bin\Release\ + TRACE + prompt + 4 + true + ..\BuildEnvironment\RealTime.ruleset + 7.3 + + + + + + + + + + + + + + + + + + + + + + + stylecop.json + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/Redirection/RedirectionHelper.cs b/src/Redirection/RedirectionHelper.cs new file mode 100644 index 00000000..469b0c73 --- /dev/null +++ b/src/Redirection/RedirectionHelper.cs @@ -0,0 +1,160 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +/* +The MIT License (MIT) +Copyright (c) 2015 Sebastian Schöner +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +namespace Redirection +{ + using System; + using System.Reflection; + using System.Security.Permissions; + + /// + /// Helper class to deal with detours. This version is for Unity 5 x64 on Windows. + /// We provide three different methods of detouring. + /// + public static class RedirectionHelper + { + /// + /// Redirects all calls from method '' to method ''. + /// + /// + /// Thrown when any argument is null. + /// + /// The method to redirect from. + /// The method to redicrect to. + /// + /// An instance that holds the data for cancelling the redirection. + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + public static RedirectCallsState RedirectCalls(MethodBase from, MethodBase to) + { + if (from == null) + { + throw new ArgumentNullException(nameof(from)); + } + + if (to == null) + { + throw new ArgumentNullException(nameof(to)); + } + + // GetFunctionPointer enforces compilation of the method. + IntPtr fptr1 = from.MethodHandle.GetFunctionPointer(); + IntPtr fptr2 = to.MethodHandle.GetFunctionPointer(); + + return PatchJumpTo(fptr1, fptr2); + } + + /// + /// Redirects all calls from method '' to method ''. + /// + /// + /// The method to redirect from. + /// The method to redicrect to. + /// + /// An instance that holds the data for reverting + /// the redirection. + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + public static RedirectCallsState RedirectCalls(RuntimeMethodHandle from, RuntimeMethodHandle to) + { + // GetFunctionPointer enforces compilation of the method. + IntPtr fptr1 = from.GetFunctionPointer(); + IntPtr fptr2 = to.GetFunctionPointer(); + + return PatchJumpTo(fptr1, fptr2); + } + + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + public static void RevertRedirect(MethodBase from, RedirectCallsState state) + { + if (from == null) + { + throw new ArgumentNullException(nameof(from)); + } + + if (state == null) + { + throw new ArgumentNullException(nameof(state)); + } + + try + { + IntPtr fptr1 = from.MethodHandle.GetFunctionPointer(); + RevertJumpTo(fptr1, state); + } + catch + { + // ignored + } + } + + /// + /// Primitive patching. Inserts a jump to '' at ''. + /// Works even if both methods' callers have already been compiled. + /// + /// + /// A pointer of the jump site. + /// A pointer to the jump target. + /// + /// An instance that holds the data for reverting + /// the redirection. + private static RedirectCallsState PatchJumpTo(IntPtr site, IntPtr target) + { + var state = new RedirectCallsState(); + + // R11 is volatile. + unsafe + { + byte* sitePtr = (byte*)site.ToPointer(); + state.CallSite = *sitePtr; + state.Offset1 = *(sitePtr + 1); + state.Offset10 = *(sitePtr + 10); + state.Offset11 = *(sitePtr + 11); + state.Offset12 = *(sitePtr + 12); + state.Addr = *((ulong*)(sitePtr + 2)); + + *sitePtr = 0x49; // mov r11, target + *(sitePtr + 1) = 0xBB; + *((ulong*)(sitePtr + 2)) = (ulong)target.ToInt64(); + *(sitePtr + 10) = 0x41; // jmp r11 + *(sitePtr + 11) = 0xFF; + *(sitePtr + 12) = 0xE3; + } + + return state; + } + + private static void RevertJumpTo(IntPtr site, RedirectCallsState state) + { + unsafe + { + byte* sitePtr = (byte*)site.ToPointer(); + *sitePtr = state.CallSite; // mov r11, target + *(sitePtr + 1) = state.Offset1; + *((ulong*)(sitePtr + 2)) = state.Addr; + *(sitePtr + 10) = state.Offset10; // jmp r11 + *(sitePtr + 11) = state.Offset11; + *(sitePtr + 12) = state.Offset12; + } + } + } +} \ No newline at end of file diff --git a/src/Redirection/Redirector.cs b/src/Redirection/Redirector.cs new file mode 100644 index 00000000..829e366b --- /dev/null +++ b/src/Redirection/Redirector.cs @@ -0,0 +1,112 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +/* +The MIT License (MIT) +Copyright (c) 2015 Sebastian Schöner +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +namespace Redirection +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Security.Permissions; + + public static class Redirector + { + private static Dictionary redirections = new Dictionary(); + + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + public static void PerformRedirections() + { + PerformRedirections(0); + } + + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + public static void PerformRedirections(ulong bitmask) + { + var callingAssembly = Assembly.GetCallingAssembly(); + + IEnumerable allMethods = callingAssembly + .GetTypes() + .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)); + + foreach (MethodInfo method in allMethods) + { + foreach (RedirectAttribute attribute in method.GetCustomAttributes(typeof(RedirectAttribute), false)) + { + ProcessMethod(method, callingAssembly, attribute, bitmask); + } + } + } + + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + public static void RevertRedirections() + { + var callingAssembly = Assembly.GetCallingAssembly(); + + var redirectionsToRemove = redirections.Values.Where(r => r.RedirectionSource == callingAssembly).ToList(); + foreach (MethodRedirection item in redirectionsToRemove) + { + redirections.Remove(item.Method); + item.Dispose(); + } + } + + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + private static void ProcessMethod(MethodInfo method, Assembly callingAssembly, RedirectAttribute attribute, ulong bitmask) + { + if (attribute.BitSetRequiredOption != 0 && (bitmask & attribute.BitSetRequiredOption) == 0) + { + return; + } + + string originalName = string.IsNullOrEmpty(attribute.MethodName) ? method.Name : attribute.MethodName; + + MethodInfo originalMethod = + attribute.ClassType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static) + .FirstOrDefault(m => m.Name == originalName && method.IsCompatibleWith(m)); + + if (originalMethod == null) + { + throw new InvalidOperationException($"Redirector: Original method {originalName} has not been found for redirection"); + } + + if (attribute is RedirectFromAttribute) + { + if (!redirections.ContainsKey(originalMethod)) + { + redirections.Add(originalMethod, originalMethod.CreateRedirectionTo(method, callingAssembly)); + } + + return; + } + + if (attribute is RedirectToAttribute) + { + if (!redirections.ContainsKey(method)) + { + redirections.Add(method, method.CreateRedirectionTo(originalMethod, callingAssembly)); + } + } + } + } +} diff --git a/src/Redirection/packages.config b/src/Redirection/packages.config new file mode 100644 index 00000000..b0aaa025 --- /dev/null +++ b/src/Redirection/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From a8499580b8a183be23769debff08f0edf607ca3b Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 06:16:44 +0200 Subject: [PATCH 009/102] Fix FxCop warnings --- src/RealTime/Core/RealTimeCore.cs | 7 +++++-- src/RealTime/Core/RealTimeMod.cs | 2 ++ src/RealTime/Tools/Log.cs | 2 ++ src/RealTime/UI/DateTooltipBehavior.cs | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs index 5ad4b05f..e12e813a 100644 --- a/src/RealTime/Core/RealTimeCore.cs +++ b/src/RealTime/Core/RealTimeCore.cs @@ -38,8 +38,11 @@ public static RealTimeCore Run() var customTimeBar = new CustomTimeBar(); customTimeBar.Enable(gameDate); - var core = new RealTimeCore(timeAdjustment, customTimeBar); - core.isEnabled = true; + var core = new RealTimeCore(timeAdjustment, customTimeBar) + { + isEnabled = true + }; + return core; } diff --git a/src/RealTime/Core/RealTimeMod.cs b/src/RealTime/Core/RealTimeMod.cs index 4b0e65d7..4f4ffbb8 100644 --- a/src/RealTime/Core/RealTimeMod.cs +++ b/src/RealTime/Core/RealTimeMod.cs @@ -36,6 +36,7 @@ public void OnEnabled() /// /// Called when this mod is disabled. /// + [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."); @@ -47,6 +48,7 @@ public void OnDisabled() /// /// An reference that can be used /// to construct the mod's settings page. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Must be instance method due to C:S API")] public void OnSettingsUI(UIHelperBase helper) { // TODO: imlement the options page diff --git a/src/RealTime/Tools/Log.cs b/src/RealTime/Tools/Log.cs index 57d13f96..76adc300 100644 --- a/src/RealTime/Tools/Log.cs +++ b/src/RealTime/Tools/Log.cs @@ -38,6 +38,8 @@ internal static class Log [ThreadStatic] private static StringBuilder messageBuilder; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Special debug configuration")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Debug build only")] static Log() { FlushTimer.Elapsed += FlushTimer_Elapsed; diff --git a/src/RealTime/UI/DateTooltipBehavior.cs b/src/RealTime/UI/DateTooltipBehavior.cs index f09652b4..85e7be4e 100644 --- a/src/RealTime/UI/DateTooltipBehavior.cs +++ b/src/RealTime/UI/DateTooltipBehavior.cs @@ -15,6 +15,7 @@ namespace RealTime.Tools /// of the to the date part of that value. The current /// is used for string conversion. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Instantiated by Unity Engine")] internal sealed class DateTooltipBehavior : MonoBehaviour { private UIComponent target; From 75494e6760cc42ea6632b02c754a3b5eb52f3994 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 06:46:03 +0200 Subject: [PATCH 010/102] Update the code documentation --- src/Redirection/MethodInfoExtensions.cs | 4 +-- src/Redirection/RedirectAttribute.cs | 32 ++++++++++++----- src/Redirection/RedirectCallsState.cs | 4 +++ src/Redirection/RedirectFromAttribute.cs | 44 +++++++++++++++-------- src/Redirection/RedirectToAttribute.cs | 45 +++++++++++++++--------- src/Redirection/RedirectionHelper.cs | 28 +++++++++++---- src/Redirection/Redirector.cs | 19 +++++++++- 7 files changed, 126 insertions(+), 50 deletions(-) diff --git a/src/Redirection/MethodInfoExtensions.cs b/src/Redirection/MethodInfoExtensions.cs index 8e04825c..4e913b3b 100644 --- a/src/Redirection/MethodInfoExtensions.cs +++ b/src/Redirection/MethodInfoExtensions.cs @@ -68,8 +68,8 @@ internal static MethodRedirection CreateRedirectionTo(this MethodInfo method, Me } /// - /// Compares the provided methods to determine whether a redirection from - /// to is possible. + /// Compares the provided methods to determine whether a redirection from this + /// to this is possible. /// /// /// Thrown when any argument is null. diff --git a/src/Redirection/RedirectAttribute.cs b/src/Redirection/RedirectAttribute.cs index cf8c95ac..29a3e509 100644 --- a/src/Redirection/RedirectAttribute.cs +++ b/src/Redirection/RedirectAttribute.cs @@ -26,34 +26,48 @@ namespace Redirection { using System; + /// + /// A base class for the special redirection attributes that can be applied to the methods. + /// public abstract class RedirectAttribute : Attribute { - protected RedirectAttribute(Type classType, string methodName, ulong bitSetRequiredOption) + protected RedirectAttribute(Type methodType, string methodName, ulong bitSetRequiredOption) { - ClassType = classType; + MethodType = methodType ?? throw new ArgumentNullException(nameof(methodType)); MethodName = methodName; BitSetRequiredOption = bitSetRequiredOption; } - protected RedirectAttribute(Type classType, string methodName) - : this(classType, methodName, 0) + protected RedirectAttribute(Type methodType, string methodName) + : this(methodType, methodName, 0) { } - protected RedirectAttribute(Type classType, ulong bitSetOption) - : this(classType, null, bitSetOption) + protected RedirectAttribute(Type methodType, ulong bitSetOption) + : this(methodType, null, bitSetOption) { } - protected RedirectAttribute(Type classType) - : this(classType, null, 0) + protected RedirectAttribute(Type methodType) + : this(methodType, null, 0) { } - public Type ClassType { get; set; } + /// + /// Gets or sets the type where the method that will be redirected is defined. + /// + public Type MethodType { get; set; } + /// + /// Gets or sets the method name to redirect. If not set, the name of the + /// method this attribute is attached to will be used. + /// public string MethodName { get; set; } + /// + /// Gets or sets the required bit set option for the method redirection. + /// 0 means not specified. + /// public ulong BitSetRequiredOption { get; set; } } } diff --git a/src/Redirection/RedirectCallsState.cs b/src/Redirection/RedirectCallsState.cs index e6dece42..d5b7c587 100644 --- a/src/Redirection/RedirectCallsState.cs +++ b/src/Redirection/RedirectCallsState.cs @@ -24,6 +24,10 @@ THE SOFTWARE. namespace Redirection { + /// + /// A container class for the redirection state. + /// Holds internal redirection data. Can be used as a token to revert the redirection. + /// public sealed class RedirectCallsState { internal byte CallSite { get; set; } diff --git a/src/Redirection/RedirectFromAttribute.cs b/src/Redirection/RedirectFromAttribute.cs index ae3eecff..a6425f3a 100644 --- a/src/Redirection/RedirectFromAttribute.cs +++ b/src/Redirection/RedirectFromAttribute.cs @@ -40,42 +40,56 @@ public sealed class RedirectFromAttribute : RedirectAttribute /// /// Initializes a new instance of the class. /// - /// The class of the method that will be redirected + /// Thrown when is null. + /// + /// The type where the method that will be redirected is defined. /// The name of the method that will be redirected. If null, /// the name of the attribute's target method will be used. /// The required bit set option. - public RedirectFromAttribute(Type classType, string methodName, ulong bitSetRequiredOption) - : base(classType, methodName, bitSetRequiredOption) + public RedirectFromAttribute(Type methodType, string methodName, ulong bitSetRequiredOption) + : base(methodType, methodName, bitSetRequiredOption) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class with + /// default . /// - /// The class of the method that will be redirected + /// Thrown when is null. + /// + /// The type where the method that will be redirected is defined. /// The name of the method that will be redirected. If null, /// the name of the attribute's target method will be used. - public RedirectFromAttribute(Type classType, string methodName) - : base(classType, methodName) + public RedirectFromAttribute(Type methodType, string methodName) + : base(methodType, methodName) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class with empty + /// . The name of the method this attribute is + /// attached to will be used. /// - /// The class of the method that will be redirected + /// Thrown when is null. + /// + /// The type where the method that will be redirected is defined. /// The required bit set option. - public RedirectFromAttribute(Type classType, ulong bitSetRequiredOption) - : base(classType, bitSetRequiredOption) + public RedirectFromAttribute(Type methodType, ulong bitSetRequiredOption) + : base(methodType, bitSetRequiredOption) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class with + /// default and empty + /// . The name of the method this attribute is + /// attached to will be used. + /// + /// Thrown when is null. /// - /// The class of the method that will be redirected - public RedirectFromAttribute(Type classType) - : base(classType) + /// The type where the method that will be redirected is defined. + public RedirectFromAttribute(Type methodType) + : base(methodType) { } } diff --git a/src/Redirection/RedirectToAttribute.cs b/src/Redirection/RedirectToAttribute.cs index bc1b76f8..27e54c27 100644 --- a/src/Redirection/RedirectToAttribute.cs +++ b/src/Redirection/RedirectToAttribute.cs @@ -38,43 +38,56 @@ public sealed class RedirectToAttribute : RedirectAttribute /// /// Initializes a new instance of the class. /// - /// The class of the target method + /// Thrown when is null. + /// + /// The type where the method that will be redirected is defined. /// The name of the target method. If null, /// the name of the attribute's target method will be used. /// The required bit set option. - public RedirectToAttribute(Type classType, string methodName, ulong bitSetRequiredOption) - : base(classType, methodName, bitSetRequiredOption) + public RedirectToAttribute(Type methodType, string methodName, ulong bitSetRequiredOption) + : base(methodType, methodName, bitSetRequiredOption) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class with + /// default . /// - /// The class of the target method + /// Thrown when is null. + /// + /// The type where the method that will be redirected is defined. /// The name of the target method. If null, /// the name of the attribute's target method will be used. - public RedirectToAttribute(Type classType, string methodName) - : base(classType, methodName) + public RedirectToAttribute(Type methodType, string methodName) + : base(methodType, methodName) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class with empty + /// . The name of the method this attribute is + /// attached to will be used. /// - /// The class of the target method + /// Thrown when is null. + /// + /// The type where the method that will be redirected is defined. /// The required bit set option. - public RedirectToAttribute(Type classType, ulong bitSetRequiredOption) - : base(classType, bitSetRequiredOption) + public RedirectToAttribute(Type methodType, ulong bitSetRequiredOption) + : base(methodType, bitSetRequiredOption) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class with + /// default and empty + /// . The name of the method this attribute is + /// attached to will be used. /// - /// The class of the target method - /// The required bit set option. - public RedirectToAttribute(Type classType) - : base(classType) + /// Thrown when is null. + /// + /// The type where the method that will be redirected is defined. + public RedirectToAttribute(Type methodType) + : base(methodType) { } } diff --git a/src/Redirection/RedirectionHelper.cs b/src/Redirection/RedirectionHelper.cs index 469b0c73..121f7bcb 100644 --- a/src/Redirection/RedirectionHelper.cs +++ b/src/Redirection/RedirectionHelper.cs @@ -32,7 +32,7 @@ namespace Redirection /// Helper class to deal with detours. This version is for Unity 5 x64 on Windows. /// We provide three different methods of detouring. /// - public static class RedirectionHelper + internal static class RedirectionHelper { /// /// Redirects all calls from method '' to method ''. @@ -43,7 +43,8 @@ public static class RedirectionHelper /// The method to redirect from. /// The method to redicrect to. /// - /// An instance that holds the data for cancelling the redirection. + /// An instance that holds the data for reverting + /// the redirection. [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public static RedirectCallsState RedirectCalls(MethodBase from, MethodBase to) { @@ -83,12 +84,24 @@ public static RedirectCallsState RedirectCalls(RuntimeMethodHandle from, Runtime return PatchJumpTo(fptr1, fptr2); } + /// + /// Reverts a method redirection previously created with + /// or methods. + /// + /// + /// Thrown when any argument is null. + /// + /// The method to revert the redirection of. + /// A instance holding the data for + /// reverting the redirection. + /// + /// True on success; otherwise, false. [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] - public static void RevertRedirect(MethodBase from, RedirectCallsState state) + public static bool RevertRedirect(MethodBase method, RedirectCallsState state) { - if (from == null) + if (method == null) { - throw new ArgumentNullException(nameof(from)); + throw new ArgumentNullException(nameof(method)); } if (state == null) @@ -98,12 +111,13 @@ public static void RevertRedirect(MethodBase from, RedirectCallsState state) try { - IntPtr fptr1 = from.MethodHandle.GetFunctionPointer(); + IntPtr fptr1 = method.MethodHandle.GetFunctionPointer(); RevertJumpTo(fptr1, state); + return true; } catch { - // ignored + return false; } } diff --git a/src/Redirection/Redirector.cs b/src/Redirection/Redirector.cs index 829e366b..b0b30d4d 100644 --- a/src/Redirection/Redirector.cs +++ b/src/Redirection/Redirector.cs @@ -30,16 +30,29 @@ namespace Redirection using System.Reflection; using System.Security.Permissions; + /// + /// A static class that provides the functionality of automatic method call redirection. + /// public static class Redirector { private static Dictionary redirections = new Dictionary(); + /// + /// Perform the method call redirections for all methods in the calling assembly + /// that are marked with or . + /// [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public static void PerformRedirections() { PerformRedirections(0); } + /// + /// Perform the method call redirections for all methods in the calling assembly + /// that are marked with or . + /// + /// + /// The bitmask to filter the methods with. [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public static void PerformRedirections(ulong bitmask) { @@ -58,6 +71,10 @@ public static void PerformRedirections(ulong bitmask) } } + /// + /// Reverts the method call redirection of all methods from the calling assembly + /// that are marked with or . + /// [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public static void RevertRedirections() { @@ -82,7 +99,7 @@ private static void ProcessMethod(MethodInfo method, Assembly callingAssembly, R string originalName = string.IsNullOrEmpty(attribute.MethodName) ? method.Name : attribute.MethodName; MethodInfo originalMethod = - attribute.ClassType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static) + attribute.MethodType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static) .FirstOrDefault(m => m.Name == originalName && method.IsCompatibleWith(m)); if (originalMethod == null) From e908b5609f1266e58fdb741b144ba09e3341498e Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 09:52:37 +0200 Subject: [PATCH 011/102] Fix the time bar tooltip issue causing the date to be shown everywhere --- src/RealTime/UI/DateTooltipBehavior.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/RealTime/UI/DateTooltipBehavior.cs b/src/RealTime/UI/DateTooltipBehavior.cs index 85e7be4e..b5ebca0e 100644 --- a/src/RealTime/UI/DateTooltipBehavior.cs +++ b/src/RealTime/UI/DateTooltipBehavior.cs @@ -48,8 +48,12 @@ public void Update() } lastValue = newValue; - target.tooltip = tooltip; - target.RefreshTooltip(); + + if (target.containsMouse) + { + target.tooltip = tooltip; + target.RefreshTooltip(); + } } } } From b697eb21aa829fe020cf6fb0cedbb2a35518f07c Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 09:53:27 +0200 Subject: [PATCH 012/102] Improve the mod's version display --- src/RealTime/Tools/GitVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RealTime/Tools/GitVersion.cs b/src/RealTime/Tools/GitVersion.cs index eccf1614..0bd092cd 100644 --- a/src/RealTime/Tools/GitVersion.cs +++ b/src/RealTime/Tools/GitVersion.cs @@ -13,7 +13,7 @@ namespace RealTime.Tools internal static class GitVersion { private const string GitVersionTypeName = ".GitVersionInformation"; - private const string VersionFieldName = "SemVer"; + private const string VersionFieldName = "FullSemVer"; /// /// Gets a string representation of the full semantic assembly version of the provided . From 5d9814be7737f520dd31148977deac818542ff59 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 09:54:04 +0200 Subject: [PATCH 013/102] Add the first ResidentAI redirection (no changes yet) --- src/RealTime/AI/RealTimeResidentAI.Home.cs | 105 + src/RealTime/AI/RealTimeResidentAI.Moving.cs | 57 + src/RealTime/AI/RealTimeResidentAI.Visit.cs | 172 ++ src/RealTime/AI/RealTimeResidentAI.Work.cs | 131 + src/RealTime/AI/RealTimeResidentAI.cs | 136 + src/RealTime/AI/ResidentAI.cs | 2871 ++++++++++++++++++ src/RealTime/Core/RealTimeCore.cs | 21 + src/RealTime/RealTime.csproj | 5 + 8 files changed, 3498 insertions(+) create mode 100644 src/RealTime/AI/RealTimeResidentAI.Home.cs create mode 100644 src/RealTime/AI/RealTimeResidentAI.Moving.cs create mode 100644 src/RealTime/AI/RealTimeResidentAI.Visit.cs create mode 100644 src/RealTime/AI/RealTimeResidentAI.Work.cs create mode 100644 src/RealTime/AI/RealTimeResidentAI.cs create mode 100644 src/RealTime/AI/ResidentAI.cs diff --git a/src/RealTime/AI/RealTimeResidentAI.Home.cs b/src/RealTime/AI/RealTimeResidentAI.Home.cs new file mode 100644 index 00000000..4f7e7d3a --- /dev/null +++ b/src/RealTime/AI/RealTimeResidentAI.Home.cs @@ -0,0 +1,105 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.AI +{ + using UnityEngine; + + internal static partial class RealTimeResidentAI + { + private static bool ProcessCitizenAtHome(Arguments args, uint citizenID, ref Citizen data) + { + if ((data.m_flags & Citizen.Flags.MovingIn) != 0) + { + args.CitizenMgr.ReleaseCitizen(citizenID); + return true; + } + + if (data.Dead) + { + if (data.m_homeBuilding == 0) + { + args.CitizenMgr.ReleaseCitizen(citizenID); + } + else + { + if (data.m_workBuilding != 0) + { + data.SetWorkplace(citizenID, 0, 0u); + } + + if (data.m_visitBuilding != 0) + { + data.SetVisitplace(citizenID, 0, 0u); + } + + if (data.m_vehicle != 0) + { + return false; + } + + if (FindHospital(args.ResidentAI, citizenID, data.m_homeBuilding, TransferManager.TransferReason.Dead)) + { + return false; + } + } + + return true; + } + + if (data.Arrested) + { + data.Arrested = false; + } + else if (data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + if (data.Sick) + { + if (FindHospital(args.ResidentAI, citizenID, data.m_homeBuilding, TransferManager.TransferReason.Sick)) + { + return false; + } + + return true; + } + + if ((args.BuildingMgr.m_buildings.m_buffer[data.m_homeBuilding].m_flags & Building.Flags.Evacuating) != 0) + { + FindEvacuationPlace(args.ResidentAI, citizenID, data.m_homeBuilding, GetEvacuationReason(args.ResidentAI, data.m_homeBuilding)); + } + else if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) + { + FindVisitPlace(args.ResidentAI, citizenID, data.m_homeBuilding, GetShoppingReason(args.ResidentAI)); + } + else + { + if (data.m_instance == 0 && !DoRandomMove(args.ResidentAI)) + { + return false; + } + + int dayTimeFrame2 = (int)args.SimMgr.m_dayTimeFrame; + int dAYTIME_FRAMES2 = (int)SimulationManager.DAYTIME_FRAMES; + int num9 = dAYTIME_FRAMES2 / 40; + int num10 = (int)(SimulationManager.DAYTIME_FRAMES * 8) / 24; + int num11 = dayTimeFrame2 - num10 & dAYTIME_FRAMES2 - 1; + int num12 = Mathf.Abs(num11 - (dAYTIME_FRAMES2 >> 1)); + num12 = num12 * num12 / (dAYTIME_FRAMES2 >> 1); + int num13 = args.SimMgr.m_randomizer.Int32((uint)dAYTIME_FRAMES2); + if (num13 < num9) + { + FindVisitPlace(args.ResidentAI, citizenID, data.m_homeBuilding, GetEntertainmentReason(args.ResidentAI)); + } + else if (num13 < num9 + num12 && data.m_workBuilding != 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, data.m_homeBuilding, data.m_workBuilding); + } + } + } + + return false; + } + } +} diff --git a/src/RealTime/AI/RealTimeResidentAI.Moving.cs b/src/RealTime/AI/RealTimeResidentAI.Moving.cs new file mode 100644 index 00000000..cf5da09a --- /dev/null +++ b/src/RealTime/AI/RealTimeResidentAI.Moving.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.AI +{ + internal static partial class RealTimeResidentAI + { + private static bool ProcessCitizenMoving(Arguments args, uint citizenID, ref Citizen data) + { + if (data.Dead) + { + if (data.m_vehicle == 0) + { + args.CitizenMgr.ReleaseCitizen(citizenID); + return true; + } + + if (data.m_homeBuilding != 0) + { + data.SetHome(citizenID, 0, 0u); + } + + if (data.m_workBuilding != 0) + { + data.SetWorkplace(citizenID, 0, 0u); + } + + if (data.m_visitBuilding != 0) + { + data.SetVisitplace(citizenID, 0, 0u); + } + } + else if (data.m_vehicle == 0 && data.m_instance == 0) + { + if (data.m_visitBuilding != 0) + { + data.SetVisitplace(citizenID, 0, 0u); + } + + data.CurrentLocation = Citizen.Location.Home; + data.Arrested = false; + } + else if (data.m_instance != 0 && (args.CitizenMgr.m_instances.m_buffer[data.m_instance].m_flags & (CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour)) == (CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour)) + { + int num = args.SimMgr.m_randomizer.Int32(40u); + if (num < 10 && data.m_homeBuilding != 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, 0, data.m_homeBuilding); + } + } + + return false; + } + } +} diff --git a/src/RealTime/AI/RealTimeResidentAI.Visit.cs b/src/RealTime/AI/RealTimeResidentAI.Visit.cs new file mode 100644 index 00000000..594aa2e4 --- /dev/null +++ b/src/RealTime/AI/RealTimeResidentAI.Visit.cs @@ -0,0 +1,172 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.AI +{ + using ColossalFramework; + + internal static partial class RealTimeResidentAI + { + private static bool ProcessCitizenVisit(Arguments args, uint citizenID, ref Citizen data) + { + if (data.Dead) + { + if (data.m_visitBuilding == 0) + { + args.CitizenMgr.ReleaseCitizen(citizenID); + } + else + { + if (data.m_homeBuilding != 0) + { + data.SetHome(citizenID, 0, 0u); + } + + if (data.m_workBuilding != 0) + { + data.SetWorkplace(citizenID, 0, 0u); + } + + if (data.m_vehicle != 0) + { + return false; + } + + if (args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service == ItemClass.Service.HealthCare) + { + return false; + } + + if (FindHospital(args.ResidentAI, citizenID, data.m_visitBuilding, TransferManager.TransferReason.Dead)) + { + return false; + } + } + + return true; + } + + if (data.Arrested) + { + if (data.m_visitBuilding == 0) + { + data.Arrested = false; + } + } + else if (!data.Collapsed) + { + if (data.Sick) + { + if (data.m_visitBuilding == 0) + { + data.CurrentLocation = Citizen.Location.Home; + return false; + } + + if (data.m_vehicle != 0) + { + return false; + } + + ItemClass.Service service = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service; + if (service == ItemClass.Service.HealthCare) + { + return false; + } + + if (service == ItemClass.Service.Disaster) + { + return false; + } + + if (FindHospital(args.ResidentAI, citizenID, data.m_visitBuilding, TransferManager.TransferReason.Sick)) + { + return false; + } + + return true; + } + + ItemClass.Service service2 = ItemClass.Service.None; + if (data.m_visitBuilding != 0) + { + service2 = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service; + } + + switch (service2) + { + case ItemClass.Service.HealthCare: + case ItemClass.Service.PoliceDepartment: + if (data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); + data.SetVisitplace(citizenID, 0, 0u); + } + + return false; + case ItemClass.Service.Disaster: + if ((args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].m_flags & Building.Flags.Downgrading) != 0 && data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); + data.SetVisitplace(citizenID, 0, 0u); + } + + return false; + default: + if (data.m_visitBuilding == 0) + { + data.CurrentLocation = Citizen.Location.Home; + } + else if ((args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].m_flags & Building.Flags.Evacuating) != 0) + { + if (data.m_vehicle == 0) + { + FindEvacuationPlace(args.ResidentAI, citizenID, data.m_visitBuilding, GetEvacuationReason(args.ResidentAI, data.m_visitBuilding)); + } + } + else if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) + { + BuildingInfo info = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info; + int num7 = -100; + info.m_buildingAI.ModifyMaterialBuffer(data.m_visitBuilding, ref args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding], TransferManager.TransferReason.Shopping, ref num7); + } + else + { + ushort eventIndex2 = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].m_eventIndex; + if (eventIndex2 != 0) + { + if ((Singleton.instance.m_events.m_buffer[eventIndex2].m_flags & (EventData.Flags.Preparing | EventData.Flags.Active | EventData.Flags.Ready)) == EventData.Flags.None && data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); + data.SetVisitplace(citizenID, 0, 0u); + } + } + else + { + if (data.m_instance == 0 && !DoRandomMove(args.ResidentAI)) + { + return false; + } + + int num8 = args.SimMgr.m_randomizer.Int32(40u); + if (num8 < 10 && data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); + data.SetVisitplace(citizenID, 0, 0u); + } + } + } + + return false; + } + } + + return false; + } + } +} diff --git a/src/RealTime/AI/RealTimeResidentAI.Work.cs b/src/RealTime/AI/RealTimeResidentAI.Work.cs new file mode 100644 index 00000000..1fff0974 --- /dev/null +++ b/src/RealTime/AI/RealTimeResidentAI.Work.cs @@ -0,0 +1,131 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.AI +{ + using ColossalFramework; + using UnityEngine; + + internal static partial class RealTimeResidentAI + { + private static bool ProcessCitizenAtWork(Arguments args, uint citizenID, ref Citizen data) + { + if (data.Dead) + { + if (data.m_workBuilding == 0) + { + args.CitizenMgr.ReleaseCitizen(citizenID); + } + else + { + if (data.m_homeBuilding != 0) + { + data.SetHome(citizenID, 0, 0u); + } + + if (data.m_visitBuilding != 0) + { + data.SetVisitplace(citizenID, 0, 0u); + } + + if (data.m_vehicle != 0) + { + return false; + } + + if (FindHospital(args.ResidentAI, citizenID, data.m_workBuilding, TransferManager.TransferReason.Dead)) + { + return false; + } + } + + return true; + } + + if (data.Arrested) + { + data.Arrested = false; + } + else + { + if (data.Sick) + { + if (data.m_workBuilding == 0) + { + data.CurrentLocation = Citizen.Location.Home; + return false; + } + + if (data.m_vehicle != 0) + { + return false; + } + + if (FindHospital(args.ResidentAI, citizenID, data.m_workBuilding, TransferManager.TransferReason.Sick)) + { + return false; + } + + return true; + } + + if (data.m_workBuilding == 0) + { + data.CurrentLocation = Citizen.Location.Home; + } + else + { + ushort eventIndex = args.BuildingMgr.m_buildings.m_buffer[data.m_workBuilding].m_eventIndex; + if ((args.BuildingMgr.m_buildings.m_buffer[data.m_workBuilding].m_flags & Building.Flags.Evacuating) != 0) + { + if (data.m_vehicle == 0) + { + FindEvacuationPlace(args.ResidentAI, citizenID, data.m_workBuilding, GetEvacuationReason(args.ResidentAI, data.m_workBuilding)); + } + } + else if (eventIndex == 0 || (Singleton.instance.m_events.m_buffer[eventIndex].m_flags & (EventData.Flags.Preparing | EventData.Flags.Active | EventData.Flags.Ready)) == EventData.Flags.None) + { + if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) + { + if (data.m_vehicle == 0) + { + FindVisitPlace(args.ResidentAI, citizenID, data.m_workBuilding, GetShoppingReason(args.ResidentAI)); + } + } + else + { + if (data.m_instance == 0 && !DoRandomMove(args.ResidentAI)) + { + return false; + } + + int dayTimeFrame = (int)args.SimMgr.m_dayTimeFrame; + int dAYTIME_FRAMES = (int)SimulationManager.DAYTIME_FRAMES; + int num2 = dAYTIME_FRAMES / 40; + int num3 = (int)(SimulationManager.DAYTIME_FRAMES * 16) / 24; + int num4 = dayTimeFrame - num3 & dAYTIME_FRAMES - 1; + int num5 = Mathf.Abs(num4 - (dAYTIME_FRAMES >> 1)); + num5 = num5 * num5 / (dAYTIME_FRAMES >> 1); + int num6 = args.SimMgr.m_randomizer.Int32((uint)dAYTIME_FRAMES); + if (num6 < num2) + { + if (data.m_vehicle == 0) + { + FindVisitPlace(args.ResidentAI, citizenID, data.m_workBuilding, GetEntertainmentReason(args.ResidentAI)); + } + } + else if (num6 < num2 + num5 && data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, data.m_workBuilding, data.m_homeBuilding); + } + } + } + } + } + + return false; + } + } +} diff --git a/src/RealTime/AI/RealTimeResidentAI.cs b/src/RealTime/AI/RealTimeResidentAI.cs new file mode 100644 index 00000000..e72fc4d0 --- /dev/null +++ b/src/RealTime/AI/RealTimeResidentAI.cs @@ -0,0 +1,136 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.AI +{ + using System; + using System.Runtime.CompilerServices; + using ColossalFramework; + using Redirection; + + internal static partial class RealTimeResidentAI + { + private const string RedirectNeededMessage = "This method must be redirected to the original implementation"; + + [RedirectFrom(typeof(ResidentAI))] + private static void UpdateLocation(ResidentAI instance, uint citizenID, ref Citizen data) + { + CitizenManager citizenMgr = Singleton.instance; + BuildingManager buildingMgr = Singleton.instance; + SimulationManager simMgr = Singleton.instance; + var args = new Arguments(instance, citizenMgr, buildingMgr, simMgr); + + if (data.m_homeBuilding == 0 && data.m_workBuilding == 0 && data.m_visitBuilding == 0 && data.m_instance == 0 && data.m_vehicle == 0) + { + citizenMgr.ReleaseCitizen(citizenID); + return; + } + + switch (data.CurrentLocation) + { + case Citizen.Location.Home: + if (ProcessCitizenAtHome(args, citizenID, ref data)) + { + return; + } + + break; + + case Citizen.Location.Work: + if (ProcessCitizenAtWork(args, citizenID, ref data)) + { + return; + } + + break; + + case Citizen.Location.Visit: + if (ProcessCitizenVisit(args, citizenID, ref data)) + { + return; + } + + break; + + case Citizen.Location.Moving: + if (ProcessCitizenMoving(args, citizenID, ref data)) + { + return; + } + + break; + } + + data.m_flags &= ~Citizen.Flags.NeedGoods; + } + + [RedirectTo(typeof(ResidentAI))] + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool FindHospital(ResidentAI instance, uint citizenID, ushort sourceBuilding, TransferManager.TransferReason reason) + { + throw new InvalidOperationException(RedirectNeededMessage); + } + + [RedirectTo(typeof(ResidentAI))] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void FindEvacuationPlace(ResidentAI instance, uint citizenID, ushort sourceBuilding, TransferManager.TransferReason reason) + { + throw new InvalidOperationException(RedirectNeededMessage); + } + + [RedirectTo(typeof(ResidentAI))] + [MethodImpl(MethodImplOptions.NoInlining)] + private static TransferManager.TransferReason GetEvacuationReason(ResidentAI instance, ushort sourceBuilding) + { + throw new InvalidOperationException(RedirectNeededMessage); + } + + [RedirectTo(typeof(ResidentAI))] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void FindVisitPlace(ResidentAI instance, uint citizenID, ushort sourceBuilding, TransferManager.TransferReason reason) + { + throw new InvalidOperationException(RedirectNeededMessage); + } + + [RedirectTo(typeof(ResidentAI))] + [MethodImpl(MethodImplOptions.NoInlining)] + private static TransferManager.TransferReason GetShoppingReason(ResidentAI instance) + { + throw new InvalidOperationException(RedirectNeededMessage); + } + + [RedirectTo(typeof(ResidentAI))] + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool DoRandomMove(ResidentAI instance) + { + throw new InvalidOperationException(RedirectNeededMessage); + } + + [RedirectTo(typeof(ResidentAI))] + [MethodImpl(MethodImplOptions.NoInlining)] + private static TransferManager.TransferReason GetEntertainmentReason(ResidentAI instance) + { + throw new InvalidOperationException(RedirectNeededMessage); + } + + private sealed class Arguments + { + public Arguments(ResidentAI residentAI, CitizenManager citizenMgr, BuildingManager buildingMgr, SimulationManager simMgr) + { + ResidentAI = residentAI; + CitizenMgr = citizenMgr; + BuildingMgr = buildingMgr; + SimMgr = simMgr; + } + + public ResidentAI ResidentAI { get; } + + public CitizenManager CitizenMgr { get; } + + public BuildingManager BuildingMgr { get; } + + public SimulationManager SimMgr { get; } + } + } +} diff --git a/src/RealTime/AI/ResidentAI.cs b/src/RealTime/AI/ResidentAI.cs new file mode 100644 index 00000000..314dcab1 --- /dev/null +++ b/src/RealTime/AI/ResidentAI.cs @@ -0,0 +1,2871 @@ +using ColossalFramework; +using ColossalFramework.Globalization; +using ColossalFramework.Math; +using ColossalFramework.PlatformServices; +using ColossalFramework.Threading; +using System; +using UnityEngine; + +public class ResidentAI1 : HumanAI +{ + public const int UNIVERSITY_DURATION = 15; + + public const int BREED_INTERVAL = 12; + + public const int GAY_PROBABILITY = 5; + + public const int CAR_PROBABILITY_CHILD = 0; + + public const int CAR_PROBABILITY_TEEN = 5; + + public const int CAR_PROBABILITY_YOUNG = 15; + + public const int CAR_PROBABILITY_ADULT = 20; + + public const int CAR_PROBABILITY_SENIOR = 10; + + public const int BIKE_PROBABILITY_CHILD = 40; + + public const int BIKE_PROBABILITY_TEEN = 30; + + public const int BIKE_PROBABILITY_YOUNG = 20; + + public const int BIKE_PROBABILITY_ADULT = 10; + + public const int BIKE_PROBABILITY_SENIOR = 0; + + public const int TAXI_PROBABILITY_CHILD = 0; + + public const int TAXI_PROBABILITY_TEEN = 2; + + public const int TAXI_PROBABILITY_YOUNG = 2; + + public const int TAXI_PROBABILITY_ADULT = 4; + + public const int TAXI_PROBABILITY_SENIOR = 6; + + public override Color GetColor(ushort instanceID, ref CitizenInstance data, InfoManager.InfoMode infoMode) + { + switch (infoMode) + { + case InfoManager.InfoMode.Health: + { + int health2 = Singleton.instance.m_citizens.m_buffer[data.m_citizen].m_health; + return Color.Lerp(Singleton.instance.m_properties.m_modeProperties[(int)infoMode].m_negativeColor, Singleton.instance.m_properties.m_modeProperties[(int)infoMode].m_targetColor, (float)Citizen.GetHealthLevel(health2) * 0.2f); + } + case InfoManager.InfoMode.Happiness: + { + int health = Singleton.instance.m_citizens.m_buffer[data.m_citizen].m_health; + int wellbeing = Singleton.instance.m_citizens.m_buffer[data.m_citizen].m_wellbeing; + int happiness = Citizen.GetHappiness(health, wellbeing); + return Color.Lerp(Singleton.instance.m_properties.m_modeProperties[(int)infoMode].m_negativeColor, Singleton.instance.m_properties.m_modeProperties[(int)infoMode].m_targetColor, (float)Citizen.GetHappinessLevel(happiness) * 0.25f); + } + default: + return base.GetColor(instanceID, ref data, infoMode); + } + } + + public override void SetRenderParameters(RenderManager.CameraInfo cameraInfo, ushort instanceID, ref CitizenInstance data, Vector3 position, Quaternion rotation, Vector3 velocity, Color color, bool underground) + { + if ((data.m_flags & CitizenInstance.Flags.AtTarget) != 0) + { + if ((data.m_flags & CitizenInstance.Flags.SittingDown) != 0) + { + base.m_info.SetRenderParameters(position, rotation, velocity, color, 2, underground); + return; + } + if ((data.m_flags & (CitizenInstance.Flags.Panicking | CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) == CitizenInstance.Flags.Panicking) + { + base.m_info.SetRenderParameters(position, rotation, velocity, color, 1, underground); + return; + } + if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating | CitizenInstance.Flags.Cheering)) == CitizenInstance.Flags.Cheering) + { + base.m_info.SetRenderParameters(position, rotation, velocity, color, 5, underground); + return; + } + } + if ((data.m_flags & CitizenInstance.Flags.RidingBicycle) != 0) + { + base.m_info.SetRenderParameters(position, rotation, velocity, color, 3, underground); + } + else if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != 0) + { + base.m_info.SetRenderParameters(position, rotation, Vector3.zero, color, 1, underground); + } + else + { + base.m_info.SetRenderParameters(position, rotation, velocity, color, instanceID & 4, underground); + } + } + + public override string GetLocalizedStatus(ushort instanceID, ref CitizenInstance data, out InstanceID target) + { + if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != 0) + { + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_CONFUSED"); + } + CitizenManager instance = Singleton.instance; + uint citizen = data.m_citizen; + bool flag = false; + ushort num = 0; + ushort num2 = 0; + ushort num3 = 0; + if (citizen != 0) + { + num = instance.m_citizens.m_buffer[citizen].m_homeBuilding; + num2 = instance.m_citizens.m_buffer[citizen].m_workBuilding; + num3 = instance.m_citizens.m_buffer[citizen].m_vehicle; + flag = ((instance.m_citizens.m_buffer[citizen].m_flags & Citizen.Flags.Student) != Citizen.Flags.None); + } + ushort targetBuilding = data.m_targetBuilding; + bool flag2; + bool flag3; + if (targetBuilding != 0) + { + if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != 0) + { + if (num3 != 0) + { + VehicleManager instance2 = Singleton.instance; + VehicleInfo info = instance2.m_vehicles.m_buffer[num3].Info; + if (info.m_class.m_service == ItemClass.Service.Residential && info.m_vehicleType != VehicleInfo.VehicleType.Bicycle) + { + if (info.m_vehicleAI.GetOwnerID(num3, ref instance2.m_vehicles.m_buffer[num3]).Citizen == citizen) + { + target = InstanceID.Empty; + target.NetNode = targetBuilding; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_DRIVINGTO"); + } + } + else if (info.m_class.m_service == ItemClass.Service.PublicTransport || info.m_class.m_service == ItemClass.Service.Disaster) + { + ushort transportLine = Singleton.instance.m_nodes.m_buffer[targetBuilding].m_transportLine; + if ((data.m_flags & CitizenInstance.Flags.WaitingTaxi) != 0) + { + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_WAITING_TAXI"); + } + if (instance2.m_vehicles.m_buffer[num3].m_transportLine != transportLine) + { + target = InstanceID.Empty; + target.NetNode = targetBuilding; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); + } + } + } + if ((data.m_flags & CitizenInstance.Flags.OnTour) != 0) + { + target = InstanceID.Empty; + target.NetNode = targetBuilding; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING"); + } + target = InstanceID.Empty; + target.NetNode = targetBuilding; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_GOINGTO"); + } + flag2 = ((Singleton.instance.m_buildings.m_buffer[targetBuilding].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None); + flag3 = (data.m_path == 0 && (data.m_flags & CitizenInstance.Flags.HangAround) != CitizenInstance.Flags.None); + if (num3 != 0) + { + VehicleManager instance3 = Singleton.instance; + VehicleInfo info2 = instance3.m_vehicles.m_buffer[num3].Info; + if (info2.m_class.m_service == ItemClass.Service.Residential && info2.m_vehicleType != VehicleInfo.VehicleType.Bicycle) + { + if (info2.m_vehicleAI.GetOwnerID(num3, ref instance3.m_vehicles.m_buffer[num3]).Citizen == citizen) + { + if (flag2) + { + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_DRIVINGTO_OUTSIDE"); + } + if (targetBuilding == num) + { + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_DRIVINGTO_HOME"); + } + if (targetBuilding == num2) + { + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get((!flag) ? "CITIZEN_STATUS_DRIVINGTO_WORK" : "CITIZEN_STATUS_DRIVINGTO_SCHOOL"); + } + target = InstanceID.Empty; + target.Building = targetBuilding; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_DRIVINGTO"); + } + goto IL_0480; + } + if (info2.m_class.m_service != ItemClass.Service.PublicTransport && info2.m_class.m_service != ItemClass.Service.Disaster) + { + goto IL_0480; + } + if ((data.m_flags & CitizenInstance.Flags.WaitingTaxi) != 0) + { + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_WAITING_TAXI"); + } + if (flag2) + { + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_OUTSIDE"); + } + if (targetBuilding == num) + { + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_HOME"); + } + if (targetBuilding == num2) + { + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get((!flag) ? "CITIZEN_STATUS_TRAVELLINGTO_WORK" : "CITIZEN_STATUS_TRAVELLINGTO_SCHOOL"); + } + target = InstanceID.Empty; + target.Building = targetBuilding; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); + } + goto IL_0480; + } + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_CONFUSED"); + IL_0480: + if (flag2) + { + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_GOINGTO_OUTSIDE"); + } + if (targetBuilding == num) + { + if (flag3) + { + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_AT_HOME"); + } + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_GOINGTO_HOME"); + } + if (targetBuilding == num2) + { + if (flag3) + { + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get((!flag) ? "CITIZEN_STATUS_AT_WORK" : "CITIZEN_STATUS_AT_SCHOOL"); + } + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get((!flag) ? "CITIZEN_STATUS_GOINGTO_WORK" : "CITIZEN_STATUS_GOINGTO_SCHOOL"); + } + if (flag3) + { + target = InstanceID.Empty; + target.Building = targetBuilding; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING"); + } + target = InstanceID.Empty; + target.Building = targetBuilding; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_GOINGTO"); + } + + public override string GetLocalizedStatus(uint citizenID, ref Citizen data, out InstanceID target) + { + CitizenManager instance = Singleton.instance; + ushort instance2 = data.m_instance; + if (instance2 != 0) + { + return GetLocalizedStatus(instance2, ref instance.m_instances.m_buffer[instance2], out target); + } + Citizen.Location currentLocation = data.CurrentLocation; + ushort homeBuilding = data.m_homeBuilding; + ushort workBuilding = data.m_workBuilding; + ushort visitBuilding = data.m_visitBuilding; + bool flag = (data.m_flags & Citizen.Flags.Student) != Citizen.Flags.None; + switch (currentLocation) + { + case Citizen.Location.Home: + if (homeBuilding == 0) + { + break; + } + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_AT_HOME"); + case Citizen.Location.Work: + if (workBuilding == 0) + { + break; + } + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get((!flag) ? "CITIZEN_STATUS_AT_WORK" : "CITIZEN_STATUS_AT_SCHOOL"); + case Citizen.Location.Visit: + if (visitBuilding == 0) + { + break; + } + target = InstanceID.Empty; + target.Building = visitBuilding; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING"); + } + target = InstanceID.Empty; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_CONFUSED"); + } + + public override void LoadInstance(ushort instanceID, ref CitizenInstance data) + { + base.LoadInstance(instanceID, ref data); + if (data.m_sourceBuilding != 0) + { + Singleton.instance.m_buildings.m_buffer[data.m_sourceBuilding].AddSourceCitizen(instanceID, ref data); + } + if (data.m_targetBuilding != 0) + { + if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != 0) + { + Singleton.instance.m_nodes.m_buffer[data.m_targetBuilding].AddTargetCitizen(instanceID, ref data); + } + else + { + Singleton.instance.m_buildings.m_buffer[data.m_targetBuilding].AddTargetCitizen(instanceID, ref data); + } + } + } + + public override void SimulationStep(ushort instanceID, ref CitizenInstance citizenData, ref CitizenInstance.Frame frameData, bool lodPhysics) + { + uint currentFrameIndex = Singleton.instance.m_currentFrameIndex; + if ((currentFrameIndex >> 4 & 0x3F) == (instanceID & 0x3F)) + { + CitizenManager instance = Singleton.instance; + uint citizen = citizenData.m_citizen; + if (citizen != 0 && (instance.m_citizens.m_buffer[citizen].m_flags & Citizen.Flags.NeedGoods) != 0) + { + BuildingManager instance2 = Singleton.instance; + ushort homeBuilding = instance.m_citizens.m_buffer[citizen].m_homeBuilding; + ushort num = instance2.FindBuilding(frameData.m_position, 32f, ItemClass.Service.Commercial, ItemClass.SubService.None, Building.Flags.Created | Building.Flags.Active, Building.Flags.Deleted); + if (homeBuilding != 0 && num != 0) + { + BuildingInfo info = instance2.m_buildings.m_buffer[num].Info; + int num2 = -100; + info.m_buildingAI.ModifyMaterialBuffer(num, ref instance2.m_buildings.m_buffer[num], TransferManager.TransferReason.Shopping, ref num2); + uint containingUnit = instance.m_citizens.m_buffer[citizen].GetContainingUnit(citizen, instance2.m_buildings.m_buffer[homeBuilding].m_citizenUnits, CitizenUnit.Flags.Home); + if (containingUnit != 0) + { + instance.m_units.m_buffer[containingUnit].m_goods += (ushort)(-num2); + } + instance.m_citizens.m_buffer[citizen].m_flags &= ~Citizen.Flags.NeedGoods; + } + } + } + base.SimulationStep(instanceID, ref citizenData, ref frameData, lodPhysics); + } + + public override void SimulationStep(uint citizenID, ref Citizen data) + { + if (!data.Dead && UpdateAge(citizenID, ref data)) + { + return; + } + if (!data.Dead) + { + UpdateHome(citizenID, ref data); + } + if (!data.Sick && !data.Dead) + { + if (UpdateHealth(citizenID, ref data)) + { + return; + } + UpdateWellbeing(citizenID, ref data); + UpdateWorkplace(citizenID, ref data); + } + UpdateLocation(citizenID, ref data); + } + + public override void SimulationStep(uint homeID, ref CitizenUnit data) + { + CitizenManager instance = Singleton.instance; + ushort building = instance.m_units.m_buffer[homeID].m_building; + if (data.m_citizen0 != 0 && data.m_citizen1 != 0 && (data.m_citizen2 == 0 || data.m_citizen3 == 0 || data.m_citizen4 == 0)) + { + bool flag = CanMakeBabies(data.m_citizen0, ref instance.m_citizens.m_buffer[data.m_citizen0]); + bool flag2 = CanMakeBabies(data.m_citizen1, ref instance.m_citizens.m_buffer[data.m_citizen1]); + if (flag && flag2 && Singleton.instance.m_randomizer.Int32(12u) == 0) + { + int family = instance.m_citizens.m_buffer[data.m_citizen0].m_family; + if (instance.CreateCitizen(out uint num, 0, family, ref Singleton.instance.m_randomizer)) + { + instance.m_citizens.m_buffer[num].SetHome(num, 0, homeID); + instance.m_citizens.m_buffer[num].m_flags |= Citizen.Flags.Original; + if (building != 0) + { + DistrictManager instance2 = Singleton.instance; + Vector3 position = Singleton.instance.m_buildings.m_buffer[building].m_position; + byte district = instance2.GetDistrict(position); + instance2.m_districts.m_buffer[district].m_birthData.m_tempCount += 1u; + } + } + } + } + if (data.m_citizen0 != 0 && data.m_citizen1 == 0) + { + TryFindPartner(data.m_citizen0, ref instance.m_citizens.m_buffer[data.m_citizen0]); + } + else if (data.m_citizen1 != 0 && data.m_citizen0 == 0) + { + TryFindPartner(data.m_citizen1, ref instance.m_citizens.m_buffer[data.m_citizen1]); + } + if (data.m_citizen2 != 0) + { + TryMoveAwayFromHome(data.m_citizen2, ref instance.m_citizens.m_buffer[data.m_citizen2]); + } + if (data.m_citizen3 != 0) + { + TryMoveAwayFromHome(data.m_citizen3, ref instance.m_citizens.m_buffer[data.m_citizen3]); + } + if (data.m_citizen4 != 0) + { + TryMoveAwayFromHome(data.m_citizen4, ref instance.m_citizens.m_buffer[data.m_citizen4]); + } + data.m_goods = (ushort)Mathf.Max(0, data.m_goods - 20); + if (data.m_goods < 200) + { + int num2 = Singleton.instance.m_randomizer.Int32(5u); + for (int i = 0; i < 5; i++) + { + uint citizen = data.GetCitizen((num2 + i) % 5); + if (citizen != 0) + { + instance.m_citizens.m_buffer[citizen].m_flags |= Citizen.Flags.NeedGoods; + break; + } + } + } + if (building != 0 && ((long)Singleton.instance.m_buildings.m_buffer[building].m_problems & -4611686018427387904L) != 0) + { + uint num3 = 0u; + int num4 = 0; + if (data.m_citizen4 != 0 && !instance.m_citizens.m_buffer[data.m_citizen4].Dead) + { + num4++; + num3 = data.m_citizen4; + } + if (data.m_citizen3 != 0 && !instance.m_citizens.m_buffer[data.m_citizen3].Dead) + { + num4++; + num3 = data.m_citizen3; + } + if (data.m_citizen2 != 0 && !instance.m_citizens.m_buffer[data.m_citizen2].Dead) + { + num4++; + num3 = data.m_citizen2; + } + if (data.m_citizen1 != 0 && !instance.m_citizens.m_buffer[data.m_citizen1].Dead) + { + num4++; + num3 = data.m_citizen1; + } + if (data.m_citizen0 != 0 && !instance.m_citizens.m_buffer[data.m_citizen0].Dead) + { + num4++; + num3 = data.m_citizen0; + } + if (num3 != 0) + { + TryMoveFamily(num3, ref instance.m_citizens.m_buffer[num3], num4); + } + } + } + + protected override void PathfindSuccess(ushort instanceID, ref CitizenInstance data) + { + uint citizen = data.m_citizen; + if (citizen != 0) + { + CitizenManager instance = Singleton.instance; + if ((instance.m_citizens.m_buffer[citizen].m_flags & (Citizen.Flags.MovingIn | Citizen.Flags.DummyTraffic)) == Citizen.Flags.MovingIn) + { + StatisticBase statisticBase = Singleton.instance.Acquire(StatisticType.MoveRate); + statisticBase.Add(1); + } + } + base.PathfindSuccess(instanceID, ref data); + } + + protected override void Spawn(ushort instanceID, ref CitizenInstance data) + { + if ((data.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) + { + data.Spawn(instanceID); + uint citizen = data.m_citizen; + ushort targetBuilding = data.m_targetBuilding; + if (citizen != 0 && targetBuilding != 0) + { + Randomizer randomizer = new Randomizer(citizen); + if (randomizer.Int32(20u) == 0) + { + CitizenManager instance = Singleton.instance; + DistrictManager instance2 = Singleton.instance; + Vector3 position; + if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != 0) + { + NetManager instance3 = Singleton.instance; + position = instance3.m_nodes.m_buffer[targetBuilding].m_position; + } + else + { + BuildingManager instance4 = Singleton.instance; + position = instance4.m_buildings.m_buffer[targetBuilding].m_position; + } + byte district = instance2.GetDistrict(data.m_targetPos); + byte district2 = instance2.GetDistrict(position); + DistrictPolicies.Services servicePolicies = instance2.m_districts.m_buffer[district].m_servicePolicies; + DistrictPolicies.Services servicePolicies2 = instance2.m_districts.m_buffer[district2].m_servicePolicies; + if (((servicePolicies | servicePolicies2) & DistrictPolicies.Services.PetBan) == DistrictPolicies.Services.None) + { + CitizenInfo groupAnimalInfo = instance.GetGroupAnimalInfo(ref randomizer, base.m_info.m_class.m_service, base.m_info.m_class.m_subService); + if ((object)groupAnimalInfo != null && instance.CreateCitizenInstance(out ushort num, ref randomizer, groupAnimalInfo, 0u)) + { + groupAnimalInfo.m_citizenAI.SetSource(num, ref instance.m_instances.m_buffer[num], instanceID); + groupAnimalInfo.m_citizenAI.SetTarget(num, ref instance.m_instances.m_buffer[num], instanceID); + } + } + } + } + } + } + + private bool UpdateAge(uint citizenID, ref Citizen data) + { + int num = data.Age + 1; + if (num <= 45) + { + if (num == 15 || num == 45) + { + FinishSchoolOrWork(citizenID, ref data); + } + } + else if (num == 90 || num == 180) + { + FinishSchoolOrWork(citizenID, ref data); + } + else if ((data.m_flags & Citizen.Flags.Student) != 0 && num % 15 == 0) + { + FinishSchoolOrWork(citizenID, ref data); + } + if ((data.m_flags & Citizen.Flags.Original) != 0) + { + CitizenManager instance = Singleton.instance; + if (instance.m_tempOldestOriginalResident < num) + { + instance.m_tempOldestOriginalResident = num; + } + if (num == 240) + { + Singleton.instance.Acquire(StatisticType.FullLifespans).Add(1); + } + } + data.Age = num; + if (num >= 240 && data.CurrentLocation != Citizen.Location.Moving && data.m_vehicle == 0 && Singleton.instance.m_randomizer.Int32(240, 255) <= num) + { + Die(citizenID, ref data); + if (Singleton.instance.m_randomizer.Int32(2u) == 0) + { + Singleton.instance.ReleaseCitizen(citizenID); + return true; + } + } + return false; + } + + private void Die(uint citizenID, ref Citizen data) + { + data.Sick = false; + data.Dead = true; + data.SetParkedVehicle(citizenID, 0); + if ((data.m_flags & Citizen.Flags.MovingIn) == Citizen.Flags.None) + { + ushort num = data.GetBuildingByLocation(); + if (num == 0) + { + num = data.m_homeBuilding; + } + if (num != 0) + { + DistrictManager instance = Singleton.instance; + Vector3 position = Singleton.instance.m_buildings.m_buffer[num].m_position; + byte district = instance.GetDistrict(position); + instance.m_districts.m_buffer[district].m_deathData.m_tempCount += 1u; + } + } + } + + private void UpdateHome(uint citizenID, ref Citizen data) + { + if (data.m_homeBuilding == 0 && (data.m_flags & Citizen.Flags.DummyTraffic) == Citizen.Flags.None) + { + TransferManager.TransferOffer offer = default(TransferManager.TransferOffer); + offer.Priority = 7; + offer.Citizen = citizenID; + offer.Amount = 1; + offer.Active = true; + if (data.m_workBuilding != 0) + { + BuildingManager instance = Singleton.instance; + offer.Position = instance.m_buildings.m_buffer[data.m_workBuilding].m_position; + } + else + { + offer.PositionX = Singleton.instance.m_randomizer.Int32(256u); + offer.PositionZ = Singleton.instance.m_randomizer.Int32(256u); + } + if (Singleton.instance.m_randomizer.Int32(2u) == 0) + { + switch (data.EducationLevel) + { + case Citizen.Education.Uneducated: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single0, offer); + break; + case Citizen.Education.OneSchool: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single1, offer); + break; + case Citizen.Education.TwoSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single2, offer); + break; + case Citizen.Education.ThreeSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single3, offer); + break; + } + } + else + { + switch (data.EducationLevel) + { + case Citizen.Education.Uneducated: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single0B, offer); + break; + case Citizen.Education.OneSchool: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single1B, offer); + break; + case Citizen.Education.TwoSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single2B, offer); + break; + case Citizen.Education.ThreeSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single3B, offer); + break; + } + } + } + } + + private void UpdateWorkplace(uint citizenID, ref Citizen data) + { + if (data.m_workBuilding == 0 && data.m_homeBuilding != 0) + { + BuildingManager instance = Singleton.instance; + Vector3 position = instance.m_buildings.m_buffer[data.m_homeBuilding].m_position; + DistrictManager instance2 = Singleton.instance; + byte district = instance2.GetDistrict(position); + DistrictPolicies.Services servicePolicies = instance2.m_districts.m_buffer[district].m_servicePolicies; + int age = data.Age; + TransferManager.TransferReason transferReason = TransferManager.TransferReason.None; + switch (Citizen.GetAgeGroup(age)) + { + case Citizen.AgeGroup.Child: + if (!data.Education1) + { + transferReason = TransferManager.TransferReason.Student1; + } + break; + case Citizen.AgeGroup.Teen: + if (!data.Education2) + { + transferReason = TransferManager.TransferReason.Student2; + } + break; + case Citizen.AgeGroup.Young: + case Citizen.AgeGroup.Adult: + if (!data.Education3) + { + transferReason = TransferManager.TransferReason.Student3; + } + break; + } + if (data.Unemployed != 0 && ((servicePolicies & DistrictPolicies.Services.EducationBoost) == DistrictPolicies.Services.None || transferReason != TransferManager.TransferReason.Student3 || age % 5 > 2)) + { + TransferManager.TransferOffer offer = default(TransferManager.TransferOffer); + offer.Priority = Singleton.instance.m_randomizer.Int32(8u); + offer.Citizen = citizenID; + offer.Position = position; + offer.Amount = 1; + offer.Active = true; + switch (data.EducationLevel) + { + case Citizen.Education.Uneducated: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Worker0, offer); + break; + case Citizen.Education.OneSchool: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Worker1, offer); + break; + case Citizen.Education.TwoSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Worker2, offer); + break; + case Citizen.Education.ThreeSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Worker3, offer); + break; + } + } + switch (transferReason) + { + case TransferManager.TransferReason.None: + return; + case TransferManager.TransferReason.Student3: + if ((servicePolicies & DistrictPolicies.Services.SchoolsOut) != 0 && age % 5 <= 1) + { + return; + } + break; + } + TransferManager.TransferOffer offer2 = default(TransferManager.TransferOffer); + offer2.Priority = Singleton.instance.m_randomizer.Int32(8u); + offer2.Citizen = citizenID; + offer2.Position = position; + offer2.Amount = 1; + offer2.Active = true; + Singleton.instance.AddOutgoingOffer(transferReason, offer2); + } + } + + private bool UpdateHealth(uint citizenID, ref Citizen data) + { + if (data.m_homeBuilding == 0) + { + return false; + } + int num = 20; + BuildingManager instance = Singleton.instance; + BuildingInfo info = instance.m_buildings.m_buffer[data.m_homeBuilding].Info; + Vector3 position = instance.m_buildings.m_buffer[data.m_homeBuilding].m_position; + DistrictManager instance2 = Singleton.instance; + byte district = instance2.GetDistrict(position); + DistrictPolicies.Services servicePolicies = instance2.m_districts.m_buffer[district].m_servicePolicies; + DistrictPolicies.CityPlanning cityPlanningPolicies = instance2.m_districts.m_buffer[district].m_cityPlanningPolicies; + if ((servicePolicies & DistrictPolicies.Services.SmokingBan) != 0) + { + num += 10; + } + if (data.Age >= 180 && (cityPlanningPolicies & DistrictPolicies.CityPlanning.AntiSlip) != 0) + { + num += 10; + } + info.m_buildingAI.GetMaterialAmount(data.m_homeBuilding, ref instance.m_buildings.m_buffer[data.m_homeBuilding], TransferManager.TransferReason.Garbage, out int num2, out int _); + num2 /= 1000; + if (num2 <= 2) + { + num += 12; + } + else if (num2 >= 4) + { + num -= num2 - 3; + } + int healthCareRequirement = Citizen.GetHealthCareRequirement(Citizen.GetAgePhase(data.EducationLevel, data.Age)); + Singleton.instance.CheckResource(ImmaterialResourceManager.Resource.HealthCare, position, out int num4, out int num5); + if (healthCareRequirement != 0) + { + if (num4 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num4, healthCareRequirement, 500, 20, 40); + } + if (num5 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num5, healthCareRequirement >> 1, 250, 5, 20); + } + } + Singleton.instance.CheckLocalResource(ImmaterialResourceManager.Resource.NoisePollution, position, out int num6); + if (num6 != 0) + { + num = ((info.m_class.m_subService != ItemClass.SubService.ResidentialLowEco && info.m_class.m_subService != ItemClass.SubService.ResidentialHighEco) ? (num - num6 * 100 / 255) : (num - num6 * 150 / 255)); + } + Singleton.instance.CheckLocalResource(ImmaterialResourceManager.Resource.CrimeRate, position, out int num7); + if (num7 > 3) + { + num = ((num7 > 30) ? ((num7 > 70) ? (num - 15) : (num - 5)) : (num - 2)); + } + Singleton.instance.CheckWater(position, out bool flag, out bool flag2, out byte b); + if (flag) + { + num += 12; + data.NoWater = 0; + } + else + { + int noWater = data.NoWater; + if (noWater < 2) + { + data.NoWater = noWater + 1; + } + else + { + num -= 5; + } + } + if (flag2) + { + num += 12; + data.NoSewage = 0; + } + else + { + int noSewage = data.NoSewage; + if (noSewage < 2) + { + data.NoSewage = noSewage + 1; + } + else + { + num -= 5; + } + } + num = ((b >= 35) ? (num - (b * 2 - 35)) : (num - b)); + Singleton.instance.CheckPollution(position, out byte b2); + if (b2 != 0) + { + num = ((info.m_class.m_subService != ItemClass.SubService.ResidentialLowEco && info.m_class.m_subService != ItemClass.SubService.ResidentialHighEco) ? (num - b2 * 100 / 255) : (num - b2 * 200 / 255)); + } + num = Mathf.Clamp(num, 0, 100); + data.m_health = (byte)num; + int num8 = 0; + if (num <= 10) + { + int badHealth = data.BadHealth; + if (badHealth < 3) + { + num8 = 15; + data.BadHealth = badHealth + 1; + } + else + { + num8 = ((num5 != 0) ? 50 : 75); + } + } + else if (num <= 25) + { + data.BadHealth = 0; + num8 += 10; + } + else if (num <= 50) + { + data.BadHealth = 0; + num8 += 3; + } + else + { + data.BadHealth = 0; + } + Citizen.Location currentLocation = data.CurrentLocation; + if (currentLocation != Citizen.Location.Moving && data.m_vehicle == 0 && num8 != 0 && Singleton.instance.m_randomizer.Int32(100u) < num8) + { + if (Singleton.instance.m_randomizer.Int32(3u) == 0) + { + Die(citizenID, ref data); + if (Singleton.instance.m_randomizer.Int32(2u) == 0) + { + Singleton.instance.ReleaseCitizen(citizenID); + return true; + } + } + else + { + data.Sick = true; + } + } + return false; + } + + private void UpdateWellbeing(uint citizenID, ref Citizen data) + { + if (data.m_homeBuilding != 0) + { + int num = 0; + BuildingManager instance = Singleton.instance; + BuildingInfo info = instance.m_buildings.m_buffer[data.m_homeBuilding].Info; + Vector3 position = instance.m_buildings.m_buffer[data.m_homeBuilding].m_position; + ItemClass @class = info.m_class; + DistrictManager instance2 = Singleton.instance; + byte district = instance2.GetDistrict(position); + DistrictPolicies.Services servicePolicies = instance2.m_districts.m_buffer[district].m_servicePolicies; + DistrictPolicies.Taxation taxationPolicies = instance2.m_districts.m_buffer[district].m_taxationPolicies; + DistrictPolicies.CityPlanning cityPlanningPolicies = instance2.m_districts.m_buffer[district].m_cityPlanningPolicies; + int health = data.m_health; + if (health > 80) + { + num += 10; + } + else if (health > 60) + { + num += 5; + } + num -= Mathf.Clamp(50 - health, 0, 30); + if ((servicePolicies & DistrictPolicies.Services.PetBan) != 0) + { + num -= 5; + } + if ((servicePolicies & DistrictPolicies.Services.SmokingBan) != 0) + { + num -= 15; + } + Building.Frame lastFrameData = instance.m_buildings.m_buffer[data.m_homeBuilding].GetLastFrameData(); + if (lastFrameData.m_fireDamage != 0) + { + num -= 15; + } + Citizen.Wealth wealthLevel = data.WealthLevel; + Citizen.AgePhase agePhase = Citizen.GetAgePhase(data.EducationLevel, data.Age); + int taxRate = Singleton.instance.GetTaxRate(@class, taxationPolicies); + int num2 = (int)(8 - wealthLevel); + int num3 = (int)(11 - wealthLevel); + if (@class.m_subService == ItemClass.SubService.ResidentialHigh) + { + num2++; + num3++; + } + if (taxRate < num2) + { + num += num2 - taxRate; + } + if (taxRate > num3) + { + num -= taxRate - num3; + } + int policeDepartmentRequirement = Citizen.GetPoliceDepartmentRequirement(agePhase); + if (policeDepartmentRequirement != 0) + { + Singleton.instance.CheckResource(ImmaterialResourceManager.Resource.PoliceDepartment, position, out int num4, out int num5); + if (num4 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num4, policeDepartmentRequirement, 500, 20, 40); + } + if (num5 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num5, policeDepartmentRequirement >> 1, 250, 5, 20); + } + } + int fireDepartmentRequirement = Citizen.GetFireDepartmentRequirement(agePhase); + if (fireDepartmentRequirement != 0) + { + Singleton.instance.CheckResource(ImmaterialResourceManager.Resource.FireDepartment, position, out int num6, out int num7); + if (num6 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num6, fireDepartmentRequirement, 500, 20, 40); + } + if (num7 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num7, fireDepartmentRequirement >> 1, 250, 5, 20); + } + } + int educationRequirement = Citizen.GetEducationRequirement(agePhase); + if (educationRequirement != 0) + { + int num8; + int num9; + if (agePhase < Citizen.AgePhase.Teen0) + { + Singleton.instance.CheckResource(ImmaterialResourceManager.Resource.EducationElementary, position, out num8, out num9); + if (num8 > 1000 && !data.Education1 && Singleton.instance.m_randomizer.Int32(9000u) < num8 - 1000) + { + data.Education1 = true; + } + } + else if (agePhase < Citizen.AgePhase.Young0) + { + Singleton.instance.CheckResource(ImmaterialResourceManager.Resource.EducationHighSchool, position, out num8, out num9); + if (num8 > 1000 && !data.Education2 && Singleton.instance.m_randomizer.Int32(9000u) < num8 - 1000) + { + data.Education2 = true; + } + } + else + { + Singleton.instance.CheckResource(ImmaterialResourceManager.Resource.EducationUniversity, position, out num8, out num9); + if (num8 > 1000 && !data.Education3 && Singleton.instance.m_randomizer.Int32(9000u) < num8 - 1000) + { + data.Education3 = true; + } + } + if (num8 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num8, educationRequirement, 500, 20, 40); + } + if (num9 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num9, educationRequirement >> 1, 250, 5, 20); + } + } + int entertainmentRequirement = Citizen.GetEntertainmentRequirement(agePhase); + if (entertainmentRequirement != 0) + { + Singleton.instance.CheckResource(ImmaterialResourceManager.Resource.Entertainment, position, out int num10, out int num11); + if (num10 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num10, entertainmentRequirement, 500, 30, 60); + } + if (num11 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num11, entertainmentRequirement >> 1, 250, 10, 40); + } + } + int transportRequirement = Citizen.GetTransportRequirement(agePhase); + if (transportRequirement != 0) + { + Singleton.instance.CheckResource(ImmaterialResourceManager.Resource.PublicTransport, position, out int num12, out int num13); + if (num12 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num12, transportRequirement, 500, 20, 40); + } + if (num13 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num13, transportRequirement >> 1, 250, 5, 20); + } + } + int deathCareRequirement = Citizen.GetDeathCareRequirement(agePhase); + Singleton.instance.CheckResource(ImmaterialResourceManager.Resource.DeathCare, position, out int num14, out int num15); + if (deathCareRequirement != 0) + { + if (num14 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num14, deathCareRequirement, 500, 10, 20); + } + if (num15 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num15, deathCareRequirement >> 1, 250, 3, 10); + } + } + Singleton.instance.CheckLocalResource(ImmaterialResourceManager.Resource.RadioCoverage, position, out int num16); + if (num16 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num16, 50, 100, 2, 3); + } + Singleton.instance.CheckLocalResource(ImmaterialResourceManager.Resource.DisasterCoverage, position, out int num17); + if (num17 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num17, 50, 100, 3, 4); + } + Singleton.instance.CheckLocalResource(ImmaterialResourceManager.Resource.FirewatchCoverage, position, out int num18); + if (num18 != 0) + { + num += ImmaterialResourceManager.CalculateResourceEffect(num18, 100, 1000, 0, 3); + } + Singleton.instance.CheckElectricity(position, out bool flag); + if (flag) + { + num += 12; + data.NoElectricity = 0; + } + else + { + int noElectricity = data.NoElectricity; + if (noElectricity < 2) + { + data.NoElectricity = noElectricity + 1; + } + else + { + num -= 5; + } + } + Singleton.instance.CheckHeating(position, out bool flag2); + if (flag2) + { + num += 5; + } + else if ((servicePolicies & DistrictPolicies.Services.NoElectricity) != 0) + { + num -= 10; + } + if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.ElectricCars) != 0) + { + int carProbability = GetCarProbability(Citizen.GetAgeGroup(data.Age)); + if (new Randomizer(citizenID).Int32(100u) < carProbability) + { + Singleton.instance.FetchResource(EconomyManager.Resource.PolicyCost, 200, @class); + } + } + bool flag3 = Singleton.instance.Unlocked(ItemClass.Service.PoliceDepartment); + int workRequirement = Citizen.GetWorkRequirement(agePhase); + if (workRequirement != 0) + { + if (data.m_workBuilding == 0) + { + int unemployed = data.Unemployed; + num -= unemployed * workRequirement / 100; + if (flag3) + { + data.Unemployed = unemployed + 1; + } + else + { + data.Unemployed = Mathf.Min(1, unemployed + 1); + } + } + else + { + data.Unemployed = 0; + } + } + else + { + data.Unemployed = 0; + } + num = Mathf.Clamp(num, 0, 100); + data.m_wellbeing = (byte)num; + if (flag3) + { + Randomizer randomizer = new Randomizer(citizenID * 7931 + 123); + int maxCrimeRate = Citizen.GetMaxCrimeRate(Citizen.GetWellbeingLevel(data.EducationLevel, num)); + int num19 = Mathf.Min(maxCrimeRate, Citizen.GetCrimeRate(data.Unemployed)); + data.Criminal = (randomizer.Int32(500u) < num19); + } + else + { + data.Criminal = false; + } + } + } + + private void FinishSchoolOrWork(uint citizenID, ref Citizen data) + { + if (data.m_workBuilding != 0) + { + if (data.CurrentLocation == Citizen.Location.Work && data.m_homeBuilding != 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + base.StartMoving(citizenID, ref data, data.m_workBuilding, data.m_homeBuilding); + } + BuildingManager instance = Singleton.instance; + CitizenManager instance2 = Singleton.instance; + uint num = instance.m_buildings.m_buffer[data.m_workBuilding].m_citizenUnits; + int num2 = 0; + do + { + if (num == 0) + { + return; + } + uint nextUnit = instance2.m_units.m_buffer[num].m_nextUnit; + CitizenUnit.Flags flags = instance2.m_units.m_buffer[num].m_flags; + if ((flags & (CitizenUnit.Flags.Work | CitizenUnit.Flags.Student)) != 0) + { + if ((flags & CitizenUnit.Flags.Student) != 0) + { + if (data.RemoveFromUnit(citizenID, ref instance2.m_units.m_buffer[num])) + { + BuildingInfo info = instance.m_buildings.m_buffer[data.m_workBuilding].Info; + if (info.m_buildingAI.GetEducationLevel1()) + { + data.Education1 = true; + } + if (info.m_buildingAI.GetEducationLevel2()) + { + data.Education2 = true; + } + if (info.m_buildingAI.GetEducationLevel3()) + { + data.Education3 = true; + } + data.m_workBuilding = 0; + data.m_flags &= ~Citizen.Flags.Student; + if ((data.m_flags & Citizen.Flags.Original) != 0 && data.EducationLevel == Citizen.Education.ThreeSchools && instance2.m_fullyEducatedOriginalResidents++ == 0 && Singleton.instance.m_metaData.m_disableAchievements != SimulationMetaData.MetaBool.True) + { + ThreadHelper.dispatcher.Dispatch(delegate + { + if (!PlatformService.achievements["ClimbingTheSocialLadder"].achieved) + { + PlatformService.achievements["ClimbingTheSocialLadder"].Unlock(); + } + }); + } + return; + } + } + else if (data.RemoveFromUnit(citizenID, ref instance2.m_units.m_buffer[num])) + { + data.m_workBuilding = 0; + data.m_flags &= ~Citizen.Flags.Student; + return; + } + } + num = nextUnit; + } + while (++num2 <= 524288); + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + } + } + + private bool DoRandomMove() + { + uint vehicleCount = (uint)Singleton.instance.m_vehicleCount; + uint instanceCount = (uint)Singleton.instance.m_instanceCount; + if (vehicleCount * 65536 > instanceCount * 16384) + { + return Singleton.instance.m_randomizer.UInt32(16384u) > vehicleCount; + } + return Singleton.instance.m_randomizer.UInt32(65536u) > instanceCount; + } + + private TransferManager.TransferReason GetShoppingReason() + { + switch (Singleton.instance.m_randomizer.Int32(8u)) + { + case 0: + return TransferManager.TransferReason.Shopping; + case 1: + return TransferManager.TransferReason.ShoppingB; + case 2: + return TransferManager.TransferReason.ShoppingC; + case 3: + return TransferManager.TransferReason.ShoppingD; + case 4: + return TransferManager.TransferReason.ShoppingE; + case 5: + return TransferManager.TransferReason.ShoppingF; + case 6: + return TransferManager.TransferReason.ShoppingG; + case 7: + return TransferManager.TransferReason.ShoppingH; + default: + return TransferManager.TransferReason.Shopping; + } + } + + private TransferManager.TransferReason GetEntertainmentReason() + { + switch (Singleton.instance.m_randomizer.Int32(4u)) + { + case 0: + return TransferManager.TransferReason.Entertainment; + case 1: + return TransferManager.TransferReason.EntertainmentB; + case 2: + return TransferManager.TransferReason.EntertainmentC; + case 3: + return TransferManager.TransferReason.EntertainmentD; + default: + return TransferManager.TransferReason.Entertainment; + } + } + + private TransferManager.TransferReason GetEvacuationReason(ushort sourceBuilding) + { + if (sourceBuilding != 0) + { + BuildingManager instance = Singleton.instance; + DistrictManager instance2 = Singleton.instance; + byte district = instance2.GetDistrict(instance.m_buildings.m_buffer[sourceBuilding].m_position); + DistrictPolicies.CityPlanning cityPlanningPolicies = instance2.m_districts.m_buffer[district].m_cityPlanningPolicies; + if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.VIPArea) != 0) + { + switch (Singleton.instance.m_randomizer.Int32(4u)) + { + case 0: + return TransferManager.TransferReason.EvacuateVipA; + case 1: + return TransferManager.TransferReason.EvacuateVipB; + case 2: + return TransferManager.TransferReason.EvacuateVipC; + case 3: + return TransferManager.TransferReason.EvacuateVipD; + default: + return TransferManager.TransferReason.EvacuateVipA; + } + } + } + switch (Singleton.instance.m_randomizer.Int32(4u)) + { + case 0: + return TransferManager.TransferReason.EvacuateA; + case 1: + return TransferManager.TransferReason.EvacuateB; + case 2: + return TransferManager.TransferReason.EvacuateC; + case 3: + return TransferManager.TransferReason.EvacuateD; + default: + return TransferManager.TransferReason.EvacuateA; + } + } + + private void UpdateLocation(uint citizenID, ref Citizen data) + { + if (data.m_homeBuilding == 0 && data.m_workBuilding == 0 && data.m_visitBuilding == 0 && data.m_instance == 0 && data.m_vehicle == 0) + { + Singleton.instance.ReleaseCitizen(citizenID); + } + else + { + switch (data.CurrentLocation) + { + case Citizen.Location.Home: + if ((data.m_flags & Citizen.Flags.MovingIn) != 0) + { + Singleton.instance.ReleaseCitizen(citizenID); + return; + } + if (data.Dead) + { + if (data.m_homeBuilding == 0) + { + Singleton.instance.ReleaseCitizen(citizenID); + } + else + { + if (data.m_workBuilding != 0) + { + data.SetWorkplace(citizenID, 0, 0u); + } + if (data.m_visitBuilding != 0) + { + data.SetVisitplace(citizenID, 0, 0u); + } + if (data.m_vehicle != 0) + { + break; + } + if (FindHospital(citizenID, data.m_homeBuilding, TransferManager.TransferReason.Dead)) + { + break; + } + } + return; + } + if (data.Arrested) + { + data.Arrested = false; + } + else if (data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + if (data.Sick) + { + if (FindHospital(citizenID, data.m_homeBuilding, TransferManager.TransferReason.Sick)) + { + break; + } + return; + } + if ((Singleton.instance.m_buildings.m_buffer[data.m_homeBuilding].m_flags & Building.Flags.Evacuating) != 0) + { + base.FindEvacuationPlace(citizenID, data.m_homeBuilding, GetEvacuationReason(data.m_homeBuilding)); + } + else if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) + { + base.FindVisitPlace(citizenID, data.m_homeBuilding, GetShoppingReason()); + } + else + { + if (data.m_instance == 0 && !DoRandomMove()) + { + break; + } + int dayTimeFrame2 = (int)Singleton.instance.m_dayTimeFrame; + int dAYTIME_FRAMES2 = (int)SimulationManager.DAYTIME_FRAMES; + int num9 = dAYTIME_FRAMES2 / 40; + int num10 = (int)(SimulationManager.DAYTIME_FRAMES * 8) / 24; + int num11 = dayTimeFrame2 - num10 & dAYTIME_FRAMES2 - 1; + int num12 = Mathf.Abs(num11 - (dAYTIME_FRAMES2 >> 1)); + num12 = num12 * num12 / (dAYTIME_FRAMES2 >> 1); + int num13 = Singleton.instance.m_randomizer.Int32((uint)dAYTIME_FRAMES2); + if (num13 < num9) + { + base.FindVisitPlace(citizenID, data.m_homeBuilding, GetEntertainmentReason()); + } + else if (num13 < num9 + num12 && data.m_workBuilding != 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + base.StartMoving(citizenID, ref data, data.m_homeBuilding, data.m_workBuilding); + } + } + } + break; + case Citizen.Location.Work: + if (data.Dead) + { + if (data.m_workBuilding == 0) + { + Singleton.instance.ReleaseCitizen(citizenID); + } + else + { + if (data.m_homeBuilding != 0) + { + data.SetHome(citizenID, 0, 0u); + } + if (data.m_visitBuilding != 0) + { + data.SetVisitplace(citizenID, 0, 0u); + } + if (data.m_vehicle != 0) + { + break; + } + if (FindHospital(citizenID, data.m_workBuilding, TransferManager.TransferReason.Dead)) + { + break; + } + } + return; + } + if (data.Arrested) + { + data.Arrested = false; + } + else + { + if (data.Sick) + { + if (data.m_workBuilding == 0) + { + data.CurrentLocation = Citizen.Location.Home; + break; + } + if (data.m_vehicle != 0) + { + break; + } + if (FindHospital(citizenID, data.m_workBuilding, TransferManager.TransferReason.Sick)) + { + break; + } + return; + } + if (data.m_workBuilding == 0) + { + data.CurrentLocation = Citizen.Location.Home; + } + else + { + BuildingManager instance = Singleton.instance; + ushort eventIndex = instance.m_buildings.m_buffer[data.m_workBuilding].m_eventIndex; + if ((instance.m_buildings.m_buffer[data.m_workBuilding].m_flags & Building.Flags.Evacuating) != 0) + { + if (data.m_vehicle == 0) + { + base.FindEvacuationPlace(citizenID, data.m_workBuilding, GetEvacuationReason(data.m_workBuilding)); + } + } + else if (eventIndex == 0 || (Singleton.instance.m_events.m_buffer[eventIndex].m_flags & (EventData.Flags.Preparing | EventData.Flags.Active | EventData.Flags.Ready)) == EventData.Flags.None) + { + if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) + { + if (data.m_vehicle == 0) + { + base.FindVisitPlace(citizenID, data.m_workBuilding, GetShoppingReason()); + } + } + else + { + if (data.m_instance == 0 && !DoRandomMove()) + { + break; + } + int dayTimeFrame = (int)Singleton.instance.m_dayTimeFrame; + int dAYTIME_FRAMES = (int)SimulationManager.DAYTIME_FRAMES; + int num2 = dAYTIME_FRAMES / 40; + int num3 = (int)(SimulationManager.DAYTIME_FRAMES * 16) / 24; + int num4 = dayTimeFrame - num3 & dAYTIME_FRAMES - 1; + int num5 = Mathf.Abs(num4 - (dAYTIME_FRAMES >> 1)); + num5 = num5 * num5 / (dAYTIME_FRAMES >> 1); + int num6 = Singleton.instance.m_randomizer.Int32((uint)dAYTIME_FRAMES); + if (num6 < num2) + { + if (data.m_vehicle == 0) + { + base.FindVisitPlace(citizenID, data.m_workBuilding, GetEntertainmentReason()); + } + } + else if (num6 < num2 + num5 && data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + base.StartMoving(citizenID, ref data, data.m_workBuilding, data.m_homeBuilding); + } + } + } + } + } + break; + case Citizen.Location.Visit: + if (data.Dead) + { + if (data.m_visitBuilding == 0) + { + Singleton.instance.ReleaseCitizen(citizenID); + } + else + { + if (data.m_homeBuilding != 0) + { + data.SetHome(citizenID, 0, 0u); + } + if (data.m_workBuilding != 0) + { + data.SetWorkplace(citizenID, 0, 0u); + } + if (data.m_vehicle != 0) + { + break; + } + if (Singleton.instance.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service == ItemClass.Service.HealthCare) + { + break; + } + if (FindHospital(citizenID, data.m_visitBuilding, TransferManager.TransferReason.Dead)) + { + break; + } + } + return; + } + if (data.Arrested) + { + if (data.m_visitBuilding == 0) + { + data.Arrested = false; + } + } + else if (!data.Collapsed) + { + if (data.Sick) + { + if (data.m_visitBuilding == 0) + { + data.CurrentLocation = Citizen.Location.Home; + break; + } + if (data.m_vehicle != 0) + { + break; + } + BuildingManager instance2 = Singleton.instance; + ItemClass.Service service = instance2.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service; + if (service == ItemClass.Service.HealthCare) + { + break; + } + if (service == ItemClass.Service.Disaster) + { + break; + } + if (FindHospital(citizenID, data.m_visitBuilding, TransferManager.TransferReason.Sick)) + { + break; + } + return; + } + BuildingManager instance3 = Singleton.instance; + ItemClass.Service service2 = ItemClass.Service.None; + if (data.m_visitBuilding != 0) + { + service2 = instance3.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service; + } + switch (service2) + { + case ItemClass.Service.HealthCare: + case ItemClass.Service.PoliceDepartment: + if (data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + base.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); + data.SetVisitplace(citizenID, 0, 0u); + } + break; + case ItemClass.Service.Disaster: + if ((instance3.m_buildings.m_buffer[data.m_visitBuilding].m_flags & Building.Flags.Downgrading) != 0 && data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + base.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); + data.SetVisitplace(citizenID, 0, 0u); + } + break; + default: + if (data.m_visitBuilding == 0) + { + data.CurrentLocation = Citizen.Location.Home; + } + else if ((instance3.m_buildings.m_buffer[data.m_visitBuilding].m_flags & Building.Flags.Evacuating) != 0) + { + if (data.m_vehicle == 0) + { + base.FindEvacuationPlace(citizenID, data.m_visitBuilding, GetEvacuationReason(data.m_visitBuilding)); + } + } + else if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) + { + BuildingInfo info = instance3.m_buildings.m_buffer[data.m_visitBuilding].Info; + int num7 = -100; + info.m_buildingAI.ModifyMaterialBuffer(data.m_visitBuilding, ref instance3.m_buildings.m_buffer[data.m_visitBuilding], TransferManager.TransferReason.Shopping, ref num7); + } + else + { + ushort eventIndex2 = instance3.m_buildings.m_buffer[data.m_visitBuilding].m_eventIndex; + if (eventIndex2 != 0) + { + if ((Singleton.instance.m_events.m_buffer[eventIndex2].m_flags & (EventData.Flags.Preparing | EventData.Flags.Active | EventData.Flags.Ready)) == EventData.Flags.None && data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + base.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); + data.SetVisitplace(citizenID, 0, 0u); + } + } + else + { + if (data.m_instance == 0 && !DoRandomMove()) + { + break; + } + int num8 = Singleton.instance.m_randomizer.Int32(40u); + if (num8 < 10 && data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + base.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); + data.SetVisitplace(citizenID, 0, 0u); + } + } + } + break; + } + } + break; + case Citizen.Location.Moving: + if (data.Dead) + { + if (data.m_vehicle == 0) + { + Singleton.instance.ReleaseCitizen(citizenID); + return; + } + if (data.m_homeBuilding != 0) + { + data.SetHome(citizenID, 0, 0u); + } + if (data.m_workBuilding != 0) + { + data.SetWorkplace(citizenID, 0, 0u); + } + if (data.m_visitBuilding != 0) + { + data.SetVisitplace(citizenID, 0, 0u); + } + } + else if (data.m_vehicle == 0 && data.m_instance == 0) + { + if (data.m_visitBuilding != 0) + { + data.SetVisitplace(citizenID, 0, 0u); + } + data.CurrentLocation = Citizen.Location.Home; + data.Arrested = false; + } + else if (data.m_instance != 0 && (Singleton.instance.m_instances.m_buffer[data.m_instance].m_flags & (CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour)) == (CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour)) + { + int num = Singleton.instance.m_randomizer.Int32(40u); + if (num < 10 && data.m_homeBuilding != 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + base.StartMoving(citizenID, ref data, 0, data.m_homeBuilding); + } + } + break; + } + data.m_flags &= ~Citizen.Flags.NeedGoods; + } + } + + public bool CanMakeBabies(uint citizenID, ref Citizen data) + { + if (data.Dead) + { + return false; + } + if (Citizen.GetAgeGroup(data.Age) != Citizen.AgeGroup.Adult) + { + return false; + } + if ((data.m_flags & Citizen.Flags.MovingIn) != 0) + { + return false; + } + return true; + } + + public void TryMoveAwayFromHome(uint citizenID, ref Citizen data) + { + if (!data.Dead && data.m_homeBuilding != 0) + { + Citizen.AgeGroup ageGroup = Citizen.GetAgeGroup(data.Age); + if (ageGroup != Citizen.AgeGroup.Young && ageGroup != Citizen.AgeGroup.Adult) + { + return; + } + TransferManager.TransferOffer offer = default(TransferManager.TransferOffer); + if (ageGroup == Citizen.AgeGroup.Young) + { + offer.Priority = 1; + } + else + { + offer.Priority = Singleton.instance.m_randomizer.Int32(2, 4); + } + offer.Citizen = citizenID; + offer.Position = Singleton.instance.m_buildings.m_buffer[data.m_homeBuilding].m_position; + offer.Amount = 1; + offer.Active = true; + if (Singleton.instance.m_randomizer.Int32(2u) == 0) + { + switch (data.EducationLevel) + { + case Citizen.Education.Uneducated: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single0, offer); + break; + case Citizen.Education.OneSchool: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single1, offer); + break; + case Citizen.Education.TwoSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single2, offer); + break; + case Citizen.Education.ThreeSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single3, offer); + break; + } + } + else + { + switch (data.EducationLevel) + { + case Citizen.Education.Uneducated: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single0B, offer); + break; + case Citizen.Education.OneSchool: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single1B, offer); + break; + case Citizen.Education.TwoSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single2B, offer); + break; + case Citizen.Education.ThreeSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single3B, offer); + break; + } + } + } + } + + public void TryMoveFamily(uint citizenID, ref Citizen data, int familySize) + { + if (!data.Dead && data.m_homeBuilding != 0) + { + TransferManager.TransferOffer offer = default(TransferManager.TransferOffer); + offer.Priority = Singleton.instance.m_randomizer.Int32(1, 7); + offer.Citizen = citizenID; + offer.Position = Singleton.instance.m_buildings.m_buffer[data.m_homeBuilding].m_position; + offer.Amount = 1; + offer.Active = true; + if (familySize == 1) + { + if (Singleton.instance.m_randomizer.Int32(2u) == 0) + { + switch (data.EducationLevel) + { + case Citizen.Education.Uneducated: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single0, offer); + break; + case Citizen.Education.OneSchool: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single1, offer); + break; + case Citizen.Education.TwoSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single2, offer); + break; + case Citizen.Education.ThreeSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single3, offer); + break; + } + } + else + { + switch (data.EducationLevel) + { + case Citizen.Education.Uneducated: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single0B, offer); + break; + case Citizen.Education.OneSchool: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single1B, offer); + break; + case Citizen.Education.TwoSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single2B, offer); + break; + case Citizen.Education.ThreeSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Single3B, offer); + break; + } + } + } + else + { + switch (data.EducationLevel) + { + case Citizen.Education.Uneducated: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Family0, offer); + break; + case Citizen.Education.OneSchool: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Family1, offer); + break; + case Citizen.Education.TwoSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Family2, offer); + break; + case Citizen.Education.ThreeSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Family3, offer); + break; + } + } + } + } + + public void TryFindPartner(uint citizenID, ref Citizen data) + { + if (!data.Dead && data.m_homeBuilding != 0) + { + Citizen.AgeGroup ageGroup = Citizen.GetAgeGroup(data.Age); + TransferManager.TransferReason material = TransferManager.TransferReason.None; + switch (ageGroup) + { + case Citizen.AgeGroup.Young: + material = TransferManager.TransferReason.PartnerYoung; + break; + case Citizen.AgeGroup.Adult: + material = TransferManager.TransferReason.PartnerAdult; + break; + } + if (ageGroup != Citizen.AgeGroup.Young && ageGroup != Citizen.AgeGroup.Adult) + { + return; + } + Vector3 position = Singleton.instance.m_buildings.m_buffer[data.m_homeBuilding].m_position; + TransferManager.TransferOffer offer = default(TransferManager.TransferOffer); + offer.Priority = Singleton.instance.m_randomizer.Int32(8u); + offer.Citizen = citizenID; + offer.Position = position; + offer.Amount = 1; + offer.Active = (Singleton.instance.m_randomizer.Int32(2u) == 0); + bool flag = Singleton.instance.m_randomizer.Int32(100u) < 5; + if (Citizen.GetGender(citizenID) == Citizen.Gender.Female != flag) + { + Singleton.instance.AddIncomingOffer(material, offer); + } + else + { + Singleton.instance.AddOutgoingOffer(material, offer); + } + } + } + + private bool FindHospital(uint citizenID, ushort sourceBuilding, TransferManager.TransferReason reason) + { + if (reason == TransferManager.TransferReason.Dead) + { + if (Singleton.instance.Unlocked(UnlockManager.Feature.DeathCare)) + { + return true; + } + Singleton.instance.ReleaseCitizen(citizenID); + return false; + } + if (Singleton.instance.Unlocked(ItemClass.Service.HealthCare)) + { + BuildingManager instance = Singleton.instance; + DistrictManager instance2 = Singleton.instance; + Vector3 position = instance.m_buildings.m_buffer[sourceBuilding].m_position; + byte district = instance2.GetDistrict(position); + DistrictPolicies.Services servicePolicies = instance2.m_districts.m_buffer[district].m_servicePolicies; + TransferManager.TransferOffer offer = default(TransferManager.TransferOffer); + offer.Priority = 6; + offer.Citizen = citizenID; + offer.Position = position; + offer.Amount = 1; + if ((servicePolicies & DistrictPolicies.Services.HelicopterPriority) != 0) + { + instance2.m_districts.m_buffer[district].m_servicePoliciesEffect |= DistrictPolicies.Services.HelicopterPriority; + offer.Active = false; + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Sick2, offer); + } + else if ((instance.m_buildings.m_buffer[sourceBuilding].m_flags & Building.Flags.RoadAccessFailed) != 0 || Singleton.instance.m_randomizer.Int32(20u) == 0) + { + offer.Active = false; + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Sick2, offer); + } + else + { + offer.Active = (Singleton.instance.m_randomizer.Int32(2u) == 0); + Singleton.instance.AddOutgoingOffer(reason, offer); + } + return true; + } + Singleton.instance.ReleaseCitizen(citizenID); + return false; + } + + public override void StartTransfer(uint citizenID, ref Citizen data, TransferManager.TransferReason reason, TransferManager.TransferOffer offer) + { + if (data.m_flags != 0) + { + if (data.Dead && reason != TransferManager.TransferReason.Dead) + { + return; + } + switch (reason) + { + case TransferManager.TransferReason.Sick: + if (data.Sick) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + if (base.StartMoving(citizenID, ref data, 0, offer)) + { + data.SetVisitplace(citizenID, offer.Building, 0u); + } + } + break; + case TransferManager.TransferReason.Dead: + if (data.Dead) + { + data.SetVisitplace(citizenID, offer.Building, 0u); + if (data.m_visitBuilding != 0) + { + data.CurrentLocation = Citizen.Location.Visit; + } + } + break; + case TransferManager.TransferReason.Worker0: + case TransferManager.TransferReason.Worker1: + case TransferManager.TransferReason.Worker2: + case TransferManager.TransferReason.Worker3: + if (data.m_workBuilding == 0) + { + data.SetWorkplace(citizenID, offer.Building, 0u); + } + break; + case TransferManager.TransferReason.Student1: + case TransferManager.TransferReason.Student2: + case TransferManager.TransferReason.Student3: + if (data.m_workBuilding == 0) + { + data.SetStudentplace(citizenID, offer.Building, 0u); + } + break; + case TransferManager.TransferReason.Shopping: + case TransferManager.TransferReason.ShoppingB: + case TransferManager.TransferReason.ShoppingC: + case TransferManager.TransferReason.ShoppingD: + case TransferManager.TransferReason.ShoppingE: + case TransferManager.TransferReason.ShoppingF: + case TransferManager.TransferReason.ShoppingG: + case TransferManager.TransferReason.ShoppingH: + if (data.m_homeBuilding != 0 && !data.Sick) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + if (base.StartMoving(citizenID, ref data, 0, offer)) + { + data.SetVisitplace(citizenID, offer.Building, 0u); + CitizenManager instance3 = Singleton.instance; + BuildingManager instance4 = Singleton.instance; + uint containingUnit = data.GetContainingUnit(citizenID, instance4.m_buildings.m_buffer[data.m_homeBuilding].m_citizenUnits, CitizenUnit.Flags.Home); + if (containingUnit != 0) + { + instance3.m_units.m_buffer[containingUnit].m_goods += 100; + } + } + } + break; + case TransferManager.TransferReason.Entertainment: + case TransferManager.TransferReason.EntertainmentB: + case TransferManager.TransferReason.EntertainmentC: + case TransferManager.TransferReason.EntertainmentD: + if (data.m_homeBuilding != 0 && !data.Sick) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + if (base.StartMoving(citizenID, ref data, 0, offer)) + { + data.SetVisitplace(citizenID, offer.Building, 0u); + } + } + break; + case TransferManager.TransferReason.Single0: + case TransferManager.TransferReason.Single1: + case TransferManager.TransferReason.Single2: + case TransferManager.TransferReason.Single3: + case TransferManager.TransferReason.Single0B: + case TransferManager.TransferReason.Single1B: + case TransferManager.TransferReason.Single2B: + case TransferManager.TransferReason.Single3B: + data.SetHome(citizenID, offer.Building, 0u); + if (data.m_homeBuilding == 0) + { + if (data.CurrentLocation == Citizen.Location.Visit && (data.m_flags & Citizen.Flags.Evacuating) != 0) + { + break; + } + Singleton.instance.ReleaseCitizen(citizenID); + } + break; + case TransferManager.TransferReason.Family0: + case TransferManager.TransferReason.Family1: + case TransferManager.TransferReason.Family2: + case TransferManager.TransferReason.Family3: + if (data.m_homeBuilding != 0 && offer.Building != 0) + { + uint num = Singleton.instance.m_buildings.m_buffer[data.m_homeBuilding].FindCitizenUnit(CitizenUnit.Flags.Home, citizenID); + if (num != 0) + { + MoveFamily(num, ref Singleton.instance.m_units.m_buffer[num], offer.Building); + } + } + break; + case TransferManager.TransferReason.PartnerYoung: + case TransferManager.TransferReason.PartnerAdult: + { + uint citizen = offer.Citizen; + if (citizen != 0) + { + CitizenManager instance = Singleton.instance; + BuildingManager instance2 = Singleton.instance; + ushort homeBuilding = instance.m_citizens.m_buffer[citizen].m_homeBuilding; + if (homeBuilding != 0 && !instance.m_citizens.m_buffer[citizen].Dead) + { + uint num2 = instance2.m_buildings.m_buffer[homeBuilding].FindCitizenUnit(CitizenUnit.Flags.Home, citizen); + if (num2 != 0) + { + data.SetHome(citizenID, 0, num2); + data.m_family = instance.m_citizens.m_buffer[citizen].m_family; + } + } + } + break; + } + case TransferManager.TransferReason.EvacuateA: + case TransferManager.TransferReason.EvacuateB: + case TransferManager.TransferReason.EvacuateC: + case TransferManager.TransferReason.EvacuateD: + case TransferManager.TransferReason.EvacuateVipA: + case TransferManager.TransferReason.EvacuateVipB: + case TransferManager.TransferReason.EvacuateVipC: + case TransferManager.TransferReason.EvacuateVipD: + data.m_flags |= Citizen.Flags.Evacuating; + if (base.StartMoving(citizenID, ref data, 0, offer)) + { + data.SetVisitplace(citizenID, offer.Building, 0u); + } + else + { + data.SetVisitplace(citizenID, offer.Building, 0u); + if (data.m_visitBuilding != 0 && data.m_visitBuilding == offer.Building) + { + data.CurrentLocation = Citizen.Location.Visit; + } + } + break; + } + } + } + + private void MoveFamily(uint homeID, ref CitizenUnit data, ushort targetBuilding) + { + BuildingManager instance = Singleton.instance; + CitizenManager instance2 = Singleton.instance; + uint unitID = 0u; + if (targetBuilding != 0) + { + unitID = instance.m_buildings.m_buffer[targetBuilding].GetEmptyCitizenUnit(CitizenUnit.Flags.Home); + } + for (int i = 0; i < 5; i++) + { + uint citizen = data.GetCitizen(i); + if (citizen != 0 && !instance2.m_citizens.m_buffer[citizen].Dead) + { + instance2.m_citizens.m_buffer[citizen].SetHome(citizen, 0, unitID); + if (instance2.m_citizens.m_buffer[citizen].m_homeBuilding == 0) + { + instance2.ReleaseCitizen(citizen); + } + } + } + } + + public override void SetSource(ushort instanceID, ref CitizenInstance data, ushort sourceBuilding) + { + if (sourceBuilding != data.m_sourceBuilding) + { + if (data.m_sourceBuilding != 0) + { + Singleton.instance.m_buildings.m_buffer[data.m_sourceBuilding].RemoveSourceCitizen(instanceID, ref data); + } + data.m_sourceBuilding = sourceBuilding; + if (data.m_sourceBuilding != 0) + { + Singleton.instance.m_buildings.m_buffer[data.m_sourceBuilding].AddSourceCitizen(instanceID, ref data); + } + } + if (sourceBuilding != 0) + { + BuildingManager instance = Singleton.instance; + BuildingInfo info = instance.m_buildings.m_buffer[sourceBuilding].Info; + data.Unspawn(instanceID); + Randomizer randomizer = new Randomizer(instanceID); + info.m_buildingAI.CalculateSpawnPosition(sourceBuilding, ref instance.m_buildings.m_buffer[sourceBuilding], ref randomizer, base.m_info, out Vector3 vector, out Vector3 a); + Quaternion rotation = Quaternion.identity; + Vector3 forward = a - vector; + if (forward.sqrMagnitude > 0.01f) + { + rotation = Quaternion.LookRotation(forward); + } + data.m_frame0.m_velocity = Vector3.zero; + data.m_frame0.m_position = vector; + data.m_frame0.m_rotation = rotation; + data.m_frame1 = data.m_frame0; + data.m_frame2 = data.m_frame0; + data.m_frame3 = data.m_frame0; + data.m_targetPos = new Vector4(a.x, a.y, a.z, 1f); + ushort eventIndex = 0; + if (data.m_citizen != 0 && Singleton.instance.m_citizens.m_buffer[data.m_citizen].m_workBuilding != sourceBuilding) + { + eventIndex = instance.m_buildings.m_buffer[sourceBuilding].m_eventIndex; + } + Color32 eventCitizenColor = Singleton.instance.GetEventCitizenColor(eventIndex, data.m_citizen); + if (eventCitizenColor.a == 255) + { + data.m_color = eventCitizenColor; + data.m_flags |= CitizenInstance.Flags.CustomColor; + } + } + } + + public override void SetTarget(ushort instanceID, ref CitizenInstance data, ushort targetIndex, bool targetIsNode) + { + int dayTimeFrame = (int)Singleton.instance.m_dayTimeFrame; + int dAYTIME_FRAMES = (int)SimulationManager.DAYTIME_FRAMES; + int num = Mathf.Max(dAYTIME_FRAMES >> 2, Mathf.Abs(dayTimeFrame - (dAYTIME_FRAMES >> 1))); + if (Singleton.instance.m_randomizer.Int32((uint)dAYTIME_FRAMES >> 1) < num) + { + data.m_flags &= ~CitizenInstance.Flags.CannotUseTaxi; + } + else + { + data.m_flags |= CitizenInstance.Flags.CannotUseTaxi; + } + data.m_flags &= ~CitizenInstance.Flags.CannotUseTransport; + if (targetIndex != data.m_targetBuilding || targetIsNode != ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None)) + { + if (data.m_targetBuilding != 0) + { + if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != 0) + { + Singleton.instance.m_nodes.m_buffer[data.m_targetBuilding].RemoveTargetCitizen(instanceID, ref data); + ushort num2 = 0; + if (targetIsNode) + { + num2 = Singleton.instance.m_nodes.m_buffer[data.m_targetBuilding].m_transportLine; + } + if ((data.m_flags & CitizenInstance.Flags.OnTour) != 0) + { + ushort transportLine = Singleton.instance.m_nodes.m_buffer[data.m_targetBuilding].m_transportLine; + uint citizen = data.m_citizen; + if (transportLine != 0 && transportLine != num2 && citizen != 0) + { + TransportManager instance = Singleton.instance; + TransportInfo info = instance.m_lines.m_buffer[transportLine].Info; + if ((object)info != null && info.m_vehicleType == VehicleInfo.VehicleType.None) + { + data.m_flags &= ~CitizenInstance.Flags.OnTour; + } + } + } + if (!targetIsNode) + { + data.m_flags &= ~CitizenInstance.Flags.TargetIsNode; + } + } + else + { + Singleton.instance.m_buildings.m_buffer[data.m_targetBuilding].RemoveTargetCitizen(instanceID, ref data); + } + } + data.m_targetBuilding = targetIndex; + if (targetIsNode) + { + data.m_flags |= CitizenInstance.Flags.TargetIsNode; + } + if (data.m_targetBuilding != 0) + { + if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != 0) + { + Singleton.instance.m_nodes.m_buffer[data.m_targetBuilding].AddTargetCitizen(instanceID, ref data); + } + else + { + Singleton.instance.m_buildings.m_buffer[data.m_targetBuilding].AddTargetCitizen(instanceID, ref data); + } + data.m_targetSeed = (byte)Singleton.instance.m_randomizer.Int32(256u); + } + } + if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) == CitizenInstance.Flags.None && IsRoadConnection(targetIndex)) + { + goto IL_02a3; + } + if (IsRoadConnection(data.m_sourceBuilding)) + { + goto IL_02a3; + } + data.m_flags &= ~CitizenInstance.Flags.BorrowCar; + goto IL_02c6; + IL_02a3: + data.m_flags |= CitizenInstance.Flags.BorrowCar; + goto IL_02c6; + IL_02c6: + if (targetIndex != 0 && (data.m_flags & (CitizenInstance.Flags.Character | CitizenInstance.Flags.TargetIsNode)) == CitizenInstance.Flags.None) + { + ushort eventIndex = 0; + if (data.m_citizen != 0 && Singleton.instance.m_citizens.m_buffer[data.m_citizen].m_workBuilding != targetIndex) + { + eventIndex = Singleton.instance.m_buildings.m_buffer[targetIndex].m_eventIndex; + } + Color32 eventCitizenColor = Singleton.instance.GetEventCitizenColor(eventIndex, data.m_citizen); + if (eventCitizenColor.a == 255) + { + data.m_color = eventCitizenColor; + data.m_flags |= CitizenInstance.Flags.CustomColor; + } + } + if (!StartPathFind(instanceID, ref data)) + { + data.Unspawn(instanceID); + } + } + + public override void BuildingRelocated(ushort instanceID, ref CitizenInstance data, ushort building) + { + base.BuildingRelocated(instanceID, ref data, building); + if (building == data.m_targetBuilding && (data.m_flags & CitizenInstance.Flags.TargetIsNode) == CitizenInstance.Flags.None) + { + base.InvalidPath(instanceID, ref data); + } + } + + public override void JoinTarget(ushort instanceID, ref CitizenInstance data, ushort otherInstance) + { + ushort num = 0; + bool flag = false; + bool flag2 = false; + if (otherInstance != 0) + { + num = Singleton.instance.m_instances.m_buffer[otherInstance].m_targetBuilding; + flag = ((Singleton.instance.m_instances.m_buffer[otherInstance].m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None); + flag2 = ((Singleton.instance.m_instances.m_buffer[otherInstance].m_flags & CitizenInstance.Flags.OnTour) != CitizenInstance.Flags.None); + } + if (num != data.m_targetBuilding || flag != ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None)) + { + if (data.m_targetBuilding != 0) + { + if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != 0) + { + Singleton.instance.m_nodes.m_buffer[data.m_targetBuilding].RemoveTargetCitizen(instanceID, ref data); + data.m_flags &= ~(CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour); + } + else + { + Singleton.instance.m_buildings.m_buffer[data.m_targetBuilding].RemoveTargetCitizen(instanceID, ref data); + } + } + data.m_targetBuilding = num; + if (flag) + { + data.m_flags |= CitizenInstance.Flags.TargetIsNode; + } + if (flag2) + { + data.m_flags |= CitizenInstance.Flags.OnTour; + } + if (data.m_targetBuilding != 0) + { + if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != 0) + { + Singleton.instance.m_nodes.m_buffer[data.m_targetBuilding].AddTargetCitizen(instanceID, ref data); + } + else + { + Singleton.instance.m_buildings.m_buffer[data.m_targetBuilding].AddTargetCitizen(instanceID, ref data); + } + } + } + if (otherInstance != 0) + { + PathManager instance = Singleton.instance; + CitizenManager instance2 = Singleton.instance; + data.Unspawn(instanceID); + data.m_frame3 = (data.m_frame2 = (data.m_frame1 = (data.m_frame0 = instance2.m_instances.m_buffer[otherInstance].GetLastFrameData()))); + data.m_targetPos = instance2.m_instances.m_buffer[otherInstance].m_targetPos; + uint path = instance2.m_instances.m_buffer[otherInstance].m_path; + if (instance.AddPathReference(path)) + { + if (data.m_path != 0) + { + instance.ReleasePath(data.m_path); + } + data.m_path = path; + if ((instance2.m_instances.m_buffer[otherInstance].m_flags & CitizenInstance.Flags.WaitingPath) != 0) + { + data.m_flags |= CitizenInstance.Flags.WaitingPath; + } + else + { + data.Spawn(instanceID); + } + } + } + } + + private bool IsRoadConnection(ushort building) + { + if (building != 0) + { + BuildingManager instance = Singleton.instance; + if ((instance.m_buildings.m_buffer[building].m_flags & Building.Flags.IncomingOutgoing) != 0 && instance.m_buildings.m_buffer[building].Info.m_class.m_service == ItemClass.Service.Road) + { + return true; + } + } + return false; + } + + protected override bool SpawnVehicle(ushort instanceID, ref CitizenInstance citizenData, PathUnit.Position pathPos) + { + VehicleManager instance = Singleton.instance; + float num = 20f; + int num2 = Mathf.Max((int)((citizenData.m_targetPos.x - num) / 32f + 270f), 0); + int num3 = Mathf.Max((int)((citizenData.m_targetPos.z - num) / 32f + 270f), 0); + int num4 = Mathf.Min((int)((citizenData.m_targetPos.x + num) / 32f + 270f), 539); + int num5 = Mathf.Min((int)((citizenData.m_targetPos.z + num) / 32f + 270f), 539); + for (int i = num3; i <= num5; i++) + { + for (int j = num2; j <= num4; j++) + { + ushort num6 = instance.m_vehicleGrid[i * 540 + j]; + int num7 = 0; + while (num6 != 0) + { + if (TryJoinVehicle(instanceID, ref citizenData, num6, ref instance.m_vehicles.m_buffer[num6])) + { + citizenData.m_flags |= CitizenInstance.Flags.EnteringVehicle; + citizenData.m_flags &= ~CitizenInstance.Flags.TryingSpawnVehicle; + citizenData.m_flags &= ~CitizenInstance.Flags.BoredOfWaiting; + citizenData.m_waitCounter = 0; + return true; + } + num6 = instance.m_vehicles.m_buffer[num6].m_nextGridVehicle; + if (++num7 > 16384) + { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + } + } + NetManager instance2 = Singleton.instance; + CitizenManager instance3 = Singleton.instance; + Vector3 vector = Vector3.zero; + Quaternion rotation = Quaternion.identity; + ushort num8 = instance3.m_citizens.m_buffer[citizenData.m_citizen].m_parkedVehicle; + if (num8 != 0) + { + vector = instance.m_parkedVehicles.m_buffer[num8].m_position; + rotation = instance.m_parkedVehicles.m_buffer[num8].m_rotation; + } + VehicleInfo vehicleInfo; + VehicleInfo vehicleInfo2 = GetVehicleInfo(instanceID, ref citizenData, false, out vehicleInfo); + if ((object)vehicleInfo2 != null && vehicleInfo2.m_vehicleType != VehicleInfo.VehicleType.Bicycle) + { + if (vehicleInfo2.m_class.m_subService == ItemClass.SubService.PublicTransportTaxi) + { + instance3.m_citizens.m_buffer[citizenData.m_citizen].SetParkedVehicle(citizenData.m_citizen, 0); + if ((citizenData.m_flags & CitizenInstance.Flags.WaitingTaxi) == CitizenInstance.Flags.None && instance2.m_segments.m_buffer[pathPos.m_segment].Info.m_hasPedestrianLanes) + { + citizenData.m_flags |= CitizenInstance.Flags.WaitingTaxi; + citizenData.m_flags &= ~CitizenInstance.Flags.BoredOfWaiting; + citizenData.m_waitCounter = 0; + } + return true; + } + uint laneID = PathManager.GetLaneID(pathPos); + Vector3 vector2 = citizenData.m_targetPos; + if (num8 != 0 && Vector3.SqrMagnitude(vector - vector2) < 1024f) + { + vector2 = vector; + } + else + { + num8 = 0; + } + instance2.m_lanes.m_buffer[laneID].GetClosestPosition(vector2, out Vector3 vector3, out float num9); + byte lastPathOffset = (byte)Mathf.Clamp(Mathf.RoundToInt(num9 * 255f), 0, 255); + vector3 = vector2 + Vector3.ClampMagnitude(vector3 - vector2, 5f); + if (instance.CreateVehicle(out ushort num10, ref Singleton.instance.m_randomizer, vehicleInfo2, vector2, TransferManager.TransferReason.None, false, false)) + { + Vehicle.Frame frame = instance.m_vehicles.m_buffer[num10].m_frame0; + if (num8 != 0) + { + frame.m_rotation = rotation; + } + else + { + Vector3 a = vector3; + CitizenInstance.Frame lastFrameData = citizenData.GetLastFrameData(); + Vector3 forward = a - lastFrameData.m_position; + if (forward.sqrMagnitude > 0.01f) + { + frame.m_rotation = Quaternion.LookRotation(forward); + } + } + instance.m_vehicles.m_buffer[num10].m_frame0 = frame; + instance.m_vehicles.m_buffer[num10].m_frame1 = frame; + instance.m_vehicles.m_buffer[num10].m_frame2 = frame; + instance.m_vehicles.m_buffer[num10].m_frame3 = frame; + vehicleInfo2.m_vehicleAI.FrameDataUpdated(num10, ref instance.m_vehicles.m_buffer[num10], ref frame); + instance.m_vehicles.m_buffer[num10].m_targetPos0 = new Vector4(vector3.x, vector3.y, vector3.z, 2f); + instance.m_vehicles.m_buffer[num10].m_flags |= Vehicle.Flags.Stopped; + instance.m_vehicles.m_buffer[num10].m_path = citizenData.m_path; + instance.m_vehicles.m_buffer[num10].m_pathPositionIndex = citizenData.m_pathPositionIndex; + instance.m_vehicles.m_buffer[num10].m_lastPathOffset = lastPathOffset; + instance.m_vehicles.m_buffer[num10].m_transferSize = (ushort)(citizenData.m_citizen & 0xFFFF); + if ((object)vehicleInfo != null) + { + instance.m_vehicles.m_buffer[num10].CreateTrailer(num10, vehicleInfo, false); + } + vehicleInfo2.m_vehicleAI.TrySpawn(num10, ref instance.m_vehicles.m_buffer[num10]); + if (num8 != 0) + { + InstanceID empty = InstanceID.Empty; + empty.ParkedVehicle = num8; + InstanceID empty2 = InstanceID.Empty; + empty2.Vehicle = num10; + Singleton.instance.ChangeInstance(empty, empty2); + } + citizenData.m_path = 0u; + instance3.m_citizens.m_buffer[citizenData.m_citizen].SetParkedVehicle(citizenData.m_citizen, 0); + instance3.m_citizens.m_buffer[citizenData.m_citizen].SetVehicle(citizenData.m_citizen, num10, 0u); + citizenData.m_flags |= CitizenInstance.Flags.EnteringVehicle; + citizenData.m_flags &= ~CitizenInstance.Flags.TryingSpawnVehicle; + citizenData.m_flags &= ~CitizenInstance.Flags.BoredOfWaiting; + citizenData.m_waitCounter = 0; + return true; + } + instance3.m_citizens.m_buffer[citizenData.m_citizen].SetParkedVehicle(citizenData.m_citizen, 0); + if ((citizenData.m_flags & CitizenInstance.Flags.TryingSpawnVehicle) == CitizenInstance.Flags.None) + { + citizenData.m_flags |= CitizenInstance.Flags.TryingSpawnVehicle; + citizenData.m_flags &= ~CitizenInstance.Flags.BoredOfWaiting; + citizenData.m_waitCounter = 0; + } + return true; + } + instance3.m_citizens.m_buffer[citizenData.m_citizen].SetParkedVehicle(citizenData.m_citizen, 0); + if ((citizenData.m_flags & CitizenInstance.Flags.TryingSpawnVehicle) == CitizenInstance.Flags.None) + { + citizenData.m_flags |= CitizenInstance.Flags.TryingSpawnVehicle; + citizenData.m_flags &= ~CitizenInstance.Flags.BoredOfWaiting; + citizenData.m_waitCounter = 0; + } + return true; + } + + protected override bool SpawnBicycle(ushort instanceID, ref CitizenInstance citizenData, PathUnit.Position pathPos) + { + VehicleInfo vehicleInfo; + VehicleInfo vehicleInfo2 = GetVehicleInfo(instanceID, ref citizenData, false, out vehicleInfo); + if ((object)vehicleInfo2 != null && vehicleInfo2.m_vehicleType == VehicleInfo.VehicleType.Bicycle) + { + CitizenManager instance = Singleton.instance; + VehicleManager instance2 = Singleton.instance; + CitizenInstance.Frame lastFrameData = citizenData.GetLastFrameData(); + if (instance2.CreateVehicle(out ushort num, ref Singleton.instance.m_randomizer, vehicleInfo2, lastFrameData.m_position, TransferManager.TransferReason.None, false, false)) + { + Vehicle.Frame frame = instance2.m_vehicles.m_buffer[num].m_frame0; + frame.m_rotation = lastFrameData.m_rotation; + instance2.m_vehicles.m_buffer[num].m_frame0 = frame; + instance2.m_vehicles.m_buffer[num].m_frame1 = frame; + instance2.m_vehicles.m_buffer[num].m_frame2 = frame; + instance2.m_vehicles.m_buffer[num].m_frame3 = frame; + vehicleInfo2.m_vehicleAI.FrameDataUpdated(num, ref instance2.m_vehicles.m_buffer[num], ref frame); + if ((object)vehicleInfo != null) + { + instance2.m_vehicles.m_buffer[num].CreateTrailer(num, vehicleInfo, false); + } + vehicleInfo2.m_vehicleAI.TrySpawn(num, ref instance2.m_vehicles.m_buffer[num]); + instance.m_citizens.m_buffer[citizenData.m_citizen].SetParkedVehicle(citizenData.m_citizen, 0); + instance.m_citizens.m_buffer[citizenData.m_citizen].SetVehicle(citizenData.m_citizen, num, 0u); + citizenData.m_flags |= CitizenInstance.Flags.RidingBicycle; + return true; + } + } + return false; + } + + private bool TryJoinVehicle(ushort instanceID, ref CitizenInstance citizenData, ushort vehicleID, ref Vehicle vehicleData) + { + if ((vehicleData.m_flags & Vehicle.Flags.Stopped) == (Vehicle.Flags)0) + { + return false; + } + CitizenManager instance = Singleton.instance; + uint num = vehicleData.m_citizenUnits; + int num2 = 0; + while (num != 0) + { + uint nextUnit = instance.m_units.m_buffer[num].m_nextUnit; + for (int i = 0; i < 5; i++) + { + uint citizen = instance.m_units.m_buffer[num].GetCitizen(i); + if (citizen != 0) + { + ushort instance2 = instance.m_citizens.m_buffer[citizen].m_instance; + if (instance2 == 0) + { + break; + } + if (instance.m_instances.m_buffer[instance2].m_targetBuilding != citizenData.m_targetBuilding) + { + break; + } + if ((instance.m_instances.m_buffer[instance2].m_flags & CitizenInstance.Flags.TargetIsNode) != (citizenData.m_flags & CitizenInstance.Flags.TargetIsNode)) + { + break; + } + instance.m_citizens.m_buffer[citizenData.m_citizen].SetVehicle(citizenData.m_citizen, vehicleID, 0u); + if (instance.m_citizens.m_buffer[citizenData.m_citizen].m_vehicle != vehicleID) + { + break; + } + if (citizenData.m_path != 0) + { + Singleton.instance.ReleasePath(citizenData.m_path); + citizenData.m_path = 0u; + } + return true; + } + } + num = nextUnit; + if (++num2 > 524288) + { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + return false; + } + + protected override void SwitchBuildingTargetPos(ushort instanceID, ref CitizenInstance citizenData) + { + if (citizenData.m_path == 0 && citizenData.m_targetBuilding != 0 && (citizenData.m_flags & CitizenInstance.Flags.TargetIsNode) == CitizenInstance.Flags.None) + { + BuildingManager instance = Singleton.instance; + BuildingInfo info = instance.m_buildings.m_buffer[citizenData.m_targetBuilding].Info; + if (info.m_hasPedestrianPaths) + { + Randomizer randomizer = new Randomizer(instanceID << 8 | citizenData.m_targetSeed); + info.m_buildingAI.CalculateUnspawnPosition(citizenData.m_targetBuilding, ref instance.m_buildings.m_buffer[citizenData.m_targetBuilding], ref randomizer, base.m_info, instanceID, out Vector3 _, out Vector3 vector2, out Vector2 _, out CitizenInstance.Flags _); + float num = Vector3.Distance(citizenData.m_targetPos, vector2); + if (num > 10f) + { + base.StartPathFind(instanceID, ref citizenData, citizenData.m_targetPos, vector2, null, true, false); + } + } + } + } + + public override void EnterParkArea(ushort instanceID, ref CitizenInstance citizenData, byte park, ushort gateID) + { + if (gateID != 0) + { + DistrictManager instance = Singleton.instance; + instance.m_parks.m_buffer[park].m_tempResidentCount++; + } + base.EnterParkArea(instanceID, ref citizenData, park, gateID); + } + + protected override bool StartPathFind(ushort instanceID, ref CitizenInstance citizenData) + { + if (citizenData.m_citizen != 0) + { + CitizenManager instance = Singleton.instance; + VehicleManager instance2 = Singleton.instance; + ushort vehicle = instance.m_citizens.m_buffer[citizenData.m_citizen].m_vehicle; + if (vehicle != 0) + { + VehicleInfo info = instance2.m_vehicles.m_buffer[vehicle].Info; + if ((object)info != null) + { + uint citizen = info.m_vehicleAI.GetOwnerID(vehicle, ref instance2.m_vehicles.m_buffer[vehicle]).Citizen; + if (citizen == citizenData.m_citizen) + { + info.m_vehicleAI.SetTarget(vehicle, ref instance2.m_vehicles.m_buffer[vehicle], 0); + return false; + } + } + bool flag = false; + if (instance2.m_vehicles.m_buffer[vehicle].m_transportLine != 0) + { + NetManager instance3 = Singleton.instance; + ushort targetBuilding = instance2.m_vehicles.m_buffer[vehicle].m_targetBuilding; + if (targetBuilding != 0) + { + uint lane = instance3.m_nodes.m_buffer[targetBuilding].m_lane; + int laneOffset = instance3.m_nodes.m_buffer[targetBuilding].m_laneOffset; + if (lane != 0) + { + ushort segment = instance3.m_lanes.m_buffer[lane].m_segment; + if (instance3.m_segments.m_buffer[segment].GetClosestLane(lane, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, out lane, out NetInfo.Lane _)) + { + citizenData.m_targetPos = instance3.m_lanes.m_buffer[lane].CalculatePosition((float)laneOffset * 0.003921569f); + flag = true; + } + } + } + } + if (!flag) + { + instance.m_citizens.m_buffer[citizenData.m_citizen].SetVehicle(citizenData.m_citizen, 0, 0u); + return false; + } + } + } + if (citizenData.m_targetBuilding != 0) + { + VehicleInfo vehicleInfo; + VehicleInfo vehicleInfo2 = GetVehicleInfo(instanceID, ref citizenData, false, out vehicleInfo); + if ((citizenData.m_flags & CitizenInstance.Flags.TargetIsNode) != 0) + { + NetManager instance4 = Singleton.instance; + Vector3 endPos = instance4.m_nodes.m_buffer[citizenData.m_targetBuilding].m_position; + uint lane3 = instance4.m_nodes.m_buffer[citizenData.m_targetBuilding].m_lane; + if (lane3 != 0) + { + ushort segment2 = instance4.m_lanes.m_buffer[lane3].m_segment; + if (instance4.m_segments.m_buffer[segment2].GetClosestLane(lane3, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, out lane3, out NetInfo.Lane _)) + { + int laneOffset2 = instance4.m_nodes.m_buffer[citizenData.m_targetBuilding].m_laneOffset; + endPos = instance4.m_lanes.m_buffer[lane3].CalculatePosition((float)laneOffset2 * 0.003921569f); + } + } + return base.StartPathFind(instanceID, ref citizenData, citizenData.m_targetPos, endPos, vehicleInfo2, true, false); + } + BuildingManager instance5 = Singleton.instance; + BuildingInfo info2 = instance5.m_buildings.m_buffer[citizenData.m_targetBuilding].Info; + Randomizer randomizer = new Randomizer(instanceID << 8 | citizenData.m_targetSeed); + info2.m_buildingAI.CalculateUnspawnPosition(citizenData.m_targetBuilding, ref instance5.m_buildings.m_buffer[citizenData.m_targetBuilding], ref randomizer, base.m_info, instanceID, out Vector3 _, out Vector3 endPos2, out Vector2 _, out CitizenInstance.Flags _); + return base.StartPathFind(instanceID, ref citizenData, citizenData.m_targetPos, endPos2, vehicleInfo2, true, false); + } + return false; + } + + protected override VehicleInfo GetVehicleInfo(ushort instanceID, ref CitizenInstance citizenData, bool forceProbability, out VehicleInfo trailer) + { + trailer = null; + if (citizenData.m_citizen != 0) + { + Citizen.AgeGroup ageGroup; + switch (base.m_info.m_agePhase) + { + case Citizen.AgePhase.Child: + ageGroup = Citizen.AgeGroup.Child; + break; + case Citizen.AgePhase.Teen0: + case Citizen.AgePhase.Teen1: + ageGroup = Citizen.AgeGroup.Teen; + break; + case Citizen.AgePhase.Young0: + case Citizen.AgePhase.Young1: + case Citizen.AgePhase.Young2: + ageGroup = Citizen.AgeGroup.Young; + break; + case Citizen.AgePhase.Adult0: + case Citizen.AgePhase.Adult1: + case Citizen.AgePhase.Adult2: + case Citizen.AgePhase.Adult3: + ageGroup = Citizen.AgeGroup.Adult; + break; + case Citizen.AgePhase.Senior0: + case Citizen.AgePhase.Senior1: + case Citizen.AgePhase.Senior2: + case Citizen.AgePhase.Senior3: + ageGroup = Citizen.AgeGroup.Senior; + break; + default: + ageGroup = Citizen.AgeGroup.Adult; + break; + } + int num; + int num2; + if (forceProbability || (citizenData.m_flags & CitizenInstance.Flags.BorrowCar) != 0) + { + num = 100; + num2 = 0; + } + else + { + num = GetCarProbability(instanceID, ref citizenData, ageGroup); + num2 = GetBikeProbability(instanceID, ref citizenData, ageGroup); + } + Randomizer randomizer = new Randomizer(citizenData.m_citizen); + bool flag = randomizer.Int32(100u) < num; + bool flag2 = randomizer.Int32(100u) < num2; + bool flag3; + bool flag4; + if (flag) + { + int electricCarProbability = GetElectricCarProbability(instanceID, ref citizenData, base.m_info.m_agePhase); + flag3 = false; + flag4 = (randomizer.Int32(100u) < electricCarProbability); + } + else + { + int taxiProbability = GetTaxiProbability(instanceID, ref citizenData, ageGroup); + flag3 = (randomizer.Int32(100u) < taxiProbability); + flag4 = false; + } + ItemClass.Service service = ItemClass.Service.Residential; + ItemClass.SubService subService = (!flag4) ? ItemClass.SubService.ResidentialLow : ItemClass.SubService.ResidentialLowEco; + if (!flag && flag3) + { + service = ItemClass.Service.PublicTransport; + subService = ItemClass.SubService.PublicTransportTaxi; + } + VehicleInfo randomVehicleInfo = Singleton.instance.GetRandomVehicleInfo(ref randomizer, service, subService, ItemClass.Level.Level1); + VehicleInfo randomVehicleInfo2 = Singleton.instance.GetRandomVehicleInfo(ref randomizer, ItemClass.Service.Residential, ItemClass.SubService.ResidentialHigh, (ageGroup != 0) ? ItemClass.Level.Level2 : ItemClass.Level.Level1); + if (flag2 && (object)randomVehicleInfo2 != null) + { + return randomVehicleInfo2; + } + if ((flag || flag3) && (object)randomVehicleInfo != null) + { + return randomVehicleInfo; + } + return null; + } + return null; + } + + private int GetCarProbability(ushort instanceID, ref CitizenInstance citizenData, Citizen.AgeGroup ageGroup) + { + return GetCarProbability(ageGroup); + } + + private int GetCarProbability(Citizen.AgeGroup ageGroup) + { + switch (ageGroup) + { + case Citizen.AgeGroup.Child: + return 0; + case Citizen.AgeGroup.Teen: + return 5; + case Citizen.AgeGroup.Young: + return 15; + case Citizen.AgeGroup.Adult: + return 20; + case Citizen.AgeGroup.Senior: + return 10; + default: + return 0; + } + } + + private int GetBikeProbability(ushort instanceID, ref CitizenInstance citizenData, Citizen.AgeGroup ageGroup) + { + CitizenManager instance = Singleton.instance; + uint citizen = citizenData.m_citizen; + ushort homeBuilding = instance.m_citizens.m_buffer[citizen].m_homeBuilding; + int num = 0; + if (homeBuilding != 0) + { + Vector3 position = Singleton.instance.m_buildings.m_buffer[homeBuilding].m_position; + DistrictManager instance2 = Singleton.instance; + byte district = instance2.GetDistrict(position); + DistrictPolicies.CityPlanning cityPlanningPolicies = instance2.m_districts.m_buffer[district].m_cityPlanningPolicies; + if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.EncourageBiking) != 0) + { + num = 10; + } + } + switch (ageGroup) + { + case Citizen.AgeGroup.Child: + return 40 + num; + case Citizen.AgeGroup.Teen: + return 30 + num; + case Citizen.AgeGroup.Young: + return 20 + num; + case Citizen.AgeGroup.Adult: + return 10 + num; + case Citizen.AgeGroup.Senior: + return num; + default: + return 0; + } + } + + private int GetTaxiProbability(ushort instanceID, ref CitizenInstance citizenData, Citizen.AgeGroup ageGroup) + { + switch (ageGroup) + { + case Citizen.AgeGroup.Child: + return 0; + case Citizen.AgeGroup.Teen: + return 2; + case Citizen.AgeGroup.Young: + return 2; + case Citizen.AgeGroup.Adult: + return 4; + case Citizen.AgeGroup.Senior: + return 6; + default: + return 0; + } + } + + private int GetElectricCarProbability(ushort instanceID, ref CitizenInstance citizenData, Citizen.AgePhase agePhase) + { + CitizenManager instance = Singleton.instance; + uint citizen = citizenData.m_citizen; + ushort homeBuilding = instance.m_citizens.m_buffer[citizen].m_homeBuilding; + if (homeBuilding != 0) + { + Vector3 position = Singleton.instance.m_buildings.m_buffer[homeBuilding].m_position; + DistrictManager instance2 = Singleton.instance; + byte district = instance2.GetDistrict(position); + DistrictPolicies.CityPlanning cityPlanningPolicies = instance2.m_districts.m_buffer[district].m_cityPlanningPolicies; + if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.ElectricCars) != 0) + { + return 100; + } + } + switch (agePhase) + { + case Citizen.AgePhase.Child: + case Citizen.AgePhase.Teen0: + case Citizen.AgePhase.Young0: + case Citizen.AgePhase.Adult0: + case Citizen.AgePhase.Senior0: + return 5; + case Citizen.AgePhase.Teen1: + case Citizen.AgePhase.Young1: + case Citizen.AgePhase.Adult1: + case Citizen.AgePhase.Senior1: + return 10; + case Citizen.AgePhase.Young2: + case Citizen.AgePhase.Adult2: + case Citizen.AgePhase.Senior2: + return 15; + case Citizen.AgePhase.Adult3: + case Citizen.AgePhase.Senior3: + return 20; + default: + return 0; + } + } +} diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs index e12e813a..1907a694 100644 --- a/src/RealTime/Core/RealTimeCore.cs +++ b/src/RealTime/Core/RealTimeCore.cs @@ -6,7 +6,9 @@ namespace RealTime.Core { using System; using RealTime.Simulation; + using RealTime.Tools; using RealTime.UI; + using Redirection; /// /// The core component of the Real Time mod. Activates and deactivates @@ -38,6 +40,15 @@ public static RealTimeCore Run() var customTimeBar = new CustomTimeBar(); customTimeBar.Enable(gameDate); + try + { + Redirector.PerformRedirections(); + } + catch (Exception ex) + { + Log.Error("Failed to perform method redirections: " + ex.Message); + } + var core = new RealTimeCore(timeAdjustment, customTimeBar) { isEnabled = true @@ -58,6 +69,16 @@ public void Stop() timeAdjustment.Disable(); timeBar.Disable(); + + try + { + Redirector.RevertRedirections(); + } + catch (Exception ex) + { + Log.Error("Failed to revert method redirections: " + ex.Message); + } + isEnabled = false; } } diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj index 63042eb3..54efbb6f 100644 --- a/src/RealTime/RealTime.csproj +++ b/src/RealTime/RealTime.csproj @@ -64,6 +64,10 @@ + + + + @@ -87,6 +91,7 @@ stylecop.json + From 8c045fde2e683a441c82fa363b8460a1c8592be0 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 11:05:27 +0200 Subject: [PATCH 014/102] Refactor the ResidentAI methods for better maintainability --- src/RealTime/AI/RealTimeResidentAI.Common.cs | 153 ++++++++++++++++ src/RealTime/AI/RealTimeResidentAI.Home.cs | 93 +++------- src/RealTime/AI/RealTimeResidentAI.Moving.cs | 34 +--- src/RealTime/AI/RealTimeResidentAI.Visit.cs | 173 ++++++------------- src/RealTime/AI/RealTimeResidentAI.Work.cs | 116 ++++--------- src/RealTime/RealTime.csproj | 1 + 6 files changed, 268 insertions(+), 302 deletions(-) create mode 100644 src/RealTime/AI/RealTimeResidentAI.Common.cs diff --git a/src/RealTime/AI/RealTimeResidentAI.Common.cs b/src/RealTime/AI/RealTimeResidentAI.Common.cs new file mode 100644 index 00000000..df873f6e --- /dev/null +++ b/src/RealTime/AI/RealTimeResidentAI.Common.cs @@ -0,0 +1,153 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.AI +{ + using System; + + internal static partial class RealTimeResidentAI + { + private static bool? ProcessCitizenCommonState(Arguments args, uint citizenID, ref Citizen data) + { + ushort currentBuilding = GetCurrentCitizenBuilding(ref data); + + if (data.Dead) + { + return ProcessCitizenDead(args, citizenID, ref data, currentBuilding); + } + + if (data.CurrentLocation == Citizen.Location.Moving) + { + return null; + } + + if (data.Arrested) + { + return ProcessCitizenArrested(ref data); + } + + if (data.CurrentLocation == Citizen.Location.Visit && data.Collapsed) + { + return false; + } + + if (data.CurrentLocation == Citizen.Location.Home && (data.m_homeBuilding == 0 || data.m_vehicle != 0)) + { + return false; + } + + if (data.Sick) + { + return ProcessCitizenSick(args, citizenID, ref data, currentBuilding); + } + + return null; + } + + private static bool ProcessCitizenDead(Arguments args, uint citizenID, ref Citizen data, ushort currentBuilding) + { + if (currentBuilding == 0 || (data.CurrentLocation == Citizen.Location.Moving && data.m_vehicle == 0)) + { + args.CitizenMgr.ReleaseCitizen(citizenID); + return true; + } + + if (data.CurrentLocation != Citizen.Location.Home && data.m_homeBuilding != 0) + { + data.SetHome(citizenID, 0, 0u); + } + + if (data.CurrentLocation != Citizen.Location.Work && data.m_workBuilding != 0) + { + data.SetWorkplace(citizenID, 0, 0u); + } + + if (data.CurrentLocation != Citizen.Location.Visit && data.m_visitBuilding != 0) + { + data.SetVisitplace(citizenID, 0, 0u); + } + + if (data.CurrentLocation == Citizen.Location.Moving || data.m_vehicle != 0) + { + return false; + } + + if (data.CurrentLocation == Citizen.Location.Visit + && + args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service == ItemClass.Service.HealthCare) + { + return false; + } + + if (FindHospital(args.ResidentAI, citizenID, currentBuilding, TransferManager.TransferReason.Dead)) + { + return false; + } + + return true; + } + + private static bool ProcessCitizenArrested(ref Citizen data) + { + if (data.CurrentLocation != Citizen.Location.Visit || data.m_visitBuilding == 0) + { + data.Arrested = false; + } + + return false; + } + + private static bool ProcessCitizenSick(Arguments args, uint citizenID, ref Citizen data, ushort currentBuilding) + { + if (data.CurrentLocation != Citizen.Location.Home && currentBuilding == 0) + { + data.CurrentLocation = Citizen.Location.Home; + return false; + } + + if (data.CurrentLocation != Citizen.Location.Home && data.m_vehicle != 0) + { + return false; + } + + if (data.CurrentLocation == Citizen.Location.Visit) + { + ItemClass.Service service = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service; + if (service == ItemClass.Service.HealthCare || service == ItemClass.Service.Disaster) + { + return false; + } + } + + if (FindHospital(args.ResidentAI, citizenID, currentBuilding, TransferManager.TransferReason.Sick)) + { + return false; + } + + return true; + } + + private static ushort GetCurrentCitizenBuilding(ref Citizen data) + { + switch (data.CurrentLocation) + { + case Citizen.Location.Home: + return data.m_homeBuilding; + + case Citizen.Location.Work: + return data.m_workBuilding; + + case Citizen.Location.Visit: + return data.m_visitBuilding; + + case Citizen.Location.Moving: + // This value will not be used anyway + return ushort.MaxValue; + + default: + throw new NotSupportedException($"The location {data.CurrentLocation} is not supported by this method"); + } + } + } +} diff --git a/src/RealTime/AI/RealTimeResidentAI.Home.cs b/src/RealTime/AI/RealTimeResidentAI.Home.cs index 4f7e7d3a..fc88257b 100644 --- a/src/RealTime/AI/RealTimeResidentAI.Home.cs +++ b/src/RealTime/AI/RealTimeResidentAI.Home.cs @@ -16,86 +16,43 @@ private static bool ProcessCitizenAtHome(Arguments args, uint citizenID, ref Cit return true; } - if (data.Dead) + bool? commonStateResult = ProcessCitizenCommonState(args, citizenID, ref data); + if (commonStateResult.HasValue) { - if (data.m_homeBuilding == 0) - { - args.CitizenMgr.ReleaseCitizen(citizenID); - } - else - { - if (data.m_workBuilding != 0) - { - data.SetWorkplace(citizenID, 0, 0u); - } - - if (data.m_visitBuilding != 0) - { - data.SetVisitplace(citizenID, 0, 0u); - } - - if (data.m_vehicle != 0) - { - return false; - } - - if (FindHospital(args.ResidentAI, citizenID, data.m_homeBuilding, TransferManager.TransferReason.Dead)) - { - return false; - } - } - - return true; + return commonStateResult.Value; } - if (data.Arrested) + if ((args.BuildingMgr.m_buildings.m_buffer[data.m_homeBuilding].m_flags & Building.Flags.Evacuating) != 0) + { + FindEvacuationPlace(args.ResidentAI, citizenID, data.m_homeBuilding, GetEvacuationReason(args.ResidentAI, data.m_homeBuilding)); + } + else if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) { - data.Arrested = false; + FindVisitPlace(args.ResidentAI, citizenID, data.m_homeBuilding, GetShoppingReason(args.ResidentAI)); } - else if (data.m_homeBuilding != 0 && data.m_vehicle == 0) + else { - if (data.Sick) + if (data.m_instance == 0 && !DoRandomMove(args.ResidentAI)) { - if (FindHospital(args.ResidentAI, citizenID, data.m_homeBuilding, TransferManager.TransferReason.Sick)) - { - return false; - } - - return true; + return false; } - if ((args.BuildingMgr.m_buildings.m_buffer[data.m_homeBuilding].m_flags & Building.Flags.Evacuating) != 0) + int dayTimeFrame2 = (int)args.SimMgr.m_dayTimeFrame; + int dAYTIME_FRAMES2 = (int)SimulationManager.DAYTIME_FRAMES; + int num9 = dAYTIME_FRAMES2 / 40; + int num10 = (int)(SimulationManager.DAYTIME_FRAMES * 8) / 24; + int num11 = dayTimeFrame2 - num10 & dAYTIME_FRAMES2 - 1; + int num12 = Mathf.Abs(num11 - (dAYTIME_FRAMES2 >> 1)); + num12 = num12 * num12 / (dAYTIME_FRAMES2 >> 1); + int num13 = args.SimMgr.m_randomizer.Int32((uint)dAYTIME_FRAMES2); + if (num13 < num9) { - FindEvacuationPlace(args.ResidentAI, citizenID, data.m_homeBuilding, GetEvacuationReason(args.ResidentAI, data.m_homeBuilding)); + FindVisitPlace(args.ResidentAI, citizenID, data.m_homeBuilding, GetEntertainmentReason(args.ResidentAI)); } - else if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) + else if (num13 < num9 + num12 && data.m_workBuilding != 0) { - FindVisitPlace(args.ResidentAI, citizenID, data.m_homeBuilding, GetShoppingReason(args.ResidentAI)); - } - else - { - if (data.m_instance == 0 && !DoRandomMove(args.ResidentAI)) - { - return false; - } - - int dayTimeFrame2 = (int)args.SimMgr.m_dayTimeFrame; - int dAYTIME_FRAMES2 = (int)SimulationManager.DAYTIME_FRAMES; - int num9 = dAYTIME_FRAMES2 / 40; - int num10 = (int)(SimulationManager.DAYTIME_FRAMES * 8) / 24; - int num11 = dayTimeFrame2 - num10 & dAYTIME_FRAMES2 - 1; - int num12 = Mathf.Abs(num11 - (dAYTIME_FRAMES2 >> 1)); - num12 = num12 * num12 / (dAYTIME_FRAMES2 >> 1); - int num13 = args.SimMgr.m_randomizer.Int32((uint)dAYTIME_FRAMES2); - if (num13 < num9) - { - FindVisitPlace(args.ResidentAI, citizenID, data.m_homeBuilding, GetEntertainmentReason(args.ResidentAI)); - } - else if (num13 < num9 + num12 && data.m_workBuilding != 0) - { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, data.m_homeBuilding, data.m_workBuilding); - } + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, data.m_homeBuilding, data.m_workBuilding); } } diff --git a/src/RealTime/AI/RealTimeResidentAI.Moving.cs b/src/RealTime/AI/RealTimeResidentAI.Moving.cs index cf5da09a..9f5971d1 100644 --- a/src/RealTime/AI/RealTimeResidentAI.Moving.cs +++ b/src/RealTime/AI/RealTimeResidentAI.Moving.cs @@ -8,30 +8,14 @@ internal static partial class RealTimeResidentAI { private static bool ProcessCitizenMoving(Arguments args, uint citizenID, ref Citizen data) { - if (data.Dead) + bool? commonStateResult = ProcessCitizenCommonState(args, citizenID, ref data); + if (commonStateResult.HasValue) { - if (data.m_vehicle == 0) - { - args.CitizenMgr.ReleaseCitizen(citizenID); - return true; - } - - if (data.m_homeBuilding != 0) - { - data.SetHome(citizenID, 0, 0u); - } - - if (data.m_workBuilding != 0) - { - data.SetWorkplace(citizenID, 0, 0u); - } - - if (data.m_visitBuilding != 0) - { - data.SetVisitplace(citizenID, 0, 0u); - } + return commonStateResult.Value; } - else if (data.m_vehicle == 0 && data.m_instance == 0) + + CitizenInstance.Flags flags = CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour; + if (data.m_vehicle == 0 && data.m_instance == 0) { if (data.m_visitBuilding != 0) { @@ -41,10 +25,10 @@ private static bool ProcessCitizenMoving(Arguments args, uint citizenID, ref Cit data.CurrentLocation = Citizen.Location.Home; data.Arrested = false; } - else if (data.m_instance != 0 && (args.CitizenMgr.m_instances.m_buffer[data.m_instance].m_flags & (CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour)) == (CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour)) + else if (data.m_instance != 0 && (args.CitizenMgr.m_instances.m_buffer[data.m_instance].m_flags & flags) == flags) { - int num = args.SimMgr.m_randomizer.Int32(40u); - if (num < 10 && data.m_homeBuilding != 0) + int r = args.SimMgr.m_randomizer.Int32(40u); + if (r < 10 && data.m_homeBuilding != 0) { data.m_flags &= ~Citizen.Flags.Evacuating; args.ResidentAI.StartMoving(citizenID, ref data, 0, data.m_homeBuilding); diff --git a/src/RealTime/AI/RealTimeResidentAI.Visit.cs b/src/RealTime/AI/RealTimeResidentAI.Visit.cs index 594aa2e4..c55f7f4d 100644 --- a/src/RealTime/AI/RealTimeResidentAI.Visit.cs +++ b/src/RealTime/AI/RealTimeResidentAI.Visit.cs @@ -10,163 +10,88 @@ internal static partial class RealTimeResidentAI { private static bool ProcessCitizenVisit(Arguments args, uint citizenID, ref Citizen data) { - if (data.Dead) + bool? commonStateResult = ProcessCitizenCommonState(args, citizenID, ref data); + if (commonStateResult.HasValue) { - if (data.m_visitBuilding == 0) - { - args.CitizenMgr.ReleaseCitizen(citizenID); - } - else - { - if (data.m_homeBuilding != 0) - { - data.SetHome(citizenID, 0, 0u); - } + return commonStateResult.Value; + } - if (data.m_workBuilding != 0) - { - data.SetWorkplace(citizenID, 0, 0u); - } + ItemClass.Service service = data.m_visitBuilding == 0 + ? ItemClass.Service.None + : args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service; - if (data.m_vehicle != 0) + switch (service) + { + case ItemClass.Service.HealthCare: + case ItemClass.Service.PoliceDepartment: + if (data.m_homeBuilding != 0 && data.m_vehicle == 0) { - return false; + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); + data.SetVisitplace(citizenID, 0, 0u); } - if (args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service == ItemClass.Service.HealthCare) - { - return false; - } + return false; - if (FindHospital(args.ResidentAI, citizenID, data.m_visitBuilding, TransferManager.TransferReason.Dead)) + case ItemClass.Service.Disaster: + if ((args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].m_flags & Building.Flags.Downgrading) != 0 && data.m_homeBuilding != 0 && data.m_vehicle == 0) { - return false; + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); + data.SetVisitplace(citizenID, 0, 0u); } - } - return true; - } + return false; - if (data.Arrested) - { - if (data.m_visitBuilding == 0) - { - data.Arrested = false; - } - } - else if (!data.Collapsed) - { - if (data.Sick) - { + default: if (data.m_visitBuilding == 0) { data.CurrentLocation = Citizen.Location.Home; - return false; } - - if (data.m_vehicle != 0) + else if ((args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].m_flags & Building.Flags.Evacuating) != 0) { - return false; - } - - ItemClass.Service service = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service; - if (service == ItemClass.Service.HealthCare) - { - return false; + if (data.m_vehicle == 0) + { + FindEvacuationPlace(args.ResidentAI, citizenID, data.m_visitBuilding, GetEvacuationReason(args.ResidentAI, data.m_visitBuilding)); + } } - - if (service == ItemClass.Service.Disaster) + else if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) { - return false; + BuildingInfo info = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info; + int num7 = -100; + info.m_buildingAI.ModifyMaterialBuffer(data.m_visitBuilding, ref args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding], TransferManager.TransferReason.Shopping, ref num7); } - - if (FindHospital(args.ResidentAI, citizenID, data.m_visitBuilding, TransferManager.TransferReason.Sick)) + else { - return false; - } - - return true; - } - - ItemClass.Service service2 = ItemClass.Service.None; - if (data.m_visitBuilding != 0) - { - service2 = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service; - } - - switch (service2) - { - case ItemClass.Service.HealthCare: - case ItemClass.Service.PoliceDepartment: - if (data.m_homeBuilding != 0 && data.m_vehicle == 0) + ushort eventIndex2 = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].m_eventIndex; + if (eventIndex2 != 0) { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); - data.SetVisitplace(citizenID, 0, 0u); - } - - return false; - case ItemClass.Service.Disaster: - if ((args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].m_flags & Building.Flags.Downgrading) != 0 && data.m_homeBuilding != 0 && data.m_vehicle == 0) - { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); - data.SetVisitplace(citizenID, 0, 0u); - } - - return false; - default: - if (data.m_visitBuilding == 0) - { - data.CurrentLocation = Citizen.Location.Home; - } - else if ((args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].m_flags & Building.Flags.Evacuating) != 0) - { - if (data.m_vehicle == 0) + if ((Singleton.instance.m_events.m_buffer[eventIndex2].m_flags & (EventData.Flags.Preparing | EventData.Flags.Active | EventData.Flags.Ready)) == EventData.Flags.None && data.m_homeBuilding != 0 && data.m_vehicle == 0) { - FindEvacuationPlace(args.ResidentAI, citizenID, data.m_visitBuilding, GetEvacuationReason(args.ResidentAI, data.m_visitBuilding)); + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); + data.SetVisitplace(citizenID, 0, 0u); } } - else if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) - { - BuildingInfo info = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info; - int num7 = -100; - info.m_buildingAI.ModifyMaterialBuffer(data.m_visitBuilding, ref args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding], TransferManager.TransferReason.Shopping, ref num7); - } else { - ushort eventIndex2 = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].m_eventIndex; - if (eventIndex2 != 0) + if (data.m_instance == 0 && !DoRandomMove(args.ResidentAI)) { - if ((Singleton.instance.m_events.m_buffer[eventIndex2].m_flags & (EventData.Flags.Preparing | EventData.Flags.Active | EventData.Flags.Ready)) == EventData.Flags.None && data.m_homeBuilding != 0 && data.m_vehicle == 0) - { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); - data.SetVisitplace(citizenID, 0, 0u); - } + return false; } - else - { - if (data.m_instance == 0 && !DoRandomMove(args.ResidentAI)) - { - return false; - } - int num8 = args.SimMgr.m_randomizer.Int32(40u); - if (num8 < 10 && data.m_homeBuilding != 0 && data.m_vehicle == 0) - { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); - data.SetVisitplace(citizenID, 0, 0u); - } + int num8 = args.SimMgr.m_randomizer.Int32(40u); + if (num8 < 10 && data.m_homeBuilding != 0 && data.m_vehicle == 0) + { + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); + data.SetVisitplace(citizenID, 0, 0u); } } + } - return false; - } + return false; } - - return false; } } } diff --git a/src/RealTime/AI/RealTimeResidentAI.Work.cs b/src/RealTime/AI/RealTimeResidentAI.Work.cs index 1fff0974..61ac83df 100644 --- a/src/RealTime/AI/RealTimeResidentAI.Work.cs +++ b/src/RealTime/AI/RealTimeResidentAI.Work.cs @@ -11,115 +11,61 @@ internal static partial class RealTimeResidentAI { private static bool ProcessCitizenAtWork(Arguments args, uint citizenID, ref Citizen data) { - if (data.Dead) + bool? commonStateResult = ProcessCitizenCommonState(args, citizenID, ref data); + if (commonStateResult.HasValue) { - if (data.m_workBuilding == 0) - { - args.CitizenMgr.ReleaseCitizen(citizenID); - } - else - { - if (data.m_homeBuilding != 0) - { - data.SetHome(citizenID, 0, 0u); - } - - if (data.m_visitBuilding != 0) - { - data.SetVisitplace(citizenID, 0, 0u); - } - - if (data.m_vehicle != 0) - { - return false; - } - - if (FindHospital(args.ResidentAI, citizenID, data.m_workBuilding, TransferManager.TransferReason.Dead)) - { - return false; - } - } - - return true; + return commonStateResult.Value; } - if (data.Arrested) + if (data.m_workBuilding == 0) { - data.Arrested = false; + data.CurrentLocation = Citizen.Location.Home; } else { - if (data.Sick) + ushort eventIndex = args.BuildingMgr.m_buildings.m_buffer[data.m_workBuilding].m_eventIndex; + if ((args.BuildingMgr.m_buildings.m_buffer[data.m_workBuilding].m_flags & Building.Flags.Evacuating) != 0) { - if (data.m_workBuilding == 0) - { - data.CurrentLocation = Citizen.Location.Home; - return false; - } - - if (data.m_vehicle != 0) + if (data.m_vehicle == 0) { - return false; + FindEvacuationPlace(args.ResidentAI, citizenID, data.m_workBuilding, GetEvacuationReason(args.ResidentAI, data.m_workBuilding)); } - - if (FindHospital(args.ResidentAI, citizenID, data.m_workBuilding, TransferManager.TransferReason.Sick)) - { - return false; - } - - return true; } - - if (data.m_workBuilding == 0) - { - data.CurrentLocation = Citizen.Location.Home; - } - else + else if (eventIndex == 0 || (Singleton.instance.m_events.m_buffer[eventIndex].m_flags & (EventData.Flags.Preparing | EventData.Flags.Active | EventData.Flags.Ready)) == EventData.Flags.None) { - ushort eventIndex = args.BuildingMgr.m_buildings.m_buffer[data.m_workBuilding].m_eventIndex; - if ((args.BuildingMgr.m_buildings.m_buffer[data.m_workBuilding].m_flags & Building.Flags.Evacuating) != 0) + if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) { if (data.m_vehicle == 0) { - FindEvacuationPlace(args.ResidentAI, citizenID, data.m_workBuilding, GetEvacuationReason(args.ResidentAI, data.m_workBuilding)); + FindVisitPlace(args.ResidentAI, citizenID, data.m_workBuilding, GetShoppingReason(args.ResidentAI)); } } - else if (eventIndex == 0 || (Singleton.instance.m_events.m_buffer[eventIndex].m_flags & (EventData.Flags.Preparing | EventData.Flags.Active | EventData.Flags.Ready)) == EventData.Flags.None) + else { - if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) + if (data.m_instance == 0 && !DoRandomMove(args.ResidentAI)) + { + return false; + } + + int dayTimeFrame = (int)args.SimMgr.m_dayTimeFrame; + int dAYTIME_FRAMES = (int)SimulationManager.DAYTIME_FRAMES; + int num2 = dAYTIME_FRAMES / 40; + int num3 = (int)(SimulationManager.DAYTIME_FRAMES * 16) / 24; + int num4 = dayTimeFrame - num3 & dAYTIME_FRAMES - 1; + int num5 = Mathf.Abs(num4 - (dAYTIME_FRAMES >> 1)); + num5 = num5 * num5 / (dAYTIME_FRAMES >> 1); + int num6 = args.SimMgr.m_randomizer.Int32((uint)dAYTIME_FRAMES); + if (num6 < num2) { if (data.m_vehicle == 0) { - FindVisitPlace(args.ResidentAI, citizenID, data.m_workBuilding, GetShoppingReason(args.ResidentAI)); + FindVisitPlace(args.ResidentAI, citizenID, data.m_workBuilding, GetEntertainmentReason(args.ResidentAI)); } } - else + else if (num6 < num2 + num5 && data.m_homeBuilding != 0 && data.m_vehicle == 0) { - if (data.m_instance == 0 && !DoRandomMove(args.ResidentAI)) - { - return false; - } - - int dayTimeFrame = (int)args.SimMgr.m_dayTimeFrame; - int dAYTIME_FRAMES = (int)SimulationManager.DAYTIME_FRAMES; - int num2 = dAYTIME_FRAMES / 40; - int num3 = (int)(SimulationManager.DAYTIME_FRAMES * 16) / 24; - int num4 = dayTimeFrame - num3 & dAYTIME_FRAMES - 1; - int num5 = Mathf.Abs(num4 - (dAYTIME_FRAMES >> 1)); - num5 = num5 * num5 / (dAYTIME_FRAMES >> 1); - int num6 = args.SimMgr.m_randomizer.Int32((uint)dAYTIME_FRAMES); - if (num6 < num2) - { - if (data.m_vehicle == 0) - { - FindVisitPlace(args.ResidentAI, citizenID, data.m_workBuilding, GetEntertainmentReason(args.ResidentAI)); - } - } - else if (num6 < num2 + num5 && data.m_homeBuilding != 0 && data.m_vehicle == 0) - { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, data.m_workBuilding, data.m_homeBuilding); - } + data.m_flags &= ~Citizen.Flags.Evacuating; + args.ResidentAI.StartMoving(citizenID, ref data, data.m_workBuilding, data.m_homeBuilding); } } } diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj index 54efbb6f..07cf4869 100644 --- a/src/RealTime/RealTime.csproj +++ b/src/RealTime/RealTime.csproj @@ -64,6 +64,7 @@ + From 1138fed45a5ed2ad868073803b1924cd25513473 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 12:17:13 +0200 Subject: [PATCH 015/102] Fix issue with method redirections (static vs instance) --- src/RealTime/Core/RealTimeCore.cs | 4 ++- src/Redirection/MethodInfoExtensions.cs | 17 ++++++++-- src/Redirection/Redirector.cs | 44 ++++++++++++++++--------- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs index 1907a694..ae935fc4 100644 --- a/src/RealTime/Core/RealTimeCore.cs +++ b/src/RealTime/Core/RealTimeCore.cs @@ -42,7 +42,8 @@ public static RealTimeCore Run() try { - Redirector.PerformRedirections(); + int redirectedCount = Redirector.PerformRedirections(); + Log.Info($"Successfully redirected {redirectedCount} methods."); } catch (Exception ex) { @@ -73,6 +74,7 @@ public void Stop() try { Redirector.RevertRedirections(); + Log.Info($"Successfully reverted all method redirections."); } catch (Exception ex) { diff --git a/src/Redirection/MethodInfoExtensions.cs b/src/Redirection/MethodInfoExtensions.cs index 4e913b3b..a97117ab 100644 --- a/src/Redirection/MethodInfoExtensions.cs +++ b/src/Redirection/MethodInfoExtensions.cs @@ -25,6 +25,8 @@ THE SOFTWARE. namespace Redirection { using System; + using System.Collections.Generic; + using System.Linq; using System.Reflection; using System.Security.Permissions; @@ -95,15 +97,24 @@ internal static bool IsCompatibleWith(this MethodInfo method, MethodInfo otherMe return false; } - ParameterInfo[] thisParameters = method.GetParameters(); + Type targetType = otherMethod.ReflectedType; + var thisParameters = method.GetParameters().ToList(); + ParameterInfo firstParameter = thisParameters.FirstOrDefault(); + if (firstParameter != null && + ((!targetType.IsValueType && firstParameter.ParameterType == targetType) || + (targetType.IsValueType && firstParameter.ParameterType == targetType.MakeByRefType()))) + { + thisParameters.RemoveAt(0); + } + ParameterInfo[] otherParameters = otherMethod.GetParameters(); - if (thisParameters.Length != otherParameters.Length) + if (thisParameters.Count != otherParameters.Length) { return false; } - for (int i = 0; i < thisParameters.Length; i++) + for (int i = 0; i < thisParameters.Count; i++) { if (!otherParameters[i].ParameterType.IsAssignableFrom(thisParameters[i].ParameterType)) { diff --git a/src/Redirection/Redirector.cs b/src/Redirection/Redirector.cs index b0b30d4d..e9b40382 100644 --- a/src/Redirection/Redirector.cs +++ b/src/Redirection/Redirector.cs @@ -41,10 +41,13 @@ public static class Redirector /// Perform the method call redirections for all methods in the calling assembly /// that are marked with or . /// + /// + /// The number of methods that have been redirected. [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] - public static void PerformRedirections() + public static int PerformRedirections() { - PerformRedirections(0); + var callingAssembly = Assembly.GetCallingAssembly(); + return PerformRedirections(0, callingAssembly); } /// @@ -53,22 +56,13 @@ public static void PerformRedirections() /// /// /// The bitmask to filter the methods with. + /// + /// The number of methods that have been redirected. [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] - public static void PerformRedirections(ulong bitmask) + public static int PerformRedirections(ulong bitmask) { var callingAssembly = Assembly.GetCallingAssembly(); - - IEnumerable allMethods = callingAssembly - .GetTypes() - .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)); - - foreach (MethodInfo method in allMethods) - { - foreach (RedirectAttribute attribute in method.GetCustomAttributes(typeof(RedirectAttribute), false)) - { - ProcessMethod(method, callingAssembly, attribute, bitmask); - } - } + return PerformRedirections(bitmask, callingAssembly); } /// @@ -88,6 +82,26 @@ public static void RevertRedirections() } } + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + private static int PerformRedirections(ulong bitmask, Assembly callingAssembly) + { + IEnumerable allMethods = callingAssembly + .GetTypes() + .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)); + + int result = 0; + foreach (MethodInfo method in allMethods) + { + foreach (RedirectAttribute attribute in method.GetCustomAttributes(typeof(RedirectAttribute), false)) + { + ProcessMethod(method, callingAssembly, attribute, bitmask); + ++result; + } + } + + return result; + } + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] private static void ProcessMethod(MethodInfo method, Assembly callingAssembly, RedirectAttribute attribute, ulong bitmask) { From 06dde832dffe12b1c5cd4065a3b4d2a6b255a046 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 12:22:08 +0200 Subject: [PATCH 016/102] Improve method redirection integrity - On exception, the already performed redirections will be reverted --- src/Redirection/Redirector.cs | 36 ++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/Redirection/Redirector.cs b/src/Redirection/Redirector.cs index e9b40382..d07cbd3a 100644 --- a/src/Redirection/Redirector.cs +++ b/src/Redirection/Redirector.cs @@ -73,13 +73,7 @@ public static int PerformRedirections(ulong bitmask) public static void RevertRedirections() { var callingAssembly = Assembly.GetCallingAssembly(); - - var redirectionsToRemove = redirections.Values.Where(r => r.RedirectionSource == callingAssembly).ToList(); - foreach (MethodRedirection item in redirectionsToRemove) - { - redirections.Remove(item.Method); - item.Dispose(); - } + RevertRedirections(callingAssembly); } [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] @@ -90,18 +84,38 @@ private static int PerformRedirections(ulong bitmask, Assembly callingAssembly) .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)); int result = 0; - foreach (MethodInfo method in allMethods) + + try { - foreach (RedirectAttribute attribute in method.GetCustomAttributes(typeof(RedirectAttribute), false)) + foreach (MethodInfo method in allMethods) { - ProcessMethod(method, callingAssembly, attribute, bitmask); - ++result; + foreach (RedirectAttribute attribute in method.GetCustomAttributes(typeof(RedirectAttribute), false)) + { + ProcessMethod(method, callingAssembly, attribute, bitmask); + ++result; + } } } + catch + { + RevertRedirections(callingAssembly); + throw; + } return result; } + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + private static void RevertRedirections(Assembly callingAssembly) + { + var redirectionsToRemove = redirections.Values.Where(r => r.RedirectionSource == callingAssembly).ToList(); + foreach (MethodRedirection item in redirectionsToRemove) + { + redirections.Remove(item.Method); + item.Dispose(); + } + } + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] private static void ProcessMethod(MethodInfo method, Assembly callingAssembly, RedirectAttribute attribute, ulong bitmask) { From 31b7b79861c0a8d7b6111afbd2f200caf1cee8c8 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 12:32:30 +0200 Subject: [PATCH 017/102] Fix the FxCop issues --- src/BuildEnvironment/RealTime.ruleset | 2 -- src/RealTime/Core/LoadingExtension.cs | 3 +++ src/RealTime/Core/RealTimeCore.cs | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/BuildEnvironment/RealTime.ruleset b/src/BuildEnvironment/RealTime.ruleset index 732223fa..9ee68edc 100644 --- a/src/BuildEnvironment/RealTime.ruleset +++ b/src/BuildEnvironment/RealTime.ruleset @@ -120,13 +120,11 @@ - - diff --git a/src/RealTime/Core/LoadingExtension.cs b/src/RealTime/Core/LoadingExtension.cs index 30aab0ea..2028d0cf 100644 --- a/src/RealTime/Core/LoadingExtension.cs +++ b/src/RealTime/Core/LoadingExtension.cs @@ -4,6 +4,7 @@ namespace RealTime.Core { + using System.Security.Permissions; using ICities; /// @@ -19,6 +20,7 @@ public sealed class LoadingExtension : LoadingExtensionBase /// /// /// The a game level is loaded in. + [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] public override void OnLevelLoaded(LoadMode mode) { switch (mode) @@ -40,6 +42,7 @@ public override void OnLevelLoaded(LoadMode mode) /// Calles when a game level is about to be unloaded. If the Real Time mod was activated /// for this level, deactivates the mod for this level. /// + [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] public override void OnLevelUnloading() { if (core != null) diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs index ae935fc4..29384619 100644 --- a/src/RealTime/Core/RealTimeCore.cs +++ b/src/RealTime/Core/RealTimeCore.cs @@ -5,6 +5,7 @@ namespace RealTime.Core { using System; + using System.Security.Permissions; using RealTime.Simulation; using RealTime.Tools; using RealTime.UI; @@ -32,6 +33,7 @@ private RealTimeCore(TimeAdjustment timeAdjustment, CustomTimeBar timeBar) /// /// /// A instance that can be used to stop the mod. + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public static RealTimeCore Run() { var timeAdjustment = new TimeAdjustment(); @@ -61,6 +63,7 @@ public static RealTimeCore Run() /// /// Stops the mod by deactivating all its parts. /// + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public void Stop() { if (!isEnabled) From 6bfb70851cdf080c97b0ba615e079a42bbc154f9 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 17:59:43 +0200 Subject: [PATCH 018/102] Implement the dynamic sunrise and sunset time changing - depends on the location latitude - depends on the current date (e.g. the days are longer in summer) --- src/RealTime/RealTime.csproj | 2 + src/RealTime/Simulation/DayTime.cs | 69 +++++++++++++++++++ .../Simulation/DaylightTimeSimulation.cs | 65 +++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 src/RealTime/Simulation/DayTime.cs create mode 100644 src/RealTime/Simulation/DaylightTimeSimulation.cs diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj index 07cf4869..d0c4a8ff 100644 --- a/src/RealTime/RealTime.csproj +++ b/src/RealTime/RealTime.csproj @@ -74,6 +74,8 @@ + + diff --git a/src/RealTime/Simulation/DayTime.cs b/src/RealTime/Simulation/DayTime.cs new file mode 100644 index 00000000..74d850b5 --- /dev/null +++ b/src/RealTime/Simulation/DayTime.cs @@ -0,0 +1,69 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Simulation +{ + using System; + using UnityEngine; + + /// + /// Calculates the sunrise and sunset time based on the map latitude and current date. + /// + internal sealed class DayTime + { + private double phase; + private double halfAmplitude; + + /// + /// Gets a value indicating whether this object has been properly set up + /// and can be used for the calculations. + /// + public bool IsReady { get; private set; } + + /// + /// Sets up this object so that it can correctly perform the sunrise and sunset time + /// calculations. + /// + /// + /// The latitude coordinate to assume for the city. + /// Valid values are -80° to 80°. + public void Setup(float latitude) + { + bool southSemisphere = latitude < 0; + + latitude = Mathf.Clamp(Mathf.Abs(latitude), 0f, 80f); + halfAmplitude = (0.5 + (latitude / 15d)) / 2d; + + phase = southSemisphere ? 0 : Math.PI; + + IsReady = true; + } + + /// + /// Calculates the sunrise and sunset hours for the provided . + /// If this object is not properly set up yet (so returns false), + /// then the out values will be initialized with default empty s. + /// + /// + /// The game date to calculate the sunrise and sunset times for. + /// The calculated sunrise hour (relative to the midnight). + /// The calculated sunset hour (relative to the midnight). + /// + /// True when the values are successfully calculated; otherwise, false. + public bool Calculate(DateTime date, out TimeSpan sunriseHour, out TimeSpan sunsetHour) + { + if (!IsReady) + { + sunriseHour = default; + sunsetHour = default; + return false; + } + + double modifier = Math.Cos((2 * Math.PI * (date.DayOfYear + 10) / 365.25) + phase); + sunriseHour = TimeSpan.FromHours(6d - (halfAmplitude * modifier)); + sunsetHour = TimeSpan.FromHours(18d + (halfAmplitude * modifier)); + return true; + } + } +} diff --git a/src/RealTime/Simulation/DaylightTimeSimulation.cs b/src/RealTime/Simulation/DaylightTimeSimulation.cs new file mode 100644 index 00000000..685e35da --- /dev/null +++ b/src/RealTime/Simulation/DaylightTimeSimulation.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Simulation +{ + using System; + using ICities; + using RealTime.Tools; + + /// + /// A simulation extension that manages the daytime calculation (sunrise and sunset). + /// + public sealed class DaylightTimeSimulation : ThreadingExtensionBase + { + private readonly DayTime dayTime; + private DateTime lastCalculation; + + /// + /// Initializes a new instance of the class. + /// + public DaylightTimeSimulation() + { + dayTime = new DayTime(); + } + + /// + /// Called after each game simulation tick. Performs the actual work. + /// + public override void OnAfterSimulationTick() + { + if (!dayTime.IsReady) + { + if (DayNightProperties.instance != null) + { + dayTime.Setup(DayNightProperties.instance.m_Latitude); + } + else + { + return; + } + } + + if (threadingManager.simulationTime.Date != lastCalculation) + { + CalculateDaylight(threadingManager.simulationTime); + } + } + + private void CalculateDaylight(DateTime date) + { + if (!dayTime.Calculate(date, out TimeSpan sunriseHour, out TimeSpan sunsetHour)) + { + return; + } + + SimulationManager.SUNRISE_HOUR = (float)sunriseHour.TotalHours; + SimulationManager.SUNSET_HOUR = (float)sunsetHour.TotalHours; + + Log.Info($"New day: {date.Date:d}, sunrise at {sunriseHour:g}, sunset at {sunsetHour:g}"); + + lastCalculation = date.Date; + } + } +} From 2dfd63feab40918ef463c807c930361341da9f90 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 18:18:06 +0200 Subject: [PATCH 019/102] Implement realistic construction - no construction at night - Next step: optionally disable constructions on weekends --- src/RealTime/AI/RealTimePrivateBuildingAI.cs | 23 ++++++++++++++++++++ src/RealTime/RealTime.csproj | 1 + 2 files changed, 24 insertions(+) create mode 100644 src/RealTime/AI/RealTimePrivateBuildingAI.cs diff --git a/src/RealTime/AI/RealTimePrivateBuildingAI.cs b/src/RealTime/AI/RealTimePrivateBuildingAI.cs new file mode 100644 index 00000000..ca0af120 --- /dev/null +++ b/src/RealTime/AI/RealTimePrivateBuildingAI.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.AI +{ + using Redirection; + + internal sealed class RealTimePrivateBuildingAI + { + [RedirectFrom(typeof(PrivateBuildingAI))] + private static int GetConstructionTime(PrivateBuildingAI instance) + { + if ((ToolManager.instance.m_properties.m_mode & ItemClass.Availability.AssetEditor) != 0) + { + return 0; + } + + // This causes the constuction to not advance in the night time + return SimulationManager.instance.m_isNightTime ? 10880 : 1088; + } + } +} diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj index d0c4a8ff..f51c6fdc 100644 --- a/src/RealTime/RealTime.csproj +++ b/src/RealTime/RealTime.csproj @@ -64,6 +64,7 @@ + From 5100555bbc31d0838cced6644b810db3b879f49b Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Fri, 1 Jun 2018 19:05:33 +0200 Subject: [PATCH 020/102] Rename the Configuration folder to correspond the namespace --- src/RealTime/{Configuration => Config}/Configuration.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/RealTime/{Configuration => Config}/Configuration.cs (100%) diff --git a/src/RealTime/Configuration/Configuration.cs b/src/RealTime/Config/Configuration.cs similarity index 100% rename from src/RealTime/Configuration/Configuration.cs rename to src/RealTime/Config/Configuration.cs From b78a4c708cec6c817875d8baf93b8a14072a7568 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Wed, 13 Jun 2018 22:26:38 +0200 Subject: [PATCH 021/102] Add a special debug log method accepting the in-game simulation time --- src/RealTime/Tools/Log.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/RealTime/Tools/Log.cs b/src/RealTime/Tools/Log.cs index 76adc300..b46bb682 100644 --- a/src/RealTime/Tools/Log.cs +++ b/src/RealTime/Tools/Log.cs @@ -60,6 +60,20 @@ public static void Debug(string text) #endif } + /// + /// Logs a debug information. This method won't be compiled in the 'Release' mode. + /// + /// + /// The current date and time in the game. + /// The text to log. + [Conditional("DEBUG")] + public static void Debug(DateTime gameTime, string text) + { +#if DEBUG + DebugLog(gameTime.ToString("dd.MM.yy HH:mm") + " --> " + text, TypeDebug); +#endif + } + /// /// Logs an information text. /// From b8441977d272f3b2df0030b28b5927ad5f8601ee Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Wed, 13 Jun 2018 22:29:25 +0200 Subject: [PATCH 022/102] Fix the issue with wrong date and time after game loading --- src/RealTime/Simulation/TimeAdjustment.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/RealTime/Simulation/TimeAdjustment.cs b/src/RealTime/Simulation/TimeAdjustment.cs index 75f8cebb..064b3ad7 100644 --- a/src/RealTime/Simulation/TimeAdjustment.cs +++ b/src/RealTime/Simulation/TimeAdjustment.cs @@ -52,13 +52,16 @@ public void Disable() private static DateTime UpdateTimeSimulationValues(uint framesPerDay, TimeSpan timePerFrame) { + SimulationManager sm = SimulationManager.instance; + DateTime originalDate = sm.m_ThreadingWrapper.simulationTime; + SimulationManager.DAYTIME_FRAMES = framesPerDay; SimulationManager.DAYTIME_FRAME_TO_HOUR = 24f / SimulationManager.DAYTIME_FRAMES; SimulationManager.DAYTIME_HOUR_TO_FRAME = SimulationManager.DAYTIME_FRAMES / 24f; - SimulationManager sm = SimulationManager.instance; sm.m_timePerFrame = timePerFrame; - sm.m_timeOffsetTicks = sm.m_currentGameTime.Ticks - (sm.m_currentFrameIndex * sm.m_timePerFrame.Ticks); + sm.m_timeOffsetTicks = originalDate.Ticks - (sm.m_currentFrameIndex * sm.m_timePerFrame.Ticks); + sm.m_currentGameTime = originalDate; sm.m_currentDayTimeHour = (float)sm.m_currentGameTime.TimeOfDay.TotalHours; sm.m_dayTimeFrame = (uint)(SimulationManager.DAYTIME_FRAMES * sm.m_currentDayTimeHour / 24f); From 6a10752cd7a79a7ebe800411a46de05d90d47901 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Wed, 13 Jun 2018 22:30:49 +0200 Subject: [PATCH 023/102] Implement the custom citizen AI logic (first revision) --- src/RealTime/AI/CitizenState.cs | 21 ++ src/RealTime/AI/ILogic.cs | 37 ++- src/RealTime/AI/Logic.cs | 234 +++++++------- src/RealTime/AI/LogicService.cs | 23 ++ src/RealTime/AI/RealTimePrivateBuildingAI.cs | 4 +- src/RealTime/AI/RealTimeResidentAI.Common.cs | 213 ++++++++----- src/RealTime/AI/RealTimeResidentAI.Home.cs | 59 ++-- src/RealTime/AI/RealTimeResidentAI.Moving.cs | 29 +- .../AI/RealTimeResidentAI.SchoolWork.cs | 70 +++++ src/RealTime/AI/RealTimeResidentAI.Visit.cs | 292 ++++++++++++++---- src/RealTime/AI/RealTimeResidentAI.Work.cs | 77 ----- src/RealTime/AI/RealTimeResidentAI.cs | 103 +++--- src/RealTime/Config/Configuration.cs | 82 ++--- src/RealTime/Core/RealTimeCore.cs | 8 + src/RealTime/RealTime.csproj | 8 +- src/RealTime/Simulation/ITimeInfo.cs | 19 ++ src/RealTime/Simulation/TimeAdjustment.cs | 2 +- src/RealTime/Simulation/TimeInfo.cs | 19 ++ src/RealTime/Tools/DateTimeExtensions.cs | 12 - 19 files changed, 795 insertions(+), 517 deletions(-) create mode 100644 src/RealTime/AI/CitizenState.cs create mode 100644 src/RealTime/AI/LogicService.cs create mode 100644 src/RealTime/AI/RealTimeResidentAI.SchoolWork.cs delete mode 100644 src/RealTime/AI/RealTimeResidentAI.Work.cs create mode 100644 src/RealTime/Simulation/ITimeInfo.cs create mode 100644 src/RealTime/Simulation/TimeInfo.cs diff --git a/src/RealTime/AI/CitizenState.cs b/src/RealTime/AI/CitizenState.cs new file mode 100644 index 00000000..2725c1c4 --- /dev/null +++ b/src/RealTime/AI/CitizenState.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.AI +{ + internal enum CitizenState + { + Unknown, + LeftCity, + MovingHome, + AtHome, + MovingToTarget, + AtSchoolOrWork, + AtLunch, + Shopping, + AtLeisureArea, + Visiting, + Evacuating + } +} diff --git a/src/RealTime/AI/ILogic.cs b/src/RealTime/AI/ILogic.cs index 000fffa1..591decd5 100644 --- a/src/RealTime/AI/ILogic.cs +++ b/src/RealTime/AI/ILogic.cs @@ -4,6 +4,8 @@ namespace RealTime.AI { + using RealTime.Config; + /// /// Describes the basic Real Time customized logic for the Cims. /// @@ -40,23 +42,28 @@ internal interface ILogic bool IsLunchHour { get; } /// - /// Determines whether the provided should go to work. + /// Gets a reference to the current logic configuration. + /// + Configuration CurrentConfig { get; } + + /// + /// Determines whether the provided should go to school or to work. /// /// /// The to check. - /// True to ignore the time constraints for the minimum work duration. + /// A reference to a instance. /// /// True if the should go go to work; otherwise, false. - bool ShouldGoToWork(ref Citizen citizen, bool ignoreMinimumDuration = false); + bool ShouldGoToSchoolOrWork(ref Citizen citizen, BuildingManager buildingManager); /// - /// Determines whether the provided should return back from work. + /// Determines whether the provided should return back from school or from work. /// /// /// The to check. /// /// True if the should return back from work; otherwise, false. - bool ShouldReturnFromWork(ref Citizen citizen); + bool ShouldReturnFromSchoolOrWork(ref Citizen citizen); /// /// Determines whether the provided should go out for lunch. @@ -84,5 +91,25 @@ internal interface ILogic /// /// True if the can stay out; otherwise, false. bool CanStayOut(ref Citizen citizen); + + /// + /// Gets a value in range from 0 to 100 that indicates a chance in percent that a Cim + /// with provided will go out at night. + /// + /// The Cim's age. + /// + /// A value in range from 0 to 100. + int GetGoOutAtNightChance(int age); + + /// + /// Determines whether the current game day is a work day and the time is between + /// the provided hours. + /// + /// The start day time hour of the range to check. + /// The end day time hour of the range to check. + /// + /// True if the curent game day is a work day and the day time hour is + /// between the provided values; otherwise, false. + bool IsWorkDayAndBetweenHours(float fromInclusive, float toExclusive); } } \ No newline at end of file diff --git a/src/RealTime/AI/Logic.cs b/src/RealTime/AI/Logic.cs index 8e41d9da..6be49946 100644 --- a/src/RealTime/AI/Logic.cs +++ b/src/RealTime/AI/Logic.cs @@ -7,6 +7,7 @@ namespace RealTime.AI using System; using ColossalFramework.Math; using RealTime.Config; + using RealTime.Simulation; using RealTime.Tools; using UnityEngine; @@ -16,7 +17,7 @@ namespace RealTime.AI internal sealed class Logic : ILogic { private readonly Configuration config; - private readonly Func timeProvider; + private readonly ITimeInfo timeInfo; private readonly Randomizer randomizer; /// @@ -26,12 +27,13 @@ internal sealed class Logic : ILogic /// Thrown when any argument is null. /// /// A instance containing the mod's configuration. - /// A method that can provide the current game date and time. + /// An object implementing the interface that provides + /// the current game date and time information. /// A instance to use for randomization. - public Logic(Configuration config, Func timeProvider, Randomizer randomizer) + public Logic(Configuration config, ITimeInfo timeInfo, ref Randomizer randomizer) { this.config = config ?? throw new ArgumentNullException(nameof(config)); - this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider)); + this.timeInfo = timeInfo ?? throw new ArgumentNullException(nameof(timeInfo)); this.randomizer = randomizer; } @@ -39,25 +41,25 @@ public Logic(Configuration config, Func timeProvider, Randomizer rand /// Gets a value indicating whether the current game time represents a weekend (no work on those days). /// Cims have free time. /// - public bool IsWeekend => config.EnableWeekends && timeProvider().IsWeekend(); + public bool IsWeekend => config.IsWeekendEnabled && timeInfo.Now.IsWeekend(); /// /// Gets a value indicating whether the current game time represents a work day. /// Cims go to work. /// - public bool IsWorkDay => !config.EnableWeekends || !timeProvider().IsWeekend(); + public bool IsWorkDay => !config.IsWeekendEnabled || !timeInfo.Now.IsWeekend(); /// /// Gets a value indicating whether the current game time represents a work hour. /// Cims are working. /// - public bool IsWorkHour => IsWorkDayAndBetweenHours(config.MinWorkHour, config.EndWorkHour); + public bool IsWorkHour => IsWorkDayAndBetweenHours(config.WorkBegin, config.WorkEnd); /// /// Gets a value indicating whether the current game time represents a school hour. /// Students are studying. /// - public bool IsSchoolHour => IsWorkDayAndBetweenHours(config.MinSchoolHour, config.EndSchoolHour); + public bool IsSchoolHour => IsWorkDayAndBetweenHours(config.SchoolBegin, config.SchoolEnd); /// /// Gets a value indicating whether the current game time represents a lunch hour. @@ -65,105 +67,92 @@ public Logic(Configuration config, Func timeProvider, Randomizer rand /// public bool IsLunchHour => IsWorkDayAndBetweenHours(config.LunchBegin, config.LunchEnd); - // Hours to attempt to go to school, if not already at school. Don't want them travelling only to go home straight away - private float MaxSchoolAttemptHour => config.EndSchoolHour - config.MinSchoolDuration; - - // Hours to attempt to go to work, if not already at work. Don't want them travelling only to go home straight away - private float MaxWorkAttemptHour => config.EndWorkHour - config.MinWorkDuration; + /// + /// Gets a reference to the current logic configuration. + /// + public Configuration CurrentConfig => config; /// - /// Determines whether the provided should go to work. + /// Determines whether the provided should go to school or to work. /// /// /// The to check. - /// True to ignore the time constraints for the minimum work duration. + /// A reference to a instance. /// /// True if the should go go to work; otherwise, false. - public bool ShouldGoToWork(ref Citizen citizen, bool ignoreMinimumDuration = false) + public bool ShouldGoToSchoolOrWork(ref Citizen citizen, BuildingManager buildingManager) { if (!CanWorkOrStudyToday(ref citizen)) { return false; } - float currentHour = timeProvider().HourOfDay(); + float currentHour = timeInfo.CurrentHour; + float leaveHomeHour; + float returnHomeHour; switch (Citizen.GetAgeGroup(citizen.Age)) { case Citizen.AgeGroup.Child: case Citizen.AgeGroup.Teen: - if (currentHour > config.MinSchoolHour - config.WorkOrSchoolTravelTime - && currentHour < config.StartSchoolHour - config.WorkOrSchoolTravelTime) - { - return randomizer.Int32(100) < config.EarlyStartQuota * 100; - } - else if (currentHour >= config.StartSchoolHour - config.WorkOrSchoolTravelTime - && currentHour < (ignoreMinimumDuration ? config.EndSchoolHour : MaxSchoolAttemptHour)) - { - return true; - } - + leaveHomeHour = config.SchoolBegin; + returnHomeHour = config.SchoolEnd; break; case Citizen.AgeGroup.Young: case Citizen.AgeGroup.Adult: - if (currentHour > config.MinWorkHour - config.WorkOrSchoolTravelTime - && currentHour < config.StartWorkHour - config.WorkOrSchoolTravelTime) - { - return randomizer.Int32(100) < config.EarlyStartQuota * 100; - } - else if (currentHour >= config.StartWorkHour - config.WorkOrSchoolTravelTime - && currentHour < (ignoreMinimumDuration ? config.EndWorkHour : MaxWorkAttemptHour)) - { - return true; - } - + leaveHomeHour = randomizer.Int32(100) < config.OnTimeQuota + ? config.WorkBegin + : config.WorkBegin - (config.MaxOvertime * randomizer.Int32(100) / 100f); + returnHomeHour = config.WorkEnd; break; + + default: + return false; } - return false; + ref Building home = ref buildingManager.m_buildings.m_buffer[citizen.m_homeBuilding]; + ref Building target = ref buildingManager.m_buildings.m_buffer[citizen.m_workBuilding]; + + float distance = Mathf.Clamp(Vector3.Distance(home.m_position, target.m_position), 0, 1000f); + float onTheWay = 0.5f + (distance / 500f); + + leaveHomeHour -= onTheWay; + + return currentHour >= leaveHomeHour && currentHour < returnHomeHour; } /// - /// Determines whether the provided should return back from work. + /// Determines whether the provided should return back from school or from work. /// /// /// The to check. /// /// True if the should return back from work; otherwise, false. - public bool ShouldReturnFromWork(ref Citizen citizen) + public bool ShouldReturnFromSchoolOrWork(ref Citizen citizen) { if (IsWeekend) { return true; } - float currentHour = timeProvider().HourOfDay(); + float currentHour = timeInfo.CurrentHour; switch (Citizen.GetAgeGroup(citizen.Age)) { case Citizen.AgeGroup.Child: case Citizen.AgeGroup.Teen: - if (currentHour >= config.EndSchoolHour && currentHour < config.MaxSchoolHour) - { - return randomizer.Int32(100) < config.LeaveOnTimeQuota * 100; - } - else if (currentHour > config.MaxSchoolHour || currentHour < config.MinSchoolHour) - { - return true; - } - - break; + return currentHour >= config.SchoolEnd || currentHour < config.SchoolBegin; case Citizen.AgeGroup.Young: case Citizen.AgeGroup.Adult: - if (currentHour >= config.EndWorkHour && currentHour < config.MaxWorkHour) + if (currentHour >= (config.WorkEnd + config.MaxOvertime) || currentHour < config.WorkBegin) { - return randomizer.Int32(100) < config.LeaveOnTimeQuota * 100; + return true; } - else if (currentHour > config.MaxWorkHour || currentHour < config.MinWorkHour) + else if (currentHour >= config.WorkEnd) { - return true; + return randomizer.Int32(100) < config.OnTimeQuota; } break; @@ -184,7 +173,7 @@ public bool ShouldReturnFromWork(ref Citizen citizen) /// True if the should go out for lunch; otherwise, false. public bool ShouldGoToLunch(ref Citizen citizen) { - if (!config.SimulateLunchTime) + if (!config.IsLunchTimeEnabled) { return false; } @@ -192,19 +181,14 @@ public bool ShouldGoToLunch(ref Citizen citizen) switch (Citizen.GetAgeGroup(citizen.Age)) { case Citizen.AgeGroup.Child: - return false; - - case Citizen.AgeGroup.Teen: - case Citizen.AgeGroup.Young: - case Citizen.AgeGroup.Adult: case Citizen.AgeGroup.Senior: - break; + return false; } - float currentHour = timeProvider().HourOfDay(); - if (currentHour > config.LunchBegin && currentHour < config.LunchEnd) + float currentHour = timeInfo.CurrentHour; + if (currentHour >= config.LunchBegin && currentHour <= config.LunchEnd) { - return randomizer.Int32(100) < config.LunchQuota * 100; + return randomizer.Int32(100) < config.LunchQuota; } return false; @@ -219,46 +203,38 @@ public bool ShouldGoToLunch(ref Citizen citizen) /// True if the should find entertainment; otherwise, false. public bool ShouldFindEntertainment(ref Citizen citizen) { - float dayHourStart = 7f; - float dayHourEnd = 20f; + float dayHourStart = timeInfo.SunriseHour; + float dayHourEnd = timeInfo.SunsetHour; switch (Citizen.GetAgeGroup(citizen.Age)) { case Citizen.AgeGroup.Child: - dayHourStart = 8f; - dayHourEnd = 18f; + dayHourStart += 1f; + dayHourEnd -= 2f; break; case Citizen.AgeGroup.Teen: - dayHourStart = 10f; - dayHourEnd = 20f; - break; - - case Citizen.AgeGroup.Young: - case Citizen.AgeGroup.Adult: - dayHourStart = 7f; - dayHourEnd = 20f; + dayHourStart += 1f; break; case Citizen.AgeGroup.Senior: - dayHourStart = 6f; - dayHourEnd = 18f; + dayHourEnd -= 2f; break; } - float currentHour = timeProvider().HourOfDay(); + float currentHour = timeInfo.CurrentHour; if (currentHour > dayHourStart && currentHour < dayHourEnd) { - return randomizer.Int32(100) < GetGoOutThroughDayChance(citizen.Age) * 100; + return randomizer.Int32(100) < GetGoOutThroughDayChance(citizen.Age); } else { - return randomizer.Int32(100) < GetGoOutAtNightChance(citizen.Age) * 100; + return randomizer.Int32(100) < GetGoOutAtNightChance(citizen.Age); } } /// - /// Determines whether the provided can stay out. + /// Determines whether the provided can stay out at current time. /// /// /// The to check. @@ -266,77 +242,91 @@ public bool ShouldFindEntertainment(ref Citizen citizen) /// True if the can stay out; otherwise, false. public bool CanStayOut(ref Citizen citizen) { - float currentHour = timeProvider().HourOfDay(); - return (currentHour >= 7f && currentHour < 20f) || GetGoOutAtNightChance(citizen.Age) > 0; - } - - private bool IsWorkDayAndBetweenHours(float fromInclusive, float toExclusive) - { - float currentHour = timeProvider().HourOfDay(); - return IsWorkDay && (currentHour >= fromInclusive && currentHour < toExclusive); - } - - private bool CanWorkOrStudyToday(ref Citizen citizen) - { - return citizen.m_workBuilding != 0 && IsWorkDay; + float currentHour = timeInfo.CurrentHour; + return (currentHour >= timeInfo.SunriseHour && currentHour < timeInfo.SunsetHour) || GetGoOutAtNightChance(citizen.Age) > 0; } - private bool IsAfterLunchTime(float afterLunchHours) + /// + /// Gets a value in range from 0 to 100 that indicates a chance in percent that a Cim + /// with provided will go out at night. + /// + /// The Cim's age. + /// + /// A value in range from 0 to 100. + public int GetGoOutAtNightChance(int age) { - return IsWorkDay && timeProvider().HourOfDay() < (config.LunchEnd + afterLunchHours); - } + float currentHour = timeInfo.CurrentHour; + int weekendMultiplier; - private float GetGoOutAtNightChance(int age) - { - float currentHour = timeProvider().HourOfDay(); - int weekendMultiplier = IsWeekend && timeProvider().IsWeekendAfter(TimeSpan.FromHours(12)) - ? currentHour > 12f + if (IsWeekend && timeInfo.Now.IsWeekendAfter(TimeSpan.FromHours(12))) + { + weekendMultiplier = currentHour > 12f ? 6 - : Mathf.RoundToInt(Mathf.Clamp(-((currentHour * 1.5f) - 6f), 0f, 6f)) - : 1; + : Mathf.RoundToInt(Mathf.Clamp(-((currentHour * 1.5f) - 6f), 0f, 6f)); + } + else + { + weekendMultiplier = 1; + } switch (Citizen.GetAgeGroup(age)) { - case Citizen.AgeGroup.Child: - case Citizen.AgeGroup.Senior: - return 0; - case Citizen.AgeGroup.Teen: - return 0.1f * weekendMultiplier; + return 8 * weekendMultiplier; case Citizen.AgeGroup.Young: - return 0.08f * weekendMultiplier; + return 10 * weekendMultiplier; case Citizen.AgeGroup.Adult: - return 0.02f * weekendMultiplier; + return 2 * weekendMultiplier; default: return 0; } } - private float GetGoOutThroughDayChance(int age) + /// + /// Determines whether the current game day is a work day and the time is between + /// the provided hours. + /// + /// The start day time hour of the range to check. + /// The end day time hour of the range to check. + /// + /// True if the curent game day is a work day and the day time hour is + /// between the provided values; otherwise, false. + public bool IsWorkDayAndBetweenHours(float fromInclusive, float toExclusive) + { + float currentHour = timeInfo.CurrentHour; + return IsWorkDay && (currentHour >= fromInclusive && currentHour < toExclusive); + } + + private bool CanWorkOrStudyToday(ref Citizen citizen) + { + return citizen.m_workBuilding != 0 && IsWorkDay; + } + + private int GetGoOutThroughDayChance(int age) { - int weekendMultiplier = IsWeekend && timeProvider().IsWeekendAfter(TimeSpan.FromHours(12)) + int weekendMultiplier = IsWeekend && timeInfo.Now.IsWeekendAfter(TimeSpan.FromHours(12)) ? 4 : 1; switch (Citizen.GetAgeGroup(age)) { case Citizen.AgeGroup.Child: - return 0.6f; + return 60; case Citizen.AgeGroup.Teen: - return 0.13f * weekendMultiplier; + return 13 * weekendMultiplier; case Citizen.AgeGroup.Young: - return 0.12f * weekendMultiplier; + return 12 * weekendMultiplier; case Citizen.AgeGroup.Adult: - return 0.1f * weekendMultiplier; + return 1 * weekendMultiplier; case Citizen.AgeGroup.Senior: - return 0.06f; + return 90; default: return 0; diff --git a/src/RealTime/AI/LogicService.cs b/src/RealTime/AI/LogicService.cs new file mode 100644 index 00000000..c1d6595c --- /dev/null +++ b/src/RealTime/AI/LogicService.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.AI +{ + using System; + + internal static class LogicService + { + public static void ProvideLogic(ILogic logic) + { + RealTimePrivateBuildingAI.Logic = logic ?? throw new ArgumentNullException(nameof(logic)); + RealTimeResidentAI.Logic = logic; + } + + public static void RevokeLogic() + { + RealTimePrivateBuildingAI.Logic = null; + RealTimeResidentAI.Logic = null; + } + } +} diff --git a/src/RealTime/AI/RealTimePrivateBuildingAI.cs b/src/RealTime/AI/RealTimePrivateBuildingAI.cs index ca0af120..24202d28 100644 --- a/src/RealTime/AI/RealTimePrivateBuildingAI.cs +++ b/src/RealTime/AI/RealTimePrivateBuildingAI.cs @@ -8,7 +8,9 @@ namespace RealTime.AI internal sealed class RealTimePrivateBuildingAI { - [RedirectFrom(typeof(PrivateBuildingAI))] + internal static ILogic Logic { get; set; } + + //[RedirectFrom(typeof(PrivateBuildingAI))] private static int GetConstructionTime(PrivateBuildingAI instance) { if ((ToolManager.instance.m_properties.m_mode & ItemClass.Availability.AssetEditor) != 0) diff --git a/src/RealTime/AI/RealTimeResidentAI.Common.cs b/src/RealTime/AI/RealTimeResidentAI.Common.cs index df873f6e..017dc1e8 100644 --- a/src/RealTime/AI/RealTimeResidentAI.Common.cs +++ b/src/RealTime/AI/RealTimeResidentAI.Common.cs @@ -4,150 +4,215 @@ namespace RealTime.AI { - using System; + using RealTime.Tools; internal static partial class RealTimeResidentAI { - private static bool? ProcessCitizenCommonState(Arguments args, uint citizenID, ref Citizen data) + private static void ProcessCitizenDead(References refs, uint citizenId, ref Citizen citizen) { - ushort currentBuilding = GetCurrentCitizenBuilding(ref data); + ushort currentBuilding = citizen.GetBuildingByLocation(); - if (data.Dead) + if (currentBuilding == 0 || (citizen.CurrentLocation == Citizen.Location.Moving && citizen.m_vehicle == 0)) { - return ProcessCitizenDead(args, citizenID, ref data, currentBuilding); + Log.Debug($"{CitizenInfo(citizenId, ref citizen)} is released"); + refs.CitizenMgr.ReleaseCitizen(citizenId); + return; } - if (data.CurrentLocation == Citizen.Location.Moving) + if (citizen.CurrentLocation != Citizen.Location.Home && citizen.m_homeBuilding != 0) { - return null; + citizen.SetHome(citizenId, 0, 0u); } - if (data.Arrested) + if (citizen.CurrentLocation != Citizen.Location.Work && citizen.m_workBuilding != 0) { - return ProcessCitizenArrested(ref data); + citizen.SetWorkplace(citizenId, 0, 0u); } - if (data.CurrentLocation == Citizen.Location.Visit && data.Collapsed) + if (citizen.CurrentLocation != Citizen.Location.Visit && citizen.m_visitBuilding != 0) { - return false; + citizen.SetVisitplace(citizenId, 0, 0u); } - if (data.CurrentLocation == Citizen.Location.Home && (data.m_homeBuilding == 0 || data.m_vehicle != 0)) + if (citizen.CurrentLocation == Citizen.Location.Moving || citizen.m_vehicle != 0) { - return false; + return; } - if (data.Sick) + if (citizen.CurrentLocation == Citizen.Location.Visit + && + refs.BuildingMgr.m_buildings.m_buffer[citizen.m_visitBuilding].Info.m_class.m_service == ItemClass.Service.HealthCare) { - return ProcessCitizenSick(args, citizenID, ref data, currentBuilding); + return; } - return null; + FindHospital(refs.ResidentAI, citizenId, currentBuilding, TransferManager.TransferReason.Dead); + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} is dead, body should go to a cemetery"); } - private static bool ProcessCitizenDead(Arguments args, uint citizenID, ref Citizen data, ushort currentBuilding) + private static bool ProcessCitizenArrested(References refs, ref Citizen citizen) { - if (currentBuilding == 0 || (data.CurrentLocation == Citizen.Location.Moving && data.m_vehicle == 0)) + if (citizen.CurrentLocation == Citizen.Location.Moving) { - args.CitizenMgr.ReleaseCitizen(citizenID); - return true; + return false; } - if (data.CurrentLocation != Citizen.Location.Home && data.m_homeBuilding != 0) + if (citizen.CurrentLocation == Citizen.Location.Visit && citizen.m_visitBuilding != 0) { - data.SetHome(citizenID, 0, 0u); + ref Building building = ref refs.BuildingMgr.m_buildings.m_buffer[citizen.m_visitBuilding]; + if (building.Info.m_class.m_service == ItemClass.Service.PoliceDepartment) + { + return true; + } } - if (data.CurrentLocation != Citizen.Location.Work && data.m_workBuilding != 0) - { - data.SetWorkplace(citizenID, 0, 0u); - } + citizen.Arrested = false; + return false; + } - if (data.CurrentLocation != Citizen.Location.Visit && data.m_visitBuilding != 0) + private static bool ProcessCitizenSick(References refs, uint citizenId, ref Citizen citizen) + { + if (citizen.CurrentLocation == Citizen.Location.Moving) { - data.SetVisitplace(citizenID, 0, 0u); + return false; } - if (data.CurrentLocation == Citizen.Location.Moving || data.m_vehicle != 0) + ushort currentBuilding = citizen.GetBuildingByLocation(); + + if (citizen.CurrentLocation != Citizen.Location.Home && currentBuilding == 0) { - return false; + Log.Warning($"Teleporting {CitizenInfo(citizenId, ref citizen)} back home because they are sick but no building is specified"); + citizen.CurrentLocation = Citizen.Location.Home; + return true; } - if (data.CurrentLocation == Citizen.Location.Visit - && - args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service == ItemClass.Service.HealthCare) + if (citizen.CurrentLocation != Citizen.Location.Home && citizen.m_vehicle != 0) { - return false; + return true; } - if (FindHospital(args.ResidentAI, citizenID, currentBuilding, TransferManager.TransferReason.Dead)) + if (citizen.CurrentLocation == Citizen.Location.Visit) { - return false; + ItemClass.Service service = refs.BuildingMgr.m_buildings.m_buffer[citizen.m_visitBuilding].Info.m_class.m_service; + if (service == ItemClass.Service.HealthCare || service == ItemClass.Service.Disaster) + { + return true; + } } + Log.Debug($"{CitizenInfo(citizenId, ref citizen)} is sick, trying to get to a hospital"); + FindHospital(refs.ResidentAI, citizenId, currentBuilding, TransferManager.TransferReason.Sick); return true; } - private static bool ProcessCitizenArrested(ref Citizen data) + private static void ProcessCitizenEvacuation(ResidentAI residentAi, uint citizenId, ref Citizen citizen) { - if (data.CurrentLocation != Citizen.Location.Visit || data.m_visitBuilding == 0) + ushort building = citizen.GetBuildingByLocation(); + if (building != 0) { - data.Arrested = false; + Log.Debug($"{CitizenInfo(citizenId, ref citizen)} is trying to find an evacuation place"); + FindEvacuationPlace(residentAi, citizenId, building, GetEvacuationReason(residentAi, building)); } - - return false; } - private static bool ProcessCitizenSick(Arguments args, uint citizenID, ref Citizen data, ushort currentBuilding) + private static CitizenState GetCitizenState(References refs, ref Citizen citizen) { - if (data.CurrentLocation != Citizen.Location.Home && currentBuilding == 0) + ushort currentBuilding = citizen.GetBuildingByLocation(); + if (currentBuilding != 0 && (refs.BuildingMgr.m_buildings.m_buffer[currentBuilding].m_flags & Building.Flags.Evacuating) != 0) { - data.CurrentLocation = Citizen.Location.Home; - return false; + return CitizenState.Evacuating; } - if (data.CurrentLocation != Citizen.Location.Home && data.m_vehicle != 0) + switch (citizen.CurrentLocation) { - return false; - } + case Citizen.Location.Home: + if ((citizen.m_flags & Citizen.Flags.MovingIn) != 0) + { + return CitizenState.LeftCity; + } - if (data.CurrentLocation == Citizen.Location.Visit) - { - ItemClass.Service service = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service; - if (service == ItemClass.Service.HealthCare || service == ItemClass.Service.Disaster) - { - return false; - } - } + if (currentBuilding != 0) + { + return CitizenState.AtHome; + } - if (FindHospital(args.ResidentAI, citizenID, currentBuilding, TransferManager.TransferReason.Sick)) - { - return false; + return CitizenState.Unknown; + + case Citizen.Location.Work: + return currentBuilding != 0 + ? CitizenState.AtSchoolOrWork + : CitizenState.Unknown; + + case Citizen.Location.Visit: + if (currentBuilding == 0) + { + return CitizenState.Unknown; + } + + ref Building building = ref refs.BuildingMgr.m_buildings.m_buffer[currentBuilding]; + switch (building.Info.GetService()) + { + case ItemClass.Service.Commercial: + if (citizen.m_workBuilding != 0 + && Logic.IsWorkDayAndBetweenHours(Logic.CurrentConfig.LunchBegin, Logic.CurrentConfig.WorkEnd)) + { + return CitizenState.AtLunch; + } + + if (building.Info.GetSubService() == ItemClass.SubService.CommercialLeisure) + { + return CitizenState.AtLeisureArea; + } + + return CitizenState.Shopping; + + case ItemClass.Service.Beautification: + return CitizenState.AtLeisureArea; + } + + return CitizenState.Visiting; + + case Citizen.Location.Moving: + if (citizen.m_instance != 0) + { + ref CitizenInstance instance = ref refs.CitizenMgr.m_instances.m_buffer[citizen.m_instance]; + if ((instance.m_flags & CitizenInstance.Flags.TargetIsNode) != 0 + && instance.m_targetBuilding == citizen.m_homeBuilding) + { + return CitizenState.MovingHome; + } + } + + return CitizenState.MovingToTarget; + + default: + return CitizenState.Unknown; } + } - return true; + private static string CitizenInfo(uint id, ref Citizen citizen) + { + string employment = citizen.m_workBuilding == 0 ? "free" : "emp."; + return $"Citizen {id} ({employment}, {Citizen.GetAgeGroup(citizen.m_age)})"; } - private static ushort GetCurrentCitizenBuilding(ref Citizen data) + private sealed class References { - switch (data.CurrentLocation) + public References(ResidentAI residentAI, CitizenManager citizenMgr, BuildingManager buildingMgr, SimulationManager simMgr) { - case Citizen.Location.Home: - return data.m_homeBuilding; + ResidentAI = residentAI; + CitizenMgr = citizenMgr; + BuildingMgr = buildingMgr; + SimMgr = simMgr; + } - case Citizen.Location.Work: - return data.m_workBuilding; + public ResidentAI ResidentAI { get; } - case Citizen.Location.Visit: - return data.m_visitBuilding; + public CitizenManager CitizenMgr { get; } - case Citizen.Location.Moving: - // This value will not be used anyway - return ushort.MaxValue; + public BuildingManager BuildingMgr { get; } - default: - throw new NotSupportedException($"The location {data.CurrentLocation} is not supported by this method"); - } + public SimulationManager SimMgr { get; } } } } diff --git a/src/RealTime/AI/RealTimeResidentAI.Home.cs b/src/RealTime/AI/RealTimeResidentAI.Home.cs index fc88257b..03c0ed49 100644 --- a/src/RealTime/AI/RealTimeResidentAI.Home.cs +++ b/src/RealTime/AI/RealTimeResidentAI.Home.cs @@ -4,59 +4,46 @@ namespace RealTime.AI { - using UnityEngine; + using RealTime.Tools; internal static partial class RealTimeResidentAI { - private static bool ProcessCitizenAtHome(Arguments args, uint citizenID, ref Citizen data) + private const float LocalSearchDistance = 1000f; + + private static void ProcessCitizenAtHome(References refs, uint citizenId, ref Citizen citizen) { - if ((data.m_flags & Citizen.Flags.MovingIn) != 0) + if (citizen.m_homeBuilding == 0) { - args.CitizenMgr.ReleaseCitizen(citizenID); - return true; + Log.Debug($"WARNING: {CitizenInfo(citizenId, ref citizen)} is in corrupt state: at home with no home building. Releasing the poor citizen."); + refs.CitizenMgr.ReleaseCitizen(citizenId); + return; } - bool? commonStateResult = ProcessCitizenCommonState(args, citizenID, ref data); - if (commonStateResult.HasValue) + if (citizen.m_vehicle != 0) { - return commonStateResult.Value; + Log.Debug( + refs.SimMgr.m_currentGameTime, + $"WARNING: {CitizenInfo(citizenId, ref citizen)} is at home but vehicle = {citizen.m_vehicle}"); + return; } - if ((args.BuildingMgr.m_buildings.m_buffer[data.m_homeBuilding].m_flags & Building.Flags.Evacuating) != 0) + if (CitizenGoesWorking(refs, citizenId, ref citizen)) { - FindEvacuationPlace(args.ResidentAI, citizenID, data.m_homeBuilding, GetEvacuationReason(args.ResidentAI, data.m_homeBuilding)); + Log.Debug(" ----------- Citizen was AT HOME"); + return; } - else if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) + + if (!DoRandomMove(refs.ResidentAI)) { - FindVisitPlace(args.ResidentAI, citizenID, data.m_homeBuilding, GetShoppingReason(args.ResidentAI)); + return; } - else + + if (!CitizenGoesShopping(refs, citizenId, ref citizen) && !CitizenGoesRelaxing(refs, citizenId, ref citizen)) { - if (data.m_instance == 0 && !DoRandomMove(args.ResidentAI)) - { - return false; - } - - int dayTimeFrame2 = (int)args.SimMgr.m_dayTimeFrame; - int dAYTIME_FRAMES2 = (int)SimulationManager.DAYTIME_FRAMES; - int num9 = dAYTIME_FRAMES2 / 40; - int num10 = (int)(SimulationManager.DAYTIME_FRAMES * 8) / 24; - int num11 = dayTimeFrame2 - num10 & dAYTIME_FRAMES2 - 1; - int num12 = Mathf.Abs(num11 - (dAYTIME_FRAMES2 >> 1)); - num12 = num12 * num12 / (dAYTIME_FRAMES2 >> 1); - int num13 = args.SimMgr.m_randomizer.Int32((uint)dAYTIME_FRAMES2); - if (num13 < num9) - { - FindVisitPlace(args.ResidentAI, citizenID, data.m_homeBuilding, GetEntertainmentReason(args.ResidentAI)); - } - else if (num13 < num9 + num12 && data.m_workBuilding != 0) - { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, data.m_homeBuilding, data.m_workBuilding); - } + return; } - return false; + Log.Debug(" ----------- Citizen was AT HOME"); } } } diff --git a/src/RealTime/AI/RealTimeResidentAI.Moving.cs b/src/RealTime/AI/RealTimeResidentAI.Moving.cs index 9f5971d1..f7227886 100644 --- a/src/RealTime/AI/RealTimeResidentAI.Moving.cs +++ b/src/RealTime/AI/RealTimeResidentAI.Moving.cs @@ -6,36 +6,27 @@ namespace RealTime.AI { internal static partial class RealTimeResidentAI { - private static bool ProcessCitizenMoving(Arguments args, uint citizenID, ref Citizen data) + private static void ProcessCitizenMoving(References refs, uint citizenId, ref Citizen citizen, bool mayCancel) { - bool? commonStateResult = ProcessCitizenCommonState(args, citizenID, ref data); - if (commonStateResult.HasValue) - { - return commonStateResult.Value; - } - CitizenInstance.Flags flags = CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour; - if (data.m_vehicle == 0 && data.m_instance == 0) + if (citizen.m_vehicle == 0 && citizen.m_instance == 0) { - if (data.m_visitBuilding != 0) + if (citizen.m_visitBuilding != 0) { - data.SetVisitplace(citizenID, 0, 0u); + citizen.SetVisitplace(citizenId, 0, 0u); } - data.CurrentLocation = Citizen.Location.Home; - data.Arrested = false; + citizen.CurrentLocation = Citizen.Location.Home; + citizen.Arrested = false; } - else if (data.m_instance != 0 && (args.CitizenMgr.m_instances.m_buffer[data.m_instance].m_flags & flags) == flags) + else if (citizen.m_instance != 0 && (refs.CitizenMgr.m_instances.m_buffer[citizen.m_instance].m_flags & flags) == flags) { - int r = args.SimMgr.m_randomizer.Int32(40u); - if (r < 10 && data.m_homeBuilding != 0) + if (refs.SimMgr.m_randomizer.Int32(40u) < 10 && citizen.m_homeBuilding != 0) { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, 0, data.m_homeBuilding); + citizen.m_flags &= ~Citizen.Flags.Evacuating; + refs.ResidentAI.StartMoving(citizenId, ref citizen, 0, citizen.m_homeBuilding); } } - - return false; } } } diff --git a/src/RealTime/AI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/AI/RealTimeResidentAI.SchoolWork.cs new file mode 100644 index 00000000..c6e59398 --- /dev/null +++ b/src/RealTime/AI/RealTimeResidentAI.SchoolWork.cs @@ -0,0 +1,70 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.AI +{ + using RealTime.Tools; + + internal static partial class RealTimeResidentAI + { + private static void ProcessCitizenAtSchoolOrWork(References refs, uint citizenId, ref Citizen citizen) + { + if (citizen.m_workBuilding == 0) + { + Log.Debug($"WARNING: {CitizenInfo(citizenId, ref citizen)} is in corrupt state: at school/work with no work building. Teleporting home."); + citizen.CurrentLocation = Citizen.Location.Home; + return; + } + + if (Logic.ShouldGoToLunch(ref citizen)) + { + ushort lunchPlace = FindLocalCommercialBuilding(refs, citizenId, ref citizen, LocalSearchDistance); + if (lunchPlace != 0) + { + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} is heading out to eat for lunch at {lunchPlace}"); + } + else + { + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} wanted to head out for lunch, but there were no buildings close enough"); + } + + Log.Debug(" ----------- Citizen was AT WORK"); + + return; + } + + if (!Logic.ShouldReturnFromSchoolOrWork(ref citizen)) + { + return; + } + + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} leaves their workplace"); + + if (!CitizenGoesShopping(refs, citizenId, ref citizen) && !CitizenGoesRelaxing(refs, citizenId, ref citizen)) + { + refs.ResidentAI.StartMoving(citizenId, ref citizen, citizen.m_workBuilding, citizen.m_homeBuilding); + } + + Log.Debug(" ----------- Citizen was AT WORK"); + } + + private static bool CitizenGoesWorking(References refs, uint citizenId, ref Citizen citizen) + { + if (!Logic.ShouldGoToSchoolOrWork(ref citizen, refs.BuildingMgr)) + { + return false; + } + + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} is going from {citizen.GetBuildingByLocation()} to school/work {citizen.m_workBuilding}"); + + if (!refs.ResidentAI.StartMoving(citizenId, ref citizen, citizen.m_homeBuilding, citizen.m_workBuilding)) + { + Log.Debug($"{CitizenInfo(citizenId, ref citizen)} has no instance, teleporting to school/work"); + citizen.CurrentLocation = Citizen.Location.Work; + } + + return true; + } + } +} diff --git a/src/RealTime/AI/RealTimeResidentAI.Visit.cs b/src/RealTime/AI/RealTimeResidentAI.Visit.cs index c55f7f4d..366d22b5 100644 --- a/src/RealTime/AI/RealTimeResidentAI.Visit.cs +++ b/src/RealTime/AI/RealTimeResidentAI.Visit.cs @@ -5,92 +5,262 @@ namespace RealTime.AI { using ColossalFramework; + using RealTime.Tools; internal static partial class RealTimeResidentAI { - private static bool ProcessCitizenVisit(Arguments args, uint citizenID, ref Citizen data) + private static void ProcessCitizenVisit(CitizenState citizenState, References refs, uint citizenId, ref Citizen citizen) { - bool? commonStateResult = ProcessCitizenCommonState(args, citizenID, ref data); - if (commonStateResult.HasValue) + if (citizen.m_visitBuilding == 0) { - return commonStateResult.Value; + Log.Debug($"WARNING: {CitizenInfo(citizenId, ref citizen)} is in corrupt state: visiting with no visit building. Teleporting home."); + citizen.CurrentLocation = Citizen.Location.Home; + return; } - ItemClass.Service service = data.m_visitBuilding == 0 - ? ItemClass.Service.None - : args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info.m_class.m_service; - - switch (service) + switch (citizenState) { - case ItemClass.Service.HealthCare: - case ItemClass.Service.PoliceDepartment: - if (data.m_homeBuilding != 0 && data.m_vehicle == 0) + case CitizenState.AtLunch: + if (CitizenReturnsFromLunch(refs, citizenId, ref citizen)) { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); - data.SetVisitplace(citizenID, 0, 0u); + Log.Debug(" ----------- Citizen was AT LUNCH"); } - return false; + return; - case ItemClass.Service.Disaster: - if ((args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].m_flags & Building.Flags.Downgrading) != 0 && data.m_homeBuilding != 0 && data.m_vehicle == 0) + case CitizenState.AtLeisureArea: + case CitizenState.Visiting: + if (CitizenGoesWorking(refs, citizenId, ref citizen) || CitizenReturnsHomeFromVisit(refs, citizenId, ref citizen)) { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); - data.SetVisitplace(citizenID, 0, 0u); + Log.Debug(" ----------- Citizen was AT LEISURE or VISIT"); } - return false; + return; - default: - if (data.m_visitBuilding == 0) + case CitizenState.Shopping: + if (CitizenGoesWorking(refs, citizenId, ref citizen)) { - data.CurrentLocation = Citizen.Location.Home; + Log.Debug(" ----------- Citizen was SHOPPING"); + return; } - else if ((args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].m_flags & Building.Flags.Evacuating) != 0) + + if (refs.SimMgr.m_randomizer.Int32(40) < 10) { - if (data.m_vehicle == 0) - { - FindEvacuationPlace(args.ResidentAI, citizenID, data.m_visitBuilding, GetEvacuationReason(args.ResidentAI, data.m_visitBuilding)); - } + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} returning from shopping back home"); + ReturnFromVisit(refs.ResidentAI, citizenId, ref citizen, citizen.m_homeBuilding); + Log.Debug(" ----------- Citizen was SHOPPING"); + return; } - else if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) + + break; + + default: + return; + } + + ref Building visitBuilding = ref refs.BuildingMgr.m_buildings.m_buffer[citizen.m_visitBuilding]; + if ((citizen.m_flags & Citizen.Flags.NeedGoods) != 0) + { + int goodsDelta = -100; + visitBuilding.Info.m_buildingAI.ModifyMaterialBuffer( + citizen.m_visitBuilding, + ref visitBuilding, + TransferManager.TransferReason.Shopping, + ref goodsDelta); + + citizen.m_flags &= ~Citizen.Flags.NeedGoods; + return; + } + } + + private static bool CitizenReturnsFromLunch(References refs, uint citizenId, ref Citizen citizen) + { + if (Logic.IsLunchHour) + { + return false; + } + + if (citizen.m_workBuilding != 0) + { + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} returning from lunch to {citizen.m_workBuilding}"); + ReturnFromVisit(refs.ResidentAI, citizenId, ref citizen, citizen.m_workBuilding); + } + else + { + Log.Debug($"WARNING: {CitizenInfo(citizenId, ref citizen)} is at lunch but no work building. Teleporting home."); + citizen.CurrentLocation = Citizen.Location.Home; + } + + return true; + } + + private static bool CitizenReturnsHomeFromVisit(References refs, uint citizenId, ref Citizen citizen) + { + ref Building visitBuilding = ref refs.BuildingMgr.m_buildings.m_buffer[citizen.m_visitBuilding]; + switch (visitBuilding.Info.m_class.m_service) + { + case ItemClass.Service.HealthCare: + case ItemClass.Service.PoliceDepartment: + if (refs.SimMgr.m_randomizer.Int32(100) < 50) { - BuildingInfo info = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].Info; - int num7 = -100; - info.m_buildingAI.ModifyMaterialBuffer(data.m_visitBuilding, ref args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding], TransferManager.TransferReason.Shopping, ref num7); + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} returning from visit back home"); + ReturnFromVisit(refs.ResidentAI, citizenId, ref citizen, citizen.m_homeBuilding); + return true; } - else + + break; + + case ItemClass.Service.Disaster: + if ((visitBuilding.m_flags & Building.Flags.Downgrading) != 0) { - ushort eventIndex2 = args.BuildingMgr.m_buildings.m_buffer[data.m_visitBuilding].m_eventIndex; - if (eventIndex2 != 0) - { - if ((Singleton.instance.m_events.m_buffer[eventIndex2].m_flags & (EventData.Flags.Preparing | EventData.Flags.Active | EventData.Flags.Ready)) == EventData.Flags.None && data.m_homeBuilding != 0 && data.m_vehicle == 0) - { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); - data.SetVisitplace(citizenID, 0, 0u); - } - } - else - { - if (data.m_instance == 0 && !DoRandomMove(args.ResidentAI)) - { - return false; - } - - int num8 = args.SimMgr.m_randomizer.Int32(40u); - if (num8 < 10 && data.m_homeBuilding != 0 && data.m_vehicle == 0) - { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, data.m_visitBuilding, data.m_homeBuilding); - data.SetVisitplace(citizenID, 0, 0u); - } - } + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} returning from evacuation place back home"); + ReturnFromVisit(refs.ResidentAI, citizenId, ref citizen, citizen.m_homeBuilding); + return true; } - return false; + break; + } + + ushort eventId = visitBuilding.m_eventIndex; + if (eventId != 0) + { + if ((Singleton.instance.m_events.m_buffer[eventId].m_flags & (EventData.Flags.Preparing | EventData.Flags.Active | EventData.Flags.Ready)) == 0) + { + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} returning from an event back home"); + ReturnFromVisit(refs.ResidentAI, citizenId, ref citizen, citizen.m_homeBuilding); + } + + return true; + } + + return false; + } + + private static void ReturnFromVisit(ResidentAI residentAi, uint citizenId, ref Citizen citizen, ushort targetBuilding) + { + if (targetBuilding != 0 && citizen.m_vehicle == 0) + { + citizen.m_flags &= ~Citizen.Flags.Evacuating; + residentAi.StartMoving(citizenId, ref citizen, citizen.m_visitBuilding, targetBuilding); + citizen.SetVisitplace(citizenId, 0, 0u); + } + } + + private static bool CitizenGoesShopping(References refs, uint citizenId, ref Citizen citizen) + { + if ((citizen.m_flags & Citizen.Flags.NeedGoods) == 0) + { + return false; + } + + int random = refs.SimMgr.m_randomizer.Int32(100); + + if (refs.SimMgr.m_isNightTime) + { + if (random < Logic.GetGoOutAtNightChance(citizen.Age) + && FindLocalCommercialBuilding(refs, citizenId, ref citizen, LocalSearchDistance) > 0) + { + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} wanna go shopping at night, heading to a local shop"); + return true; + } + } + else if (random < 50) + { + ushort localVisitPlace = 0; + + if (Logic.CurrentConfig.AllowLocalBuildingSearch + && refs.SimMgr.m_randomizer.UInt32(100) < Logic.CurrentConfig.LocalBuildingSearchQuota) + { + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} wanna go shopping, tries to find a local shop"); + localVisitPlace = FindLocalCommercialBuilding(refs, citizenId, ref citizen, LocalSearchDistance); + } + + if (localVisitPlace == 0) + { + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} wanna go shopping, heading to a random shop"); + FindVisitPlace(refs.ResidentAI, citizenId, citizen.m_homeBuilding, GetShoppingReason(refs.ResidentAI)); + } + + return true; + } + + return false; + } + + private static bool CitizenGoesRelaxing(References refs, uint citizenId, ref Citizen citizen) + { + // TODO: add events here + if (!Logic.ShouldFindEntertainment(ref citizen)) + { + return false; + } + + ushort buildingId = citizen.GetBuildingByLocation(); + if (buildingId == 0) + { + return false; + } + + if (refs.SimMgr.m_isNightTime) + { + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} wanna relax at night, heading to a leisure area"); + FindLeisure(refs, citizenId, ref citizen, buildingId); + } + else + { + Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} wanna relax, heading to an entertainment place"); + FindVisitPlace(refs.ResidentAI, citizenId, buildingId, GetEntertainmentReason(refs.ResidentAI)); + } + + return true; + } + + private static ushort FindLocalCommercialBuilding(References refs, uint citizenId, ref Citizen citizen, float distance) + { + ushort buildingId = citizen.GetBuildingByLocation(); + if (buildingId == 0) + { + return 0; + } + + ushort foundBuilding = 0; + ref Building currentBuilding = ref refs.BuildingMgr.m_buildings.m_buffer[buildingId]; + + foundBuilding = refs.BuildingMgr.FindBuilding( + currentBuilding.m_position, + distance, + ItemClass.Service.Commercial, + ItemClass.SubService.None, + Building.Flags.Created | Building.Flags.Active, + Building.Flags.Deleted); + + if (foundBuilding != 0) + { + refs.ResidentAI.StartMoving(citizenId, ref citizen, buildingId, foundBuilding); + citizen.SetVisitplace(citizenId, foundBuilding, 0U); + citizen.m_visitBuilding = foundBuilding; + } + + return foundBuilding; + } + + private static void FindLeisure(References refs, uint citizenId, ref Citizen citizen, ushort buildingId) + { + ref Building currentBuilding = ref refs.BuildingMgr.m_buildings.m_buffer[buildingId]; + + ushort leisureBuilding = refs.BuildingMgr.FindBuilding( + currentBuilding.m_position, + float.MaxValue, + ItemClass.Service.Commercial, + ItemClass.SubService.CommercialLeisure, + Building.Flags.Created | Building.Flags.Active, + Building.Flags.Deleted); + + if (leisureBuilding != 0 && refs.SimMgr.m_randomizer.Int32(10) > 2) + { + refs.ResidentAI.StartMoving(citizenId, ref citizen, buildingId, leisureBuilding); + citizen.SetVisitplace(citizenId, leisureBuilding, 0U); + citizen.m_visitBuilding = leisureBuilding; } } } diff --git a/src/RealTime/AI/RealTimeResidentAI.Work.cs b/src/RealTime/AI/RealTimeResidentAI.Work.cs deleted file mode 100644 index 61ac83df..00000000 --- a/src/RealTime/AI/RealTimeResidentAI.Work.cs +++ /dev/null @@ -1,77 +0,0 @@ -// -// Copyright (c) dymanoid. All rights reserved. -// - -namespace RealTime.AI -{ - using ColossalFramework; - using UnityEngine; - - internal static partial class RealTimeResidentAI - { - private static bool ProcessCitizenAtWork(Arguments args, uint citizenID, ref Citizen data) - { - bool? commonStateResult = ProcessCitizenCommonState(args, citizenID, ref data); - if (commonStateResult.HasValue) - { - return commonStateResult.Value; - } - - if (data.m_workBuilding == 0) - { - data.CurrentLocation = Citizen.Location.Home; - } - else - { - ushort eventIndex = args.BuildingMgr.m_buildings.m_buffer[data.m_workBuilding].m_eventIndex; - if ((args.BuildingMgr.m_buildings.m_buffer[data.m_workBuilding].m_flags & Building.Flags.Evacuating) != 0) - { - if (data.m_vehicle == 0) - { - FindEvacuationPlace(args.ResidentAI, citizenID, data.m_workBuilding, GetEvacuationReason(args.ResidentAI, data.m_workBuilding)); - } - } - else if (eventIndex == 0 || (Singleton.instance.m_events.m_buffer[eventIndex].m_flags & (EventData.Flags.Preparing | EventData.Flags.Active | EventData.Flags.Ready)) == EventData.Flags.None) - { - if ((data.m_flags & Citizen.Flags.NeedGoods) != 0) - { - if (data.m_vehicle == 0) - { - FindVisitPlace(args.ResidentAI, citizenID, data.m_workBuilding, GetShoppingReason(args.ResidentAI)); - } - } - else - { - if (data.m_instance == 0 && !DoRandomMove(args.ResidentAI)) - { - return false; - } - - int dayTimeFrame = (int)args.SimMgr.m_dayTimeFrame; - int dAYTIME_FRAMES = (int)SimulationManager.DAYTIME_FRAMES; - int num2 = dAYTIME_FRAMES / 40; - int num3 = (int)(SimulationManager.DAYTIME_FRAMES * 16) / 24; - int num4 = dayTimeFrame - num3 & dAYTIME_FRAMES - 1; - int num5 = Mathf.Abs(num4 - (dAYTIME_FRAMES >> 1)); - num5 = num5 * num5 / (dAYTIME_FRAMES >> 1); - int num6 = args.SimMgr.m_randomizer.Int32((uint)dAYTIME_FRAMES); - if (num6 < num2) - { - if (data.m_vehicle == 0) - { - FindVisitPlace(args.ResidentAI, citizenID, data.m_workBuilding, GetEntertainmentReason(args.ResidentAI)); - } - } - else if (num6 < num2 + num5 && data.m_homeBuilding != 0 && data.m_vehicle == 0) - { - data.m_flags &= ~Citizen.Flags.Evacuating; - args.ResidentAI.StartMoving(citizenID, ref data, data.m_workBuilding, data.m_homeBuilding); - } - } - } - } - - return false; - } - } -} diff --git a/src/RealTime/AI/RealTimeResidentAI.cs b/src/RealTime/AI/RealTimeResidentAI.cs index e72fc4d0..78007faf 100644 --- a/src/RealTime/AI/RealTimeResidentAI.cs +++ b/src/RealTime/AI/RealTimeResidentAI.cs @@ -7,62 +7,86 @@ namespace RealTime.AI using System; using System.Runtime.CompilerServices; using ColossalFramework; + using RealTime.Tools; using Redirection; internal static partial class RealTimeResidentAI { private const string RedirectNeededMessage = "This method must be redirected to the original implementation"; + private static References references; + + internal static ILogic Logic { get; set; } [RedirectFrom(typeof(ResidentAI))] - private static void UpdateLocation(ResidentAI instance, uint citizenID, ref Citizen data) + private static void UpdateLocation(ResidentAI instance, uint citizenId, ref Citizen citizen) { - CitizenManager citizenMgr = Singleton.instance; - BuildingManager buildingMgr = Singleton.instance; - SimulationManager simMgr = Singleton.instance; - var args = new Arguments(instance, citizenMgr, buildingMgr, simMgr); + if (references == null) + { + CitizenManager citizenMgr = Singleton.instance; + BuildingManager buildingMgr = Singleton.instance; + SimulationManager simMgr = Singleton.instance; + references = new References(instance, citizenMgr, buildingMgr, simMgr); + } + + if (citizen.m_homeBuilding == 0 && citizen.m_workBuilding == 0 && citizen.m_visitBuilding == 0 + && citizen.m_instance == 0 && citizen.m_vehicle == 0) + { + references.CitizenMgr.ReleaseCitizen(citizenId); + return; + } - if (data.m_homeBuilding == 0 && data.m_workBuilding == 0 && data.m_visitBuilding == 0 && data.m_instance == 0 && data.m_vehicle == 0) + if (citizen.Collapsed) { - citizenMgr.ReleaseCitizen(citizenID); return; } - switch (data.CurrentLocation) + if (citizen.Dead) { - case Citizen.Location.Home: - if (ProcessCitizenAtHome(args, citizenID, ref data)) - { - return; - } + ProcessCitizenDead(references, citizenId, ref citizen); + return; + } + + if ((citizen.Sick && ProcessCitizenSick(references, citizenId, ref citizen)) + || (citizen.Arrested && ProcessCitizenArrested(references, ref citizen))) + { + return; + } + + CitizenState citizenState = GetCitizenState(references, ref citizen); + switch (citizenState) + { + case CitizenState.LeftCity: + references.CitizenMgr.ReleaseCitizen(citizenId); break; - case Citizen.Location.Work: - if (ProcessCitizenAtWork(args, citizenID, ref data)) - { - return; - } + case CitizenState.MovingHome: + ProcessCitizenMoving(references, citizenId, ref citizen, false); + break; + case CitizenState.AtHome: + ProcessCitizenAtHome(references, citizenId, ref citizen); break; - case Citizen.Location.Visit: - if (ProcessCitizenVisit(args, citizenID, ref data)) - { - return; - } + case CitizenState.MovingToTarget: + ProcessCitizenMoving(references, citizenId, ref citizen, true); + break; + case CitizenState.AtSchoolOrWork: + ProcessCitizenAtSchoolOrWork(references, citizenId, ref citizen); break; - case Citizen.Location.Moving: - if (ProcessCitizenMoving(args, citizenID, ref data)) - { - return; - } + case CitizenState.AtLunch: + case CitizenState.Shopping: + case CitizenState.AtLeisureArea: + case CitizenState.Visiting: + ProcessCitizenVisit(citizenState, references, citizenId, ref citizen); + break; + case CitizenState.Evacuating: + ProcessCitizenEvacuation(instance, citizenId, ref citizen); break; } - - data.m_flags &= ~Citizen.Flags.NeedGoods; } [RedirectTo(typeof(ResidentAI))] @@ -113,24 +137,5 @@ private static TransferManager.TransferReason GetEntertainmentReason(ResidentAI { throw new InvalidOperationException(RedirectNeededMessage); } - - private sealed class Arguments - { - public Arguments(ResidentAI residentAI, CitizenManager citizenMgr, BuildingManager buildingMgr, SimulationManager simMgr) - { - ResidentAI = residentAI; - CitizenMgr = citizenMgr; - BuildingMgr = buildingMgr; - SimMgr = simMgr; - } - - public ResidentAI ResidentAI { get; } - - public CitizenManager CitizenMgr { get; } - - public BuildingManager BuildingMgr { get; } - - public SimulationManager SimMgr { get; } - } } } diff --git a/src/RealTime/Config/Configuration.cs b/src/RealTime/Config/Configuration.cs index 2ab7e4b6..de55bb44 100644 --- a/src/RealTime/Config/Configuration.cs +++ b/src/RealTime/Config/Configuration.cs @@ -15,19 +15,20 @@ internal sealed class Configuration public static Configuration Current { get; set; } = new Configuration(); /// - /// Gets or sets a value indicating whether the Weekends are enabled. Cims don't go to work on Weekends. + /// Gets or sets a value indicating whether the weekends are enabled. Cims don't go to work on weekends. /// - public bool EnableWeekends { get; set; } = true; + public bool IsWeekendEnabled { get; set; } = true; /// /// Gets or sets a value indicating whether Cims should go out at lunch for food. /// - public bool SimulateLunchTime { get; set; } = true; + public bool IsLunchTimeEnabled { get; set; } = true; /// /// Gets or sets the percentage of the Cims that will go out for lunch. + /// Valid values are 0..100. /// - public float LunchQuota { get; set; } = 0.6f; + public int LunchQuota { get; set; } = 80; /// /// Gets or sets a value indicating whether Cims will search locally for buildings to visit, @@ -37,61 +38,43 @@ internal sealed class Configuration /// /// Gets or sets the percentage of the population that will search locally for buildings. - /// Valid values are 0.0 to 1.0. + /// Valid values are 0..100. /// - public float LocalBuildingSearchQuota { get; set; } = 0.5f; + public int LocalBuildingSearchQuota { get; set; } = 60; /// - /// Gets or sets the percentage of the Cims that will attend their way to work or to school - /// as soon as possible. - /// - public float EarlyStartQuota { get; set; } = 0.4f; - - /// - /// Gets or sets the percentage of the Cims that will leave their work or school + /// Gets or sets the percentage of the Cims that will go to and leave their work or school /// on time (no overtime!). + /// Valid values are 0..100. /// - public float LeaveOnTimeQuota { get; set; } = 0.8f; - - /// - /// Gets or sets the earliest daytime hour when the young Cims head to school or university. - /// - public float MinSchoolHour { get; set; } = 7.5f; + public int OnTimeQuota { get; set; } = 80; /// - /// Gets or sets the latest daytime hour when the young Cims head to school or university. + /// Gets or sets the maximum overtime for the Cims. They come to work earlier or stay at work longer for at most this + /// amout of hours. This applies only for those Cims that are not on time, see . + /// The young Cims (school and university) don't do overtime. /// - public float StartSchoolHour { get; set; } = 8f; + public float MaxOvertime { get; set; } = 2f; /// - /// Gets or sets the earliest daytime hour when the young Cims return from school or university. + /// Gets or sets the the school start daytime hour. The young Cims must be at school or university. /// - public float EndSchoolHour { get; set; } = 13.5f; + public float SchoolBegin { get; set; } = 8f; /// - /// Gets or sets the latest daytime hour when the young Cims return from school or university. + /// Gets or sets the daytime hour when the young Cims return from school or university. /// - public float MaxSchoolHour { get; set; } = 14f; + public float SchoolEnd { get; set; } = 14f; /// - /// Gets or sets the earliest daytime hour when the adult Cims head to work. + /// Gets or sets the work start daytime hour. The adult Cims must be at work. /// - public float MinWorkHour { get; set; } = 6f; + public float WorkBegin { get; set; } = 9f; /// - /// Gets or sets the latest daytime hour when the adult Cims head to work. + /// Gets or sets the daytime hour when the adult Cims return from work. /// - public float StartWorkHour { get; set; } = 9f; - - /// - /// Gets or sets the earliest daytime hour when the adult Cims return from work. - /// - public float EndWorkHour { get; set; } = 15f; - - /// - /// Gets or sets the latest daytime hour when the adult Cims return from work. - /// - public float MaxWorkHour { get; set; } = 18f; + public float WorkEnd { get; set; } = 18f; /// /// Gets or sets the daytime hour when the Cims go out for lunch. @@ -99,25 +82,8 @@ internal sealed class Configuration public float LunchBegin { get; set; } = 12f; /// - /// Gets or sets the daytime hour when the Cims return for lunch. - /// - public float LunchEnd { get; set; } = 12.75f; - - /// - /// Gets or sets the minimum school duration (in hours). - /// Cims will not travel to school or university if the time spent there will not reach at least this value. - /// - public float MinSchoolDuration { get; set; } = 1f; - - /// - /// Gets or sets the minimum work duration (in hours). - /// Cims will not travel to work if the time spent there will not reach at least this value. - /// - public float MinWorkDuration { get; set; } = 1f; - - /// - /// Gets or sets the assumed maximum travel time (in hours) to work or to school or to university. + /// Gets or sets the lunch time duration. /// - public float WorkOrSchoolTravelTime { get; set; } = 2f; + public float LunchEnd { get; set; } = 13f; } } diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs index 29384619..2b378cfb 100644 --- a/src/RealTime/Core/RealTimeCore.cs +++ b/src/RealTime/Core/RealTimeCore.cs @@ -6,6 +6,8 @@ namespace RealTime.Core { using System; using System.Security.Permissions; + using RealTime.AI; + using RealTime.Config; using RealTime.Simulation; using RealTime.Tools; using RealTime.UI; @@ -36,12 +38,17 @@ private RealTimeCore(TimeAdjustment timeAdjustment, CustomTimeBar timeBar) [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public static RealTimeCore Run() { + SimulationManager simMgr = SimulationManager.instance; + var timeAdjustment = new TimeAdjustment(); DateTime gameDate = timeAdjustment.Enable(); var customTimeBar = new CustomTimeBar(); customTimeBar.Enable(gameDate); + ILogic logic = new Logic(Configuration.Current, new TimeInfo(), ref simMgr.m_randomizer); + LogicService.ProvideLogic(logic); + try { int redirectedCount = Redirector.PerformRedirections(); @@ -73,6 +80,7 @@ public void Stop() timeAdjustment.Disable(); timeBar.Disable(); + LogicService.RevokeLogic(); try { diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj index f51c6fdc..56c2c32e 100644 --- a/src/RealTime/RealTime.csproj +++ b/src/RealTime/RealTime.csproj @@ -62,22 +62,26 @@ + + - - + + + + diff --git a/src/RealTime/Simulation/ITimeInfo.cs b/src/RealTime/Simulation/ITimeInfo.cs new file mode 100644 index 00000000..1198c991 --- /dev/null +++ b/src/RealTime/Simulation/ITimeInfo.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Simulation +{ + using System; + + internal interface ITimeInfo + { + DateTime Now { get; } + + float CurrentHour { get; } + + float SunriseHour { get; } + + float SunsetHour { get; } + } +} diff --git a/src/RealTime/Simulation/TimeAdjustment.cs b/src/RealTime/Simulation/TimeAdjustment.cs index 064b3ad7..8e1e09a9 100644 --- a/src/RealTime/Simulation/TimeAdjustment.cs +++ b/src/RealTime/Simulation/TimeAdjustment.cs @@ -12,7 +12,7 @@ namespace RealTime.Simulation /// internal sealed class TimeAdjustment { - private const int CustomFramesPerDay = 1 << 16; + private const int CustomFramesPerDay = 1 << 17; private static readonly TimeSpan CustomTimePerFrame = new TimeSpan(24L * 3600L * 10_000_000L / CustomFramesPerDay); private readonly uint vanillaFramesPerDay; diff --git a/src/RealTime/Simulation/TimeInfo.cs b/src/RealTime/Simulation/TimeInfo.cs new file mode 100644 index 00000000..9adf2d37 --- /dev/null +++ b/src/RealTime/Simulation/TimeInfo.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Simulation +{ + using System; + + internal sealed class TimeInfo : ITimeInfo + { + public DateTime Now => SimulationManager.instance.m_currentGameTime; + + public float CurrentHour => SimulationManager.instance.m_currentDayTimeHour; + + public float SunriseHour => SimulationManager.SUNRISE_HOUR; + + public float SunsetHour => SimulationManager.SUNSET_HOUR; + } +} diff --git a/src/RealTime/Tools/DateTimeExtensions.cs b/src/RealTime/Tools/DateTimeExtensions.cs index e63556c6..7eeb0942 100644 --- a/src/RealTime/Tools/DateTimeExtensions.cs +++ b/src/RealTime/Tools/DateTimeExtensions.cs @@ -39,17 +39,5 @@ public static bool IsWeekendAfter(this DateTime dateTime, TimeSpan interval) { return (dateTime + interval).IsWeekend(); } - - /// - /// Gets a value representing the current day's hour. - /// - /// - /// The to act on. - /// - /// A value that represents the cuurent day's hour. - public static float HourOfDay(this DateTime dateTime) - { - return (float)dateTime.TimeOfDay.TotalHours; - } } } From a352cff8363d672fc74578cfd3ff164e8cc9490e Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Thu, 14 Jun 2018 20:25:10 +0200 Subject: [PATCH 024/102] Fix issue causing wrong AI used for tourists, remove unneeded logging --- src/RealTime/AI/RealTimeResidentAI.Common.cs | 13 ++--- src/RealTime/AI/RealTimeResidentAI.Home.cs | 13 ++--- src/RealTime/AI/RealTimeResidentAI.Moving.cs | 4 +- .../AI/RealTimeResidentAI.SchoolWork.cs | 16 +++--- src/RealTime/AI/RealTimeResidentAI.Visit.cs | 51 +++++++++---------- src/RealTime/AI/RealTimeResidentAI.cs | 16 +++--- 6 files changed, 49 insertions(+), 64 deletions(-) diff --git a/src/RealTime/AI/RealTimeResidentAI.Common.cs b/src/RealTime/AI/RealTimeResidentAI.Common.cs index 017dc1e8..c12cb037 100644 --- a/src/RealTime/AI/RealTimeResidentAI.Common.cs +++ b/src/RealTime/AI/RealTimeResidentAI.Common.cs @@ -8,7 +8,7 @@ namespace RealTime.AI internal static partial class RealTimeResidentAI { - private static void ProcessCitizenDead(References refs, uint citizenId, ref Citizen citizen) + private static void ProcessCitizenDead(ResidentAI instance, References refs, uint citizenId, ref Citizen citizen) { ushort currentBuilding = citizen.GetBuildingByLocation(); @@ -46,7 +46,7 @@ private static void ProcessCitizenDead(References refs, uint citizenId, ref Citi return; } - FindHospital(refs.ResidentAI, citizenId, currentBuilding, TransferManager.TransferReason.Dead); + FindHospital(instance, citizenId, currentBuilding, TransferManager.TransferReason.Dead); Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} is dead, body should go to a cemetery"); } @@ -70,7 +70,7 @@ private static bool ProcessCitizenArrested(References refs, ref Citizen citizen) return false; } - private static bool ProcessCitizenSick(References refs, uint citizenId, ref Citizen citizen) + private static bool ProcessCitizenSick(ResidentAI instance, References refs, uint citizenId, ref Citizen citizen) { if (citizen.CurrentLocation == Citizen.Location.Moving) { @@ -101,7 +101,7 @@ private static bool ProcessCitizenSick(References refs, uint citizenId, ref Citi } Log.Debug($"{CitizenInfo(citizenId, ref citizen)} is sick, trying to get to a hospital"); - FindHospital(refs.ResidentAI, citizenId, currentBuilding, TransferManager.TransferReason.Sick); + FindHospital(instance, citizenId, currentBuilding, TransferManager.TransferReason.Sick); return true; } @@ -198,16 +198,13 @@ private static string CitizenInfo(uint id, ref Citizen citizen) private sealed class References { - public References(ResidentAI residentAI, CitizenManager citizenMgr, BuildingManager buildingMgr, SimulationManager simMgr) + public References(CitizenManager citizenMgr, BuildingManager buildingMgr, SimulationManager simMgr) { - ResidentAI = residentAI; CitizenMgr = citizenMgr; BuildingMgr = buildingMgr; SimMgr = simMgr; } - public ResidentAI ResidentAI { get; } - public CitizenManager CitizenMgr { get; } public BuildingManager BuildingMgr { get; } diff --git a/src/RealTime/AI/RealTimeResidentAI.Home.cs b/src/RealTime/AI/RealTimeResidentAI.Home.cs index 03c0ed49..e6436aa7 100644 --- a/src/RealTime/AI/RealTimeResidentAI.Home.cs +++ b/src/RealTime/AI/RealTimeResidentAI.Home.cs @@ -10,7 +10,7 @@ internal static partial class RealTimeResidentAI { private const float LocalSearchDistance = 1000f; - private static void ProcessCitizenAtHome(References refs, uint citizenId, ref Citizen citizen) + private static void ProcessCitizenAtHome(ResidentAI instance, References refs, uint citizenId, ref Citizen citizen) { if (citizen.m_homeBuilding == 0) { @@ -27,23 +27,20 @@ private static void ProcessCitizenAtHome(References refs, uint citizenId, ref Ci return; } - if (CitizenGoesWorking(refs, citizenId, ref citizen)) + if (CitizenGoesWorking(instance, refs, citizenId, ref citizen)) { - Log.Debug(" ----------- Citizen was AT HOME"); return; } - if (!DoRandomMove(refs.ResidentAI)) + if (!DoRandomMove(instance)) { return; } - if (!CitizenGoesShopping(refs, citizenId, ref citizen) && !CitizenGoesRelaxing(refs, citizenId, ref citizen)) + if (!CitizenGoesShopping(instance, refs, citizenId, ref citizen)) { - return; + CitizenGoesRelaxing(instance, refs, citizenId, ref citizen); } - - Log.Debug(" ----------- Citizen was AT HOME"); } } } diff --git a/src/RealTime/AI/RealTimeResidentAI.Moving.cs b/src/RealTime/AI/RealTimeResidentAI.Moving.cs index f7227886..eb944927 100644 --- a/src/RealTime/AI/RealTimeResidentAI.Moving.cs +++ b/src/RealTime/AI/RealTimeResidentAI.Moving.cs @@ -6,7 +6,7 @@ namespace RealTime.AI { internal static partial class RealTimeResidentAI { - private static void ProcessCitizenMoving(References refs, uint citizenId, ref Citizen citizen, bool mayCancel) + private static void ProcessCitizenMoving(ResidentAI instance, References refs, uint citizenId, ref Citizen citizen, bool mayCancel) { CitizenInstance.Flags flags = CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour; if (citizen.m_vehicle == 0 && citizen.m_instance == 0) @@ -24,7 +24,7 @@ private static void ProcessCitizenMoving(References refs, uint citizenId, ref Ci if (refs.SimMgr.m_randomizer.Int32(40u) < 10 && citizen.m_homeBuilding != 0) { citizen.m_flags &= ~Citizen.Flags.Evacuating; - refs.ResidentAI.StartMoving(citizenId, ref citizen, 0, citizen.m_homeBuilding); + instance.StartMoving(citizenId, ref citizen, 0, citizen.m_homeBuilding); } } } diff --git a/src/RealTime/AI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/AI/RealTimeResidentAI.SchoolWork.cs index c6e59398..de7f70d9 100644 --- a/src/RealTime/AI/RealTimeResidentAI.SchoolWork.cs +++ b/src/RealTime/AI/RealTimeResidentAI.SchoolWork.cs @@ -8,7 +8,7 @@ namespace RealTime.AI internal static partial class RealTimeResidentAI { - private static void ProcessCitizenAtSchoolOrWork(References refs, uint citizenId, ref Citizen citizen) + private static void ProcessCitizenAtSchoolOrWork(ResidentAI instance, References refs, uint citizenId, ref Citizen citizen) { if (citizen.m_workBuilding == 0) { @@ -19,7 +19,7 @@ private static void ProcessCitizenAtSchoolOrWork(References refs, uint citizenId if (Logic.ShouldGoToLunch(ref citizen)) { - ushort lunchPlace = FindLocalCommercialBuilding(refs, citizenId, ref citizen, LocalSearchDistance); + ushort lunchPlace = FindLocalCommercialBuilding(instance, refs, citizenId, ref citizen, LocalSearchDistance); if (lunchPlace != 0) { Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} is heading out to eat for lunch at {lunchPlace}"); @@ -29,8 +29,6 @@ private static void ProcessCitizenAtSchoolOrWork(References refs, uint citizenId Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} wanted to head out for lunch, but there were no buildings close enough"); } - Log.Debug(" ----------- Citizen was AT WORK"); - return; } @@ -41,15 +39,13 @@ private static void ProcessCitizenAtSchoolOrWork(References refs, uint citizenId Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} leaves their workplace"); - if (!CitizenGoesShopping(refs, citizenId, ref citizen) && !CitizenGoesRelaxing(refs, citizenId, ref citizen)) + if (!CitizenGoesShopping(instance, refs, citizenId, ref citizen) && !CitizenGoesRelaxing(instance, refs, citizenId, ref citizen)) { - refs.ResidentAI.StartMoving(citizenId, ref citizen, citizen.m_workBuilding, citizen.m_homeBuilding); + instance.StartMoving(citizenId, ref citizen, citizen.m_workBuilding, citizen.m_homeBuilding); } - - Log.Debug(" ----------- Citizen was AT WORK"); } - private static bool CitizenGoesWorking(References refs, uint citizenId, ref Citizen citizen) + private static bool CitizenGoesWorking(ResidentAI instance, References refs, uint citizenId, ref Citizen citizen) { if (!Logic.ShouldGoToSchoolOrWork(ref citizen, refs.BuildingMgr)) { @@ -58,7 +54,7 @@ private static bool CitizenGoesWorking(References refs, uint citizenId, ref Citi Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} is going from {citizen.GetBuildingByLocation()} to school/work {citizen.m_workBuilding}"); - if (!refs.ResidentAI.StartMoving(citizenId, ref citizen, citizen.m_homeBuilding, citizen.m_workBuilding)) + if (!instance.StartMoving(citizenId, ref citizen, citizen.m_homeBuilding, citizen.m_workBuilding)) { Log.Debug($"{CitizenInfo(citizenId, ref citizen)} has no instance, teleporting to school/work"); citizen.CurrentLocation = Citizen.Location.Work; diff --git a/src/RealTime/AI/RealTimeResidentAI.Visit.cs b/src/RealTime/AI/RealTimeResidentAI.Visit.cs index 366d22b5..efdaebb3 100644 --- a/src/RealTime/AI/RealTimeResidentAI.Visit.cs +++ b/src/RealTime/AI/RealTimeResidentAI.Visit.cs @@ -9,7 +9,7 @@ namespace RealTime.AI internal static partial class RealTimeResidentAI { - private static void ProcessCitizenVisit(CitizenState citizenState, References refs, uint citizenId, ref Citizen citizen) + private static void ProcessCitizenVisit(CitizenState citizenState, ResidentAI instance, References refs, uint citizenId, ref Citizen citizen) { if (citizen.m_visitBuilding == 0) { @@ -21,34 +21,29 @@ private static void ProcessCitizenVisit(CitizenState citizenState, References re switch (citizenState) { case CitizenState.AtLunch: - if (CitizenReturnsFromLunch(refs, citizenId, ref citizen)) - { - Log.Debug(" ----------- Citizen was AT LUNCH"); - } + CitizenReturnsFromLunch(instance, refs, citizenId, ref citizen); return; case CitizenState.AtLeisureArea: case CitizenState.Visiting: - if (CitizenGoesWorking(refs, citizenId, ref citizen) || CitizenReturnsHomeFromVisit(refs, citizenId, ref citizen)) + if (!CitizenGoesWorking(instance, refs, citizenId, ref citizen)) { - Log.Debug(" ----------- Citizen was AT LEISURE or VISIT"); + CitizenReturnsHomeFromVisit(instance, refs, citizenId, ref citizen); } return; case CitizenState.Shopping: - if (CitizenGoesWorking(refs, citizenId, ref citizen)) + if (CitizenGoesWorking(instance, refs, citizenId, ref citizen)) { - Log.Debug(" ----------- Citizen was SHOPPING"); return; } if (refs.SimMgr.m_randomizer.Int32(40) < 10) { Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} returning from shopping back home"); - ReturnFromVisit(refs.ResidentAI, citizenId, ref citizen, citizen.m_homeBuilding); - Log.Debug(" ----------- Citizen was SHOPPING"); + ReturnFromVisit(instance, citizenId, ref citizen, citizen.m_homeBuilding); return; } @@ -73,7 +68,7 @@ private static void ProcessCitizenVisit(CitizenState citizenState, References re } } - private static bool CitizenReturnsFromLunch(References refs, uint citizenId, ref Citizen citizen) + private static bool CitizenReturnsFromLunch(ResidentAI instance, References refs, uint citizenId, ref Citizen citizen) { if (Logic.IsLunchHour) { @@ -83,7 +78,7 @@ private static bool CitizenReturnsFromLunch(References refs, uint citizenId, ref if (citizen.m_workBuilding != 0) { Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} returning from lunch to {citizen.m_workBuilding}"); - ReturnFromVisit(refs.ResidentAI, citizenId, ref citizen, citizen.m_workBuilding); + ReturnFromVisit(instance, citizenId, ref citizen, citizen.m_workBuilding); } else { @@ -94,7 +89,7 @@ private static bool CitizenReturnsFromLunch(References refs, uint citizenId, ref return true; } - private static bool CitizenReturnsHomeFromVisit(References refs, uint citizenId, ref Citizen citizen) + private static bool CitizenReturnsHomeFromVisit(ResidentAI instance, References refs, uint citizenId, ref Citizen citizen) { ref Building visitBuilding = ref refs.BuildingMgr.m_buildings.m_buffer[citizen.m_visitBuilding]; switch (visitBuilding.Info.m_class.m_service) @@ -104,7 +99,7 @@ private static bool CitizenReturnsHomeFromVisit(References refs, uint citizenId, if (refs.SimMgr.m_randomizer.Int32(100) < 50) { Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} returning from visit back home"); - ReturnFromVisit(refs.ResidentAI, citizenId, ref citizen, citizen.m_homeBuilding); + ReturnFromVisit(instance, citizenId, ref citizen, citizen.m_homeBuilding); return true; } @@ -114,7 +109,7 @@ private static bool CitizenReturnsHomeFromVisit(References refs, uint citizenId, if ((visitBuilding.m_flags & Building.Flags.Downgrading) != 0) { Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} returning from evacuation place back home"); - ReturnFromVisit(refs.ResidentAI, citizenId, ref citizen, citizen.m_homeBuilding); + ReturnFromVisit(instance, citizenId, ref citizen, citizen.m_homeBuilding); return true; } @@ -127,7 +122,7 @@ private static bool CitizenReturnsHomeFromVisit(References refs, uint citizenId, if ((Singleton.instance.m_events.m_buffer[eventId].m_flags & (EventData.Flags.Preparing | EventData.Flags.Active | EventData.Flags.Ready)) == 0) { Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} returning from an event back home"); - ReturnFromVisit(refs.ResidentAI, citizenId, ref citizen, citizen.m_homeBuilding); + ReturnFromVisit(instance, citizenId, ref citizen, citizen.m_homeBuilding); } return true; @@ -146,7 +141,7 @@ private static void ReturnFromVisit(ResidentAI residentAi, uint citizenId, ref C } } - private static bool CitizenGoesShopping(References refs, uint citizenId, ref Citizen citizen) + private static bool CitizenGoesShopping(ResidentAI instance, References refs, uint citizenId, ref Citizen citizen) { if ((citizen.m_flags & Citizen.Flags.NeedGoods) == 0) { @@ -158,7 +153,7 @@ private static bool CitizenGoesShopping(References refs, uint citizenId, ref Cit if (refs.SimMgr.m_isNightTime) { if (random < Logic.GetGoOutAtNightChance(citizen.Age) - && FindLocalCommercialBuilding(refs, citizenId, ref citizen, LocalSearchDistance) > 0) + && FindLocalCommercialBuilding(instance, refs, citizenId, ref citizen, LocalSearchDistance) > 0) { Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} wanna go shopping at night, heading to a local shop"); return true; @@ -172,13 +167,13 @@ private static bool CitizenGoesShopping(References refs, uint citizenId, ref Cit && refs.SimMgr.m_randomizer.UInt32(100) < Logic.CurrentConfig.LocalBuildingSearchQuota) { Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} wanna go shopping, tries to find a local shop"); - localVisitPlace = FindLocalCommercialBuilding(refs, citizenId, ref citizen, LocalSearchDistance); + localVisitPlace = FindLocalCommercialBuilding(instance, refs, citizenId, ref citizen, LocalSearchDistance); } if (localVisitPlace == 0) { Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} wanna go shopping, heading to a random shop"); - FindVisitPlace(refs.ResidentAI, citizenId, citizen.m_homeBuilding, GetShoppingReason(refs.ResidentAI)); + FindVisitPlace(instance, citizenId, citizen.m_homeBuilding, GetShoppingReason(instance)); } return true; @@ -187,7 +182,7 @@ private static bool CitizenGoesShopping(References refs, uint citizenId, ref Cit return false; } - private static bool CitizenGoesRelaxing(References refs, uint citizenId, ref Citizen citizen) + private static bool CitizenGoesRelaxing(ResidentAI instance, References refs, uint citizenId, ref Citizen citizen) { // TODO: add events here if (!Logic.ShouldFindEntertainment(ref citizen)) @@ -204,18 +199,18 @@ private static bool CitizenGoesRelaxing(References refs, uint citizenId, ref Cit if (refs.SimMgr.m_isNightTime) { Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} wanna relax at night, heading to a leisure area"); - FindLeisure(refs, citizenId, ref citizen, buildingId); + FindLeisure(instance, refs, citizenId, ref citizen, buildingId); } else { Log.Debug(refs.SimMgr.m_currentGameTime, $"{CitizenInfo(citizenId, ref citizen)} wanna relax, heading to an entertainment place"); - FindVisitPlace(refs.ResidentAI, citizenId, buildingId, GetEntertainmentReason(refs.ResidentAI)); + FindVisitPlace(instance, citizenId, buildingId, GetEntertainmentReason(instance)); } return true; } - private static ushort FindLocalCommercialBuilding(References refs, uint citizenId, ref Citizen citizen, float distance) + private static ushort FindLocalCommercialBuilding(ResidentAI instance, References refs, uint citizenId, ref Citizen citizen, float distance) { ushort buildingId = citizen.GetBuildingByLocation(); if (buildingId == 0) @@ -236,7 +231,7 @@ private static ushort FindLocalCommercialBuilding(References refs, uint citizenI if (foundBuilding != 0) { - refs.ResidentAI.StartMoving(citizenId, ref citizen, buildingId, foundBuilding); + instance.StartMoving(citizenId, ref citizen, buildingId, foundBuilding); citizen.SetVisitplace(citizenId, foundBuilding, 0U); citizen.m_visitBuilding = foundBuilding; } @@ -244,7 +239,7 @@ private static ushort FindLocalCommercialBuilding(References refs, uint citizenI return foundBuilding; } - private static void FindLeisure(References refs, uint citizenId, ref Citizen citizen, ushort buildingId) + private static void FindLeisure(ResidentAI instance, References refs, uint citizenId, ref Citizen citizen, ushort buildingId) { ref Building currentBuilding = ref refs.BuildingMgr.m_buildings.m_buffer[buildingId]; @@ -258,7 +253,7 @@ private static void FindLeisure(References refs, uint citizenId, ref Citizen cit if (leisureBuilding != 0 && refs.SimMgr.m_randomizer.Int32(10) > 2) { - refs.ResidentAI.StartMoving(citizenId, ref citizen, buildingId, leisureBuilding); + instance.StartMoving(citizenId, ref citizen, buildingId, leisureBuilding); citizen.SetVisitplace(citizenId, leisureBuilding, 0U); citizen.m_visitBuilding = leisureBuilding; } diff --git a/src/RealTime/AI/RealTimeResidentAI.cs b/src/RealTime/AI/RealTimeResidentAI.cs index 78007faf..98e09b75 100644 --- a/src/RealTime/AI/RealTimeResidentAI.cs +++ b/src/RealTime/AI/RealTimeResidentAI.cs @@ -25,7 +25,7 @@ private static void UpdateLocation(ResidentAI instance, uint citizenId, ref Citi CitizenManager citizenMgr = Singleton.instance; BuildingManager buildingMgr = Singleton.instance; SimulationManager simMgr = Singleton.instance; - references = new References(instance, citizenMgr, buildingMgr, simMgr); + references = new References(citizenMgr, buildingMgr, simMgr); } if (citizen.m_homeBuilding == 0 && citizen.m_workBuilding == 0 && citizen.m_visitBuilding == 0 @@ -42,11 +42,11 @@ private static void UpdateLocation(ResidentAI instance, uint citizenId, ref Citi if (citizen.Dead) { - ProcessCitizenDead(references, citizenId, ref citizen); + ProcessCitizenDead(instance, references, citizenId, ref citizen); return; } - if ((citizen.Sick && ProcessCitizenSick(references, citizenId, ref citizen)) + if ((citizen.Sick && ProcessCitizenSick(instance, references, citizenId, ref citizen)) || (citizen.Arrested && ProcessCitizenArrested(references, ref citizen))) { return; @@ -61,26 +61,26 @@ private static void UpdateLocation(ResidentAI instance, uint citizenId, ref Citi break; case CitizenState.MovingHome: - ProcessCitizenMoving(references, citizenId, ref citizen, false); + ProcessCitizenMoving(instance, references, citizenId, ref citizen, false); break; case CitizenState.AtHome: - ProcessCitizenAtHome(references, citizenId, ref citizen); + ProcessCitizenAtHome(instance, references, citizenId, ref citizen); break; case CitizenState.MovingToTarget: - ProcessCitizenMoving(references, citizenId, ref citizen, true); + ProcessCitizenMoving(instance, references, citizenId, ref citizen, true); break; case CitizenState.AtSchoolOrWork: - ProcessCitizenAtSchoolOrWork(references, citizenId, ref citizen); + ProcessCitizenAtSchoolOrWork(instance, references, citizenId, ref citizen); break; case CitizenState.AtLunch: case CitizenState.Shopping: case CitizenState.AtLeisureArea: case CitizenState.Visiting: - ProcessCitizenVisit(citizenState, references, citizenId, ref citizen); + ProcessCitizenVisit(citizenState, instance, references, citizenId, ref citizen); break; case CitizenState.Evacuating: From 0244193a9451ea074b33ddf0e0091f551638d8de Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Sun, 17 Jun 2018 21:24:41 +0200 Subject: [PATCH 025/102] Enable more flexible method redirection, remove BitSetRequiredOption --- src/Redirection/MethodInfoExtensions.cs | 47 +++++++++++++----- ...tAttribute.cs => RedirectAttributeBase.cs} | 26 +++++----- src/Redirection/RedirectFromAttribute.cs | 49 ++++++++++--------- src/Redirection/RedirectToAttribute.cs | 47 ++++++++++-------- src/Redirection/Redirection.csproj | 6 +-- src/Redirection/Redirector.cs | 47 ++++++++++-------- 6 files changed, 128 insertions(+), 94 deletions(-) rename src/Redirection/{RedirectAttribute.cs => RedirectAttributeBase.cs} (71%) diff --git a/src/Redirection/MethodInfoExtensions.cs b/src/Redirection/MethodInfoExtensions.cs index a97117ab..a0bbafa0 100644 --- a/src/Redirection/MethodInfoExtensions.cs +++ b/src/Redirection/MethodInfoExtensions.cs @@ -70,17 +70,20 @@ internal static MethodRedirection CreateRedirectionTo(this MethodInfo method, Me } /// - /// Compares the provided methods to determine whether a redirection from this - /// to this is possible. + /// Compares the provided methods to determine whether a redirection from + /// to this method is possible. /// /// /// Thrown when any argument is null. /// /// The method to compare. /// The method to compare with. + /// if true, a static method with first argument that is + /// compatible with the type of the will be considered + /// compatible. /// /// True if the methods are compatible; otherwise, false. - internal static bool IsCompatibleWith(this MethodInfo method, MethodInfo otherMethod) + internal static bool IsCompatibleWith(this MethodInfo method, MethodInfo otherMethod, bool asInstanceMethod) { if (method == null) { @@ -97,19 +100,15 @@ internal static bool IsCompatibleWith(this MethodInfo method, MethodInfo otherMe return false; } - Type targetType = otherMethod.ReflectedType; var thisParameters = method.GetParameters().ToList(); - ParameterInfo firstParameter = thisParameters.FirstOrDefault(); - if (firstParameter != null && - ((!targetType.IsValueType && firstParameter.ParameterType == targetType) || - (targetType.IsValueType && firstParameter.ParameterType == targetType.MakeByRefType()))) + var otherParameters = otherMethod.GetParameters().ToList(); + if (asInstanceMethod) { - thisParameters.RemoveAt(0); + RemoveInstanceParameter(thisParameters, otherMethod.ReflectedType); + RemoveInstanceParameter(otherParameters, method.ReflectedType); } - ParameterInfo[] otherParameters = otherMethod.GetParameters(); - - if (thisParameters.Count != otherParameters.Length) + if (thisParameters.Count != otherParameters.Count) { return false; } @@ -124,5 +123,29 @@ internal static bool IsCompatibleWith(this MethodInfo method, MethodInfo otherMe return true; } + + private static void RemoveInstanceParameter(List parameters, Type methodClass) + { + ParameterInfo firstParameter = parameters.FirstOrDefault(); + + if (firstParameter == null) + { + return; + } + + if (methodClass.IsValueType) + { + if (firstParameter.ParameterType != methodClass.MakeByRefType()) + { + return; + } + } + else if (!methodClass.IsAssignableFrom(firstParameter.ParameterType)) + { + return; + } + + parameters.RemoveAt(0); + } } } \ No newline at end of file diff --git a/src/Redirection/RedirectAttribute.cs b/src/Redirection/RedirectAttributeBase.cs similarity index 71% rename from src/Redirection/RedirectAttribute.cs rename to src/Redirection/RedirectAttributeBase.cs index 29a3e509..fbad4573 100644 --- a/src/Redirection/RedirectAttribute.cs +++ b/src/Redirection/RedirectAttributeBase.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) dymanoid. All rights reserved. // @@ -29,27 +29,27 @@ namespace Redirection /// /// A base class for the special redirection attributes that can be applied to the methods. /// - public abstract class RedirectAttribute : Attribute + public abstract class RedirectAttributeBase : Attribute { - protected RedirectAttribute(Type methodType, string methodName, ulong bitSetRequiredOption) + protected RedirectAttributeBase(Type methodType, string methodName, bool isInstanceMethod) { MethodType = methodType ?? throw new ArgumentNullException(nameof(methodType)); MethodName = methodName; - BitSetRequiredOption = bitSetRequiredOption; + IsInstanceMethod = isInstanceMethod; } - protected RedirectAttribute(Type methodType, string methodName) - : this(methodType, methodName, 0) + protected RedirectAttributeBase(Type methodType, string methodName) + : this(methodType, methodName, true) { } - protected RedirectAttribute(Type methodType, ulong bitSetOption) - : this(methodType, null, bitSetOption) + protected RedirectAttributeBase(Type methodType, bool isInstanceMethod) + : this(methodType, null, isInstanceMethod) { } - protected RedirectAttribute(Type methodType) - : this(methodType, null, 0) + protected RedirectAttributeBase(Type methodType) + : this(methodType, null, true) { } @@ -65,9 +65,9 @@ protected RedirectAttribute(Type methodType) public string MethodName { get; set; } /// - /// Gets or sets the required bit set option for the method redirection. - /// 0 means not specified. + /// Gets or sets a value indicating whether the method of the foreign class is an instance + /// method. /// - public ulong BitSetRequiredOption { get; set; } + public bool IsInstanceMethod { get; set; } } } diff --git a/src/Redirection/RedirectFromAttribute.cs b/src/Redirection/RedirectFromAttribute.cs index a6425f3a..0ac78056 100644 --- a/src/Redirection/RedirectFromAttribute.cs +++ b/src/Redirection/RedirectFromAttribute.cs @@ -35,59 +35,64 @@ namespace Redirection /// NOTE: only the methods belonging to the same assembly that calls Perform/RevertRedirections are redirected. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class RedirectFromAttribute : RedirectAttribute + public sealed class RedirectFromAttribute : RedirectAttributeBase { /// /// Initializes a new instance of the class. /// /// Thrown when is null. /// - /// The type where the method that will be redirected is defined. - /// The name of the method that will be redirected. If null, + /// The type where the source method is defined. + /// The name of the source method that will be redirected. If null, /// the name of the attribute's target method will be used. - /// The required bit set option. - public RedirectFromAttribute(Type methodType, string methodName, ulong bitSetRequiredOption) - : base(methodType, methodName, bitSetRequiredOption) + /// true if the source method is an instance method; + /// otherwise, false. + public RedirectFromAttribute(Type methodType, string methodName, bool isInstanceMethod) + : base(methodType, methodName, isInstanceMethod) { } /// /// Initializes a new instance of the class with - /// default . + /// set to true. /// /// Thrown when is null. + /// Thrown when is null or an empty string. /// - /// The type where the method that will be redirected is defined. - /// The name of the method that will be redirected. If null, - /// the name of the attribute's target method will be used. + /// The type where the source method is defined. + /// The name of the source method that will be redirected. public RedirectFromAttribute(Type methodType, string methodName) - : base(methodType, methodName) + : base(methodType) { + if (string.IsNullOrEmpty(methodName)) + { + throw new ArgumentException($"The {nameof(methodName)} cannot be null or an empty string"); + } } /// - /// Initializes a new instance of the class with empty - /// . The name of the method this attribute is - /// attached to will be used. + /// Initializes a new instance of the class with + /// empty . + /// The name of the method this attribute is attached to will be used. /// /// Thrown when is null. /// - /// The type where the method that will be redirected is defined. - /// The required bit set option. - public RedirectFromAttribute(Type methodType, ulong bitSetRequiredOption) - : base(methodType, bitSetRequiredOption) + /// The type where the source method is defined. + /// true if the source method is an instance method; + /// otherwise, false. + public RedirectFromAttribute(Type methodType, bool isInstanceMethod) + : base(methodType, isInstanceMethod) { } /// /// Initializes a new instance of the class with - /// default and empty - /// . The name of the method this attribute is - /// attached to will be used. + /// empty and + /// set to true. The name of the method this attribute is attached to will be used. /// /// Thrown when is null. /// - /// The type where the method that will be redirected is defined. + /// The type where the source method is defined. public RedirectFromAttribute(Type methodType) : base(methodType) { diff --git a/src/Redirection/RedirectToAttribute.cs b/src/Redirection/RedirectToAttribute.cs index 27e54c27..b82a2d63 100644 --- a/src/Redirection/RedirectToAttribute.cs +++ b/src/Redirection/RedirectToAttribute.cs @@ -33,59 +33,64 @@ namespace Redirection /// /// NOTE: only the methods belonging to the same assembly that calls Perform/RevertRedirections are redirected. [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class RedirectToAttribute : RedirectAttribute + public sealed class RedirectToAttribute : RedirectAttributeBase { /// /// Initializes a new instance of the class. /// /// Thrown when is null. /// - /// The type where the method that will be redirected is defined. + /// The type where the target method is defined. /// The name of the target method. If null, /// the name of the attribute's target method will be used. - /// The required bit set option. - public RedirectToAttribute(Type methodType, string methodName, ulong bitSetRequiredOption) - : base(methodType, methodName, bitSetRequiredOption) + /// true if the target method is an instance method; + /// otherwise, false. + public RedirectToAttribute(Type methodType, string methodName, bool isInstanceMethod) + : base(methodType, methodName) { } /// /// Initializes a new instance of the class with - /// default . + /// set to true. /// /// Thrown when is null. + /// Thrown when is null or an empty string. /// - /// The type where the method that will be redirected is defined. - /// The name of the target method. If null, - /// the name of the attribute's target method will be used. + /// The type where the target method is defined. + /// The name of the target method. public RedirectToAttribute(Type methodType, string methodName) - : base(methodType, methodName) + : base(methodType) { + if (string.IsNullOrEmpty(methodName)) + { + throw new ArgumentException($"The {nameof(methodName)} cannot be null or empty string"); + } } /// - /// Initializes a new instance of the class with empty - /// . The name of the method this attribute is - /// attached to will be used. + /// Initializes a new instance of the class with + /// empty . + /// The name of the method this attribute is attached to will be used. /// /// Thrown when is null. /// - /// The type where the method that will be redirected is defined. - /// The required bit set option. - public RedirectToAttribute(Type methodType, ulong bitSetRequiredOption) - : base(methodType, bitSetRequiredOption) + /// The type where the target method is defined. + /// true if the target method is an instance method; + /// otherwise, false. + public RedirectToAttribute(Type methodType, bool isInstanceMethod) + : base(methodType, isInstanceMethod) { } /// /// Initializes a new instance of the class with - /// default and empty - /// . The name of the method this attribute is - /// attached to will be used. + /// empty and + /// set to true. The name of the method this attribute is attached to will be used. /// /// Thrown when is null. /// - /// The type where the method that will be redirected is defined. + /// The type where the target method is defined. public RedirectToAttribute(Type methodType) : base(methodType) { diff --git a/src/Redirection/Redirection.csproj b/src/Redirection/Redirection.csproj index 1fdb4a1c..4e18b4a8 100644 --- a/src/Redirection/Redirection.csproj +++ b/src/Redirection/Redirection.csproj @@ -42,15 +42,11 @@ - - - - - + diff --git a/src/Redirection/Redirector.cs b/src/Redirection/Redirector.cs index d07cbd3a..24381489 100644 --- a/src/Redirection/Redirector.cs +++ b/src/Redirection/Redirector.cs @@ -89,7 +89,7 @@ private static int PerformRedirections(ulong bitmask, Assembly callingAssembly) { foreach (MethodInfo method in allMethods) { - foreach (RedirectAttribute attribute in method.GetCustomAttributes(typeof(RedirectAttribute), false)) + foreach (RedirectAttributeBase attribute in method.GetCustomAttributes(typeof(RedirectAttributeBase), false)) { ProcessMethod(method, callingAssembly, attribute, bitmask); ++result; @@ -117,41 +117,46 @@ private static void RevertRedirections(Assembly callingAssembly) } [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] - private static void ProcessMethod(MethodInfo method, Assembly callingAssembly, RedirectAttribute attribute, ulong bitmask) + private static void ProcessMethod(MethodInfo method, Assembly callingAssembly, RedirectAttributeBase attribute, ulong bitmask) { - if (attribute.BitSetRequiredOption != 0 && (bitmask & attribute.BitSetRequiredOption) == 0) - { - return; - } + string methodName = string.IsNullOrEmpty(attribute.MethodName) ? method.Name : attribute.MethodName; - string originalName = string.IsNullOrEmpty(attribute.MethodName) ? method.Name : attribute.MethodName; - - MethodInfo originalMethod = + IEnumerable externalMethods = attribute.MethodType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static) - .FirstOrDefault(m => m.Name == originalName && method.IsCompatibleWith(m)); - - if (originalMethod == null) - { - throw new InvalidOperationException($"Redirector: Original method {originalName} has not been found for redirection"); - } + .Where(m => m.Name == methodName); if (attribute is RedirectFromAttribute) { - if (!redirections.ContainsKey(originalMethod)) + MethodInfo sourceMethod = externalMethods.FirstOrDefault(m => method.IsCompatibleWith(m, attribute.IsInstanceMethod)); + + if (sourceMethod == null) { - redirections.Add(originalMethod, originalMethod.CreateRedirectionTo(method, callingAssembly)); + throw new InvalidOperationException($"No compatible source method '{methodName}' found in '{attribute.MethodType.FullName}'"); } - return; + if (!redirections.ContainsKey(sourceMethod)) + { + redirections.Add(sourceMethod, sourceMethod.CreateRedirectionTo(method, callingAssembly)); + } } - - if (attribute is RedirectToAttribute) + else if (attribute is RedirectToAttribute) { + MethodInfo targetMethod = externalMethods.FirstOrDefault(m => m.IsCompatibleWith(method, attribute.IsInstanceMethod)); + + if (targetMethod == null) + { + throw new InvalidOperationException($"No compatible target method '{methodName}' found in '{attribute.MethodType.FullName}'"); + } + if (!redirections.ContainsKey(method)) { - redirections.Add(method, method.CreateRedirectionTo(originalMethod, callingAssembly)); + redirections.Add(method, method.CreateRedirectionTo(targetMethod, callingAssembly)); } } + else + { + throw new NotSupportedException($"The attribute '{attribute.GetType().Name}' is currently not supported"); + } } } } From 6c8f5ee13b9a00458a70ec260abfa7b44fda2f09 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Sun, 17 Jun 2018 21:28:14 +0200 Subject: [PATCH 026/102] Change target platform to x64, improve post-build events --- src/RealTime.sln | 20 +++++++++--------- src/RealTime/RealTime.csproj | 33 ++++++++++-------------------- src/Redirection/Redirection.csproj | 27 +++++++++++++----------- 3 files changed, 36 insertions(+), 44 deletions(-) diff --git a/src/RealTime.sln b/src/RealTime.sln index 8a91c32f..5576b1a9 100644 --- a/src/RealTime.sln +++ b/src/RealTime.sln @@ -9,18 +9,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redirection", "Redirection\ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}.Release|Any CPU.Build.0 = Release|Any CPU - {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}.Release|Any CPU.Build.0 = Release|Any CPU + {7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}.Debug|x64.ActiveCfg = Debug|x64 + {7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}.Debug|x64.Build.0 = Debug|x64 + {7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}.Release|x64.ActiveCfg = Release|x64 + {7CD7702C-E7D3-4E61-BF3A-B10F7950DE52}.Release|x64.Build.0 = Release|x64 + {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}.Debug|x64.ActiveCfg = Debug|x64 + {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}.Debug|x64.Build.0 = Debug|x64 + {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}.Release|x64.ActiveCfg = Release|x64 + {7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj index 56c2c32e..dac4c980 100644 --- a/src/RealTime/RealTime.csproj +++ b/src/RealTime/RealTime.csproj @@ -15,32 +15,25 @@ - + true - full - false ..\bin\Debug\ DEBUG;TRACE + full + x64 + 7.3 prompt - 4 - - - false ..\BuildEnvironment\RealTime.ruleset - 7.3 - - pdbonly - true + ..\bin\Release\ TRACE + true + pdbonly + x64 + 7.3 prompt - 4 - - - false ..\BuildEnvironment\RealTime.ruleset - 7.3 @@ -54,9 +47,6 @@ - - - False @@ -108,9 +98,8 @@ - mkdir "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)" -del "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)\$(TargetFileName)" -xcopy /y "$(TargetDir)*.dll" "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)" + if not exist "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)" mkdir "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)" +xcopy /y /q "$(TargetPath)" "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)" diff --git a/src/Redirection/Redirection.csproj b/src/Redirection/Redirection.csproj index 4e18b4a8..0d2101ac 100644 --- a/src/Redirection/Redirection.csproj +++ b/src/Redirection/Redirection.csproj @@ -16,28 +16,27 @@ - + true - full - false ..\bin\Debug\ DEBUG;TRACE - prompt - 4 true - ..\BuildEnvironment\RealTime.ruleset + full + x64 7.3 + prompt + ..\BuildEnvironment\RealTime.ruleset - - pdbonly - true + ..\bin\Release\ TRACE - prompt - 4 true - ..\BuildEnvironment\RealTime.ruleset + true + pdbonly + x64 7.3 + prompt + ..\BuildEnvironment\RealTime.ruleset @@ -72,6 +71,10 @@ + + if not exist "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)" mkdir "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)" +xcopy /y /q "$(TargetPath)" "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)" + + + + + + + + + 100 + 40 + + 0 + 0 + 0 + 20 + 100 + + 0 + 20 + 100 + + 0 + 20 + 60 + 100 + + 0 + 30 + 80 + 100 + 100 + + 0 + 10 + 50 + 100 + 100 + + + + + + YES! FINALLY! An actually exciting #event going on at the Expo Center. #comicbooks + + + + The doors have opened! #comicbooks #event + + + + Shame it closes so early. I still had tons of places to visit #comicbooks #event + + + + 80 + 20 + + 5 + 100 + 100 + 20 + 0 + + 0 + 80 + 100 + + 0 + 20 + 100 + 100 + + 0 + 30 + 80 + 100 + 100 + + 0 + 10 + 50 + 100 + 100 + + + + 160000 + 20 + 5000 + 5000 + 100 + + + + + Allows citizens to buy food at the event. + 0 + 25 + + + Allows citizens to buy drinks at the event. + 0 + 50 + + + Gives citizens free food at the event. Costs more to purchase, and there's no return on your investment. + 30 + 0 + + + Gives citizens free drinks at the event. Costs more to purchase, and there's no return on your investment. + 30 + 0 + + + Allows attendees to use WiFi in the event for a small cost. + 10 + 10 + + + Gives citizens free WiFi at the event. Costs more to purchase, and there's no return on your investment. + 30 + 0 + + + Allows citizens to get their comic books signed by the authors. + 30 + 0 + + + + + + + An electronic expo?! In {0}?! Tickets please! #event + + + + I wonder how much all these #electronics cost... #event + + + + Awwww, the event's over already? I was enjoying myself :( #event + Got myself some awesome stuff! A new #chirper phone? Yes please! #event + + + + 80 + 60 + + 0 + 100 + 100 + 100 + 40 + + 0 + 80 + 100 + + 0 + 20 + 60 + 100 + + 0 + 30 + 80 + 100 + 100 + + 0 + 10 + 50 + 100 + 100 + + + + 200000 + 10 + 5000 + 5000 + 120 + + + + + Allows citizens to buy food at the event. + 0 + 25 + + + Allows citizens to buy drinks at the event. + 0 + 50 + + + Gives citizens free food at the event. Costs more to purchase, and there's no return on your investment. + 30 + 0 + + + Gives citizens free drinks at the event. Costs more to purchase, and there's no return on your investment. + 30 + 0 + + + Allows attendees to use WiFi in the event for a small cost. + 10 + 10 + + + Gives citizens free WiFi at the event. Costs more to purchase, and there's no return on your investment. + 30 + 0 + + + Allows citizens to have the chance to win some electronics! + 50 + 0 + + + + + + + A #games expo? In my city? In {0}? Whaaaat? #event + Can't wait to see what companies are attending the #games expo in {0}. I wonder if the city building game will be there... #event + Got my tickets for the #game expo in {0}! So excited! #event + + + + So many #games! I'm so excited! #event + Can't wait to get my hands on some of the #VR equipment! #event + The games are so realistic this year! #event + + + + Over already? I can't believe it. I hope there's another soon. #event + + + + 80 + 30 + + 5 + 100 + 100 + 20 + 0 + + 0 + 80 + 100 + + 0 + 20 + 60 + 100 + + 0 + 30 + 80 + 100 + 100 + + 0 + 10 + 50 + 100 + 100 + + + + 120000 + 30 + 5000 + 5000 + 110 + + + + + Allows citizens to buy food at the event. + 0 + 25 + + + Allows citizens to buy drinks at the event. + 0 + 50 + + + Gives citizens free food at the event. Costs more to purchase, and there's no return on your investment. + 30 + 0 + + + Gives citizens free drinks at the event. Costs more to purchase, and there's no return on your investment. + 30 + 0 + + + Allows attendees to use WiFi in the event for a small cost. + 10 + 10 + + + Gives citizens free WiFi at the event. Costs more to purchase, and there's no return on your investment. + 30 + 0 + + + Allows citizens to buy games! + 7 + 5 + + + Allows citizens to have the chance to win some games! + 50 + 0 + + + + + \ No newline at end of file diff --git a/src/RealTime/RealTimeEvents/Library.xml b/src/RealTime/RealTimeEvents/Library.xml new file mode 100644 index 00000000..5a3504be --- /dev/null +++ b/src/RealTime/RealTimeEvents/Library.xml @@ -0,0 +1,54 @@ + + + + + + Yes! My favourite author is in town doing book signings in {0}. Count me there. #event + Ooo, a book signing #event? Shame I've never heard of the author. It's on in {0} if anyone's interested. + If anyone wants to get their #book signed, the author's in town in {0} for a few hours. #event + + + + Next in queue to get my book signed! Can't wait. #event + Just got my book signed! How do I sell things online again? #event + + + + Well, that was fun while it lasted. #event + Didn't even manage to get my #book signed. Too many people :( #event + + + + 100 + 100 + + 0 + 80 + 100 + 60 + 1 + + 50 + 100 + 100 + + 100 + 100 + 100 + 100 + + 30 + 30 + 80 + 100 + 100 + + 0 + 10 + 50 + 100 + 100 + + + + \ No newline at end of file diff --git a/src/RealTime/RealTimeEvents/Opera.xml b/src/RealTime/RealTimeEvents/Opera.xml new file mode 100644 index 00000000..cc273ab6 --- /dev/null +++ b/src/RealTime/RealTimeEvents/Opera.xml @@ -0,0 +1,50 @@ + + + + + + Got my tickets to the #Opera in {0}. #event + + + + Phone's off, so I won't be replying any time soon. #opera #event + + + + That Opera was fantastic. I'd watch that again! #event + + + + 100 + 100 + + 0 + 10 + 50 + 100 + 100 + + 0 + 20 + 100 + + 0 + 20 + 60 + 100 + + 0 + 30 + 80 + 100 + 100 + + 0 + 10 + 50 + 100 + 100 + + + + \ No newline at end of file diff --git a/src/RealTime/RealTimeEvents/PoshMall.xml b/src/RealTime/RealTimeEvents/PoshMall.xml new file mode 100644 index 00000000..275895d7 --- /dev/null +++ b/src/RealTime/RealTimeEvents/PoshMall.xml @@ -0,0 +1,55 @@ + + + + + + Yet another shop opening in {0}? How many more can they fit in there? #event + Finally! They're opening another shop! #event + + + + This #shop's pretty huge! It's going to take years to get around here. #event + Wow, and I thought I'd seen everything. #event + So... Expensive... #event + + + + Got sooo much stuff... #event + Ok... How am I going to get all this stuff home? Could someone pick me up? #shops #event + Well, that was #underwhelming... #event + + + + 60 + 100 + + 10 + 50 + 100 + 60 + 5 + + 0 + 10 + 100 + + 0 + 10 + 80 + 100 + + 0 + 30 + 80 + 100 + 100 + + 0 + 10 + 50 + 100 + 100 + + + + \ No newline at end of file diff --git a/src/RealTime/RealTimeEvents/Stadium.xml b/src/RealTime/RealTimeEvents/Stadium.xml new file mode 100644 index 00000000..b9671626 --- /dev/null +++ b/src/RealTime/RealTimeEvents/Stadium.xml @@ -0,0 +1,89 @@ + + + + + + Yeahhh. My favourite team is playing at the #stadium in {0}. Hope I can get some tickets. #event + "Ahhh! Not another game at the #stadium. At least I know when to head out #shopping. {0}... #event + "Why is it never the team I want playing at the #stadium? Oh well, maybe next time. #event + + + + Kickoff! See you in 90 minutes. #stadium #event + + + + What a match! Can't wait until the next one. #stadium #event + See that ludicrous display? #walkitin #event + + + + 80 + 30 + + 30 + 90 + 100 + 100 + 60 + + 100 + 90 + 10 + + 100 + 100 + 50 + 10 + + 0 + 30 + 80 + 100 + 100 + + 0 + 10 + 50 + 100 + 100 + + + + 85000 + 15 + 5000 + 5000 + 60 + + + + + Allows citizens to buy beer at the event. + 0 + 16 + + + Allows citizens to buy food at the event. + 0 + 25 + + + Gives citizens free beer at the event. Costs more to purchase, and there's no return on your investment. + 30 + 0 + + + Gives citizens free food at the event. Costs more to purchase, and there's no return on your investment. + 30 + 0 + + + Allows citizens to meet with some of the players after the match. + 10 + 0 + + + + + \ No newline at end of file diff --git a/src/RealTime/RealTimeEvents/Theater.xml b/src/RealTime/RealTimeEvents/Theater.xml new file mode 100644 index 00000000..987b2979 --- /dev/null +++ b/src/RealTime/RealTimeEvents/Theater.xml @@ -0,0 +1,50 @@ + + + + + + Anyone want to go to the #theater with me in {0}? I've got some free time. #event + + + + Wow, this really is a theater of wonders! #event + + + + That was awesome! #event + + + + 100 + 100 + + 20 + 40 + 100 + 100 + 60 + + 0 + 60 + 100 + + 0 + 20 + 50 + 100 + + 0 + 30 + 80 + 100 + 100 + + 0 + 10 + 50 + 100 + 100 + + + + \ No newline at end of file From 19b1961ff625fb5b38808ca81e1cfeeb1bcb3f77 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Sat, 23 Jun 2018 00:45:41 +0200 Subject: [PATCH 069/102] Rename the constant values --- src/RealTime/CustomAI/Constants.cs | 4 ++-- .../CustomResidentAI/RealTimeResidentAI.SchoolWork.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/RealTime/CustomAI/Constants.cs b/src/RealTime/CustomAI/Constants.cs index 14cc561e..face9514 100644 --- a/src/RealTime/CustomAI/Constants.cs +++ b/src/RealTime/CustomAI/Constants.cs @@ -21,8 +21,8 @@ internal static class Constants public const int TouristDoNothingProbability = 5000; - public const float MaxHoursOnTheWayToWork = 2.5f; - public const float MinHoursOnTheWayToWork = 0.5f; + public const float MaxHoursOnTheWay = 2.5f; + public const float MinHoursOnTheWay = 0.5f; public const float OnTheWayDistancePerHour = 500f; } } diff --git a/src/RealTime/CustomResidentAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomResidentAI/RealTimeResidentAI.SchoolWork.cs index a80b70c1..d539e3fc 100644 --- a/src/RealTime/CustomResidentAI/RealTimeResidentAI.SchoolWork.cs +++ b/src/RealTime/CustomResidentAI/RealTimeResidentAI.SchoolWork.cs @@ -105,11 +105,11 @@ private bool ShouldMoveToSchoolOrWork(ushort workBuilding, ushort currentBuildin // Performance optimization: // If the current hour is far away from the working hours, don't even calculate the overtime and on the way time if (overtime - && (currentHour < gotoWorkHour - MaxHoursOnTheWayToWork - Config.MaxOvertime || currentHour > leaveWorkHour + Config.MaxOvertime)) + && (currentHour < gotoWorkHour - MaxHoursOnTheWay - Config.MaxOvertime || currentHour > leaveWorkHour + Config.MaxOvertime)) { return false; } - else if (currentHour < gotoWorkHour - MaxHoursOnTheWayToWork || currentHour > leaveWorkHour) + else if (currentHour < gotoWorkHour - MaxHoursOnTheWay || currentHour > leaveWorkHour) { return false; } @@ -121,7 +121,7 @@ private bool ShouldMoveToSchoolOrWork(ushort workBuilding, ushort currentBuildin } float distance = BuildingManager.GetDistanceBetweenBuildings(currentBuilding, workBuilding); - float onTheWay = Mathf.Clamp(distance / OnTheWayDistancePerHour, MinHoursOnTheWayToWork, MaxHoursOnTheWayToWork); + float onTheWay = Mathf.Clamp(distance / OnTheWayDistancePerHour, MinHoursOnTheWay, MaxHoursOnTheWay); gotoWorkHour -= onTheWay; @@ -141,11 +141,11 @@ private bool ShouldReturnFromSchoolOrWork(Citizen.AgeGroup citizenAge) { case Citizen.AgeGroup.Child: case Citizen.AgeGroup.Teen: - return currentHour >= Config.SchoolEnd || currentHour < Config.SchoolBegin - MaxHoursOnTheWayToWork; + return currentHour >= Config.SchoolEnd || currentHour < Config.SchoolBegin - MaxHoursOnTheWay; case Citizen.AgeGroup.Young: case Citizen.AgeGroup.Adult: - if (currentHour >= (Config.WorkEnd + Config.MaxOvertime) || currentHour < Config.WorkBegin - MaxHoursOnTheWayToWork) + if (currentHour >= (Config.WorkEnd + Config.MaxOvertime) || currentHour < Config.WorkBegin - MaxHoursOnTheWay) { return true; } From 0ae2ea4c8eece8f551de41b53510a56bb842effb Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Sat, 23 Jun 2018 00:46:15 +0200 Subject: [PATCH 070/102] Improve the post-build xcopy event (only copy when necessary) --- src/Redirection/Redirection.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Redirection/Redirection.csproj b/src/Redirection/Redirection.csproj index 77fa4e69..34cbaf3f 100644 --- a/src/Redirection/Redirection.csproj +++ b/src/Redirection/Redirection.csproj @@ -73,7 +73,7 @@ if not exist "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)" mkdir "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)" -xcopy /y /q "$(TargetPath)" "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)" +xcopy /y /q /d "$(TargetPath)" "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)"