Skip to content

Commit

Permalink
Merge branch 'feature-1.13'
Browse files Browse the repository at this point in the history
  • Loading branch information
dymanoid committed Aug 12, 2018
2 parents d66fd21 + ffe962e commit 0c83688
Show file tree
Hide file tree
Showing 29 changed files with 391 additions and 618 deletions.
7 changes: 6 additions & 1 deletion src/RealTime/Core/RealTimeCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public static RealTimeCore Run(
var buildingManager = new BuildingManagerConnection();
var randomizer = new GameRandomizer();

var weatherInfo = new WeatherInfo(new WeatherManagerConnection());
var weatherInfo = new WeatherInfo(new WeatherManagerConnection(), randomizer);

var gameConnections = new GameConnections<Citizen>(
timeInfo,
Expand Down Expand Up @@ -233,6 +233,7 @@ public void Stop()
ResidentAIPatch.RealTimeAI = null;
TouristAIPatch.RealTimeAI = null;
BuildingAIPatches.RealTimeAI = null;
BuildingAIPatches.WeatherInfo = null;
TransferManagerPatch.RealTimeAI = null;
SimulationHandler.EventManager = null;
SimulationHandler.DayTimeSimulation = null;
Expand Down Expand Up @@ -280,6 +281,9 @@ private static List<IPatch> GetMethodPatches()
BuildingAIPatches.CommercialSimulation,
BuildingAIPatches.PrivateShowConsumption,
BuildingAIPatches.PlayerShowConsumption,
BuildingAIPatches.CalculateUnspawnPosition,
BuildingAIPatches.GetUpgradeInfo,
BuildingAIPatches.CreateBuilding,
ResidentAIPatch.Location,
ResidentAIPatch.ArriveAtTarget,
TouristAIPatch.Location,
Expand Down Expand Up @@ -346,6 +350,7 @@ private static bool SetupCustomAI(
travelBehavior);

BuildingAIPatches.RealTimeAI = realTimePrivateBuildingAI;
BuildingAIPatches.WeatherInfo = gameConnections.WeatherInfo;
TransferManagerPatch.RealTimeAI = realTimePrivateBuildingAI;

var realTimeResidentAI = new RealTimeResidentAI<ResidentAI, Citizen>(
Expand Down
5 changes: 4 additions & 1 deletion src/RealTime/CustomAI/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ internal static class Constants
public const float BadWeatherPrecipitationThreshold = 0.05f;

/// <summary>The minimum probability that a citizen will stay inside a building on any precipitation.</summary>
public const uint MinimumStayInsideChanceOnPrecipitation = 75u;
public const uint MinimumStayInsideChanceOnPrecipitation = 85u;

/// <summary>The interval in minutes for the buildings problem timers.</summary>
public const int ProblemTimersInterval = 10;
Expand All @@ -75,5 +75,8 @@ internal static class Constants
/// cycle at maximum time speed (6).
/// This value was determined empirically.</summary>
public const float AverageDistancePerSimulationCycle = 750f;

/// <summary>The maximum number of buildings (of one zone type) that are in construction or upgrading process.</summary>
public const int MaximumBuildingsInConstruction = 50;
}
}
123 changes: 123 additions & 0 deletions src/RealTime/CustomAI/RealTimeBuildingAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace RealTime.CustomAI
{
using System;
using System.Collections.Generic;
using System.Linq;
using RealTime.Config;
using RealTime.GameConnection;
using RealTime.Simulation;
Expand All @@ -19,6 +21,13 @@ internal sealed class RealTimeBuildingAI : IRealTimeBuildingAI
private const int ConstructionSpeedMinimum = 1088;
private const int StepMask = 0xFF;
private const int BuildingStepSize = 192;
private const int ConstructionRestrictionThreshold1 = 100;
private const int ConstructionRestrictionThreshold2 = 1_000;
private const int ConstructionRestrictionThreshold3 = 10_000;
private const int ConstructionRestrictionStep1 = MaximumBuildingsInConstruction / 10;
private const int ConstructionRestrictionStep2 = MaximumBuildingsInConstruction / 5;
private const int ConstructionRestrictionScale2 = ConstructionRestrictionThreshold2 / (ConstructionRestrictionStep2 - ConstructionRestrictionStep1);
private const int ConstructionRestrictionScale3 = ConstructionRestrictionThreshold3 / (MaximumBuildingsInConstruction - ConstructionRestrictionStep2);

private static readonly string[] BannedEntertainmentBuildings = { "parking", "garage", "car park" };
private readonly TimeSpan lightStateCheckInterval = TimeSpan.FromSeconds(15);
Expand All @@ -31,6 +40,7 @@ internal sealed class RealTimeBuildingAI : IRealTimeBuildingAI
private readonly ITravelBehavior travelBehavior;

private readonly bool[] lightStates;
private readonly HashSet<ushort>[] buildingsInConstruction;

private int lastProcessedMinute = -1;
private bool freezeProblemTimers;
Expand Down Expand Up @@ -69,6 +79,22 @@ public RealTimeBuildingAI(
this.travelBehavior = travelBehavior ?? throw new ArgumentNullException(nameof(travelBehavior));

lightStates = new bool[buildingManager.GetMaxBuildingsCount()];

// This is to preallocate the hash sets to a large capacity, .NET 3.5 doesn't provide a proper way.
var preallocated = Enumerable.Range(0, MaximumBuildingsInConstruction * 2).Select(v => (ushort)v).ToList();
buildingsInConstruction = new[]
{
new HashSet<ushort>(preallocated),
new HashSet<ushort>(preallocated),
new HashSet<ushort>(preallocated),
new HashSet<ushort>(preallocated)
};

for (int i = 0; i < buildingsInConstruction.Length; ++i)
{
// Calling Clear() doesn't trim the capacity, we're using this trick for preallocating the hash sets
buildingsInConstruction[i].Clear();
}
}

/// <summary>
Expand Down Expand Up @@ -96,6 +122,78 @@ public int GetConstructionTime()
: (int)(ConstructionSpeedMinimum * constructionSpeedValue);
}

/// <summary>
/// Determines whether a building can be constructed or upgraded in the specified building zone.
/// </summary>
/// <param name="buildingZone">The building zone to check.</param>
/// <param name="buildingId">The building ID. Can be 0 if we're about to construct a new building.</param>
/// <returns>
/// <c>true</c> if a building can be constructed or upgraded; otherwise, <c>false</c>.
/// </returns>
public bool CanBuildOrUpgrade(ItemClass.Service buildingZone, ushort buildingId = 0)
{
int index;
switch (buildingZone)
{
case ItemClass.Service.Residential:
index = 0;
break;

case ItemClass.Service.Commercial:
index = 1;
break;

case ItemClass.Service.Industrial:
index = 2;
break;

case ItemClass.Service.Office:
index = 3;
break;

default:
return true;
}

HashSet<ushort> buildings = buildingsInConstruction[index];
buildings.RemoveWhere(IsBuildingCompleted);

int allowedCount = GetAllowedConstructingUpradingCount(buildingManager.GeBuildingsCount());
bool result = buildings.Count < allowedCount;
if (result && buildingId != 0)
{
buildings.Add(buildingId);
}

return result;
}

/// <summary>Registers the building with specified <paramref name="buildingId"/> as being constructed or
/// upgraded.</summary>
/// <param name="buildingId">The building ID to register.</param>
/// <param name="buildingZone">The building zone.</param>
public void RegisterConstructingBuilding(ushort buildingId, ItemClass.Service buildingZone)
{
switch (buildingZone)
{
case ItemClass.Service.Residential:
buildingsInConstruction[0].Add(buildingId);
return;

case ItemClass.Service.Commercial:
buildingsInConstruction[1].Add(buildingId);
return;

case ItemClass.Service.Industrial:
buildingsInConstruction[2].Add(buildingId);
return;

case ItemClass.Service.Office:
buildingsInConstruction[3].Add(buildingId);
return;
}
}

/// <summary>
/// Performs the custom processing of the outgoing problem timer.
/// </summary>
Expand Down Expand Up @@ -258,6 +356,31 @@ public bool IsNoiseRestricted(ushort buildingId, ushort currentBuildingId = 0)
return false;
}

