Skip to content

Commit

Permalink
Merge branch 'feature-1.12'
Browse files Browse the repository at this point in the history
  • Loading branch information
dymanoid committed Aug 10, 2018
2 parents 092bb5b + 89113c8 commit d66fd21
Show file tree
Hide file tree
Showing 27 changed files with 413 additions and 56 deletions.
1 change: 0 additions & 1 deletion src/BuildEnvironment/RealTime.ruleset
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@
<Rule Id="CA2119" Action="Warning" />
<Rule Id="CA2120" Action="Warning" />
<Rule Id="CA2121" Action="Warning" />
<Rule Id="CA2122" Action="Warning" />
<Rule Id="CA2123" Action="Warning" />
<Rule Id="CA2124" Action="Warning" />
<Rule Id="CA2126" Action="Warning" />
Expand Down
15 changes: 11 additions & 4 deletions src/RealTime/Config/RealTimeConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ public RealTimeConfig(bool latestVersion)
[ConfigItemComboBox]
public VirtualCitizensLevel VirtualCitizens { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the citizens aging and birth rates must be slowed down.
/// </summary>
[ConfigItem("1General", "1Other", 1)]
[ConfigItemCheckBox]
public bool UseSlowAging { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the weekends are enabled. Cims don't go to work on weekends.
/// </summary>
Expand All @@ -111,21 +118,21 @@ public RealTimeConfig(bool latestVersion)
/// <summary>
/// Gets or sets a value indicating whether the construction sites should pause at night time.
/// </summary>
[ConfigItem("1General", "1Other", 1)]
[ConfigItem("1General", "1Other", 2)]
[ConfigItemCheckBox]
public bool StopConstructionAtNight { get; set; }

/// <summary>
/// Gets or sets the percentage value of the building construction speed. Valid values are 1..100.
/// </summary>
[ConfigItem("1General", "1Other", 2)]
[ConfigItem("1General", "1Other", 3)]
[ConfigItemSlider(1, 100)]
public uint ConstructionSpeed { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the inactive buildings should switch off the lights at night time.
/// </summary>
[ConfigItem("1General", "1Other", 3)]
[ConfigItem("1General", "1Other", 4)]
[ConfigItemCheckBox]
public bool SwitchOffLightsAtNight { get; set; }

Expand Down Expand Up @@ -358,7 +365,7 @@ public void ResetToDefaults()
NightTimeSpeed = 5;

VirtualCitizens = VirtualCitizensLevel.Vanilla;

UseSlowAging = true;
IsWeekendEnabled = true;
IsLunchtimeEnabled = true;

Expand Down
4 changes: 2 additions & 2 deletions src/RealTime/Core/Compatibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal static class Compatibility

private static readonly HashSet<ulong> IncompatibleModIds = new HashSet<ulong>
{
605590542, 672248733, 814698320, 629713122, 702070768, 649522495, 1181352643
605590542, 629713122, 702070768, 649522495, 1181352643
};

/// <summary>Checks for any enabled incompatible mods and notifies the player when any found.</summary>
Expand All @@ -38,7 +38,7 @@ internal static class Compatibility
/// <returns><c>true</c> if the check was successful and no messages were shown; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="modName"/> is null or an empty string.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="localizationProvider"/> is null.</exception>
public static bool CheckAndNotify(string modName, ILocalizationProvider localizationProvider, string additionalInfo = null)
public static bool Check(string modName, ILocalizationProvider localizationProvider, string additionalInfo = null)
{
if (string.IsNullOrEmpty(modName))
{
Expand Down
16 changes: 13 additions & 3 deletions src/RealTime/Core/RealTimeCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,16 @@ private RealTimeCore(
/// <param name="configProvider">The configuration provider that provides the mod's configuration.</param>
/// <param name="rootPath">The path to the mod's assembly. Additional files are stored here too.</param>
/// <param name="localizationProvider">The <see cref="ILocalizationProvider"/> to use for text translation.</param>
/// <param name="setDefaultTime"><c>true</c> to initialize the game time to a default value (real world date and city wake up hour);
/// <c>false</c> to leave the game time unchanged.</param>
///
/// <returns>A <see cref="RealTimeCore"/> instance that can be used to stop the mod.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This is the entry point and needs to instantiate all parts")]
public static RealTimeCore Run(ConfigurationProvider<RealTimeConfig> configProvider, string rootPath, ILocalizationProvider localizationProvider)
public static RealTimeCore Run(
ConfigurationProvider<RealTimeConfig> configProvider,
string rootPath,
ILocalizationProvider localizationProvider,
bool setDefaultTime)
{
if (configProvider == null)
{
Expand Down Expand Up @@ -134,7 +140,7 @@ public static RealTimeCore Run(ConfigurationProvider<RealTimeConfig> configProvi
}

var timeAdjustment = new TimeAdjustment(configProvider.Configuration);
DateTime gameDate = timeAdjustment.Enable();
DateTime gameDate = timeAdjustment.Enable(setDefaultTime);
SimulationHandler.CitizenProcessor.UpdateFrameDuration();

CityEventsLoader.Instance.ReloadEvents(rootPath);
Expand Down Expand Up @@ -292,8 +298,11 @@ private static List<IPatch> GetMethodPatches()
else
{
patches.Add(ResidentAIPatch.UpdateAge);
patches.Add(ResidentAIPatch.CanMakeBabies);
}

patches.AddRange(TimeControlCompatibility.GetCompatibilityPatches());

return patches;
}

Expand Down Expand Up @@ -350,7 +359,8 @@ private static bool SetupCustomAI(
travelBehavior);

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

TouristAIConnection<TouristAI, Citizen> touristAIConnection = TouristAIPatch.GetTouristAIConnection();
if (touristAIConnection == null)
Expand Down
41 changes: 26 additions & 15 deletions src/RealTime/Core/RealTimeMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ public override void OnLevelLoaded(LoadMode mode)
core.Stop();
}

core = RealTimeCore.Run(configProvider, modPath, localizationProvider);
bool isNewGame = mode == LoadMode.NewGame || mode == LoadMode.NewGameFromScenario;
core = RealTimeCore.Run(configProvider, modPath, localizationProvider, isNewGame);
if (core == null)
{
Log.Warning("Showing a warning message to user because the mod isn't working");
Expand All @@ -152,20 +153,7 @@ public override void OnLevelLoaded(LoadMode mode)
}
else
{
string restricted = core.IsRestrictedMode
? localizationProvider.Translate(TranslationKeys.RestrictedMode)
: null;

bool showMessage = core.IsRestrictedMode;
if (configProvider.Configuration.ShowIncompatibilityNotifications)
{
showMessage = Compatibility.CheckAndNotify(Name, localizationProvider, restricted) && core.IsRestrictedMode;
}

if (showMessage)
{
Compatibility.Notify(Name + " - " + localizationProvider.Translate(TranslationKeys.Warning), restricted);
}
CheckCompatibility();
}
}

Expand Down Expand Up @@ -198,6 +186,29 @@ private static string GetModPath()
return pluginInfo?.modPath;
}

private void CheckCompatibility()
{
if (core == null)
{
return;
}

string restrictedText = core.IsRestrictedMode
? localizationProvider.Translate(TranslationKeys.RestrictedMode)
: null;

if (configProvider.Configuration.ShowIncompatibilityNotifications
&& !Compatibility.Check(Name, localizationProvider, restrictedText))
{
return;
}

if (core.IsRestrictedMode)
{
Compatibility.Notify(Name + " - " + localizationProvider.Translate(TranslationKeys.Warning), restrictedText);
}
}

private void ApplyLanguage()
{
if (!SingletonLite<LocaleManager>.exists)
Expand Down
14 changes: 8 additions & 6 deletions src/RealTime/CustomAI/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,6 @@ internal static class Constants
/// <summary>An earliest hour when citizens wake up at home.</summary>
public const float EarliestWakeUp = 5.5f;

/// <summary>
/// An assumed average speed of a citizen when moving to target (this is not just a walking speed, but also takes
/// into account moving by car or public transport).
/// </summary>
public const float OnTheWayDistancePerHour = 500f;

/// <summary>A chance in percent that a virtual citizen will not be realized in 'few virtual citizens' mode.</summary>
public const uint FewVirtualCitizensChance = 20;

Expand All @@ -73,5 +67,13 @@ internal static class Constants

/// <summary>The interval in minutes for the buildings problem timers.</summary>
public const int ProblemTimersInterval = 10;

/// <summary>The chance of a young female to get pregnant.</summary>
public const uint YoungFemalePregnancyChance = 50u;

/// <summary>The average distance a citizen can move for (walking, by car, by public transport) during a full simulation
/// cycle at maximum time speed (6).
/// This value was determined empirically.</summary>
public const float AverageDistancePerSimulationCycle = 750f;
}
}
1 change: 1 addition & 0 deletions src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ private bool ProcessCitizenInShelter(ref CitizenSchedule schedule, ref TCitizen
return false;
}

// TODO: refactor to decrease cyclomatic complexity
private ScheduleAction UpdateCitizenState(ref TCitizen citizen, ref CitizenSchedule schedule)
{
if (schedule.CurrentState == ResidentState.Ignored)
Expand Down
44 changes: 43 additions & 1 deletion src/RealTime/CustomAI/RealTimeResidentAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ public void BeginNewHourCycleProcessing(int hour)
/// <summary>Disables the 'new cycle processing' for the citizens.</summary>
public void EndHourCycleProcessing()
{
CanCitizensGrowUp = false;
if (Config.UseSlowAging)
{
CanCitizensGrowUp = false;
}

Log.Debug(LogCategory.Generic, TimeInfo.Now, "The 'new cycle' processing for the citizens is now completed.");
}

Expand All @@ -169,6 +173,44 @@ public void BeginNewDayForCitizen(uint citizenId)
}
}

/// <summary>
/// Determines whether the specified <paramref name="citizen"/> can give life to a new citizen.
/// </summary>
/// <param name="citizenId">The ID of the citizen to check.</param>
/// <param name="citizen">The citizen to check.</param>
/// <returns>
/// <c>true</c> if the specified <paramref name="citizen"/> can make babies; otherwise, <c>false</c>.
/// </returns>
public bool CanMakeBabies(uint citizenId, ref TCitizen citizen)
{
uint idFlag = citizenId % 3;
uint timeFlag = (uint)TimeInfo.CurrentHour % 3;
if (!Config.UseSlowAging)
{
idFlag = 0;
timeFlag = 0;
}

if (timeFlag != idFlag || CitizenProxy.IsDead(ref citizen) || CitizenProxy.HasFlags(ref citizen, Citizen.Flags.MovingIn))
{
return false;
}

switch (CitizenProxy.GetAge(ref citizen))
{
case Citizen.AgeGroup.Young:
return CitizenProxy.GetGender(citizenId) == Citizen.Gender.Male
? true
: Random.ShouldOccur(Constants.YoungFemalePregnancyChance);

case Citizen.AgeGroup.Adult:
return true;

default:
return false;
}
}

/// <summary>Sets the duration (in hours) of a full simulation cycle for all citizens.
/// The game calls the simulation methods for a particular citizen with this period.</summary>
/// <param name="cyclePeriod">The citizens simulation cycle period, in game hours.</param>
Expand Down
1 change: 1 addition & 0 deletions src/RealTime/CustomAI/RealTimeTouristAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ private void ProcessVisit(TAI instance, uint citizenId, ref TCitizen citizen)
case ItemClass.Service.Disaster:
if (BuildingMgr.BuildingHasFlags(visitBuilding, Building.Flags.Downgrading))
{
CitizenProxy.RemoveFlags(ref citizen, Citizen.Flags.Evacuating);
FindRandomVisitPlace(instance, citizenId, ref citizen, 0, visitBuilding);
}

Expand Down
22 changes: 21 additions & 1 deletion src/RealTime/CustomAI/TravelBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace RealTime.CustomAI
internal sealed class TravelBehavior : ITravelBehavior
{
private readonly IBuildingManagerConnection buildingManager;
private float averageCitizenSpeed;

/// <summary>Initializes a new instance of the <see cref="TravelBehavior"/> class.</summary>
/// <param name="buildingManager">
Expand All @@ -23,6 +24,20 @@ internal sealed class TravelBehavior : ITravelBehavior
public TravelBehavior(IBuildingManagerConnection buildingManager)
{
this.buildingManager = buildingManager ?? throw new System.ArgumentNullException(nameof(buildingManager));
averageCitizenSpeed = AverageDistancePerSimulationCycle;
}

/// <summary>Sets the duration (in hours) of a full simulation cycle for all citizens.
/// The game calls the simulation methods for a particular citizen with this period.</summary>
/// <param name="cyclePeriod">The citizens simulation cycle period, in game hours.</param>
public void SetSimulationCyclePeriod(float cyclePeriod)
{
if (cyclePeriod == 0)
{
cyclePeriod = 1f;
}

averageCitizenSpeed = AverageDistancePerSimulationCycle / cyclePeriod;
}

/// <summary>Gets an estimated travel time (in hours) between two specified buildings.</summary>
Expand All @@ -37,7 +52,12 @@ public float GetEstimatedTravelTime(ushort building1, ushort building2)
}

float distance = buildingManager.GetDistanceBetweenBuildings(building1, building2);
return FastMath.Clamp(distance / OnTheWayDistancePerHour, MinTravelTime, MaxTravelTime);
if (distance == 0)
{
return MinTravelTime;
}

return FastMath.Clamp(distance / averageCitizenSpeed, MinTravelTime, MaxTravelTime);
}
}
}
2 changes: 1 addition & 1 deletion src/RealTime/GameConnection/BuildingManagerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public float GetDistanceBetweenBuildings(ushort building1, ushort building2)
{
if (building1 == 0 || building2 == 0)
{
return float.MaxValue;
return 0;
}

Building[] buildings = BuildingManager.instance.m_buildings.m_buffer;
Expand Down
2 changes: 1 addition & 1 deletion src/RealTime/GameConnection/IBuildingManagerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ internal interface IBuildingManagerConnection
/// <param name="building1">The ID of the first building.</param>
/// <param name="building2">The ID of the second building.</param>
/// <returns>
/// A distance between the buildings with specified IDs, 0 when any of the IDs is 0.
/// The distance between the buildings with specified IDs, 0 when any of the IDs is 0.
/// </returns>
float GetDistanceBetweenBuildings(ushort building1, ushort building2);

Expand Down
29 changes: 29 additions & 0 deletions src/RealTime/GameConnection/Patches/ResidentAIPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ internal static class ResidentAIPatch
/// <summary>Gets the patch object for the update age method.</summary>
public static IPatch UpdateAge { get; } = new ResidentAI_UpdateAge();

/// <summary>Gets the patch object for the 'can make babies' method.</summary>
public static IPatch CanMakeBabies { get; } = new ResidentAI_CanMakeBabies();

/// <summary>Creates a game connection object for the resident AI class.</summary>
/// <returns>A new <see cref="ResidentAIConnection{ResidentAI, Citizen}"/> object.</returns>
public static ResidentAIConnection<ResidentAI, Citizen> GetResidentAIConnection()
Expand Down Expand Up @@ -147,5 +150,31 @@ private static bool Prefix(uint citizenID, ref Citizen data, ref bool __result)
}
#pragma warning restore SA1313 // Parameter names must begin with lower-case letter
}