private static int GetAllowedConstructingUpradingCount(int currentBuildingCount)
{
if (currentBuildingCount < ConstructionRestrictionThreshold1)
{
return ConstructionRestrictionStep1;
}

if (currentBuildingCount < ConstructionRestrictionThreshold2)
{
return ConstructionRestrictionStep1 + (currentBuildingCount / ConstructionRestrictionScale2);
}

if (currentBuildingCount < ConstructionRestrictionThreshold3)
{
return ConstructionRestrictionStep2 + (currentBuildingCount / ConstructionRestrictionScale3);
}

return MaximumBuildingsInConstruction;
}

private bool IsBuildingCompleted(ushort buildingId)
{
return buildingManager.BuildingHasFlags(buildingId, Building.Flags.Completed);
}

private void UpdateLightState()
{
if (lightStateCheckCounter > 0)
Expand Down
13 changes: 0 additions & 13 deletions src/RealTime/CustomAI/RealTimeHumanAIBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,19 +219,6 @@ protected bool IsCitizenVirtual<TAI>(TAI humanAI, ref TCitizen citizen, Func<TAI
}
}

/// <summary>Determines whether the weather is currently so bad that the citizen would like to stay inside a building.</summary>
/// <returns>
/// <c>true</c> if the weather is bad; otherwise, <c>false</c>.</returns>
protected bool IsBadWeather()
{
if (WeatherInfo.IsDisasterHazardActive)
{
return true;
}

return WeatherInfo.StayInsideChance != 0 && Random.ShouldOccur(WeatherInfo.StayInsideChance);
}

private bool CanAttendEvent(uint citizenId, ref TCitizen citizen, ICityEvent cityEvent)
{
Citizen.AgeGroup age = CitizenProxy.GetAge(ref citizen);
Expand Down
2 changes: 1 addition & 1 deletion src/RealTime/CustomAI/RealTimeResidentAI.Home.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private bool RescheduleAtHome(ref CitizenSchedule schedule, uint citizenId, ref
return false;
}

if (schedule.ScheduledState != ResidentState.Shopping && IsBadWeather())
if (schedule.ScheduledState != ResidentState.Shopping && WeatherInfo.IsBadWeather)
{
Log.Debug(LogCategory.Schedule, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} re-schedules an activity because of bad weather");
schedule.Schedule(ResidentState.Unknown);
Expand Down
2 changes: 1 addition & 1 deletion src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ private bool ProcessCitizenMoving(ref CitizenSchedule schedule, TAI instance, ui
}

ItemClass.Service targetService = BuildingMgr.GetBuildingService(targetBuilding);
if (targetService == ItemClass.Service.Beautification && IsBadWeather())
if (targetService == ItemClass.Service.Beautification && WeatherInfo.IsBadWeather)
{
Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} cancels the trip to a park due to bad weather");
schedule.Schedule(ResidentState.AtHome);
Expand Down
6 changes: 3 additions & 3 deletions src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ private bool ScheduleRelaxing(ref CitizenSchedule schedule, uint citizenId, ref
Citizen.AgeGroup citizenAge = CitizenProxy.GetAge(ref citizen);

uint relaxChance = spareTimeBehavior.GetRelaxingChance(citizenAge, schedule.WorkShift, schedule.WorkStatus == WorkStatus.OnVacation);
if (!Random.ShouldOccur(relaxChance) || IsBadWeather())
if (!Random.ShouldOccur(relaxChance) || WeatherInfo.IsBadWeather)
{
return false;
}
Expand Down Expand Up @@ -131,7 +131,7 @@ private bool ScheduleShopping(ref CitizenSchedule schedule, ref TCitizen citizen
// If the citizen doesn't need any goods, he/she still can go shopping just for fun
if (!CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods))
{
if (schedule.Hint == ScheduleHint.NoShoppingAnyMore || IsBadWeather() || !Random.ShouldOccur(Config.ShoppingForFunQuota))
if (schedule.Hint == ScheduleHint.NoShoppingAnyMore || WeatherInfo.IsBadWeather || !Random.ShouldOccur(Config.ShoppingForFunQuota))
{
schedule.Hint = ScheduleHint.None;
return false;
Expand Down Expand Up @@ -250,7 +250,7 @@ private bool RescheduleVisit(ref CitizenSchedule schedule, uint citizenId, ref T
return false;
}

if (schedule.CurrentState != ResidentState.Shopping && IsBadWeather())
if (schedule.CurrentState != ResidentState.Shopping && WeatherInfo.IsBadWeather)
{
Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} quits a visit because of bad weather");
schedule.Schedule(ResidentState.AtHome);
Expand Down
4 changes: 2 additions & 2 deletions src/RealTime/CustomAI/RealTimeTouristAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ when BuildingMgr.GetBuildingSubService(visitBuilding) == ItemClass.SubService.Co
return;
}

if (Random.ShouldOccur(TouristEventChance) && !IsBadWeather())
if (Random.ShouldOccur(TouristEventChance) && !WeatherInfo.IsBadWeather)
{
ICityEvent cityEvent = GetUpcomingEventToAttend(citizenId, ref citizen);
if (cityEvent != null
Expand Down Expand Up @@ -324,7 +324,7 @@ private uint GetTouristGoingOutChance(ref TCitizen citizen, TouristTarget target
case TouristTarget.Shopping:
return spareTimeBehavior.GetShoppingChance(age);

case TouristTarget.Relaxing when TimeInfo.IsNightTime || IsBadWeather():
case TouristTarget.Relaxing when TimeInfo.IsNightTime || WeatherInfo.IsBadWeather:
return 0u;

case TouristTarget.Party:
Expand Down
26 changes: 17 additions & 9 deletions src/RealTime/CustomAI/WeatherInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,35 @@ namespace RealTime.CustomAI
internal sealed class WeatherInfo : IWeatherInfo
{
private readonly IWeatherManagerConnection weatherManager;

private readonly IRandomizer randomizer;
private uint stayInsideChance;
private bool isDisasterHazardActive;

/// <summary>Initializes a new instance of the <see cref="WeatherInfo"/> class.</summary>
/// <param name="weatherManager">The game's weather manager.</param>
/// <exception cref="ArgumentNullException">Thrown when the argument is null.</exception>
public WeatherInfo(IWeatherManagerConnection weatherManager)
/// <param name="randomizer">The randomizer implementation</param>
/// <exception cref="ArgumentNullException">Thrown when any argument is null.</exception>
public WeatherInfo(IWeatherManagerConnection weatherManager, IRandomizer randomizer)
{
this.weatherManager = weatherManager ?? throw new ArgumentNullException(nameof(weatherManager));
this.randomizer = randomizer ?? throw new ArgumentNullException(nameof(randomizer));
}

/// <summary>
/// Gets a probability (0-100) that the citizens will stay inside buildings due to weather conditions.
/// Gets a value indicating whether current weather conditions cause citizens not to stay outside.
/// </summary>
uint IWeatherInfo.StayInsideChance => stayInsideChance;
bool IWeatherInfo.IsBadWeather
{
get
{
if (isDisasterHazardActive)
{
return true;
}

/// <summary>
/// Gets a value indicating whether a disaster hazard is currently active in the city.
/// </summary>
bool IWeatherInfo.IsDisasterHazardActive => isDisasterHazardActive;
return stayInsideChance != 0 && randomizer.ShouldOccur(stayInsideChance);
}
}

/// <summary>Updates this object's state using the current game state.</summary>
public void Update()
Expand Down
Loading

0 comments on commit 0c83688

Please sign in to comment.