private sealed class ResidentAI_CanMakeBabies : PatchBase
{
protected override MethodInfo GetMethod()
{
return typeof(ResidentAI).GetMethod(
"CanMakeBabies",
BindingFlags.Instance | BindingFlags.Public,
null,
new[] { typeof(uint), typeof(Citizen).MakeByRefType() },
new ParameterModifier[0]);
}

#pragma warning disable SA1313 // Parameter names must begin with lower-case letter
private static bool Prefix(uint citizenID, ref Citizen data, ref bool __result)
{
if (RealTimeAI != null)
{
__result = RealTimeAI.CanMakeBabies(citizenID, ref data);
return false;
}

return true;
}
#pragma warning restore SA1313 // Parameter names must begin with lower-case letter
}
}
}
2 changes: 2 additions & 0 deletions src/RealTime/Localization/Translations/de.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
<translation id="VirtualCitizensTooltip" value="Wählen sie 'Alle' aus für eine reale Stadt (aber max 65.535 Einw.) oder 'Original' bei schwachem PC oder großer Stadt" />
<translation id="VirtualCitizens.None" value="Alle Einwohner sind real" />
<translation id="VirtualCitizens.Vanilla" value="Wie im Originalspiel" />
<translation id="UseSlowAging" value="Bessere Altern und Geburtenrate" />
<translation id="UseSlowAgingTooltip" value="Wenn aktiviert, 1 Tag im Spiel bedeutet 1 Jahr für Cims. Wenn deaktiviert, 1 Tag im Spiel bedeutet 10 (bei Zeitgeschw. 6) bis 340 (bei Zeitgeschw. 1) Jahre für Cims." />
<translation id="IsWeekendEnabled" value="Wochenende aktiv" />
<translation id="IsWeekendEnabledTooltip" value="Cims gehen an den Wochenenden nicht in die Schule oder zur Arbeit" />
<translation id="IsLunchtimeEnabled" value="Mittagspause aktiv" />
Expand Down
Loading

0 comments on commit d66fd21

Please sign in to comment.