From 070ede6a610b68c5571b7d6d17436c5379ca6d65 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Fri, 13 Jul 2018 19:51:32 +0200
Subject: [PATCH 01/42] Update .gitignore
---
.gitignore | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.gitignore b/.gitignore
index 25b7a3f8..42dbee36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,4 +31,6 @@ _ReSharper*/
#Nuget packages folder
packages/
+
+#MacOS stuff
.DS_Store
From 85419162b0b0e933c74f4a7336b07e8fb8d2b441 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 15 Jul 2018 14:59:18 +0200
Subject: [PATCH 02/42] Remove the incomplete ride abandonment logic (will be
implemented in #2)
---
src/RealTime/CustomAI/Constants.cs | 3 --
.../CustomAI/RealTimeResidentAI.Moving.cs | 32 ++++++-------------
2 files changed, 9 insertions(+), 26 deletions(-)
diff --git a/src/RealTime/CustomAI/Constants.cs b/src/RealTime/CustomAI/Constants.cs
index f00bf551..bae2c3ab 100644
--- a/src/RealTime/CustomAI/Constants.cs
+++ b/src/RealTime/CustomAI/Constants.cs
@@ -17,9 +17,6 @@ internal static class Constants
/// A distance in game units that corresponds to the complete map.
public const float FullSearchDistance = BuildingManager.BUILDINGGRID_RESOLUTION * BuildingManager.BUILDINGGRID_CELL_SIZE / 2f;
- /// A chance in percent for a citizen to abandon the transport waiting if it lasts too long.
- public const uint AbandonTransportWaitChance = 80;
-
/// A chance in percent for a citizen to go shopping.
public const uint GoShoppingChance = 50;
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
index 9737ac99..fe7adf45 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
@@ -5,7 +5,6 @@
namespace RealTime.CustomAI
{
using RealTime.Tools;
- using static Constants;
internal sealed partial class RealTimeResidentAI
{
@@ -27,7 +26,7 @@ private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen cit
}
else
{
- // TODO: check whether this makes sense and maybe remove/replace this logic
+ // TODO: check whether this makes sense and maybe remove/replace this logic.
// Don't know why the original game does this...
CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
CitizenProxy.SetArrested(ref citizen, false);
@@ -43,35 +42,22 @@ private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen cit
return;
}
- bool returnHome = false;
ushort targetBuilding = CitizenMgr.GetTargetBuilding(instanceId);
- if (targetBuilding != CitizenProxy.GetWorkBuilding(ref citizen))
+ if (targetBuilding == CitizenProxy.GetWorkBuilding(ref citizen))
{
- ItemClass.Service targetService = BuildingMgr.GetBuildingService(targetBuilding);
- if (targetService == ItemClass.Service.Beautification && IsBadWeather(citizenId))
- {
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, false)} cancels the trip to a park due to bad weather");
- returnHome = true;
- }
+ return;
}
- if (!returnHome && CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.WaitingTransport | CitizenInstance.Flags.WaitingTaxi))
+ ItemClass.Service targetService = BuildingMgr.GetBuildingService(targetBuilding);
+ if (targetService != ItemClass.Service.Beautification || !IsBadWeather(citizenId))
{
- if (mayCancel && CitizenMgr.GetInstanceWaitCounter(instanceId) == 255 && Random.ShouldOccur(AbandonTransportWaitChance))
- {
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, false)} goes back home");
- returnHome = true;
- }
+ return;
}
- if (returnHome)
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, false)} cancels the trip to a park due to bad weather");
+ ushort home = CitizenProxy.GetHomeBuilding(ref citizen);
+ if (home != 0)
{
- ushort home = CitizenProxy.GetHomeBuilding(ref citizen);
- if (home == 0)
- {
- return;
- }
-
residentAI.StartMoving(instance, citizenId, ref citizen, 0, home);
}
}
From 747f7446fc13cbf595cc8956b2b6ce16255bf638 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 15 Jul 2018 16:57:11 +0200
Subject: [PATCH 03/42] Create a custom resident citizen state structure
---
.../CustomAI/RealTimeResidentAI.Common.cs | 5 +-
.../CustomAI/RealTimeResidentAI.SchoolWork.cs | 3 ++
.../CustomAI/RealTimeResidentAI.Visit.cs | 1 -
src/RealTime/CustomAI/RealTimeResidentAI.cs | 7 ++-
src/RealTime/CustomAI/ResidentState.cs | 2 +-
.../CustomAI/ResidentStateDescriptor.cs | 49 +++++++++++++++++++
src/RealTime/CustomAI/WorkStatus.cs | 24 +++++++++
.../CitizenManagerConnection.cs | 7 +++
.../ICitizenManagerConnection.cs | 4 ++
src/RealTime/RealTime.csproj | 2 +
10 files changed, 98 insertions(+), 6 deletions(-)
create mode 100644 src/RealTime/CustomAI/ResidentStateDescriptor.cs
create mode 100644 src/RealTime/CustomAI/WorkStatus.cs
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
index 9fc1412e..d01b60b4 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
@@ -137,7 +137,7 @@ private bool StartMovingToVisitBuilding(TAI instance, uint citizenId, ref TCitiz
return false;
}
- private ResidentState GetResidentState(ref TCitizen citizen)
+ private ResidentState GetResidentState(uint citizenId, ref TCitizen citizen)
{
if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.DummyTraffic))
{
@@ -185,8 +185,7 @@ private ResidentState GetResidentState(ref TCitizen citizen)
switch (buildingService)
{
case ItemClass.Service.Commercial:
- if (CitizenProxy.GetWorkBuilding(ref citizen) != 0 && IsWorkDay
- && TimeInfo.CurrentHour > Config.LunchBegin && TimeInfo.CurrentHour < GetSpareTimeBeginHour(CitizenProxy.GetAge(ref citizen)))
+ if (residentStates[citizenId].WorkStatus == WorkStatus.AtLunch)
{
return ResidentState.AtLunch;
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index 64b4ae60..f88174c1 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -178,6 +178,7 @@ private void ProcessCitizenAtSchoolOrWork(TAI instance, uint citizenId, ref TCit
ushort lunchPlace = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance, isVirtual);
if (lunchPlace != 0)
{
+ residentStates[citizenId].WorkStatus = WorkStatus.AtLunch;
Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} is going for lunch from {currentBuilding} to {lunchPlace}");
}
else
@@ -253,6 +254,7 @@ private bool ShouldMoveToSchoolOrWork(uint citizenId, ushort workBuilding, ushor
return false;
}
+ // TODO: replace the day off logic.
if ((citizenId & 0x7FF) == TimeInfo.Now.Day)
{
Log.Debug(TimeInfo.Now, $"Citizen {citizenId} has a day off work today");
@@ -416,6 +418,7 @@ private bool CitizenReturnsFromLunch(TAI instance, uint citizenId, ref TCitizen
CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
}
+ residentStates[citizenId].WorkStatus = WorkStatus.Default;
return true;
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
index 21788773..66db57ff 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
@@ -24,7 +24,6 @@ private void ProcessCitizenVisit(TAI instance, ResidentState citizenState, uint
{
case ResidentState.AtLunch:
CitizenReturnsFromLunch(instance, citizenId, ref citizen, isVirtual);
-
return;
case ResidentState.AtLeisureArea:
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.cs b/src/RealTime/CustomAI/RealTimeResidentAI.cs
index dd60f12f..175cba6a 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.cs
@@ -17,6 +17,7 @@ internal sealed partial class RealTimeResidentAI : RealTimeHumanA
where TCitizen : struct
{
private readonly ResidentAIConnection residentAI;
+ private readonly ResidentStateDescriptor[] residentStates;
/// Initializes a new instance of the class.
/// Thrown when any argument is null.
@@ -32,6 +33,7 @@ public RealTimeResidentAI(
: base(config, connections, eventManager)
{
this.residentAI = residentAI ?? throw new ArgumentNullException(nameof(residentAI));
+ residentStates = new ResidentStateDescriptor[CitizenMgr.GetMaxCitizensCount()];
}
/// The entry method of the custom AI.
@@ -57,7 +59,7 @@ public void UpdateLocation(TAI instance, uint citizenId, ref TCitizen citizen)
return;
}
- ResidentState residentState = GetResidentState(ref citizen);
+ ResidentState residentState = GetResidentState(citizenId, ref citizen);
bool isVirtual;
switch (residentState)
@@ -110,6 +112,9 @@ public void UpdateLocation(TAI instance, uint citizenId, ref TCitizen citizen)
break;
}
+
+ residentStates[citizenId].State = residentState;
+ }
}
private bool ShouldRealizeCitizen(TAI ai)
diff --git a/src/RealTime/CustomAI/ResidentState.cs b/src/RealTime/CustomAI/ResidentState.cs
index dee3cc68..345f47e9 100644
--- a/src/RealTime/CustomAI/ResidentState.cs
+++ b/src/RealTime/CustomAI/ResidentState.cs
@@ -3,7 +3,7 @@
namespace RealTime.CustomAI
{
/// Possible citizen's states.
- internal enum ResidentState
+ internal enum ResidentState : byte
{
/// The state could not be determined.
Unknown,
diff --git a/src/RealTime/CustomAI/ResidentStateDescriptor.cs b/src/RealTime/CustomAI/ResidentStateDescriptor.cs
new file mode 100644
index 00000000..9ef09542
--- /dev/null
+++ b/src/RealTime/CustomAI/ResidentStateDescriptor.cs
@@ -0,0 +1,49 @@
+// Copyright (c) dymanoid. All rights reserved.
+
+namespace RealTime.CustomAI
+{
+ using System;
+ using RealTime.Tools;
+ using static Constants;
+
+ /// A container struct that holds information about the detailed resident citizen state.
+ internal struct ResidentStateDescriptor
+ {
+ /// The citizen's last known state.
+ public ResidentState State;
+
+ /// The citizen's current work status.
+ public WorkStatus WorkStatus;
+
+ /// The time when citizen started their last movement in the city.
+ public DateTime DepartureTime;
+
+ ///
+ /// Gets or sets the travel time (in hours) from citizen's home to the work building. The maximum value is
+ /// determined by the constant.
+ ///
+ public float TravelTimeToWork;
+
+ /// Updates the travel time that the citizen needs to read the work building or school/university.
+ ///
+ /// The arrival time at the work building or school/university. Must be great than .
+ ///
+ public void UpdateTravelTimeToWork(DateTime arrivalTime)
+ {
+ if (arrivalTime < DepartureTime)
+ {
+ return;
+ }
+
+ float onTheWayHours = (float)(arrivalTime - DepartureTime).TotalHours;
+ if (onTheWayHours > MaxHoursOnTheWay)
+ {
+ onTheWayHours = MaxHoursOnTheWay;
+ }
+
+ TravelTimeToWork = TravelTimeToWork == 0
+ ? onTheWayHours
+ : (TravelTimeToWork + onTheWayHours) / 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RealTime/CustomAI/WorkStatus.cs b/src/RealTime/CustomAI/WorkStatus.cs
new file mode 100644
index 00000000..13c6b08e
--- /dev/null
+++ b/src/RealTime/CustomAI/WorkStatus.cs
@@ -0,0 +1,24 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.CustomAI
+{
+ ///
+ /// Describes the work (or school/university) status of a citizen.
+ ///
+ internal enum WorkStatus : byte
+ {
+ /// No special handling.
+ Default,
+
+ /// The citizen takes a break and goes out for lunch.
+ AtLunch,
+
+ /// The citizen has a day off work today.
+ DayOff,
+
+ /// The citizen is on vacation.
+ OnVacation
+ }
+}
diff --git a/src/RealTime/GameConnection/CitizenManagerConnection.cs b/src/RealTime/GameConnection/CitizenManagerConnection.cs
index c4f9c86f..375bb95a 100644
--- a/src/RealTime/GameConnection/CitizenManagerConnection.cs
+++ b/src/RealTime/GameConnection/CitizenManagerConnection.cs
@@ -105,5 +105,12 @@ public uint GetMaxInstancesCount()
{
return CitizenManager.instance.m_instances.m_size;
}
+
+ /// Gets the maximum count of the citizens.
+ /// The maximum number of the citizens.
+ public uint GetMaxCitizensCount()
+ {
+ return CitizenManager.instance.m_citizens.m_size;
+ }
}
}
\ No newline at end of file
diff --git a/src/RealTime/GameConnection/ICitizenManagerConnection.cs b/src/RealTime/GameConnection/ICitizenManagerConnection.cs
index bdc9f369..e888a799 100644
--- a/src/RealTime/GameConnection/ICitizenManagerConnection.cs
+++ b/src/RealTime/GameConnection/ICitizenManagerConnection.cs
@@ -52,5 +52,9 @@ internal interface ICitizenManagerConnection
/// Gets the maximum count of the active citizens instances.
/// The maximum number of active citizens instances.
uint GetMaxInstancesCount();
+
+ /// Gets the maximum count of the citizens.
+ /// The maximum number of the citizens.
+ uint GetMaxCitizensCount();
}
}
\ No newline at end of file
diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj
index c44e9053..4dbe9926 100644
--- a/src/RealTime/RealTime.csproj
+++ b/src/RealTime/RealTime.csproj
@@ -64,7 +64,9 @@
+
+
From 06a66883f070adcdf02ec69e1ec233b7a994580b Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 15 Jul 2018 16:57:45 +0200
Subject: [PATCH 04/42] Implement citizen travel-to-work time tracking
---
src/RealTime/Core/RealTimeCore.cs | 1 +
.../CustomAI/RealTimeResidentAI.SchoolWork.cs | 1 +
src/RealTime/CustomAI/RealTimeResidentAI.cs | 22 +++++++++++++++++
.../CustomAI/ResidentStateDescriptor.cs | 2 +-
.../CitizenManagerConnection.cs | 15 ++++++++++++
.../ICitizenManagerConnection.cs | 6 +++++
.../GameConnection/Patches/ResidentAIPatch.cs | 24 +++++++++++++++++++
7 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs
index 5744c5a3..ac50412e 100644
--- a/src/RealTime/Core/RealTimeCore.cs
+++ b/src/RealTime/Core/RealTimeCore.cs
@@ -76,6 +76,7 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, ILocaliza
BuildingAIPatches.PrivateHandleWorkers,
BuildingAIPatches.CommercialSimulation,
ResidentAIPatch.Location,
+ ResidentAIPatch.ArriveAtDestination,
TouristAIPatch.Location,
UIGraphPatches.MinDataPoints,
UIGraphPatches.VisibleEndTime,
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index f88174c1..80c30631 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -233,6 +233,7 @@ private bool CitizenGoesWorking(TAI instance, uint citizenId, ref TCitizen citiz
}
else
{
+ residentStates[citizenId].DepartureTime = TimeInfo.Now;
residentAI.StartMoving(instance, citizenId, ref citizen, homeBuilding, workBuilding);
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.cs b/src/RealTime/CustomAI/RealTimeResidentAI.cs
index 175cba6a..9725555f 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.cs
@@ -115,6 +115,28 @@ public void UpdateLocation(TAI instance, uint citizenId, ref TCitizen citizen)
residentStates[citizenId].State = residentState;
}
+
+ /// Notifies that a citizen has arrived their destination.
+ /// The citizen ID to process.
+ public void RegisterCitizenArrival(uint citizenId)
+ {
+ if (citizenId == 0 || citizenId >= residentStates.Length)
+ {
+ return;
+ }
+
+ switch (CitizenMgr.GetCitizenLocation(citizenId))
+ {
+ case Citizen.Location.Work:
+ ref ResidentStateDescriptor state = ref residentStates[citizenId];
+ state.UpdateTravelTimeToWork(TimeInfo.Now);
+ state.DepartureTime = default;
+ Log.Debug($"The citizen {citizenId} arrived at work at {TimeInfo.Now} and needs {residentStates[citizenId].TravelTimeToWork} hours to get to work");
+ break;
+
+ default:
+ return;
+ }
}
private bool ShouldRealizeCitizen(TAI ai)
diff --git a/src/RealTime/CustomAI/ResidentStateDescriptor.cs b/src/RealTime/CustomAI/ResidentStateDescriptor.cs
index 9ef09542..d8eaebc7 100644
--- a/src/RealTime/CustomAI/ResidentStateDescriptor.cs
+++ b/src/RealTime/CustomAI/ResidentStateDescriptor.cs
@@ -30,7 +30,7 @@ internal struct ResidentStateDescriptor
///
public void UpdateTravelTimeToWork(DateTime arrivalTime)
{
- if (arrivalTime < DepartureTime)
+ if (arrivalTime < DepartureTime || DepartureTime == default)
{
return;
}
diff --git a/src/RealTime/GameConnection/CitizenManagerConnection.cs b/src/RealTime/GameConnection/CitizenManagerConnection.cs
index 375bb95a..c18fbea0 100644
--- a/src/RealTime/GameConnection/CitizenManagerConnection.cs
+++ b/src/RealTime/GameConnection/CitizenManagerConnection.cs
@@ -2,6 +2,7 @@
namespace RealTime.GameConnection
{
+ using System;
using UnityEngine;
/// The default implementation of the interface.
@@ -112,5 +113,19 @@ public uint GetMaxCitizensCount()
{
return CitizenManager.instance.m_citizens.m_size;
}
+
+ /// Gets the location of the citizen with specified ID.
+ /// The ID of the citizen to query location of.
+ /// A value that describes the citizen's current location.
+ /// Thrown when the argument is 0.
+ public Citizen.Location GetCitizenLocation(uint citizenId)
+ {
+ if (citizenId == 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(citizenId), "The citizen ID cannot be 0");
+ }
+
+ return CitizenManager.instance.m_citizens.m_buffer[citizenId].CurrentLocation;
+ }
}
}
\ No newline at end of file
diff --git a/src/RealTime/GameConnection/ICitizenManagerConnection.cs b/src/RealTime/GameConnection/ICitizenManagerConnection.cs
index e888a799..afdf5a9b 100644
--- a/src/RealTime/GameConnection/ICitizenManagerConnection.cs
+++ b/src/RealTime/GameConnection/ICitizenManagerConnection.cs
@@ -56,5 +56,11 @@ internal interface ICitizenManagerConnection
/// Gets the maximum count of the citizens.
/// The maximum number of the citizens.
uint GetMaxCitizensCount();
+
+ /// Gets the location of the citizen with specified ID.
+ /// The ID of the citizen to query location of.
+ /// A value that describes the citizen's current location.
+ /// Thrown when the argument is 0.
+ Citizen.Location GetCitizenLocation(uint citizenId);
}
}
\ No newline at end of file
diff --git a/src/RealTime/GameConnection/Patches/ResidentAIPatch.cs b/src/RealTime/GameConnection/Patches/ResidentAIPatch.cs
index 670f27ad..34fe20e9 100644
--- a/src/RealTime/GameConnection/Patches/ResidentAIPatch.cs
+++ b/src/RealTime/GameConnection/Patches/ResidentAIPatch.cs
@@ -21,6 +21,9 @@ internal static class ResidentAIPatch
/// Gets the patch object for the location method.
public static IPatch Location { get; } = new ResidentAI_UpdateLocation();
+ /// Gets the patch object for the arrive at destination method.
+ public static IPatch ArriveAtDestination { get; } = new HumanAI_ArriveAtDestination();
+
/// Creates a game connection object for the resident AI class.
/// A new object.
public static ResidentAIConnection GetResidentAIConnection()
@@ -92,5 +95,26 @@ private static bool Prefix(ResidentAI __instance, uint citizenID, ref Citizen da
}
#pragma warning restore SA1313 // Parameter names must begin with lower-case letter
}
+
+ private sealed class HumanAI_ArriveAtDestination : PatchBase
+ {
+ protected override MethodInfo GetMethod()
+ {
+ return typeof(HumanAI).GetMethod(
+ "ArriveAtDestination",
+ BindingFlags.Instance | BindingFlags.NonPublic,
+ null,
+ new[] { typeof(ushort), typeof(CitizenInstance).MakeByRefType(), typeof(bool) },
+ new ParameterModifier[0]);
+ }
+
+ private static void Postfix(ref CitizenInstance citizenData, bool success)
+ {
+ if (success && citizenData.Info.m_citizenAI is ResidentAI)
+ {
+ RealTimeAI?.RegisterCitizenArrival(citizenData.m_citizen);
+ }
+ }
+ }
}
}
\ No newline at end of file
From 3b433ed41fbaeaeb10f68e24f3a3c9a30d42f98e Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Mon, 16 Jul 2018 20:11:26 +0200
Subject: [PATCH 05/42] Make citizens remember the travel time needed to reach
the work building
---
src/RealTime/CustomAI/Constants.cs | 4 +--
.../CustomAI/RealTimeResidentAI.SchoolWork.cs | 35 +++++++++++++------
src/RealTime/CustomAI/RealTimeResidentAI.cs | 9 ++---
3 files changed, 32 insertions(+), 16 deletions(-)
diff --git a/src/RealTime/CustomAI/Constants.cs b/src/RealTime/CustomAI/Constants.cs
index bae2c3ab..9f0bfa7b 100644
--- a/src/RealTime/CustomAI/Constants.cs
+++ b/src/RealTime/CustomAI/Constants.cs
@@ -39,10 +39,10 @@ internal static class Constants
public const int TouristDoNothingProbability = 5000;
/// An assumed maximum on-the-way time to a target building.
- public const float MaxHoursOnTheWay = 2.5f;
+ public const float MaxHoursOnTheWay = 4f;
/// An assumed minimum on-the-way time to a target building.
- public const float MinHoursOnTheWay = 0.5f;
+ public const float MinHoursOnTheWay = 0.1f;
/// A minimum work shift duration in hours.
public const float MinimumWorkShiftDuration = 2f;
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index 80c30631..78011a9e 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -220,7 +220,7 @@ private bool CitizenGoesWorking(TAI instance, uint citizenId, ref TCitizen citiz
ushort workBuilding = CitizenProxy.GetWorkBuilding(ref citizen);
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
- if (!ShouldMoveToSchoolOrWork(citizenId, workBuilding, currentBuilding, CitizenProxy.GetAge(ref citizen)))
+ if (!ShouldMoveToSchoolOrWork(citizenId, homeBuilding, currentBuilding, workBuilding, CitizenProxy.GetAge(ref citizen)))
{
return false;
}
@@ -240,7 +240,7 @@ private bool CitizenGoesWorking(TAI instance, uint citizenId, ref TCitizen citiz
return true;
}
- private bool ShouldMoveToSchoolOrWork(uint citizenId, ushort workBuilding, ushort currentBuilding, Citizen.AgeGroup citizenAge)
+ private bool ShouldMoveToSchoolOrWork(uint citizenId, ushort homeBuilding, ushort currentBuilding, ushort workBuilding, Citizen.AgeGroup citizenAge)
{
if (workBuilding == 0 || citizenAge == Citizen.AgeGroup.Senior)
{
@@ -262,9 +262,13 @@ private bool ShouldMoveToSchoolOrWork(uint citizenId, ushort workBuilding, ushor
return false;
}
+ float travelTime = currentBuilding == homeBuilding
+ ? GetTravelTimeHomeToWork(citizenId, homeBuilding, workBuilding)
+ : 0;
+
if (citizenAge == Citizen.AgeGroup.Child || citizenAge == Citizen.AgeGroup.Teen)
{
- return ShouldMoveToSchoolOrWork(currentBuilding, workBuilding, Config.SchoolBegin, Config.SchoolEnd, 0);
+ return ShouldMoveToSchoolOrWork(workBuilding, Config.SchoolBegin, Config.SchoolEnd, 0, travelTime);
}
GetWorkShiftTimes(citizenId, buildingSevice, buildingSubService, out float workBeginHour, out float workEndHour);
@@ -273,12 +277,14 @@ private bool ShouldMoveToSchoolOrWork(uint citizenId, ushort workBuilding, ushor
return false;
}
- float overtime = Random.ShouldOccur(Config.OnTimeQuota) ? 0 : Config.MaxOvertime * Random.GetRandomValue(100u) / 200f;
+ float overtime = currentBuilding != homeBuilding || Random.ShouldOccur(Config.OnTimeQuota)
+ ? 0
+ : Config.MaxOvertime * Random.GetRandomValue(100u) / 200f;
- return ShouldMoveToSchoolOrWork(currentBuilding, workBuilding, workBeginHour, workEndHour, overtime);
+ return ShouldMoveToSchoolOrWork(workBuilding, workBeginHour, workEndHour, overtime, travelTime);
}
- private bool ShouldMoveToSchoolOrWork(ushort currentBuilding, ushort workBuilding, float workBeginHour, float workEndHour, float overtime)
+ private bool ShouldMoveToSchoolOrWork(ushort workBuilding, float workBeginHour, float workEndHour, float overtime, float travelTime)
{
float gotoHour = workBeginHour - overtime - MaxHoursOnTheWay;
if (gotoHour < 0)
@@ -298,10 +304,7 @@ private bool ShouldMoveToSchoolOrWork(ushort currentBuilding, ushort workBuildin
return false;
}
- float distance = BuildingMgr.GetDistanceBetweenBuildings(currentBuilding, workBuilding);
- float onTheWay = Mathf.Clamp(distance / OnTheWayDistancePerHour, MinHoursOnTheWay, MaxHoursOnTheWay);
-
- gotoHour = workBeginHour - overtime - onTheWay;
+ gotoHour = workBeginHour - overtime - travelTime;
if (gotoHour < 0)
{
gotoHour += 24f;
@@ -462,5 +465,17 @@ private void GetWorkShiftTimes(uint citizenId, ItemClass.Service sevice, ItemCla
beginHour = begin;
endHour = end;
}
+
+ private float GetTravelTimeHomeToWork(uint citizenId, ushort homeBuilding, ushort workBuilding)
+ {
+ float result = residentStates[citizenId].TravelTimeToWork;
+ if (result <= 0)
+ {
+ float distance = BuildingMgr.GetDistanceBetweenBuildings(homeBuilding, workBuilding);
+ result = Mathf.Clamp(distance / OnTheWayDistancePerHour, MinHoursOnTheWay, MaxHoursOnTheWay);
+ }
+
+ return result;
+ }
}
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.cs b/src/RealTime/CustomAI/RealTimeResidentAI.cs
index 9725555f..5aec3dec 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.cs
@@ -125,18 +125,19 @@ public void RegisterCitizenArrival(uint citizenId)
return;
}
+ ref ResidentStateDescriptor citizenState = ref residentStates[citizenId];
switch (CitizenMgr.GetCitizenLocation(citizenId))
{
case Citizen.Location.Work:
- ref ResidentStateDescriptor state = ref residentStates[citizenId];
- state.UpdateTravelTimeToWork(TimeInfo.Now);
- state.DepartureTime = default;
+ citizenState.UpdateTravelTimeToWork(TimeInfo.Now);
Log.Debug($"The citizen {citizenId} arrived at work at {TimeInfo.Now} and needs {residentStates[citizenId].TravelTimeToWork} hours to get to work");
break;
- default:
+ case Citizen.Location.Moving:
return;
}
+
+ citizenState.DepartureTime = default;
}
private bool ShouldRealizeCitizen(TAI ai)
From 7d555e3d737e57e01c2856846636ee58547e52f0 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Wed, 18 Jul 2018 18:30:09 +0200
Subject: [PATCH 06/42] Disable teleporting virtual citizens
- teleporting might cause issues with pathfinding and parked cars
- virtual citizens stay at home
- citizens that are not at home are always real
- one exception from above rule: moving citizens without instances
---
src/RealTime/CustomAI/RealTimeHumanAIBase.cs | 8 +-
.../CustomAI/RealTimeResidentAI.Common.cs | 14 +--
.../CustomAI/RealTimeResidentAI.Home.cs | 12 +--
.../CustomAI/RealTimeResidentAI.Moving.cs | 6 +-
.../CustomAI/RealTimeResidentAI.SchoolWork.cs | 49 ++++-------
.../CustomAI/RealTimeResidentAI.Visit.cs | 85 +++++++++----------
src/RealTime/CustomAI/RealTimeResidentAI.cs | 18 ++--
src/RealTime/CustomAI/RealTimeTouristAI.cs | 70 +++++----------
8 files changed, 104 insertions(+), 158 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
index 366babb6..b0e85f9f 100644
--- a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
+++ b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
@@ -266,7 +266,7 @@ protected bool EnsureCitizenCanBeProcessed(uint citizenId, ref TCitizen citizen)
if (CitizenProxy.IsCollapsed(ref citizen))
{
- Log.Debug($"{GetCitizenDesc(citizenId, ref citizen, false)} is collapsed, doing nothing...");
+ Log.Debug($"{GetCitizenDesc(citizenId, ref citizen)} is collapsed, doing nothing...");
return false;
}
@@ -323,18 +323,16 @@ protected void FindEvacuationPlace(uint citizenId, TransferManager.TransferReaso
///
/// The citizen ID.
/// The citizen data reference.
- /// true if the citizen is in a virtual mode; otherwise, false.
///
/// A short string describing the provided citizen.
- protected string GetCitizenDesc(uint citizenId, ref TCitizen citizen, bool? isVirtual)
+ protected string GetCitizenDesc(uint citizenId, ref TCitizen citizen)
{
ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
string home = homeBuilding == 0 ? "homeless" : "lives at " + homeBuilding;
ushort workBuilding = CitizenProxy.GetWorkBuilding(ref citizen);
string employment = workBuilding == 0 ? "unemployed" : "works at " + workBuilding;
Citizen.Location location = CitizenProxy.GetLocation(ref citizen);
- string virt = isVirtual.HasValue ? (isVirtual.Value ? " (virtual)" : " (real)") : null;
- return $"Citizen {citizenId} ({CitizenProxy.GetAge(ref citizen)}, {home}, {employment}, currently {location} at {CitizenProxy.GetCurrentBuilding(ref citizen)}) / instance {CitizenProxy.GetInstance(ref citizen)}{virt}";
+ return $"Citizen {citizenId} ({CitizenProxy.GetAge(ref citizen)}, {home}, {employment}, currently {location} at {CitizenProxy.GetCurrentBuilding(ref citizen)}) / instance {CitizenProxy.GetInstance(ref citizen)}";
}
/// Determines whether the specified citizen must be processed as a virtual citizen.
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
index d01b60b4..4fca304f 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
@@ -15,7 +15,7 @@ private void ProcessCitizenDead(TAI instance, uint citizenId, ref TCitizen citiz
if (currentBuilding == 0 || (currentLocation == Citizen.Location.Moving && CitizenProxy.GetVehicle(ref citizen) == 0))
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, false)} is released");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is released");
CitizenMgr.ReleaseCitizen(citizenId);
return;
}
@@ -47,7 +47,7 @@ private void ProcessCitizenDead(TAI instance, uint citizenId, ref TCitizen citiz
}
residentAI.FindHospital(instance, citizenId, currentBuilding, TransferManager.TransferReason.Dead);
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, false)} is dead, body should get serviced");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is dead, body should get serviced");
}
private bool ProcessCitizenArrested(ref TCitizen citizen)
@@ -77,7 +77,7 @@ private bool ProcessCitizenSick(TAI instance, uint citizenId, ref TCitizen citiz
if (currentLocation != Citizen.Location.Home && currentBuilding == 0)
{
- Log.Debug($"Teleporting {GetCitizenDesc(citizenId, ref citizen, false)} back home because they are sick but no building is specified");
+ Log.Debug($"Teleporting {GetCitizenDesc(citizenId, ref citizen)} back home because they are sick but no building is specified");
CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
return true;
}
@@ -96,7 +96,7 @@ private bool ProcessCitizenSick(TAI instance, uint citizenId, ref TCitizen citiz
}
}
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, false)} is sick, trying to get to a hospital");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is sick, trying to get to a hospital");
residentAI.FindHospital(instance, citizenId, currentBuilding, TransferManager.TransferReason.Sick);
return true;
}
@@ -106,12 +106,12 @@ private void ProcessCitizenEvacuation(TAI instance, uint citizenId, ref TCitizen
ushort building = CitizenProxy.GetCurrentBuilding(ref citizen);
if (building != 0)
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, false)} is trying to find an evacuation place");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is trying to find an evacuation place");
residentAI.FindEvacuationPlace(instance, citizenId, building, residentAI.GetEvacuationReason(instance, building));
}
}
- private bool StartMovingToVisitBuilding(TAI instance, uint citizenId, ref TCitizen citizen, ushort visitBuilding, bool isVirtual)
+ private bool StartMovingToVisitBuilding(TAI instance, uint citizenId, ref TCitizen citizen, ushort visitBuilding)
{
if (visitBuilding == 0)
{
@@ -120,7 +120,7 @@ private bool StartMovingToVisitBuilding(TAI instance, uint citizenId, ref TCitiz
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
- if (isVirtual || currentBuilding == visitBuilding)
+ if (currentBuilding == visitBuilding)
{
CitizenProxy.SetVisitPlace(ref citizen, citizenId, visitBuilding);
CitizenProxy.SetVisitBuilding(ref citizen, visitBuilding);
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Home.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Home.cs
index af3ab18f..26201c8a 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Home.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Home.cs
@@ -9,11 +9,11 @@ namespace RealTime.CustomAI
internal sealed partial class RealTimeResidentAI
{
- private void ProcessCitizenAtHome(TAI instance, uint citizenId, ref TCitizen citizen, bool isVirtual)
+ private void ProcessCitizenAtHome(TAI instance, uint citizenId, ref TCitizen citizen)
{
if (CitizenProxy.GetHomeBuilding(ref citizen) == 0)
{
- Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen, isVirtual)} is in corrupt state: at home with no home building. Releasing the poor citizen.");
+ Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is in corrupt state: at home with no home building. Releasing the poor citizen.");
CitizenMgr.ReleaseCitizen(citizenId);
return;
}
@@ -21,11 +21,11 @@ private void ProcessCitizenAtHome(TAI instance, uint citizenId, ref TCitizen cit
ushort vehicle = CitizenProxy.GetVehicle(ref citizen);
if (vehicle != 0)
{
- Log.Debug(TimeInfo.Now, $"WARNING: {GetCitizenDesc(citizenId, ref citizen, isVirtual)} is at home but vehicle = {vehicle}");
+ Log.Debug(TimeInfo.Now, $"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is at home but vehicle = {vehicle}");
return;
}
- if (CitizenGoesWorking(instance, citizenId, ref citizen, isVirtual))
+ if (CitizenGoesWorking(instance, citizenId, ref citizen))
{
return;
}
@@ -35,12 +35,12 @@ private void ProcessCitizenAtHome(TAI instance, uint citizenId, ref TCitizen cit
return;
}
- if (CitizenGoesShopping(instance, citizenId, ref citizen, isVirtual) || CitizenGoesToEvent(instance, citizenId, ref citizen, isVirtual))
+ if (CitizenGoesShopping(instance, citizenId, ref citizen) || CitizenGoesToEvent(instance, citizenId, ref citizen))
{
return;
}
- CitizenGoesRelaxing(instance, citizenId, ref citizen, isVirtual);
+ CitizenGoesRelaxing(instance, citizenId, ref citizen);
}
private bool IsBusyAtHomeInTheMorning(Citizen.AgeGroup citizenAge)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
index fe7adf45..9d47fd56 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
@@ -26,8 +26,6 @@ private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen cit
}
else
{
- // TODO: check whether this makes sense and maybe remove/replace this logic.
- // Don't know why the original game does this...
CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
CitizenProxy.SetArrested(ref citizen, false);
}
@@ -37,7 +35,7 @@ private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen cit
if (vehicleId == 0 && CitizenMgr.IsAreaEvacuating(instanceId) && !CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating))
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, false)} was on the way, but the area evacuates. Finding an evacuation place.");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} was on the way, but the area evacuates. Finding an evacuation place.");
TransferMgr.AddOutgoingOfferFromCurrentPosition(citizenId, residentAI.GetEvacuationReason(instance, 0));
return;
}
@@ -54,7 +52,7 @@ private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen cit
return;
}
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, false)} cancels the trip to a park due to bad weather");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} cancels the trip to a park due to bad weather");
ushort home = CitizenProxy.GetHomeBuilding(ref citizen);
if (home != 0)
{
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index 78011a9e..55b00908 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -162,12 +162,12 @@ private void CalculateWorkShiftValues()
nightShiftValue = Config.NightShiftQuota - 1;
}
- private void ProcessCitizenAtSchoolOrWork(TAI instance, uint citizenId, ref TCitizen citizen, bool isVirtual)
+ private void ProcessCitizenAtSchoolOrWork(TAI instance, uint citizenId, ref TCitizen citizen)
{
ushort workBuilding = CitizenProxy.GetWorkBuilding(ref citizen);
if (workBuilding == 0)
{
- Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen, isVirtual)} is in corrupt state: at school/work with no work building. Teleporting home.");
+ Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is in corrupt state: at school/work with no work building. Teleporting home.");
CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
return;
}
@@ -175,15 +175,15 @@ private void ProcessCitizenAtSchoolOrWork(TAI instance, uint citizenId, ref TCit
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
if (ShouldGoToLunch(CitizenProxy.GetAge(ref citizen), citizenId))
{
- ushort lunchPlace = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance, isVirtual);
+ ushort lunchPlace = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance);
if (lunchPlace != 0)
{
residentStates[citizenId].WorkStatus = WorkStatus.AtLunch;
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} is going for lunch from {currentBuilding} to {lunchPlace}");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going for lunch from {currentBuilding} to {lunchPlace}");
}
else
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} wanted to go for lunch from {currentBuilding}, but there were no buildings close enough");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted to go for lunch from {currentBuilding}, but there were no buildings close enough");
}
return;
@@ -194,27 +194,20 @@ private void ProcessCitizenAtSchoolOrWork(TAI instance, uint citizenId, ref TCit
return;
}
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} leaves their workplace {workBuilding}");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} leaves their workplace {workBuilding}");
- if (CitizenGoesToEvent(instance, citizenId, ref citizen, isVirtual))
+ if (CitizenGoesToEvent(instance, citizenId, ref citizen))
{
return;
}
- if (!CitizenGoesShopping(instance, citizenId, ref citizen, isVirtual) && !CitizenGoesRelaxing(instance, citizenId, ref citizen, isVirtual))
+ if (!CitizenGoesShopping(instance, citizenId, ref citizen) && !CitizenGoesRelaxing(instance, citizenId, ref citizen))
{
- if (isVirtual)
- {
- CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
- }
- else
- {
- residentAI.StartMoving(instance, citizenId, ref citizen, workBuilding, CitizenProxy.GetHomeBuilding(ref citizen));
- }
+ residentAI.StartMoving(instance, citizenId, ref citizen, workBuilding, CitizenProxy.GetHomeBuilding(ref citizen));
}
}
- private bool CitizenGoesWorking(TAI instance, uint citizenId, ref TCitizen citizen, bool isVirtual)
+ private bool CitizenGoesWorking(TAI instance, uint citizenId, ref TCitizen citizen)
{
ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
ushort workBuilding = CitizenProxy.GetWorkBuilding(ref citizen);
@@ -225,17 +218,11 @@ private bool CitizenGoesWorking(TAI instance, uint citizenId, ref TCitizen citiz
return false;
}
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} is going from {currentBuilding} to school/work {workBuilding}");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going from {currentBuilding} to school/work {workBuilding}");
- if (isVirtual)
- {
- CitizenProxy.SetLocation(ref citizen, Citizen.Location.Work);
- }
- else
- {
- residentStates[citizenId].DepartureTime = TimeInfo.Now;
- residentAI.StartMoving(instance, citizenId, ref citizen, homeBuilding, workBuilding);
- }
+ ref ResidentStateDescriptor state = ref residentStates[citizenId];
+ state.DepartureTime = TimeInfo.Now;
+ residentAI.StartMoving(instance, citizenId, ref citizen, homeBuilding, workBuilding);
return true;
}
@@ -403,7 +390,7 @@ private bool ShouldGoToLunch(Citizen.AgeGroup citizenAge, uint citizenId)
return false;
}
- private bool CitizenReturnsFromLunch(TAI instance, uint citizenId, ref TCitizen citizen, bool isVirtual)
+ private bool CitizenReturnsFromLunch(TAI instance, uint citizenId, ref TCitizen citizen)
{
if (IsLunchHour)
{
@@ -413,12 +400,12 @@ private bool CitizenReturnsFromLunch(TAI instance, uint citizenId, ref TCitizen
ushort workBuilding = CitizenProxy.GetWorkBuilding(ref citizen);
if (workBuilding != 0)
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} returning from lunch to {workBuilding}");
- ReturnFromVisit(instance, citizenId, ref citizen, workBuilding, Citizen.Location.Work, isVirtual);
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} returning from lunch to {workBuilding}");
+ ReturnFromVisit(instance, citizenId, ref citizen, workBuilding, Citizen.Location.Work);
}
else
{
- Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen, isVirtual)} is at lunch but no work building. Teleporting home.");
+ Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is at lunch but no work building. Teleporting home.");
CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
index 66db57ff..03d5776c 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
@@ -10,12 +10,12 @@ namespace RealTime.CustomAI
internal sealed partial class RealTimeResidentAI
{
- private void ProcessCitizenVisit(TAI instance, ResidentState citizenState, uint citizenId, ref TCitizen citizen, bool isVirtual)
+ private void ProcessCitizenVisit(TAI instance, ResidentState citizenState, uint citizenId, ref TCitizen citizen)
{
ushort currentBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
if (currentBuilding == 0)
{
- Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen, isVirtual)} is in corrupt state: visiting with no visit building. Teleporting home.");
+ Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is in corrupt state: visiting with no visit building. Teleporting home.");
CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
return;
}
@@ -23,7 +23,7 @@ private void ProcessCitizenVisit(TAI instance, ResidentState citizenState, uint
switch (citizenState)
{
case ResidentState.AtLunch:
- CitizenReturnsFromLunch(instance, citizenId, ref citizen, isVirtual);
+ CitizenReturnsFromLunch(instance, citizenId, ref citizen);
return;
case ResidentState.AtLeisureArea:
@@ -37,9 +37,9 @@ private void ProcessCitizenVisit(TAI instance, ResidentState citizenState, uint
goto case ResidentState.Visiting;
case ResidentState.Visiting:
- if (!CitizenGoesWorking(instance, citizenId, ref citizen, isVirtual))
+ if (!CitizenGoesWorking(instance, citizenId, ref citizen))
{
- CitizenReturnsHomeFromVisit(instance, citizenId, ref citizen, isVirtual);
+ CitizenReturnsHomeFromVisit(instance, citizenId, ref citizen);
}
return;
@@ -51,16 +51,16 @@ private void ProcessCitizenVisit(TAI instance, ResidentState citizenState, uint
CitizenProxy.RemoveFlags(ref citizen, Citizen.Flags.NeedGoods);
}
- if (CitizenGoesWorking(instance, citizenId, ref citizen, isVirtual)
- || CitizenGoesToEvent(instance, citizenId, ref citizen, isVirtual))
+ if (CitizenGoesWorking(instance, citizenId, ref citizen)
+ || CitizenGoesToEvent(instance, citizenId, ref citizen))
{
return;
}
if (Random.ShouldOccur(ReturnFromShoppingChance) || IsWorkDayMorning(CitizenProxy.GetAge(ref citizen)))
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} returning from shopping at {currentBuilding} back home");
- ReturnFromVisit(instance, citizenId, ref citizen, CitizenProxy.GetHomeBuilding(ref citizen), Citizen.Location.Home, isVirtual);
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} returning from shopping at {currentBuilding} back home");
+ ReturnFromVisit(instance, citizenId, ref citizen, CitizenProxy.GetHomeBuilding(ref citizen), Citizen.Location.Home);
}
return;
@@ -77,12 +77,12 @@ private void ProcessCitizenOnTour(TAI instance, uint citizenId, ref TCitizen cit
ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
if (homeBuilding != 0)
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, false)} exits a guided tour and moves back home.");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} exits a guided tour and moves back home.");
residentAI.StartMoving(instance, citizenId, ref citizen, 0, homeBuilding);
}
}
- private bool CitizenReturnsFromShelter(TAI instance, uint citizenId, ref TCitizen citizen, bool isVirtual)
+ private bool CitizenReturnsFromShelter(TAI instance, uint citizenId, ref TCitizen citizen)
{
ushort visitBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
if (BuildingMgr.GetBuildingService(visitBuilding) != ItemClass.Service.Disaster)
@@ -98,17 +98,17 @@ private bool CitizenReturnsFromShelter(TAI instance, uint citizenId, ref TCitize
ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
if (homeBuilding == 0)
{
- Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen, isVirtual)} was in a shelter but seems to be homeless. Releasing the citizen.");
+ Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} was in a shelter but seems to be homeless. Releasing the citizen.");
CitizenMgr.ReleaseCitizen(citizenId);
return true;
}
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} returning from evacuation place {visitBuilding} back home");
- ReturnFromVisit(instance, citizenId, ref citizen, homeBuilding, Citizen.Location.Home, isVirtual);
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} returning from evacuation place {visitBuilding} back home");
+ ReturnFromVisit(instance, citizenId, ref citizen, homeBuilding, Citizen.Location.Home);
return true;
}
- private bool CitizenReturnsHomeFromVisit(TAI instance, uint citizenId, ref TCitizen citizen, bool isVirtual)
+ private bool CitizenReturnsHomeFromVisit(TAI instance, uint citizenId, ref TCitizen citizen)
{
ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
if (homeBuilding == 0 || CitizenProxy.GetVehicle(ref citizen) != 0)
@@ -124,8 +124,8 @@ private bool CitizenReturnsHomeFromVisit(TAI instance, uint citizenId, ref TCiti
return false;
case CityEventState.Finished:
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} returning from an event at {visitBuilding} back home to {homeBuilding}");
- ReturnFromVisit(instance, citizenId, ref citizen, homeBuilding, Citizen.Location.Home, isVirtual);
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} returning from an event at {visitBuilding} back home to {homeBuilding}");
+ ReturnFromVisit(instance, citizenId, ref citizen, homeBuilding, Citizen.Location.Home);
return true;
}
@@ -133,8 +133,8 @@ private bool CitizenReturnsHomeFromVisit(TAI instance, uint citizenId, ref TCiti
if (Random.ShouldOccur(ReturnFromVisitChance) ||
(visitedSubService == ItemClass.SubService.CommercialLeisure && TimeInfo.IsNightTime && BuildingMgr.IsBuildingNoiseRestricted(visitBuilding)))
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} returning from visit back home");
- ReturnFromVisit(instance, citizenId, ref citizen, homeBuilding, Citizen.Location.Home, isVirtual);
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} returning from visit back home");
+ ReturnFromVisit(instance, citizenId, ref citizen, homeBuilding, Citizen.Location.Home);
return true;
}
@@ -146,8 +146,7 @@ private void ReturnFromVisit(
uint citizenId,
ref TCitizen citizen,
ushort targetBuilding,
- Citizen.Location targetLocation,
- bool isVirtual)
+ Citizen.Location targetLocation)
{
if (targetBuilding == 0 || targetLocation == Citizen.Location.Visit || CitizenProxy.GetVehicle(ref citizen) != 0)
{
@@ -159,7 +158,7 @@ private void ReturnFromVisit(
CitizenProxy.RemoveFlags(ref citizen, Citizen.Flags.Evacuating);
CitizenProxy.SetVisitPlace(ref citizen, citizenId, 0);
- if (isVirtual || targetBuilding == currentBuilding)
+ if (targetBuilding == currentBuilding)
{
CitizenProxy.SetLocation(ref citizen, targetLocation);
}
@@ -169,7 +168,7 @@ private void ReturnFromVisit(
}
}
- private bool CitizenGoesShopping(TAI instance, uint citizenId, ref TCitizen citizen, bool isVirtual)
+ private bool CitizenGoesShopping(TAI instance, uint citizenId, ref TCitizen citizen)
{
if (!CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods) || IsBadWeather(citizenId))
{
@@ -180,8 +179,8 @@ private bool CitizenGoesShopping(TAI instance, uint citizenId, ref TCitizen citi
{
if (Random.ShouldOccur(GetGoOutChance(CitizenProxy.GetAge(ref citizen))))
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} wanna go shopping at night");
- ushort localVisitPlace = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance, isVirtual);
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna go shopping at night");
+ ushort localVisitPlace = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance);
Log.DebugIf(localVisitPlace != 0, $"Citizen {citizenId} is going shopping at night to a local shop {localVisitPlace}");
return localVisitPlace > 0;
}
@@ -196,8 +195,8 @@ private bool CitizenGoesShopping(TAI instance, uint citizenId, ref TCitizen citi
if (Random.ShouldOccur(Config.LocalBuildingSearchQuota))
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} wanna go shopping");
- localVisitPlace = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance, isVirtual);
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna go shopping");
+ localVisitPlace = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance);
Log.DebugIf(localVisitPlace != 0, $"Citizen {citizenId} is going shopping to a local shop {localVisitPlace}");
}
@@ -205,11 +204,11 @@ private bool CitizenGoesShopping(TAI instance, uint citizenId, ref TCitizen citi
{
if (localOnly)
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} wanna go shopping, but didn't find a local shop");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna go shopping, but didn't find a local shop");
return false;
}
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} wanna go shopping, heading to a random shop");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna go shopping, heading to a random shop");
residentAI.FindVisitPlace(instance, citizenId, CitizenProxy.GetHomeBuilding(ref citizen), residentAI.GetShoppingReason(instance));
}
@@ -219,7 +218,7 @@ private bool CitizenGoesShopping(TAI instance, uint citizenId, ref TCitizen citi
return false;
}
- private bool CitizenGoesToEvent(TAI instance, uint citizenId, ref TCitizen citizen, bool isVirtual)
+ private bool CitizenGoesToEvent(TAI instance, uint citizenId, ref TCitizen citizen)
{
if (!Random.ShouldOccur(GetGoOutChance(CitizenProxy.GetAge(ref citizen))) || IsBadWeather(citizenId))
{
@@ -231,11 +230,11 @@ private bool CitizenGoesToEvent(TAI instance, uint citizenId, ref TCitizen citiz
return false;
}
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} wanna attend an event at '{buildingId}', on the way now.");
- return StartMovingToVisitBuilding(instance, citizenId, ref citizen, buildingId, isVirtual);
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna attend an event at '{buildingId}', on the way now.");
+ return StartMovingToVisitBuilding(instance, citizenId, ref citizen, buildingId);
}
- private bool CitizenGoesRelaxing(TAI instance, uint citizenId, ref TCitizen citizen, bool isVirtual)
+ private bool CitizenGoesRelaxing(TAI instance, uint citizenId, ref TCitizen citizen)
{
Citizen.AgeGroup citizenAge = CitizenProxy.GetAge(ref citizen);
if (!Random.ShouldOccur(GetGoOutChance(citizenAge)) || IsBadWeather(citizenId))
@@ -251,8 +250,8 @@ private bool CitizenGoesRelaxing(TAI instance, uint citizenId, ref TCitizen citi
if (TimeInfo.IsNightTime)
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} wanna relax at night");
- ushort leisure = MoveToLeisure(instance, citizenId, ref citizen, buildingId, isVirtual);
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna relax at night");
+ ushort leisure = MoveToLeisure(instance, citizenId, ref citizen, buildingId);
Log.DebugIf(leisure != 0, $"Citizen {citizenId} is heading to leisure building {leisure}");
return leisure != 0;
}
@@ -262,16 +261,12 @@ private bool CitizenGoesRelaxing(TAI instance, uint citizenId, ref TCitizen citi
return false;
}
- if (!isVirtual)
- {
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} wanna relax, heading to an entertainment place");
- residentAI.FindVisitPlace(instance, citizenId, buildingId, residentAI.GetEntertainmentReason(instance));
- }
-
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna relax, heading to an entertainment place");
+ residentAI.FindVisitPlace(instance, citizenId, buildingId, residentAI.GetEntertainmentReason(instance));
return true;
}
- private ushort MoveToCommercialBuilding(TAI instance, uint citizenId, ref TCitizen citizen, float distance, bool isVirtual)
+ private ushort MoveToCommercialBuilding(TAI instance, uint citizenId, ref TCitizen citizen, float distance)
{
ushort buildingId = CitizenProxy.GetCurrentBuilding(ref citizen);
if (buildingId == 0)
@@ -286,7 +281,7 @@ private ushort MoveToCommercialBuilding(TAI instance, uint citizenId, ref TCitiz
return 0;
}
- if (StartMovingToVisitBuilding(instance, citizenId, ref citizen, foundBuilding, isVirtual))
+ if (StartMovingToVisitBuilding(instance, citizenId, ref citizen, foundBuilding))
{
ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
uint homeUnit = BuildingMgr.GetCitizenUnit(homeBuilding);
@@ -300,7 +295,7 @@ private ushort MoveToCommercialBuilding(TAI instance, uint citizenId, ref TCitiz
return foundBuilding;
}
- private ushort MoveToLeisure(TAI instance, uint citizenId, ref TCitizen citizen, ushort buildingId, bool isVirtual)
+ private ushort MoveToLeisure(TAI instance, uint citizenId, ref TCitizen citizen, ushort buildingId)
{
ushort leisureBuilding = BuildingMgr.FindActiveBuilding(
buildingId,
@@ -314,7 +309,7 @@ private ushort MoveToLeisure(TAI instance, uint citizenId, ref TCitizen citizen,
return 0;
}
- StartMovingToVisitBuilding(instance, citizenId, ref citizen, leisureBuilding, isVirtual);
+ StartMovingToVisitBuilding(instance, citizenId, ref citizen, leisureBuilding);
return leisureBuilding;
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.cs b/src/RealTime/CustomAI/RealTimeResidentAI.cs
index 5aec3dec..6504c6cf 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.cs
@@ -60,17 +60,14 @@ public void UpdateLocation(TAI instance, uint citizenId, ref TCitizen citizen)
}
ResidentState residentState = GetResidentState(citizenId, ref citizen);
- bool isVirtual;
-
switch (residentState)
{
case ResidentState.MovingHome:
ProcessCitizenMoving(instance, citizenId, ref citizen, false);
break;
- case ResidentState.AtHome:
- isVirtual = IsCitizenVirtual(instance, ref citizen, ShouldRealizeCitizen);
- ProcessCitizenAtHome(instance, citizenId, ref citizen, isVirtual);
+ case ResidentState.AtHome when !IsCitizenVirtual(instance, ref citizen, ShouldRealizeCitizen):
+ ProcessCitizenAtHome(instance, citizenId, ref citizen);
break;
case ResidentState.MovingToTarget:
@@ -78,16 +75,14 @@ public void UpdateLocation(TAI instance, uint citizenId, ref TCitizen citizen)
break;
case ResidentState.AtSchoolOrWork:
- isVirtual = IsCitizenVirtual(instance, ref citizen, ShouldRealizeCitizen);
- ProcessCitizenAtSchoolOrWork(instance, citizenId, ref citizen, isVirtual);
+ ProcessCitizenAtSchoolOrWork(instance, citizenId, ref citizen);
break;
case ResidentState.AtLunch:
case ResidentState.Shopping:
case ResidentState.AtLeisureArea:
case ResidentState.Visiting:
- isVirtual = IsCitizenVirtual(instance, ref citizen, ShouldRealizeCitizen);
- ProcessCitizenVisit(instance, residentState, citizenId, ref citizen, isVirtual);
+ ProcessCitizenVisit(instance, residentState, citizenId, ref citizen);
break;
case ResidentState.OnTour:
@@ -99,12 +94,11 @@ public void UpdateLocation(TAI instance, uint citizenId, ref TCitizen citizen)
break;
case ResidentState.InShelter:
- isVirtual = IsCitizenVirtual(instance, ref citizen, ShouldRealizeCitizen);
- CitizenReturnsFromShelter(instance, citizenId, ref citizen, isVirtual);
+ CitizenReturnsFromShelter(instance, citizenId, ref citizen);
break;
case ResidentState.Unknown:
- Log.Debug(TimeInfo.Now, $"WARNING: {GetCitizenDesc(citizenId, ref citizen, null)} is in an UNKNOWN state! Teleporting back home");
+ Log.Debug(TimeInfo.Now, $"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is in an UNKNOWN state! Teleporting back home");
if (CitizenProxy.GetHomeBuilding(ref citizen) != 0)
{
CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
diff --git a/src/RealTime/CustomAI/RealTimeTouristAI.cs b/src/RealTime/CustomAI/RealTimeTouristAI.cs
index 0a792a7f..e5f00a4e 100644
--- a/src/RealTime/CustomAI/RealTimeTouristAI.cs
+++ b/src/RealTime/CustomAI/RealTimeTouristAI.cs
@@ -71,7 +71,7 @@ public void UpdateLocation(TAI instance, uint citizenId, ref TCitizen citizen)
break;
case Citizen.Location.Visit:
- ProcessVisit(instance, citizenId, ref citizen, IsCitizenVirtual(instance, ref citizen, ShouldRealizeCitizen));
+ ProcessVisit(instance, citizenId, ref citizen);
break;
case Citizen.Location.Moving:
@@ -97,7 +97,7 @@ private void ProcessMoving(TAI instance, uint citizenId, ref TCitizen citizen)
if (vehicleId == 0 && CitizenMgr.IsAreaEvacuating(instanceId) && !CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating))
{
- Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen, false)} was on the way, but the area evacuates. Leaving the city.");
+ Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} was on the way, but the area evacuates. Leaving the city.");
touristAI.FindVisitPlace(instance, citizenId, CitizenProxy.GetCurrentBuilding(ref citizen), touristAI.GetLeavingReason(instance, citizenId, ref citizen));
return;
}
@@ -105,20 +105,20 @@ private void ProcessMoving(TAI instance, uint citizenId, ref TCitizen citizen)
bool badWeather = IsBadWeather(citizenId);
if (CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour, true))
{
- Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen, false)} exits the guided tour.");
+ Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} exits the guided tour.");
if (!badWeather)
{
- FindRandomVisitPlace(instance, citizenId, ref citizen, TouristDoNothingProbability, 0, false);
+ FindRandomVisitPlace(instance, citizenId, ref citizen, TouristDoNothingProbability, 0);
}
}
if (badWeather)
{
- FindHotel(instance, citizenId, ref citizen, false);
+ FindHotel(instance, citizenId, ref citizen);
}
}
- private void ProcessVisit(TAI instance, uint citizenId, ref TCitizen citizen, bool isVirtual)
+ private void ProcessVisit(TAI instance, uint citizenId, ref TCitizen citizen)
{
ushort visitBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
if (visitBuilding == 0)
@@ -138,7 +138,7 @@ private void ProcessVisit(TAI instance, uint citizenId, ref TCitizen citizen, bo
case ItemClass.Service.Disaster:
if (BuildingMgr.BuildingHasFlags(visitBuilding, Building.Flags.Downgrading))
{
- FindRandomVisitPlace(instance, citizenId, ref citizen, 0, visitBuilding, false);
+ FindRandomVisitPlace(instance, citizenId, ref citizen, 0, visitBuilding);
}
return;
@@ -153,8 +153,8 @@ private void ProcessVisit(TAI instance, uint citizenId, ref TCitizen citizen, bo
&& !IsBadWeather(citizenId)
&& AttendUpcomingEvent(citizenId, ref citizen, out ushort eventBuilding))
{
- StartMovingToVisitBuilding(instance, citizenId, ref citizen, CitizenProxy.GetCurrentBuilding(ref citizen), eventBuilding, isVirtual);
- Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen, isVirtual)} attending an event at {eventBuilding}");
+ StartMovingToVisitBuilding(instance, citizenId, ref citizen, CitizenProxy.GetCurrentBuilding(ref citizen), eventBuilding);
+ Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} attending an event at {eventBuilding}");
return;
}
@@ -178,34 +178,22 @@ private void ProcessVisit(TAI instance, uint citizenId, ref TCitizen citizen, bo
break;
}
- FindRandomVisitPlace(instance, citizenId, ref citizen, doNothingChance, visitBuilding, isVirtual);
+ FindRandomVisitPlace(instance, citizenId, ref citizen, doNothingChance, visitBuilding);
}
- private void FindRandomVisitPlace(TAI instance, uint citizenId, ref TCitizen citizen, int doNothingProbability, ushort currentBuilding, bool isVirtual)
+ private void FindRandomVisitPlace(TAI instance, uint citizenId, ref TCitizen citizen, int doNothingProbability, ushort currentBuilding)
{
int targetType = touristAI.GetRandomTargetType(instance, doNothingProbability);
if (targetType == 1)
{
- Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen, isVirtual)} decides to leave the city");
+ Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} decides to leave the city");
touristAI.FindVisitPlace(instance, citizenId, currentBuilding, touristAI.GetLeavingReason(instance, citizenId, ref citizen));
return;
}
if (!Random.ShouldOccur(GetGoOutChance(CitizenProxy.GetAge(ref citizen))) || IsBadWeather(citizenId))
{
- FindHotel(instance, citizenId, ref citizen, isVirtual);
- return;
- }
-
- if (isVirtual)
- {
- if (Random.ShouldOccur(TouristShoppingChance) && BuildingMgr.GetBuildingService(currentBuilding) == ItemClass.Service.Commercial)
- {
- BuildingMgr.ModifyMaterialBuffer(currentBuilding, TransferManager.TransferReason.Shopping, -ShoppingGoodsAmount);
- }
-
- touristAI.AddTouristVisit(instance, citizenId, currentBuilding);
-
+ FindHotel(instance, citizenId, ref citizen);
return;
}
@@ -213,22 +201,22 @@ private void FindRandomVisitPlace(TAI instance, uint citizenId, ref TCitizen cit
{
case 2:
touristAI.FindVisitPlace(instance, citizenId, currentBuilding, touristAI.GetShoppingReason(instance));
- Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen, isVirtual)} stays in the city, goes shopping");
+ Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} stays in the city, goes shopping");
break;
case 3:
- Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen, isVirtual)} stays in the city, goes relaxing");
+ Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} stays in the city, goes relaxing");
touristAI.FindVisitPlace(instance, citizenId, currentBuilding, touristAI.GetEntertainmentReason(instance));
break;
}
}
- private void FindHotel(TAI instance, uint citizenId, ref TCitizen citizen, bool isVirtual)
+ private void FindHotel(TAI instance, uint citizenId, ref TCitizen citizen)
{
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
if (!Random.ShouldOccur(FindHotelChance))
{
- Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen, isVirtual)} didn't want to stay in a hotel, leaving the city");
+ Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} didn't want to stay in a hotel, leaving the city");
touristAI.FindVisitPlace(instance, citizenId, currentBuilding, touristAI.GetLeavingReason(instance, citizenId, ref citizen));
return;
}
@@ -241,34 +229,20 @@ private void FindHotel(TAI instance, uint citizenId, ref TCitizen citizen, bool
if (hotel == 0)
{
- Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen, isVirtual)} didn't find a hotel, leaving the city");
+ Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} didn't find a hotel, leaving the city");
touristAI.FindVisitPlace(instance, citizenId, currentBuilding, touristAI.GetLeavingReason(instance, citizenId, ref citizen));
return;
}
- StartMovingToVisitBuilding(instance, citizenId, ref citizen, currentBuilding, hotel, isVirtual);
- Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen, isVirtual)} stays in a hotel {hotel}");
+ StartMovingToVisitBuilding(instance, citizenId, ref citizen, currentBuilding, hotel);
+ Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} stays in a hotel {hotel}");
}
- private void StartMovingToVisitBuilding(TAI instance, uint citizenId, ref TCitizen citizen, ushort currentBuilding, ushort visitBuilding, bool isVirtual)
+ private void StartMovingToVisitBuilding(TAI instance, uint citizenId, ref TCitizen citizen, ushort currentBuilding, ushort visitBuilding)
{
CitizenProxy.SetVisitPlace(ref citizen, citizenId, visitBuilding);
CitizenProxy.SetVisitBuilding(ref citizen, visitBuilding);
-
- if (isVirtual)
- {
- CitizenProxy.SetLocation(ref citizen, Citizen.Location.Visit);
- touristAI.AddTouristVisit(instance, citizenId, visitBuilding);
- }
- else
- {
- touristAI.StartMoving(instance, citizenId, ref citizen, currentBuilding, visitBuilding);
- }
- }
-
- private bool ShouldRealizeCitizen(TAI ai)
- {
- return touristAI.DoRandomMove(ai);
+ touristAI.StartMoving(instance, citizenId, ref citizen, currentBuilding, visitBuilding);
}
}
}
From 170e7fe6627cfd3758a1c0d226f953b9e9f32981 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Wed, 18 Jul 2018 19:02:51 +0200
Subject: [PATCH 07/42] Implement a math utility class to decouple from
UnityEngine namespace
---
src/RealTime/Config/RealTimeConfig.cs | 63 ++++++----------
.../CustomAI/RealTimeResidentAI.SchoolWork.cs | 3 +-
src/RealTime/RealTime.csproj | 1 +
src/RealTime/Tools/RealTimeMath.cs | 72 +++++++++++++++++++
4 files changed, 97 insertions(+), 42 deletions(-)
create mode 100644 src/RealTime/Tools/RealTimeMath.cs
diff --git a/src/RealTime/Config/RealTimeConfig.cs b/src/RealTime/Config/RealTimeConfig.cs
index 4d4359c0..041191da 100644
--- a/src/RealTime/Config/RealTimeConfig.cs
+++ b/src/RealTime/Config/RealTimeConfig.cs
@@ -4,7 +4,7 @@
namespace RealTime.Config
{
- using System.Collections.Generic;
+ using RealTime.Tools;
using RealTime.UI;
///
@@ -216,42 +216,42 @@ public RealTimeConfig()
/// This instance.
public RealTimeConfig Validate()
{
- WakeupHour = Clamp(WakeupHour, 4f, 8f);
- GoToSleepUpHour = Clamp(GoToSleepUpHour, 20f, 23.75f);
+ WakeupHour = RealTimeMath.Clamp(WakeupHour, 4f, 8f);
+ GoToSleepUpHour = RealTimeMath.Clamp(GoToSleepUpHour, 20f, 23.75f);
- DayTimeSpeed = Clamp(DayTimeSpeed, 1u, 7u);
- NightTimeSpeed = Clamp(NightTimeSpeed, 1u, 7u);
+ DayTimeSpeed = RealTimeMath.Clamp(DayTimeSpeed, 1u, 7u);
+ NightTimeSpeed = RealTimeMath.Clamp(NightTimeSpeed, 1u, 7u);
- VirtualCitizens = (VirtualCitizensLevel)Clamp((int)VirtualCitizens, (int)VirtualCitizensLevel.None, (int)VirtualCitizensLevel.Many);
- ConstructionSpeed = Clamp(ConstructionSpeed, 0u, 100u);
+ VirtualCitizens = (VirtualCitizensLevel)RealTimeMath.Clamp((int)VirtualCitizens, (int)VirtualCitizensLevel.None, (int)VirtualCitizensLevel.Many);
+ ConstructionSpeed = RealTimeMath.Clamp(ConstructionSpeed, 0u, 100u);
- SecondShiftQuota = Clamp(SecondShiftQuota, 1u, 8u);
- NightShiftQuota = Clamp(NightShiftQuota, 1u, 8u);
- LunchQuota = Clamp(LunchQuota, 0u, 100u);
- LocalBuildingSearchQuota = Clamp(LocalBuildingSearchQuota, 0u, 100u);
- OnTimeQuota = Clamp(OnTimeQuota, 0u, 100u);
+ SecondShiftQuota = RealTimeMath.Clamp(SecondShiftQuota, 1u, 8u);
+ NightShiftQuota = RealTimeMath.Clamp(NightShiftQuota, 1u, 8u);
+ LunchQuota = RealTimeMath.Clamp(LunchQuota, 0u, 100u);
+ LocalBuildingSearchQuota = RealTimeMath.Clamp(LocalBuildingSearchQuota, 0u, 100u);
+ OnTimeQuota = RealTimeMath.Clamp(OnTimeQuota, 0u, 100u);
- EarliestHourEventStartWeekday = Clamp(EarliestHourEventStartWeekday, 0f, 23.75f);
- LatestHourEventStartWeekday = Clamp(LatestHourEventStartWeekday, 0f, 23.75f);
+ EarliestHourEventStartWeekday = RealTimeMath.Clamp(EarliestHourEventStartWeekday, 0f, 23.75f);
+ LatestHourEventStartWeekday = RealTimeMath.Clamp(LatestHourEventStartWeekday, 0f, 23.75f);
if (LatestHourEventStartWeekday < EarliestHourEventStartWeekday)
{
LatestHourEventStartWeekday = EarliestHourEventStartWeekday;
}
- EarliestHourEventStartWeekend = Clamp(EarliestHourEventStartWeekend, 0f, 23.75f);
- LatestHourEventStartWeekend = Clamp(LatestHourEventStartWeekend, 0f, 23.75f);
+ EarliestHourEventStartWeekend = RealTimeMath.Clamp(EarliestHourEventStartWeekend, 0f, 23.75f);
+ LatestHourEventStartWeekend = RealTimeMath.Clamp(LatestHourEventStartWeekend, 0f, 23.75f);
if (LatestHourEventStartWeekend < EarliestHourEventStartWeekend)
{
LatestHourEventStartWeekend = EarliestHourEventStartWeekend;
}
- WorkBegin = Clamp(WorkBegin, 4f, 11f);
- WorkEnd = Clamp(WorkEnd, 12f, 20f);
- LunchBegin = Clamp(LunchBegin, 11f, 13f);
- LunchEnd = Clamp(LunchEnd, 13f, 15f);
- SchoolBegin = Clamp(SchoolBegin, 4f, 10f);
- SchoolEnd = Clamp(SchoolEnd, 11f, 16f);
- MaxOvertime = Clamp(MaxOvertime, 0f, 4f);
+ WorkBegin = RealTimeMath.Clamp(WorkBegin, 4f, 11f);
+ WorkEnd = RealTimeMath.Clamp(WorkEnd, 12f, 20f);
+ LunchBegin = RealTimeMath.Clamp(LunchBegin, 11f, 13f);
+ LunchEnd = RealTimeMath.Clamp(LunchEnd, 13f, 15f);
+ SchoolBegin = RealTimeMath.Clamp(SchoolBegin, 4f, 10f);
+ SchoolEnd = RealTimeMath.Clamp(SchoolEnd, 11f, 16f);
+ MaxOvertime = RealTimeMath.Clamp(MaxOvertime, 0f, 4f);
return this;
}
@@ -293,22 +293,5 @@ public void ResetToDefaults()
SchoolBegin = 8f;
SchoolEnd = 14f;
}
-
- private static T Clamp(T value, T min, T max)
- where T : struct
- {
- Comparer comparer = Comparer.Default;
- if (comparer.Compare(value, min) < 0)
- {
- return min;
- }
-
- if (comparer.Compare(value, max) > 0)
- {
- return max;
- }
-
- return value;
- }
}
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index 55b00908..a9ccc06c 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -5,7 +5,6 @@
namespace RealTime.CustomAI
{
using RealTime.Tools;
- using UnityEngine;
using static Constants;
internal sealed partial class RealTimeResidentAI
@@ -459,7 +458,7 @@ private float GetTravelTimeHomeToWork(uint citizenId, ushort homeBuilding, ushor
if (result <= 0)
{
float distance = BuildingMgr.GetDistanceBetweenBuildings(homeBuilding, workBuilding);
- result = Mathf.Clamp(distance / OnTheWayDistancePerHour, MinHoursOnTheWay, MaxHoursOnTheWay);
+ result = RealTimeMath.Clamp(distance / OnTheWayDistancePerHour, MinHoursOnTheWay, MaxHoursOnTheWay);
}
return result;
diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj
index 4dbe9926..74be79a5 100644
--- a/src/RealTime/RealTime.csproj
+++ b/src/RealTime/RealTime.csproj
@@ -134,6 +134,7 @@
+
diff --git a/src/RealTime/Tools/RealTimeMath.cs b/src/RealTime/Tools/RealTimeMath.cs
new file mode 100644
index 00000000..8bbe5d07
--- /dev/null
+++ b/src/RealTime/Tools/RealTimeMath.cs
@@ -0,0 +1,72 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.Tools
+{
+ ///
+ /// A static class containing various math methods.
+ ///
+ internal static class RealTimeMath
+ {
+ /// Ensures that a value is constrained by the specified range.
+ /// The value to check.
+ /// The minimum constraint.
+ /// The maximum constraint.
+ /// A value that is guaranteed to be in the specified range.
+ public static float Clamp(float value, float min, float max)
+ {
+ if (value < min)
+ {
+ return min;
+ }
+
+ if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ /// Ensures that a value is constrained by the specified range.
+ /// The value to check.
+ /// The minimum constraint.
+ /// The maximum constraint.
+ /// A value that is guaranteed to be in the specified range.
+ public static uint Clamp(uint value, uint min, uint max)
+ {
+ if (value < min)
+ {
+ return min;
+ }
+
+ if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+
+ /// Ensures that a value is constrained by the specified range.
+ /// The value to check.
+ /// The minimum constraint.
+ /// The maximum constraint.
+ /// A value that is guaranteed to be in the specified range.
+ public static int Clamp(int value, int min, int max)
+ {
+ if (value < min)
+ {
+ return min;
+ }
+
+ if (value > max)
+ {
+ return max;
+ }
+
+ return value;
+ }
+ }
+}
From 0cb69f5bc883ff63a62bef28238ff9ce82d51e81 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Wed, 18 Jul 2018 19:05:54 +0200
Subject: [PATCH 08/42] Improve work/lunch tracking and wakeup/extended first
shift behavior
---
src/RealTime/CustomAI/RealTimeHumanAIBase.cs | 17 +---------
.../CustomAI/RealTimeResidentAI.Home.cs | 9 +++---
.../CustomAI/RealTimeResidentAI.SchoolWork.cs | 32 +++++++++----------
src/RealTime/CustomAI/WorkStatus.cs | 8 ++---
4 files changed, 26 insertions(+), 40 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
index b0e85f9f..d579ca55 100644
--- a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
+++ b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
@@ -106,21 +106,6 @@ protected RealTimeHumanAIBase(RealTimeConfig config, GameConnections c
/// Gets the maximum count of the citizen instances.
protected uint CitizenInstancesMaxCount { get; }
- ///
- /// Determines whether the current date and time represent the specified time interval on a work day.
- ///
- ///
- /// The hour representing the interval start to check (inclusive).
- /// The hour representing the interval end to check (exclusive).
- ///
- /// true if the current date and time represent the specified time interval on a work day; otherwise, false.
- ///
- protected bool IsWorkDayAndBetweenHours(float fromInclusive, float toExclusive)
- {
- float currentHour = TimeInfo.CurrentHour;
- return IsWorkDay && (currentHour >= fromInclusive && currentHour < toExclusive);
- }
-
///
/// Determines whether the current time represents a morning hour of a work day
/// for a citizen with the provided .
@@ -157,7 +142,7 @@ protected bool IsWorkDayMorning(Citizen.AgeGroup citizenAge)
}
float currentHour = TimeInfo.CurrentHour;
- return currentHour >= TimeInfo.SunriseHour && currentHour <= workBeginHour;
+ return currentHour >= Config.WakeupHour && currentHour <= workBeginHour;
}
///
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Home.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Home.cs
index 26201c8a..a1f3b9fc 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Home.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Home.cs
@@ -67,16 +67,17 @@ private bool IsBusyAtHomeInTheMorning(Citizen.AgeGroup citizenAge)
}
}
+ // TODO: make this method to a part of time simulation (no need to calculate for each citizen)
private bool IsBusyAtHomeInTheMorning(float currentHour, float latestHour)
{
- if (currentHour >= latestHour || currentHour < EarliestWakeUp)
+ if (currentHour >= latestHour || currentHour < Config.WakeupHour)
{
return false;
}
- float sunriseHour = EarliestWakeUp;
- float dx = latestHour - sunriseHour;
- float x = currentHour - sunriseHour;
+ float wakeupHour = Config.WakeupHour;
+ float dx = latestHour - wakeupHour;
+ float x = currentHour - wakeupHour;
// A cubic probability curve from the earliest wake up hour (0%) to latest hour (100%)
uint chance = (uint)((100f / dx * x) - ((dx - x) * (dx - x) * x));
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index a9ccc06c..a767c29f 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -4,6 +4,7 @@
namespace RealTime.CustomAI
{
+ using System;
using RealTime.Tools;
using static Constants;
@@ -26,8 +27,6 @@ private enum WorkerShift
Any
}
- private bool IsLunchHour => IsWorkDayAndBetweenHours(Config.LunchBegin, Config.LunchEnd);
-
private static bool IsBuildingActiveOnWeekend(ItemClass.Service service, ItemClass.SubService subService)
{
switch (service)
@@ -84,7 +83,7 @@ private static int GetBuildingWorkShiftCount(ItemClass.Service service)
}
}
- private static bool ShouldWorkAtDawn(ItemClass.Service service, ItemClass.SubService subService)
+ private static bool HasExtendedFirstWorkShift(ItemClass.Service service, ItemClass.SubService subService)
{
switch (service)
{
@@ -194,6 +193,7 @@ private void ProcessCitizenAtSchoolOrWork(TAI instance, uint citizenId, ref TCit
}
Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} leaves their workplace {workBuilding}");
+ residentStates[citizenId].WorkStatus = WorkStatus.Default;
if (CitizenGoesToEvent(instance, citizenId, ref citizen))
{
@@ -221,6 +221,7 @@ private bool CitizenGoesWorking(TAI instance, uint citizenId, ref TCitizen citiz
ref ResidentStateDescriptor state = ref residentStates[citizenId];
state.DepartureTime = TimeInfo.Now;
+ state.WorkStatus = WorkStatus.AtWork;
residentAI.StartMoving(instance, citizenId, ref citizen, homeBuilding, workBuilding);
return true;
@@ -391,7 +392,13 @@ private bool ShouldGoToLunch(Citizen.AgeGroup citizenAge, uint citizenId)
private bool CitizenReturnsFromLunch(TAI instance, uint citizenId, ref TCitizen citizen)
{
- if (IsLunchHour)
+ if (citizenId == 0)
+ {
+ return false;
+ }
+
+ ref ResidentStateDescriptor state = ref residentStates[citizenId];
+ if (state.WorkStatus != WorkStatus.AtLunch || TimeInfo.CurrentHour < Config.LunchEnd)
{
return false;
}
@@ -401,14 +408,15 @@ private bool CitizenReturnsFromLunch(TAI instance, uint citizenId, ref TCitizen
{
Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} returning from lunch to {workBuilding}");
ReturnFromVisit(instance, citizenId, ref citizen, workBuilding, Citizen.Location.Work);
+ state.WorkStatus = WorkStatus.AtWork;
}
else
{
- Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is at lunch but no work building. Teleporting home.");
- CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
+ Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is at lunch but no work building.");
+ ReturnFromVisit(instance, citizenId, ref citizen, CitizenProxy.GetHomeBuilding(ref citizen), Citizen.Location.Home);
+ state.WorkStatus = WorkStatus.Default;
}
- residentStates[citizenId].WorkStatus = WorkStatus.Default;
return true;
}
@@ -437,15 +445,7 @@ private void GetWorkShiftTimes(uint citizenId, ItemClass.Service sevice, ItemCla
if (begin < 0 || end < 0)
{
end = Config.WorkEnd;
-
- if (ShouldWorkAtDawn(sevice, subService))
- {
- begin = Mathf.Min(TimeInfo.SunriseHour, EarliestWakeUp);
- }
- else
- {
- begin = Config.WorkBegin;
- }
+ begin = HasExtendedFirstWorkShift(sevice, subService) ? Math.Min(Config.WakeupHour, EarliestWakeUp) : Config.WorkBegin;
}
beginHour = begin;
diff --git a/src/RealTime/CustomAI/WorkStatus.cs b/src/RealTime/CustomAI/WorkStatus.cs
index 13c6b08e..0721549e 100644
--- a/src/RealTime/CustomAI/WorkStatus.cs
+++ b/src/RealTime/CustomAI/WorkStatus.cs
@@ -12,13 +12,13 @@ internal enum WorkStatus : byte
/// No special handling.
Default,
+ /// The citizen is at work or is heading to the work building.
+ AtWork,
+
/// The citizen takes a break and goes out for lunch.
AtLunch,
- /// The citizen has a day off work today.
- DayOff,
-
- /// The citizen is on vacation.
+ /// The citizen is on vacation or has a day off work.
OnVacation
}
}
From ac8f3425f1f564fe60e7e1a638100966cbfbf052 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Wed, 18 Jul 2018 19:26:00 +0200
Subject: [PATCH 09/42] Prepare the data structures for vacation tracking
---
src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs | 6 ++++++
src/RealTime/CustomAI/ResidentStateDescriptor.cs | 3 +++
2 files changed, 9 insertions(+)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index a767c29f..83bd1b32 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -234,6 +234,12 @@ private bool ShouldMoveToSchoolOrWork(uint citizenId, ushort homeBuilding, ushor
return false;
}
+ ref ResidentStateDescriptor state = ref residentStates[citizenId];
+ if (state.WorkStatus == WorkStatus.OnVacation && state.VacationDaysLeft > 0)
+ {
+ return false;
+ }
+
ItemClass.Service buildingSevice = BuildingMgr.GetBuildingService(workBuilding);
ItemClass.SubService buildingSubService = BuildingMgr.GetBuildingSubService(workBuilding);
diff --git a/src/RealTime/CustomAI/ResidentStateDescriptor.cs b/src/RealTime/CustomAI/ResidentStateDescriptor.cs
index d8eaebc7..44060e98 100644
--- a/src/RealTime/CustomAI/ResidentStateDescriptor.cs
+++ b/src/RealTime/CustomAI/ResidentStateDescriptor.cs
@@ -24,6 +24,9 @@ internal struct ResidentStateDescriptor
///
public float TravelTimeToWork;
+ /// The number of days the citizen will be on vacation (as of current game date).
+ public byte VacationDaysLeft;
+
/// Updates the travel time that the citizen needs to read the work building or school/university.
///
/// The arrival time at the work building or school/university. Must be great than .
From 53632b95691d550f3dd5e14d67429f8543f80dce Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Wed, 18 Jul 2018 19:43:40 +0200
Subject: [PATCH 10/42] Decouple remaining 'night' considerations from 'sunset'
---
src/RealTime/CustomAI/RealTimeHumanAIBase.cs | 6 +++---
src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
index d579ca55..60c3e9b4 100644
--- a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
+++ b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
@@ -160,7 +160,7 @@ protected uint GetGoOutChance(Citizen.AgeGroup citizenAge)
uint weekdayModifier;
if (Config.IsWeekendEnabled)
{
- weekdayModifier = TimeInfo.Now.IsWeekendTime(GetSpareTimeBeginHour(citizenAge), TimeInfo.SunsetHour)
+ weekdayModifier = TimeInfo.Now.IsWeekendTime(GetSpareTimeBeginHour(citizenAge), Config.GoToSleepUpHour)
? 11u
: 1u;
}
@@ -177,8 +177,8 @@ protected uint GetGoOutChance(Citizen.AgeGroup citizenAge)
}
else
{
- float nightDuration = TimeInfo.NightDuration;
- float relativeHour = currentHour - TimeInfo.SunsetHour;
+ float nightDuration = 24f - (Config.GoToSleepUpHour - Config.WakeupHour);
+ float relativeHour = currentHour - Config.GoToSleepUpHour;
if (relativeHour < 0)
{
relativeHour += 24f;
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
index 03d5776c..49f6b61c 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
@@ -316,8 +316,8 @@ private ushort MoveToLeisure(TAI instance, uint citizenId, ref TCitizen citizen,
private bool IsBuildingNoiseRestricted(ushort building)
{
float arriveHour = (float)TimeInfo.Now.AddHours(MaxHoursOnTheWay).TimeOfDay.TotalHours;
- return (arriveHour >= TimeInfo.SunsetHour || TimeInfo.CurrentHour >= TimeInfo.SunsetHour
- || arriveHour <= TimeInfo.SunriseHour || TimeInfo.CurrentHour <= TimeInfo.SunriseHour)
+ return (arriveHour >= Config.GoToSleepUpHour || TimeInfo.CurrentHour >= Config.GoToSleepUpHour
+ || arriveHour <= Config.WakeupHour || TimeInfo.CurrentHour <= Config.WakeupHour)
&& BuildingMgr.IsBuildingNoiseRestricted(building);
}
}
From 2f17e46efac70a7dd2a912b944bb8204958647f7 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Wed, 18 Jul 2018 20:03:38 +0200
Subject: [PATCH 11/42] Revise the citizens relaxing chances
---
src/RealTime/CustomAI/RealTimeHumanAIBase.cs | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
index 60c3e9b4..0e85823b 100644
--- a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
+++ b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
@@ -153,6 +153,7 @@ protected bool IsWorkDayMorning(Citizen.AgeGroup citizenAge)
///
/// A percentage value in range of 0..100 that describes the probability whether
/// a citizen with provided age would go out on current time.
+ // TODO: make this method to a part of time simulation (no need to calculate for each citizen)
protected uint GetGoOutChance(Citizen.AgeGroup citizenAge)
{
float currentHour = TimeInfo.CurrentHour;
@@ -169,22 +170,23 @@ protected uint GetGoOutChance(Citizen.AgeGroup citizenAge)
weekdayModifier = 1u;
}
- bool isDayTime = !TimeInfo.IsNightTime;
+ float latestGoOutHour = Config.GoToSleepUpHour - 2f;
+ bool isDayTime = currentHour >= Config.WakeupHour && currentHour < latestGoOutHour;
float timeModifier;
if (isDayTime)
{
- timeModifier = 5f;
+ timeModifier = 4f;
}
else
{
- float nightDuration = 24f - (Config.GoToSleepUpHour - Config.WakeupHour);
- float relativeHour = currentHour - Config.GoToSleepUpHour;
+ float nightDuration = 24f - (latestGoOutHour - Config.WakeupHour);
+ float relativeHour = currentHour - latestGoOutHour;
if (relativeHour < 0)
{
relativeHour += 24f;
}
- timeModifier = 5f / nightDuration * (nightDuration - relativeHour);
+ timeModifier = 3f / nightDuration * (nightDuration - relativeHour);
}
switch (citizenAge)
@@ -196,7 +198,7 @@ protected uint GetGoOutChance(Citizen.AgeGroup citizenAge)
return (uint)((timeModifier + weekdayModifier) * timeModifier);
case Citizen.AgeGroup.Senior when isDayTime:
- return 80 + weekdayModifier;
+ return 30 + weekdayModifier;
default:
return 0;
@@ -367,14 +369,14 @@ protected bool IsBadWeather(uint citizenId)
{
if (WeatherInfo.IsDisasterHazardActive)
{
- Log.Debug($"Citizen {citizenId} is uncomfortable because of a disaster");
+ Log.Debug($"Citizen {citizenId} feels uncomfortable because of a disaster");
return true;
}
bool result = WeatherInfo.StayInsideChance != 0 && Random.ShouldOccur(WeatherInfo.StayInsideChance);
if (result)
{
- Log.Debug($"Citizen {citizenId} is uncomfortable because of bad weather");
+ Log.Debug($"Citizen {citizenId} feels uncomfortable because of bad weather");
}
return result;
From f0148e6f35546ad932ef06b85837006e822ba410 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Fri, 20 Jul 2018 18:44:05 +0200
Subject: [PATCH 12/42] Optimize current hour calculation performance
(micro-optimization)
---
src/RealTime/GameConnection/TimeInfo.cs | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/RealTime/GameConnection/TimeInfo.cs b/src/RealTime/GameConnection/TimeInfo.cs
index e58bec39..9623db3f 100644
--- a/src/RealTime/GameConnection/TimeInfo.cs
+++ b/src/RealTime/GameConnection/TimeInfo.cs
@@ -15,6 +15,8 @@ namespace RealTime.GameConnection
internal sealed class TimeInfo : ITimeInfo
{
private readonly RealTimeConfig config;
+ private DateTime currentTime;
+ private float currentHour;
/// Initializes a new instance of the class.
/// The configuration to run with.
@@ -28,7 +30,19 @@ public TimeInfo(RealTimeConfig config)
public DateTime Now => SimulationManager.instance.m_currentGameTime;
/// Gets the current daytime hour.
- public float CurrentHour => (float)Now.TimeOfDay.TotalHours;
+ public float CurrentHour
+ {
+ get
+ {
+ if (SimulationManager.instance.m_currentGameTime != currentTime)
+ {
+ currentTime = SimulationManager.instance.m_currentGameTime;
+ currentHour = (float)Now.TimeOfDay.TotalHours;
+ }
+
+ return currentHour;
+ }
+ }
/// Gets the sunrise hour of the current day.
public float SunriseHour => SimulationManager.SUNRISE_HOUR;
From 093a9c49c1ce3b6ec350965a7131bf0df0807c97 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Fri, 20 Jul 2018 19:03:31 +0200
Subject: [PATCH 13/42] Move frame processing logic into the designated class
---
src/RealTime/CustomAI/RealTimePrivateBuildingAI.cs | 9 ++++++++-
src/RealTime/Simulation/SimulationHandler.cs | 6 ++----
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimePrivateBuildingAI.cs b/src/RealTime/CustomAI/RealTimePrivateBuildingAI.cs
index 86a49142..c9d2ad52 100644
--- a/src/RealTime/CustomAI/RealTimePrivateBuildingAI.cs
+++ b/src/RealTime/CustomAI/RealTimePrivateBuildingAI.cs
@@ -16,6 +16,7 @@ internal sealed class RealTimePrivateBuildingAI
{
private const int ConstructionSpeedPaused = 10880;
private const int ConstructionSpeedMinimum = 1088;
+ private const int StartFrameMask = 0xFF;
private readonly RealTimeConfig config;
private readonly ITimeInfo timeInfo;
@@ -108,8 +109,14 @@ public void ProcessWorkerProblems(ushort buildingId, byte oldValue, byte newValu
/// Notifies this simulation object that a new simulation frame is started.
/// The buildings will be processed again from the beginning of the list.
- public void StartBuildingProcessingFrame()
+ /// The simulation frame index to process.
+ public void ProcessFrame(uint frameIndex)
{
+ if ((frameIndex & StartFrameMask) != 0)
+ {
+ return;
+ }
+
int currentMinute = timeInfo.Now.Minute;
if (lastProcessedMinute != currentMinute)
{
diff --git a/src/RealTime/Simulation/SimulationHandler.cs b/src/RealTime/Simulation/SimulationHandler.cs
index 0ae2ddd6..1c68c27d 100644
--- a/src/RealTime/Simulation/SimulationHandler.cs
+++ b/src/RealTime/Simulation/SimulationHandler.cs
@@ -75,10 +75,8 @@ public override void OnAfterSimulationTick()
///
public override void OnBeforeSimulationFrame()
{
- if ((SimulationManager.instance.m_currentFrameIndex & 0xFF) == 0)
- {
- Buildings?.StartBuildingProcessingFrame();
- }
+ uint currentFrame = SimulationManager.instance.m_currentFrameIndex;
+ Buildings?.ProcessFrame(currentFrame);
}
private static void OnNewDay(SimulationHandler sender)
From da96ecacb4b47137a3e26665b9f03eb126cb10ff Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 12:46:09 +0200
Subject: [PATCH 14/42] Implement extension method for getting future time with
particular hour
---
src/RealTime/Tools/DateTimeExtensions.cs | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/src/RealTime/Tools/DateTimeExtensions.cs b/src/RealTime/Tools/DateTimeExtensions.cs
index 7a2ec904..bdaad519 100644
--- a/src/RealTime/Tools/DateTimeExtensions.cs
+++ b/src/RealTime/Tools/DateTimeExtensions.cs
@@ -62,5 +62,25 @@ public static DateTime RoundCeil(this DateTime dateTime, TimeSpan interval)
long overflow = dateTime.Ticks % interval.Ticks;
return overflow == 0 ? dateTime : dateTime.AddTicks(interval.Ticks - overflow);
}
+
+ /// Gets a new value that is greater than this
+ /// and whose daytime hour equals to the specified one. If is negative,
+ /// it will be shifted in the next day to become positive.
+ /// The to get the future value for.
+ /// The daytime hour for the result value. Can be negative.
+ /// A new value that is greater than this
+ /// and whose daytime hour is set to the specified .
+ public static DateTime FutureHour(this DateTime dateTime, float hour)
+ {
+ if (hour < 0)
+ {
+ hour += 24f;
+ }
+
+ float delta = hour - (float)dateTime.TimeOfDay.TotalHours;
+ return delta >= 0
+ ? dateTime.AddHours(delta)
+ : dateTime.AddHours(24f + delta);
+ }
}
}
From ea28f620d84ec6f2c16b046cab774f0c3efada4d Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 18:07:42 +0200
Subject: [PATCH 15/42] Implement the advanced simulation logic for #57
---
src/RealTime/Core/RealTimeCore.cs | 2 +
src/RealTime/CustomAI/CitizenSchedule.cs | 100 ++++
src/RealTime/CustomAI/Constants.cs | 21 +-
src/RealTime/CustomAI/RealTimeHumanAIBase.cs | 109 +----
.../CustomAI/RealTimeResidentAI.Common.cs | 279 ++++++++---
.../CustomAI/RealTimeResidentAI.Home.cs | 79 +--
.../CustomAI/RealTimeResidentAI.Moving.cs | 87 +++-
.../CustomAI/RealTimeResidentAI.SchoolWork.cs | 457 +-----------------
.../CustomAI/RealTimeResidentAI.Visit.cs | 326 ++++---------
src/RealTime/CustomAI/RealTimeResidentAI.cs | 105 ++--
src/RealTime/CustomAI/RealTimeTouristAI.cs | 18 +-
src/RealTime/CustomAI/ResidentState.cs | 30 +-
.../CustomAI/ResidentStateDescriptor.cs | 52 --
src/RealTime/CustomAI/ScheduleHint.cs | 25 +
src/RealTime/CustomAI/WorkBehavior.cs | 329 +++++++++++++
src/RealTime/CustomAI/WorkShift.cs | 24 +
src/RealTime/CustomAI/WorkStatus.cs | 12 +-
src/RealTime/Events/RealTimeEventManager.cs | 27 ++
src/RealTime/RealTime.csproj | 6 +-
src/RealTime/Simulation/CitizenProcessor.cs | 78 +++
src/RealTime/Simulation/SimulationHandler.cs | 18 +-
src/RealTime/Simulation/TimeAdjustment.cs | 9 +-
22 files changed, 1149 insertions(+), 1044 deletions(-)
create mode 100644 src/RealTime/CustomAI/CitizenSchedule.cs
delete mode 100644 src/RealTime/CustomAI/ResidentStateDescriptor.cs
create mode 100644 src/RealTime/CustomAI/ScheduleHint.cs
create mode 100644 src/RealTime/CustomAI/WorkBehavior.cs
create mode 100644 src/RealTime/CustomAI/WorkShift.cs
create mode 100644 src/RealTime/Simulation/CitizenProcessor.cs
diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs
index ac50412e..c7fc25b6 100644
--- a/src/RealTime/Core/RealTimeCore.cs
+++ b/src/RealTime/Core/RealTimeCore.cs
@@ -187,6 +187,7 @@ public void Stop()
SimulationHandler.TimeAdjustment = null;
SimulationHandler.WeatherInfo = null;
SimulationHandler.Buildings = null;
+ SimulationHandler.CitizenProcessor = null;
isEnabled = false;
}
@@ -229,6 +230,7 @@ private static bool SetupCustomAI(
eventManager);
ResidentAIPatch.RealTimeAI = realTimeResidentAI;
+ SimulationHandler.CitizenProcessor = new CitizenProcessor(realTimeResidentAI);
TouristAIConnection touristAIConnection = TouristAIPatch.GetTouristAIConnection();
if (touristAIConnection == null)
diff --git a/src/RealTime/CustomAI/CitizenSchedule.cs b/src/RealTime/CustomAI/CitizenSchedule.cs
new file mode 100644
index 00000000..9d8d63e1
--- /dev/null
+++ b/src/RealTime/CustomAI/CitizenSchedule.cs
@@ -0,0 +1,100 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.CustomAI
+{
+ using System;
+ using static Constants;
+
+ /// A container struct that holds information about the detailed resident citizen state.
+ /// Note that this struct is intentionally made mutable to increase performance.
+ internal struct CitizenSchedule
+ {
+ /// The citizen's current state.
+ public ResidentState CurrentState;
+
+ /// The citizen's schedule hint.
+ public ScheduleHint Hint;
+
+ /// The ID of the building where an event takes place, if the citizen schedules to attend one.
+ public ushort EventBuilding;
+
+ /// The citizen's work status.
+ public WorkStatus WorkStatus;
+
+ /// The ID of the citizen's work building. If it doesn't equal the game's value, the work shift data needs to be updated.
+ public ushort WorkBuilding;
+
+ /// The time when citizen started their last ride to the work building.
+ public DateTime DepartureToWorkTime;
+
+ /// Gets the citizen's next scheduled state.
+ public ResidentState ScheduledState { get; private set; }
+
+ /// Gets the time when the citizen will perform the next state change.
+ public DateTime ScheduledStateTime { get; private set; }
+
+ ///
+ /// Gets the travel time (in hours) from citizen's home to the work building. The maximum value is
+ /// determined by the constant.
+ ///
+ public float TravelTimeToWork { get; private set; }
+
+ /// Gets the citizen's work shift.
+ public WorkShift WorkShift { get; private set; }
+
+ /// Gets the daytime hour when the citizen's work shift starts.
+ public float WorkShiftStartHour { get; private set; }
+
+ /// Gets the daytime hour when the citizen's work shift ends.
+ public float WorkShiftEndHour { get; private set; }
+
+ /// Gets a value indicating whether this citizen works on weekends.
+ public bool WorksOnWeekends { get; private set; }
+
+ /// Updates the travel time that the citizen needs to read the work building or school/university.
+ ///
+ /// The arrival time at the work building or school/university. Must be great than .
+ ///
+ public void UpdateTravelTimeToWork(DateTime arrivalTime)
+ {
+ if (arrivalTime < DepartureToWorkTime || DepartureToWorkTime == default)
+ {
+ return;
+ }
+
+ float onTheWayHours = (float)(arrivalTime - DepartureToWorkTime).TotalHours;
+ if (onTheWayHours > MaxTravelTime)
+ {
+ onTheWayHours = MaxTravelTime;
+ }
+
+ TravelTimeToWork = TravelTimeToWork == 0
+ ? onTheWayHours
+ : (TravelTimeToWork + onTheWayHours) / 2;
+ }
+
+ /// Updates the work shift data for this citizen's schedule.
+ /// The citizen's work shift.
+ /// The work shift start hour.
+ /// The work shift end hour.
+ /// if true, the citizen works on weekends.
+ public void UpdateWorkShift(WorkShift workShift, float startHour, float endHour, bool worksOnWeekends)
+ {
+ WorkShift = workShift;
+ WorkShiftStartHour = startHour;
+ WorkShiftEndHour = endHour;
+ WorksOnWeekends = worksOnWeekends;
+ }
+
+ /// Schedules next actions for the citizen.
+ /// The next scheduled citizen's state.
+ /// The time when the scheduled state must change.
+ public void Schedule(ResidentState nextState, DateTime nextStateTime)
+ {
+ ScheduledState = nextState;
+ ScheduledStateTime = nextStateTime;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RealTime/CustomAI/Constants.cs b/src/RealTime/CustomAI/Constants.cs
index 9f0bfa7b..11b722fd 100644
--- a/src/RealTime/CustomAI/Constants.cs
+++ b/src/RealTime/CustomAI/Constants.cs
@@ -18,13 +18,16 @@ internal static class Constants
public const float FullSearchDistance = BuildingManager.BUILDINGGRID_RESOLUTION * BuildingManager.BUILDINGGRID_CELL_SIZE / 2f;
/// A chance in percent for a citizen to go shopping.
- public const uint GoShoppingChance = 50;
+ public const uint GoShoppingChance = 80;
/// A chance in percent for a citizen to return shopping.
- public const uint ReturnFromShoppingChance = 60;
+ public const uint ReturnFromShoppingChance = 75;
/// A chance in percent for a citizen to return from a visited building.
- public const uint ReturnFromVisitChance = 40;
+ public const uint ReturnFromVisitChance = 50;
+
+ /// A chance in percent for a citizen to go to sleep when he or she is at home and doesn't go out.
+ public const uint GoSleepingChance = 75;
/// A chance in percent for a tourist to find a hotel for sleepover.
public const uint FindHotelChance = 80;
@@ -38,14 +41,14 @@ internal static class Constants
/// A hard coded game value describing a 'do nothing' probability for a tourist.
public const int TouristDoNothingProbability = 5000;
- /// An assumed maximum on-the-way time to a target building.
- public const float MaxHoursOnTheWay = 4f;
+ /// The amount of hours the citizen will spend preparing to work and not going out.
+ public const float PrepareToWorkHours = 1f;
- /// An assumed minimum on-the-way time to a target building.
- public const float MinHoursOnTheWay = 0.1f;
+ /// An assumed maximum travel time to a target building.
+ public const float MaxTravelTime = 4f;
- /// A minimum work shift duration in hours.
- public const float MinimumWorkShiftDuration = 2f;
+ /// An assumed minimum travel to a target building.
+ public const float MinTravelTime = 0.25f;
/// An earliest hour when citizens wake up at home.
public const float EarliestWakeUp = 5.5f;
diff --git a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
index 0e85823b..530cf14c 100644
--- a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
+++ b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
@@ -106,45 +106,6 @@ protected RealTimeHumanAIBase(RealTimeConfig config, GameConnections c
/// Gets the maximum count of the citizen instances.
protected uint CitizenInstancesMaxCount { get; }
- ///
- /// Determines whether the current time represents a morning hour of a work day
- /// for a citizen with the provided .
- ///
- ///
- /// The citizen age to check.
- ///
- ///
- /// true if the current time represents a morning hour of a work day
- /// for a citizen with the provided age; otherwise, false.
- ///
- protected bool IsWorkDayMorning(Citizen.AgeGroup citizenAge)
- {
- if (!IsWorkDay)
- {
- return false;
- }
-
- float workBeginHour;
- switch (citizenAge)
- {
- case Citizen.AgeGroup.Child:
- case Citizen.AgeGroup.Teen:
- workBeginHour = Config.SchoolBegin;
- break;
-
- case Citizen.AgeGroup.Young:
- case Citizen.AgeGroup.Adult:
- workBeginHour = Config.WorkBegin;
- break;
-
- default:
- return false;
- }
-
- float currentHour = TimeInfo.CurrentHour;
- return currentHour >= Config.WakeupHour && currentHour <= workBeginHour;
- }
-
///
/// Gets the probability whether a citizen with provided age would go out on current time.
///
@@ -161,7 +122,7 @@ protected uint GetGoOutChance(Citizen.AgeGroup citizenAge)
uint weekdayModifier;
if (Config.IsWeekendEnabled)
{
- weekdayModifier = TimeInfo.Now.IsWeekendTime(GetSpareTimeBeginHour(citizenAge), Config.GoToSleepUpHour)
+ weekdayModifier = TimeInfo.Now.IsWeekendTime(12f, Config.GoToSleepUpHour)
? 11u
: 1u;
}
@@ -205,30 +166,6 @@ protected uint GetGoOutChance(Citizen.AgeGroup citizenAge)
}
}
- ///
- /// Gets the spare time begin hour for a citizen with provided age.
- ///
- ///
- /// The citizen age to check.
- ///
- /// A value representing the hour of the day when the citizen's spare time begins.
- protected float GetSpareTimeBeginHour(Citizen.AgeGroup citizenAge)
- {
- switch (citizenAge)
- {
- case Citizen.AgeGroup.Child:
- case Citizen.AgeGroup.Teen:
- return Config.SchoolEnd;
-
- case Citizen.AgeGroup.Young:
- case Citizen.AgeGroup.Adult:
- return Config.WorkEnd;
-
- default:
- return 0;
- }
- }
-
///
/// Ensures that the provided citizen is in a valid state and can be processed.
///
@@ -261,35 +198,32 @@ protected bool EnsureCitizenCanBeProcessed(uint citizenId, ref TCitizen citizen)
}
///
- /// Lets the provided citizen try attending the next upcoming event.
+ /// Searches for an upcoming event and checks whether the specified citizen ca attend it.
+ /// Returns null if no matching events found.
///
///
- /// The citizen ID.
+ /// The ID of the citizen to check.
/// The citizen data reference.
- /// The building ID where the upcoming event will take place.
///
- /// true if the provided citizen will attend the next event; otherwise, false.
- protected bool AttendUpcomingEvent(uint citizenId, ref TCitizen citizen, out ushort eventBuildingId)
+ /// The city event or null if none found.
+ protected ICityEvent GetUpcomingEventToAttend(uint citizenId, ref TCitizen citizen)
{
- eventBuildingId = default;
-
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
if (EventMgr.GetEventState(currentBuilding, DateTime.MaxValue) == CityEventState.Ongoing)
{
- return false;
+ return null;
}
- DateTime earliestStart = TimeInfo.Now.AddHours(MinHoursOnTheWay);
- DateTime latestStart = TimeInfo.Now.AddHours(MaxHoursOnTheWay);
+ DateTime earliestStart = TimeInfo.Now.AddHours(MinTravelTime);
+ DateTime latestStart = TimeInfo.Now.AddHours(MaxTravelTime);
ICityEvent upcomingEvent = EventMgr.GetUpcomingCityEvent(earliestStart, latestStart);
if (upcomingEvent != null && CanAttendEvent(citizenId, ref citizen, upcomingEvent))
{
- eventBuildingId = upcomingEvent.BuildingId;
- return true;
+ return upcomingEvent;
}
- return false;
+ return null;
}
///
@@ -362,24 +296,31 @@ protected bool IsCitizenVirtual(TAI humanAI, ref TCitizen citizen, FuncDetermines whether the weather is currently so bad that the citizen would like to stay inside a building.
- /// The ID of the citizen to check the weather for.
///
/// true if the weather is bad; otherwise, false.
- protected bool IsBadWeather(uint citizenId)
+ protected bool IsBadWeather()
{
if (WeatherInfo.IsDisasterHazardActive)
{
- Log.Debug($"Citizen {citizenId} feels uncomfortable because of a disaster");
return true;
}
- bool result = WeatherInfo.StayInsideChance != 0 && Random.ShouldOccur(WeatherInfo.StayInsideChance);
- if (result)
+ return WeatherInfo.StayInsideChance != 0 && Random.ShouldOccur(WeatherInfo.StayInsideChance);
+ }
+
+ /// Gets an estimated travel time (in hours) between two specified buildings.
+ /// The ID of the first building.
+ /// The ID of the second building.
+ /// An estimated travel time in hours.
+ protected float GetEstimatedTravelTime(ushort building1, ushort building2)
+ {
+ if (building1 == 0 || building2 == 0)
{
- Log.Debug($"Citizen {citizenId} feels uncomfortable because of bad weather");
+ return 0;
}
- return result;
+ float distance = BuildingMgr.GetDistanceBetweenBuildings(building1, building2);
+ return RealTimeMath.Clamp(distance / OnTheWayDistancePerHour, MinTravelTime, MaxTravelTime);
}
private bool CanAttendEvent(uint citizenId, ref TCitizen citizen, ICityEvent cityEvent)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
index 4fca304f..e8e1f0a2 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
@@ -4,10 +4,19 @@
namespace RealTime.CustomAI
{
+ using System;
using RealTime.Tools;
+ using static Constants;
internal sealed partial class RealTimeResidentAI
{
+ private enum ScheduleAction
+ {
+ Ignore,
+ ProcessTransition,
+ ProcessState
+ }
+
private void ProcessCitizenDead(TAI instance, uint citizenId, ref TCitizen citizen)
{
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
@@ -16,6 +25,7 @@ private void ProcessCitizenDead(TAI instance, uint citizenId, ref TCitizen citiz
if (currentBuilding == 0 || (currentLocation == Citizen.Location.Moving && CitizenProxy.GetVehicle(ref citizen) == 0))
{
Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is released");
+ residentSchedules[citizenId] = default;
CitizenMgr.ReleaseCitizen(citizenId);
return;
}
@@ -101,69 +111,89 @@ private bool ProcessCitizenSick(TAI instance, uint citizenId, ref TCitizen citiz
return true;
}
- private void ProcessCitizenEvacuation(TAI instance, uint citizenId, ref TCitizen citizen)
+ private void DoScheduledEvacuation(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
{
ushort building = CitizenProxy.GetCurrentBuilding(ref citizen);
- if (building != 0)
+ if (building == 0)
+ {
+ schedule.Schedule(ResidentState.AtHome, default);
+ return;
+ }
+
+ schedule.Schedule(ResidentState.InShelter, default);
+ if (schedule.CurrentState != ResidentState.InShelter)
{
Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is trying to find an evacuation place");
residentAI.FindEvacuationPlace(instance, citizenId, building, residentAI.GetEvacuationReason(instance, building));
}
}
- private bool StartMovingToVisitBuilding(TAI instance, uint citizenId, ref TCitizen citizen, ushort visitBuilding)
+ private void ProcessCitizenInShelter(ref CitizenSchedule schedule, ref TCitizen citizen)
{
- if (visitBuilding == 0)
+ ushort shelter = CitizenProxy.GetVisitBuilding(ref citizen);
+ if (BuildingMgr.BuildingHasFlags(shelter, Building.Flags.Downgrading))
{
- return false;
+ schedule.Schedule(ResidentState.Unknown, default);
}
+ }
- ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
-
- if (currentBuilding == visitBuilding)
+ private ScheduleAction UpdateCitizenState(uint citizenId, ref TCitizen citizen, ref CitizenSchedule schedule)
+ {
+ if (schedule.CurrentState == ResidentState.Ignored)
{
- CitizenProxy.SetVisitPlace(ref citizen, citizenId, visitBuilding);
- CitizenProxy.SetVisitBuilding(ref citizen, visitBuilding);
- CitizenProxy.SetLocation(ref citizen, Citizen.Location.Visit);
- return true;
+ return ScheduleAction.Ignore;
}
- else if (residentAI.StartMoving(instance, citizenId, ref citizen, currentBuilding, visitBuilding))
+
+ if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.DummyTraffic))
{
- CitizenProxy.SetVisitPlace(ref citizen, citizenId, visitBuilding);
- CitizenProxy.SetVisitBuilding(ref citizen, visitBuilding);
- return true;
+ schedule.CurrentState = ResidentState.Ignored;
+ return ScheduleAction.Ignore;
}
- return false;
- }
-
- private ResidentState GetResidentState(uint citizenId, ref TCitizen citizen)
- {
- if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.DummyTraffic))
+ Citizen.Location location = CitizenProxy.GetLocation(ref citizen);
+ if (location == Citizen.Location.Moving)
{
- return ResidentState.Ignored;
+ if (CitizenMgr.InstanceHasFlags(
+ CitizenProxy.GetInstance(ref citizen),
+ CitizenInstance.Flags.OnTour | CitizenInstance.Flags.TargetIsNode,
+ true))
+ {
+ // Guided tours are treated as visits
+ schedule.CurrentState = ResidentState.Visiting;
+ schedule.Hint = ScheduleHint.OnTour;
+ return ScheduleAction.ProcessState;
+ }
+
+ return ScheduleAction.ProcessTransition;
}
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
- ItemClass.Service buildingService = BuildingMgr.GetBuildingService(currentBuilding);
+ if (currentBuilding == 0)
+ {
+ schedule.CurrentState = ResidentState.Unknown;
+ return ScheduleAction.ProcessState;
+ }
+ ItemClass.Service buildingService = BuildingMgr.GetBuildingService(currentBuilding);
if (BuildingMgr.BuildingHasFlags(currentBuilding, Building.Flags.Evacuating)
&& buildingService != ItemClass.Service.Disaster)
{
- return ResidentState.Evacuating;
+ schedule.CurrentState = ResidentState.Evacuation;
+ schedule.Schedule(ResidentState.InShelter, default);
+ return ScheduleAction.ProcessState;
}
- switch (CitizenProxy.GetLocation(ref citizen))
+ switch (location)
{
case Citizen.Location.Home:
- return currentBuilding != 0
- ? ResidentState.AtHome
- : ResidentState.Unknown;
+ schedule.CurrentState = ResidentState.AtHome;
+ return ScheduleAction.ProcessState;
case Citizen.Location.Work:
if (buildingService == ItemClass.Service.Disaster && CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating))
{
- return ResidentState.InShelter;
+ schedule.CurrentState = ResidentState.InShelter;
+ return ScheduleAction.ProcessState;
}
if (CitizenProxy.GetVisitBuilding(ref citizen) == currentBuilding)
@@ -172,55 +202,178 @@ private ResidentState GetResidentState(uint citizenId, ref TCitizen citizen)
goto case Citizen.Location.Visit;
}
- return currentBuilding != 0
- ? ResidentState.AtSchoolOrWork
- : ResidentState.Unknown;
+ schedule.CurrentState = ResidentState.AtSchoolOrWork;
+ return ScheduleAction.ProcessState;
case Citizen.Location.Visit:
- if (currentBuilding == 0)
- {
- return ResidentState.Unknown;
- }
-
switch (buildingService)
{
- case ItemClass.Service.Commercial:
- if (residentStates[citizenId].WorkStatus == WorkStatus.AtLunch)
- {
- return ResidentState.AtLunch;
- }
-
- if (BuildingMgr.GetBuildingSubService(currentBuilding) == ItemClass.SubService.CommercialLeisure)
- {
- return ResidentState.AtLeisureArea;
- }
+ case ItemClass.Service.Beautification:
+ case ItemClass.Service.Commercial
+ when BuildingMgr.GetBuildingSubService(currentBuilding) == ItemClass.SubService.CommercialLeisure
+ && schedule.WorkStatus != WorkStatus.Working:
- return ResidentState.Shopping;
+ schedule.CurrentState = ResidentState.Relaxing;
+ return ScheduleAction.ProcessState;
- case ItemClass.Service.Beautification:
- return ResidentState.AtLeisureArea;
+ case ItemClass.Service.Commercial:
+ schedule.CurrentState = ResidentState.Shopping;
+ return ScheduleAction.ProcessState;
case ItemClass.Service.Disaster:
- return ResidentState.InShelter;
+ schedule.CurrentState = ResidentState.InShelter;
+ return ScheduleAction.ProcessState;
}
- return ResidentState.Visiting;
+ schedule.CurrentState = ResidentState.Visiting;
+ break;
+ }
- case Citizen.Location.Moving:
- ushort instanceId = CitizenProxy.GetInstance(ref citizen);
- if (CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.OnTour | CitizenInstance.Flags.TargetIsNode, true))
+ return ScheduleAction.Ignore;
+ }
+
+ private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId, ref TCitizen citizen)
+ {
+ // If the game changed the work building, we have to update the work shifts first
+ ushort workBuilding = CitizenProxy.GetWorkBuilding(ref citizen);
+ if (schedule.WorkBuilding != workBuilding)
+ {
+ schedule.WorkBuilding = workBuilding;
+ workBehavior.UpdateWorkShift(ref schedule, CitizenProxy.GetAge(ref citizen));
+ }
+
+ if (schedule.ScheduledState != ResidentState.Unknown)
+ {
+ return;
+ }
+
+ Log.Debug($"Calculating schedule for citizen instance {CitizenProxy.GetInstance(ref citizen)}...");
+
+ if (schedule.WorkStatus == WorkStatus.Working)
+ {
+ schedule.WorkStatus = WorkStatus.None;
+ }
+
+ DateTime nextActivityTime = TimeInfo.Now.FutureHour(Config.WakeupHour);
+ if (schedule.CurrentState != ResidentState.AtSchoolOrWork && workBuilding != 0)
+ {
+ DateTime workTime = ScheduleWork(ref schedule, CitizenProxy.GetCurrentBuilding(ref citizen));
+ if (schedule.ScheduledState == ResidentState.AtSchoolOrWork)
+ {
+ Log.Debug($" - Schedule work at {workTime}");
+ nextActivityTime = workTime;
+
+ float timeLeft = (float)(nextActivityTime - TimeInfo.Now).TotalHours;
+ if (timeLeft <= PrepareToWorkHours)
{
- return ResidentState.OnTour;
+ // Just sit at home if the work time will come soon
+ Log.Debug($" - Worktime in {timeLeft} hours, doing nothing");
+ return;
}
- ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
- return homeBuilding != 0 && CitizenMgr.GetTargetBuilding(instanceId) == homeBuilding
- ? ResidentState.MovingHome
- : ResidentState.MovingToTarget;
+ if (timeLeft <= MaxTravelTime)
+ {
+ // If we have some time, try to shop locally.
+ Log.Debug($" - Worktime in {timeLeft} hours, trying local shop");
+ ScheduleShopping(ref schedule, ref citizen, true);
+ return;
+ }
+ }
+ }
+
+ if (ScheduleShopping(ref schedule, ref citizen, false))
+ {
+ Log.Debug($" - Schedule shopping");
+ return;
+ }
+
+ if (ScheduleRelaxing(ref schedule, citizenId, ref citizen))
+ {
+ Log.Debug($" - Schedule relaxing");
+ return;
+ }
+
+ if (schedule.CurrentState == ResidentState.AtHome)
+ {
+ Log.Debug($" - Scheduled sleeping at home until {nextActivityTime}");
+ schedule.Schedule(ResidentState.Unknown, nextActivityTime);
+ }
+ else
+ {
+ Log.Debug($" - Schedule moving home");
+ schedule.Schedule(ResidentState.AtHome, default);
+ }
+ }
+
+ private void ExecuteCitizenSchedule(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
+ {
+ ProcessCurrentState(ref schedule, ref citizen);
+
+ if (TimeInfo.Now < schedule.ScheduledStateTime)
+ {
+ return;
+ }
- default:
- return ResidentState.Unknown;
+ if (schedule.CurrentState == ResidentState.AtHome && IsCitizenVirtual(instance, ref citizen, ShouldRealizeCitizen))
+ {
+ Log.Debug($" *** Citizen {citizenId} is virtual this time");
+ schedule.Schedule(ResidentState.Unknown, default);
+ return;
}
+
+ switch (schedule.ScheduledState)
+ {
+ case ResidentState.AtHome:
+ DoScheduledHome(ref schedule, instance, citizenId, ref citizen);
+ break;
+
+ case ResidentState.AtSchoolOrWork:
+ DoScheduledWork(ref schedule, instance, citizenId, ref citizen);
+ break;
+
+ case ResidentState.Shopping when schedule.WorkStatus == WorkStatus.Working:
+ DoScheduledLunch(ref schedule, instance, citizenId, ref citizen);
+ break;
+
+ case ResidentState.Shopping:
+ DoScheduledShopping(ref schedule, instance, citizenId, ref citizen);
+ break;
+
+ case ResidentState.Relaxing:
+ DoScheduledRelaxing(ref schedule, instance, citizenId, ref citizen);
+ break;
+
+ case ResidentState.InShelter:
+ DoScheduledEvacuation(ref schedule, instance, citizenId, ref citizen);
+ break;
+ }
+ }
+
+ private void ProcessCurrentState(ref CitizenSchedule schedule, ref TCitizen citizen)
+ {
+ switch (schedule.CurrentState)
+ {
+ case ResidentState.Shopping:
+ ProcessCitizenShopping(ref citizen);
+ break;
+
+ case ResidentState.Relaxing:
+ ProcessCitizenRelaxing(ref citizen);
+ break;
+
+ case ResidentState.Visiting:
+ ProcessCitizenVisit(ref schedule, ref citizen);
+ break;
+
+ case ResidentState.InShelter:
+ ProcessCitizenInShelter(ref schedule, ref citizen);
+ break;
+ }
+ }
+
+ private bool ShouldRealizeCitizen(TAI ai)
+ {
+ return residentAI.DoRandomMove(ai);
}
}
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Home.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Home.cs
index a1f3b9fc..b3de2cbc 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Home.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Home.cs
@@ -5,83 +5,26 @@
namespace RealTime.CustomAI
{
using RealTime.Tools;
- using static Constants;
internal sealed partial class RealTimeResidentAI
{
- private void ProcessCitizenAtHome(TAI instance, uint citizenId, ref TCitizen citizen)
+ private void DoScheduledHome(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
{
- if (CitizenProxy.GetHomeBuilding(ref citizen) == 0)
+ ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
+ if (homeBuilding == 0)
{
- Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is in corrupt state: at home with no home building. Releasing the poor citizen.");
+ Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is in corrupt state: want to go home with no home building. Releasing the poor citizen.");
CitizenMgr.ReleaseCitizen(citizenId);
+ schedule = default;
return;
}
- ushort vehicle = CitizenProxy.GetVehicle(ref citizen);
- if (vehicle != 0)
- {
- Log.Debug(TimeInfo.Now, $"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is at home but vehicle = {vehicle}");
- return;
- }
-
- if (CitizenGoesWorking(instance, citizenId, ref citizen))
- {
- return;
- }
-
- if (IsBusyAtHomeInTheMorning(CitizenProxy.GetAge(ref citizen)))
- {
- return;
- }
-
- if (CitizenGoesShopping(instance, citizenId, ref citizen) || CitizenGoesToEvent(instance, citizenId, ref citizen))
- {
- return;
- }
-
- CitizenGoesRelaxing(instance, citizenId, ref citizen);
- }
-
- private bool IsBusyAtHomeInTheMorning(Citizen.AgeGroup citizenAge)
- {
- float currentHour = TimeInfo.CurrentHour;
- float offset = IsWeekend ? 2 : 0;
- switch (citizenAge)
- {
- case Citizen.AgeGroup.Child:
- return IsBusyAtHomeInTheMorning(currentHour, 8 + offset);
-
- case Citizen.AgeGroup.Teen:
- case Citizen.AgeGroup.Young:
- return IsBusyAtHomeInTheMorning(currentHour, 9 + offset);
-
- case Citizen.AgeGroup.Adult:
- return IsBusyAtHomeInTheMorning(currentHour, 8 + (offset / 2f));
-
- case Citizen.AgeGroup.Senior:
- return IsBusyAtHomeInTheMorning(currentHour, 7);
-
- default:
- return true;
- }
- }
-
- // TODO: make this method to a part of time simulation (no need to calculate for each citizen)
- private bool IsBusyAtHomeInTheMorning(float currentHour, float latestHour)
- {
- if (currentHour >= latestHour || currentHour < Config.WakeupHour)
- {
- return false;
- }
-
- float wakeupHour = Config.WakeupHour;
- float dx = latestHour - wakeupHour;
- float x = currentHour - wakeupHour;
-
- // A cubic probability curve from the earliest wake up hour (0%) to latest hour (100%)
- uint chance = (uint)((100f / dx * x) - ((dx - x) * (dx - x) * x));
- return !Random.ShouldOccur(chance);
+ ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
+ CitizenProxy.RemoveFlags(ref citizen, Citizen.Flags.Evacuating);
+ CitizenProxy.SetVisitPlace(ref citizen, citizenId, 0);
+ residentAI.StartMoving(instance, citizenId, ref citizen, currentBuilding, homeBuilding);
+ schedule.Schedule(ResidentState.Unknown, default);
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going from {currentBuilding} back home");
}
}
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
index 9d47fd56..333718eb 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
@@ -5,10 +5,11 @@
namespace RealTime.CustomAI
{
using RealTime.Tools;
+ using static Constants;
internal sealed partial class RealTimeResidentAI
{
- private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen citizen, bool mayCancel)
+ private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen citizen)
{
ushort instanceId = CitizenProxy.GetInstance(ref citizen);
ushort vehicleId = CitizenProxy.GetVehicle(ref citizen);
@@ -23,6 +24,7 @@ private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen cit
if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.MovingIn))
{
CitizenMgr.ReleaseCitizen(citizenId);
+ residentSchedules[citizenId] = default;
}
else
{
@@ -47,17 +49,88 @@ private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen cit
}
ItemClass.Service targetService = BuildingMgr.GetBuildingService(targetBuilding);
- if (targetService != ItemClass.Service.Beautification || !IsBadWeather(citizenId))
+ if (targetService == ItemClass.Service.Beautification && IsBadWeather())
{
- return;
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} cancels the trip to a park due to bad weather");
+ ushort home = CitizenProxy.GetHomeBuilding(ref citizen);
+ if (home != 0)
+ {
+ residentAI.StartMoving(instance, citizenId, ref citizen, 0, home);
+ }
}
+ }
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} cancels the trip to a park due to bad weather");
- ushort home = CitizenProxy.GetHomeBuilding(ref citizen);
- if (home != 0)
+ private ushort MoveToCommercialBuilding(TAI instance, uint citizenId, ref TCitizen citizen, float distance)
+ {
+ ushort buildingId = CitizenProxy.GetCurrentBuilding(ref citizen);
+ if (buildingId == 0)
{
- residentAI.StartMoving(instance, citizenId, ref citizen, 0, home);
+ return 0;
}
+
+ ushort foundBuilding = BuildingMgr.FindActiveBuilding(buildingId, distance, ItemClass.Service.Commercial);
+ if (IsBuildingNoiseRestricted(foundBuilding))
+ {
+ Log.Debug($"Citizen {citizenId} won't go to the commercial building {foundBuilding}, it has a NIMBY policy");
+ return 0;
+ }
+
+ if (StartMovingToVisitBuilding(instance, citizenId, ref citizen, foundBuilding))
+ {
+ ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
+ uint homeUnit = BuildingMgr.GetCitizenUnit(homeBuilding);
+ uint citizenUnit = CitizenProxy.GetContainingUnit(ref citizen, citizenId, homeUnit, CitizenUnit.Flags.Home);
+ if (citizenUnit != 0)
+ {
+ CitizenMgr.ModifyUnitGoods(citizenUnit, ShoppingGoodsAmount);
+ }
+ }
+
+ return foundBuilding;
+ }
+
+ private ushort MoveToLeisureBuilding(TAI instance, uint citizenId, ref TCitizen citizen, ushort buildingId)
+ {
+ ushort leisureBuilding = BuildingMgr.FindActiveBuilding(
+ buildingId,
+ LeisureSearchDistance,
+ ItemClass.Service.Commercial,
+ ItemClass.SubService.CommercialLeisure);
+
+ if (IsBuildingNoiseRestricted(leisureBuilding))
+ {
+ Log.Debug($"Citizen {citizenId} won't go to the leisure building {leisureBuilding}, it has a NIMBY policy");
+ return 0;
+ }
+
+ StartMovingToVisitBuilding(instance, citizenId, ref citizen, leisureBuilding);
+ return leisureBuilding;
+ }
+
+ private bool StartMovingToVisitBuilding(TAI instance, uint citizenId, ref TCitizen citizen, ushort visitBuilding)
+ {
+ if (visitBuilding == 0)
+ {
+ return false;
+ }
+
+ ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
+
+ if (currentBuilding == visitBuilding)
+ {
+ CitizenProxy.SetVisitPlace(ref citizen, citizenId, visitBuilding);
+ CitizenProxy.SetVisitBuilding(ref citizen, visitBuilding);
+ CitizenProxy.SetLocation(ref citizen, Citizen.Location.Visit);
+ return true;
+ }
+ else if (residentAI.StartMoving(instance, citizenId, ref citizen, currentBuilding, visitBuilding))
+ {
+ CitizenProxy.SetVisitPlace(ref citizen, citizenId, visitBuilding);
+ CitizenProxy.SetVisitBuilding(ref citizen, visitBuilding);
+ return true;
+ }
+
+ return false;
}
}
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index 83bd1b32..d58635b8 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -10,464 +10,47 @@ namespace RealTime.CustomAI
internal sealed partial class RealTimeResidentAI
{
- private const int ShiftBitsCount = 5;
- private const uint WorkShiftsMask = (1u << ShiftBitsCount) - 1;
-
- private uint secondShiftQuota;
- private uint nightShiftQuota;
- private uint secondShiftValue;
- private uint nightShiftValue;
-
- private enum WorkerShift
- {
- None,
- First,
- Second,
- Night,
- Any
- }
-
- private static bool IsBuildingActiveOnWeekend(ItemClass.Service service, ItemClass.SubService subService)
- {
- switch (service)
- {
- case ItemClass.Service.Commercial
- when subService != ItemClass.SubService.CommercialHigh && subService != ItemClass.SubService.CommercialEco:
- case ItemClass.Service.Tourism:
- case ItemClass.Service.Electricity:
- case ItemClass.Service.Water:
- case ItemClass.Service.Beautification:
- case ItemClass.Service.HealthCare:
- case ItemClass.Service.PoliceDepartment:
- case ItemClass.Service.FireDepartment:
- case ItemClass.Service.PublicTransport:
- case ItemClass.Service.Disaster:
- case ItemClass.Service.Monument:
- return true;
-
- default:
- return false;
- }
- }
-
- private static int GetBuildingWorkShiftCount(ItemClass.Service service)
- {
- switch (service)
- {
- case ItemClass.Service.Office:
- case ItemClass.Service.Garbage:
- case ItemClass.Service.Education:
- return 1;
-
- case ItemClass.Service.Road:
- case ItemClass.Service.Beautification:
- case ItemClass.Service.Monument:
- case ItemClass.Service.Citizen:
- return 2;
-
- case ItemClass.Service.Commercial:
- case ItemClass.Service.Industrial:
- case ItemClass.Service.Tourism:
- case ItemClass.Service.Electricity:
- case ItemClass.Service.Water:
- case ItemClass.Service.HealthCare:
- case ItemClass.Service.PoliceDepartment:
- case ItemClass.Service.FireDepartment:
- case ItemClass.Service.PublicTransport:
- case ItemClass.Service.Disaster:
- case ItemClass.Service.Natural:
- return 3;
-
- default:
- return 1;
- }
- }
-
- private static bool HasExtendedFirstWorkShift(ItemClass.Service service, ItemClass.SubService subService)
- {
- switch (service)
- {
- case ItemClass.Service.Commercial when subService == ItemClass.SubService.CommercialLow:
- case ItemClass.Service.Beautification:
- case ItemClass.Service.Garbage:
- case ItemClass.Service.Road:
- return true;
-
- default:
- return false;
- }
- }
-
- private static bool CheckMinimumShiftDuration(float beginHour, float endHour)
- {
- if (beginHour < endHour)
- {
- return endHour - beginHour >= MinimumWorkShiftDuration;
- }
- else
- {
- return 24f - beginHour + endHour >= MinimumWorkShiftDuration;
- }
- }
-
- private static bool IsWorkHour(float currentHour, float gotoWorkHour, float leaveWorkHour)
- {
- if (gotoWorkHour < leaveWorkHour)
- {
- if (currentHour >= leaveWorkHour || currentHour < gotoWorkHour)
- {
- return false;
- }
- }
- else
- {
- if (currentHour >= leaveWorkHour && currentHour < gotoWorkHour)
- {
- return false;
- }
- }
-
- return true;
- }
-
- private WorkerShift GetWorkerShift(uint citizenId)
- {
- if (secondShiftQuota != Config.SecondShiftQuota || nightShiftQuota != Config.NightShiftQuota)
- {
- secondShiftQuota = Config.SecondShiftQuota;
- nightShiftQuota = Config.NightShiftQuota;
- CalculateWorkShiftValues();
- }
-
- uint value = citizenId & WorkShiftsMask;
- if (value <= secondShiftValue)
- {
- return WorkerShift.Second;
- }
-
- value = (citizenId >> ShiftBitsCount) & WorkShiftsMask;
- if (value <= nightShiftValue)
- {
- return WorkerShift.Night;
- }
-
- return WorkerShift.First;
- }
-
- private void CalculateWorkShiftValues()
+ private DateTime ScheduleWork(ref CitizenSchedule schedule, ushort currentBuilding)
{
- secondShiftValue = Config.SecondShiftQuota - 1;
- nightShiftValue = Config.NightShiftQuota - 1;
+ return workBehavior.ScheduleGoToWork(ref schedule, currentBuilding, simulationCycle)
+ ? schedule.ScheduledStateTime
+ : default;
}
- private void ProcessCitizenAtSchoolOrWork(TAI instance, uint citizenId, ref TCitizen citizen)
+ private void DoScheduledWork(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
{
- ushort workBuilding = CitizenProxy.GetWorkBuilding(ref citizen);
- if (workBuilding == 0)
- {
- Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is in corrupt state: at school/work with no work building. Teleporting home.");
- CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
- return;
- }
-
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
- if (ShouldGoToLunch(CitizenProxy.GetAge(ref citizen), citizenId))
- {
- ushort lunchPlace = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance);
- if (lunchPlace != 0)
- {
- residentStates[citizenId].WorkStatus = WorkStatus.AtLunch;
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going for lunch from {currentBuilding} to {lunchPlace}");
- }
- else
- {
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted to go for lunch from {currentBuilding}, but there were no buildings close enough");
- }
+ schedule.WorkStatus = WorkStatus.Working;
- return;
- }
-
- if (!ShouldReturnFromSchoolOrWork(citizenId, currentBuilding, CitizenProxy.GetAge(ref citizen)))
+ if (residentAI.StartMoving(instance, citizenId, ref citizen, currentBuilding, schedule.WorkBuilding)
+ && schedule.CurrentState == ResidentState.AtHome)
{
- return;
+ schedule.DepartureToWorkTime = TimeInfo.Now;
}
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} leaves their workplace {workBuilding}");
- residentStates[citizenId].WorkStatus = WorkStatus.Default;
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going from {currentBuilding} to school/work {schedule.WorkBuilding}");
- if (CitizenGoesToEvent(instance, citizenId, ref citizen))
+ Citizen.AgeGroup citizenAge = CitizenProxy.GetAge(ref citizen);
+ if (!workBehavior.ScheduleLunch(ref schedule, citizenAge))
{
- return;
- }
-
- if (!CitizenGoesShopping(instance, citizenId, ref citizen) && !CitizenGoesRelaxing(instance, citizenId, ref citizen))
- {
- residentAI.StartMoving(instance, citizenId, ref citizen, workBuilding, CitizenProxy.GetHomeBuilding(ref citizen));
+ workBehavior.ScheduleReturnFromWork(ref schedule, citizenAge);
}
}
- private bool CitizenGoesWorking(TAI instance, uint citizenId, ref TCitizen citizen)
+ private void DoScheduledLunch(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
{
- ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
- ushort workBuilding = CitizenProxy.GetWorkBuilding(ref citizen);
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
-
- if (!ShouldMoveToSchoolOrWork(citizenId, homeBuilding, currentBuilding, workBuilding, CitizenProxy.GetAge(ref citizen)))
- {
- return false;
- }
-
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going from {currentBuilding} to school/work {workBuilding}");
-
- ref ResidentStateDescriptor state = ref residentStates[citizenId];
- state.DepartureTime = TimeInfo.Now;
- state.WorkStatus = WorkStatus.AtWork;
- residentAI.StartMoving(instance, citizenId, ref citizen, homeBuilding, workBuilding);
-
- return true;
- }
-
- private bool ShouldMoveToSchoolOrWork(uint citizenId, ushort homeBuilding, ushort currentBuilding, ushort workBuilding, Citizen.AgeGroup citizenAge)
- {
- if (workBuilding == 0 || citizenAge == Citizen.AgeGroup.Senior)
- {
- return false;
- }
-
- ref ResidentStateDescriptor state = ref residentStates[citizenId];
- if (state.WorkStatus == WorkStatus.OnVacation && state.VacationDaysLeft > 0)
- {
- return false;
- }
-
- ItemClass.Service buildingSevice = BuildingMgr.GetBuildingService(workBuilding);
- ItemClass.SubService buildingSubService = BuildingMgr.GetBuildingSubService(workBuilding);
-
- if (IsWeekend && !IsBuildingActiveOnWeekend(buildingSevice, buildingSubService))
- {
- return false;
- }
-
- // TODO: replace the day off logic.
- if ((citizenId & 0x7FF) == TimeInfo.Now.Day)
- {
- Log.Debug(TimeInfo.Now, $"Citizen {citizenId} has a day off work today");
- return false;
- }
-
- float travelTime = currentBuilding == homeBuilding
- ? GetTravelTimeHomeToWork(citizenId, homeBuilding, workBuilding)
- : 0;
-
- if (citizenAge == Citizen.AgeGroup.Child || citizenAge == Citizen.AgeGroup.Teen)
- {
- return ShouldMoveToSchoolOrWork(workBuilding, Config.SchoolBegin, Config.SchoolEnd, 0, travelTime);
- }
-
- GetWorkShiftTimes(citizenId, buildingSevice, buildingSubService, out float workBeginHour, out float workEndHour);
- if (!CheckMinimumShiftDuration(workBeginHour, workEndHour))
- {
- return false;
- }
-
- float overtime = currentBuilding != homeBuilding || Random.ShouldOccur(Config.OnTimeQuota)
- ? 0
- : Config.MaxOvertime * Random.GetRandomValue(100u) / 200f;
-
- return ShouldMoveToSchoolOrWork(workBuilding, workBeginHour, workEndHour, overtime, travelTime);
- }
-
- private bool ShouldMoveToSchoolOrWork(ushort workBuilding, float workBeginHour, float workEndHour, float overtime, float travelTime)
- {
- float gotoHour = workBeginHour - overtime - MaxHoursOnTheWay;
- if (gotoHour < 0)
- {
- gotoHour += 24f;
- }
-
- float leaveHour = workEndHour + overtime;
- if (leaveHour >= 24f)
- {
- leaveHour -= 24f;
- }
-
- float currentHour = TimeInfo.CurrentHour;
- if (!IsWorkHour(currentHour, gotoHour, leaveHour))
- {
- return false;
- }
-
- gotoHour = workBeginHour - overtime - travelTime;
- if (gotoHour < 0)
- {
- gotoHour += 24f;
- }
-
- return IsWorkHour(currentHour, gotoHour, leaveHour);
- }
-
- private bool ShouldReturnFromSchoolOrWork(uint citizenId, ushort buildingId, Citizen.AgeGroup citizenAge)
- {
- if (citizenAge == Citizen.AgeGroup.Senior)
- {
- return true;
- }
-
- ItemClass.Service buildingSevice = BuildingMgr.GetBuildingService(buildingId);
- ItemClass.SubService buildingSubService = BuildingMgr.GetBuildingSubService(buildingId);
-
- if (IsWeekend && !IsBuildingActiveOnWeekend(buildingSevice, buildingSubService))
- {
- return true;
- }
-
- float currentHour = TimeInfo.CurrentHour;
-
- if (citizenAge == Citizen.AgeGroup.Child || citizenAge == Citizen.AgeGroup.Teen)
- {
- return currentHour >= Config.SchoolEnd || currentHour < Config.SchoolBegin - MaxHoursOnTheWay;
- }
-
- GetWorkShiftTimes(citizenId, buildingSevice, buildingSubService, out float workBeginHour, out float workEndHour);
- if (!CheckMinimumShiftDuration(workBeginHour, workEndHour))
- {
- return true;
- }
-
- float earliestGotoHour = workBeginHour - MaxHoursOnTheWay - Config.MaxOvertime;
- if (earliestGotoHour < 0)
- {
- earliestGotoHour += 24f;
- }
-
- float latestLeaveHour = workEndHour + Config.MaxOvertime;
- if (latestLeaveHour >= 24f)
- {
- latestLeaveHour -= 24f;
- }
-
- if (earliestGotoHour < latestLeaveHour)
- {
- if (currentHour >= latestLeaveHour || currentHour < earliestGotoHour)
- {
- return true;
- }
- else if (currentHour >= workEndHour)
- {
- return Random.ShouldOccur(Config.OnTimeQuota);
- }
- }
- else
- {
- if (currentHour >= latestLeaveHour && currentHour < earliestGotoHour)
- {
- return true;
- }
- else if (currentHour >= workEndHour && currentHour < earliestGotoHour)
- {
- return Random.ShouldOccur(Config.OnTimeQuota);
- }
- }
-
- return false;
- }
-
- private bool ShouldGoToLunch(Citizen.AgeGroup citizenAge, uint citizenId)
- {
- if (!Config.IsLunchtimeEnabled)
- {
- return false;
- }
-
- switch (citizenAge)
- {
- case Citizen.AgeGroup.Child:
- case Citizen.AgeGroup.Teen:
- case Citizen.AgeGroup.Senior:
- return false;
- }
-
- float currentHour = TimeInfo.CurrentHour;
- if (!IsBadWeather(citizenId) && currentHour >= Config.LunchBegin && currentHour <= Config.LunchEnd)
- {
- return Random.ShouldOccur(Config.LunchQuota);
- }
-
- return false;
- }
-
- private bool CitizenReturnsFromLunch(TAI instance, uint citizenId, ref TCitizen citizen)
- {
- if (citizenId == 0)
- {
- return false;
- }
-
- ref ResidentStateDescriptor state = ref residentStates[citizenId];
- if (state.WorkStatus != WorkStatus.AtLunch || TimeInfo.CurrentHour < Config.LunchEnd)
- {
- return false;
- }
-
- ushort workBuilding = CitizenProxy.GetWorkBuilding(ref citizen);
- if (workBuilding != 0)
+ ushort lunchPlace = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance);
+ if (lunchPlace != 0)
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} returning from lunch to {workBuilding}");
- ReturnFromVisit(instance, citizenId, ref citizen, workBuilding, Citizen.Location.Work);
- state.WorkStatus = WorkStatus.AtWork;
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going for lunch from {currentBuilding} to {lunchPlace}");
+ workBehavior.ScheduleReturnFromLunch(ref schedule);
}
else
{
- Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is at lunch but no work building.");
- ReturnFromVisit(instance, citizenId, ref citizen, CitizenProxy.GetHomeBuilding(ref citizen), Citizen.Location.Home);
- state.WorkStatus = WorkStatus.Default;
- }
-
- return true;
- }
-
- private void GetWorkShiftTimes(uint citizenId, ItemClass.Service sevice, ItemClass.SubService subService, out float beginHour, out float endHour)
- {
- float begin = -1;
- float end = -1;
-
- int shiftCount = GetBuildingWorkShiftCount(sevice);
- if (shiftCount > 1)
- {
- switch (GetWorkerShift(citizenId))
- {
- case WorkerShift.Second:
- begin = Config.WorkEnd;
- end = 0;
- break;
-
- case WorkerShift.Night when shiftCount == 3:
- begin = 0;
- end = Config.WorkBegin;
- break;
- }
- }
-
- if (begin < 0 || end < 0)
- {
- end = Config.WorkEnd;
- begin = HasExtendedFirstWorkShift(sevice, subService) ? Math.Min(Config.WakeupHour, EarliestWakeUp) : Config.WorkBegin;
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted to go for lunch from {currentBuilding}, but there were no buildings close enough");
+ workBehavior.ScheduleReturnFromWork(ref schedule, CitizenProxy.GetAge(ref citizen));
}
-
- beginHour = begin;
- endHour = end;
- }
-
- private float GetTravelTimeHomeToWork(uint citizenId, ushort homeBuilding, ushort workBuilding)
- {
- float result = residentStates[citizenId].TravelTimeToWork;
- if (result <= 0)
- {
- float distance = BuildingMgr.GetDistanceBetweenBuildings(homeBuilding, workBuilding);
- result = RealTimeMath.Clamp(distance / OnTheWayDistancePerHour, MinHoursOnTheWay, MaxHoursOnTheWay);
- }
-
- return result;
}
}
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
index 49f6b61c..a3142a93 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
@@ -4,173 +4,97 @@
namespace RealTime.CustomAI
{
+ using System;
using RealTime.Events;
using RealTime.Tools;
using static Constants;
internal sealed partial class RealTimeResidentAI
{
- private void ProcessCitizenVisit(TAI instance, ResidentState citizenState, uint citizenId, ref TCitizen citizen)
+ private bool ScheduleRelaxing(ref CitizenSchedule schedule, uint citizenId, ref TCitizen citizen)
{
- ushort currentBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
- if (currentBuilding == 0)
- {
- Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is in corrupt state: visiting with no visit building. Teleporting home.");
- CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
- return;
- }
-
- switch (citizenState)
- {
- case ResidentState.AtLunch:
- CitizenReturnsFromLunch(instance, citizenId, ref citizen);
- return;
-
- case ResidentState.AtLeisureArea:
- if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods)
- && BuildingMgr.GetBuildingSubService(currentBuilding) == ItemClass.SubService.CommercialLeisure)
- {
- // No Citizen.Flags.NeedGoods flag reset here, because we only bought 'beer' or 'champagne' in a leisure building.
- BuildingMgr.ModifyMaterialBuffer(CitizenProxy.GetVisitBuilding(ref citizen), TransferManager.TransferReason.Shopping, -ShoppingGoodsAmount);
- }
-
- goto case ResidentState.Visiting;
-
- case ResidentState.Visiting:
- if (!CitizenGoesWorking(instance, citizenId, ref citizen))
- {
- CitizenReturnsHomeFromVisit(instance, citizenId, ref citizen);
- }
-
- return;
-
- case ResidentState.Shopping:
- if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods))
- {
- BuildingMgr.ModifyMaterialBuffer(CitizenProxy.GetVisitBuilding(ref citizen), TransferManager.TransferReason.Shopping, -ShoppingGoodsAmount);
- CitizenProxy.RemoveFlags(ref citizen, Citizen.Flags.NeedGoods);
- }
-
- if (CitizenGoesWorking(instance, citizenId, ref citizen)
- || CitizenGoesToEvent(instance, citizenId, ref citizen))
- {
- return;
- }
-
- if (Random.ShouldOccur(ReturnFromShoppingChance) || IsWorkDayMorning(CitizenProxy.GetAge(ref citizen)))
- {
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} returning from shopping at {currentBuilding} back home");
- ReturnFromVisit(instance, citizenId, ref citizen, CitizenProxy.GetHomeBuilding(ref citizen), Citizen.Location.Home);
- }
-
- return;
- }
- }
-
- private void ProcessCitizenOnTour(TAI instance, uint citizenId, ref TCitizen citizen)
- {
- if (!CitizenMgr.InstanceHasFlags(CitizenProxy.GetInstance(ref citizen), CitizenInstance.Flags.TargetIsNode))
- {
- return;
- }
-
- ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
- if (homeBuilding != 0)
- {
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} exits a guided tour and moves back home.");
- residentAI.StartMoving(instance, citizenId, ref citizen, 0, homeBuilding);
- }
- }
-
- private bool CitizenReturnsFromShelter(TAI instance, uint citizenId, ref TCitizen citizen)
- {
- ushort visitBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
- if (BuildingMgr.GetBuildingService(visitBuilding) != ItemClass.Service.Disaster)
- {
- return true;
- }
-
- if (!BuildingMgr.BuildingHasFlags(visitBuilding, Building.Flags.Downgrading))
+ Citizen.AgeGroup citizenAge = CitizenProxy.GetAge(ref citizen);
+ if (!Random.ShouldOccur(GetGoOutChance(citizenAge)) || IsBadWeather())
{
return false;
}
- ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
- if (homeBuilding == 0)
+ ICityEvent cityEvent = GetUpcomingEventToAttend(citizenId, ref citizen);
+ if (cityEvent != null)
{
- Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen)} was in a shelter but seems to be homeless. Releasing the citizen.");
- CitizenMgr.ReleaseCitizen(citizenId);
+ ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
+ DateTime departureTime = cityEvent.StartTime.AddHours(-GetEstimatedTravelTime(currentBuilding, cityEvent.BuildingId));
+ schedule.Schedule(ResidentState.Relaxing, departureTime);
+ schedule.EventBuilding = cityEvent.BuildingId;
+ schedule.Hint = ScheduleHint.AttendingEvent;
return true;
}
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} returning from evacuation place {visitBuilding} back home");
- ReturnFromVisit(instance, citizenId, ref citizen, homeBuilding, Citizen.Location.Home);
+ schedule.Schedule(ResidentState.Relaxing, default);
+ schedule.Hint = TimeInfo.IsNightTime
+ ? ScheduleHint.RelaxAtLeisureBuilding
+ : ScheduleHint.None;
+
return true;
}
- private bool CitizenReturnsHomeFromVisit(TAI instance, uint citizenId, ref TCitizen citizen)
+ private void DoScheduledRelaxing(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
{
- ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
- if (homeBuilding == 0 || CitizenProxy.GetVehicle(ref citizen) != 0)
- {
- return false;
- }
+ ushort buildingId = CitizenProxy.GetCurrentBuilding(ref citizen);
- ushort visitBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
- switch (EventMgr.GetEventState(visitBuilding, TimeInfo.Now.AddHours(MaxHoursOnTheWay)))
+ switch (schedule.Hint)
{
- case CityEventState.Upcoming:
- case CityEventState.Ongoing:
- return false;
+ case ScheduleHint.RelaxAtLeisureBuilding:
+ schedule.Schedule(ResidentState.Unknown, default);
- case CityEventState.Finished:
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} returning from an event at {visitBuilding} back home to {homeBuilding}");
- ReturnFromVisit(instance, citizenId, ref citizen, homeBuilding, Citizen.Location.Home);
- return true;
- }
+ ushort leisure = MoveToLeisureBuilding(instance, citizenId, ref citizen, buildingId);
+ if (leisure != 0)
+ {
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} heading to a leisure building {leisure}");
+ }
- ItemClass.SubService visitedSubService = BuildingMgr.GetBuildingSubService(visitBuilding);
- if (Random.ShouldOccur(ReturnFromVisitChance) ||
- (visitedSubService == ItemClass.SubService.CommercialLeisure && TimeInfo.IsNightTime && BuildingMgr.IsBuildingNoiseRestricted(visitBuilding)))
- {
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} returning from visit back home");
- ReturnFromVisit(instance, citizenId, ref citizen, homeBuilding, Citizen.Location.Home);
- return true;
- }
+ return;
- return false;
- }
+ case ScheduleHint.AttendingEvent:
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna attend an event at '{schedule.EventBuilding}', on the way now.");
+ StartMovingToVisitBuilding(instance, citizenId, ref citizen, schedule.EventBuilding);
+ ICityEvent cityEvent = EventMgr.GetUpcomingCityEvent(schedule.EventBuilding);
+ DateTime returnTime = cityEvent == null
+ ? default
+ : cityEvent.EndTime;
- private void ReturnFromVisit(
- TAI instance,
- uint citizenId,
- ref TCitizen citizen,
- ushort targetBuilding,
- Citizen.Location targetLocation)
- {
- if (targetBuilding == 0 || targetLocation == Citizen.Location.Visit || CitizenProxy.GetVehicle(ref citizen) != 0)
- {
- return;
+ schedule.Schedule(ResidentState.Unknown, returnTime);
+ schedule.EventBuilding = 0;
+ return;
}
- ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
+ ResidentState nextState = Random.ShouldOccur(ReturnFromVisitChance)
+ ? ResidentState.Unknown
+ : ResidentState.Relaxing;
- CitizenProxy.RemoveFlags(ref citizen, Citizen.Flags.Evacuating);
- CitizenProxy.SetVisitPlace(ref citizen, citizenId, 0);
+ schedule.Schedule(nextState, default);
- if (targetBuilding == currentBuilding)
+ if (schedule.CurrentState != ResidentState.Relaxing)
{
- CitizenProxy.SetLocation(ref citizen, targetLocation);
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna relax, heading to an entertainment building.");
+ residentAI.FindVisitPlace(instance, citizenId, buildingId, residentAI.GetEntertainmentReason(instance));
}
- else
+ }
+
+ private void ProcessCitizenRelaxing(ref TCitizen citizen)
+ {
+ ushort currentBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
+ if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods)
+ && BuildingMgr.GetBuildingSubService(currentBuilding) == ItemClass.SubService.CommercialLeisure)
{
- residentAI.StartMoving(instance, citizenId, ref citizen, currentBuilding, targetBuilding);
+ // No Citizen.Flags.NeedGoods flag reset here, because we only bought 'beer' or 'champagne' in a leisure building.
+ BuildingMgr.ModifyMaterialBuffer(currentBuilding, TransferManager.TransferReason.Shopping, -ShoppingGoodsAmount);
}
}
- private bool CitizenGoesShopping(TAI instance, uint citizenId, ref TCitizen citizen)
+ private bool ScheduleShopping(ref CitizenSchedule schedule, ref TCitizen citizen, bool localOnly)
{
- if (!CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods) || IsBadWeather(citizenId))
+ if (!CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods) || IsBadWeather())
{
return false;
}
@@ -179,143 +103,97 @@ private bool CitizenGoesShopping(TAI instance, uint citizenId, ref TCitizen citi
{
if (Random.ShouldOccur(GetGoOutChance(CitizenProxy.GetAge(ref citizen))))
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna go shopping at night");
- ushort localVisitPlace = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance);
- Log.DebugIf(localVisitPlace != 0, $"Citizen {citizenId} is going shopping at night to a local shop {localVisitPlace}");
- return localVisitPlace > 0;
+ schedule.Hint = ScheduleHint.LocalShoppingOnly;
+ schedule.Schedule(ResidentState.Shopping, default);
+ return true;
}
return false;
}
+ schedule.Hint = ScheduleHint.None;
if (Random.ShouldOccur(GoShoppingChance))
{
- bool localOnly = CitizenProxy.GetWorkBuilding(ref citizen) != 0 && IsWorkDayMorning(CitizenProxy.GetAge(ref citizen));
- ushort localVisitPlace = 0;
-
- if (Random.ShouldOccur(Config.LocalBuildingSearchQuota))
+ if (localOnly || Random.ShouldOccur(Config.LocalBuildingSearchQuota))
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna go shopping");
- localVisitPlace = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance);
- Log.DebugIf(localVisitPlace != 0, $"Citizen {citizenId} is going shopping to a local shop {localVisitPlace}");
- }
-
- if (localVisitPlace == 0)
- {
- if (localOnly)
- {
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna go shopping, but didn't find a local shop");
- return false;
- }
-
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna go shopping, heading to a random shop");
- residentAI.FindVisitPlace(instance, citizenId, CitizenProxy.GetHomeBuilding(ref citizen), residentAI.GetShoppingReason(instance));
+ schedule.Hint = ScheduleHint.LocalShoppingOnly;
}
+ schedule.Schedule(ResidentState.Shopping, default);
return true;
}
return false;
}
- private bool CitizenGoesToEvent(TAI instance, uint citizenId, ref TCitizen citizen)
+ private void DoScheduledShopping(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
{
- if (!Random.ShouldOccur(GetGoOutChance(CitizenProxy.GetAge(ref citizen))) || IsBadWeather(citizenId))
- {
- return false;
- }
+ ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
- if (!AttendUpcomingEvent(citizenId, ref citizen, out ushort buildingId))
+ if ((schedule.Hint & ScheduleHint.LocalShoppingOnly) != 0)
{
- return false;
- }
-
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna attend an event at '{buildingId}', on the way now.");
- return StartMovingToVisitBuilding(instance, citizenId, ref citizen, buildingId);
- }
+ schedule.Schedule(ResidentState.Unknown, default);
- private bool CitizenGoesRelaxing(TAI instance, uint citizenId, ref TCitizen citizen)
- {
- Citizen.AgeGroup citizenAge = CitizenProxy.GetAge(ref citizen);
- if (!Random.ShouldOccur(GetGoOutChance(citizenAge)) || IsBadWeather(citizenId))
- {
- return false;
+ ushort shop = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance);
+ if (shop == 0)
+ {
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted go shopping, but didn't find a local shop");
+ }
}
-
- ushort buildingId = CitizenProxy.GetCurrentBuilding(ref citizen);
- if (buildingId == 0)
+ else
{
- return false;
- }
+ ResidentState nextState = Random.ShouldOccur(ReturnFromShoppingChance)
+ ? ResidentState.Unknown
+ : ResidentState.Shopping;
- if (TimeInfo.IsNightTime)
- {
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna relax at night");
- ushort leisure = MoveToLeisure(instance, citizenId, ref citizen, buildingId);
- Log.DebugIf(leisure != 0, $"Citizen {citizenId} is heading to leisure building {leisure}");
- return leisure != 0;
- }
+ schedule.Schedule(nextState, default);
- if (CitizenProxy.GetWorkBuilding(ref citizen) != 0 && IsWorkDayMorning(citizenAge))
- {
- return false;
+ if (schedule.CurrentState != ResidentState.Shopping)
+ {
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna go shopping, heading to a random shop");
+ residentAI.FindVisitPlace(instance, citizenId, currentBuilding, residentAI.GetShoppingReason(instance));
+ }
}
-
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna relax, heading to an entertainment place");
- residentAI.FindVisitPlace(instance, citizenId, buildingId, residentAI.GetEntertainmentReason(instance));
- return true;
}
- private ushort MoveToCommercialBuilding(TAI instance, uint citizenId, ref TCitizen citizen, float distance)
+ private void ProcessCitizenShopping(ref TCitizen citizen)
{
- ushort buildingId = CitizenProxy.GetCurrentBuilding(ref citizen);
- if (buildingId == 0)
- {
- return 0;
- }
-
- ushort foundBuilding = BuildingMgr.FindActiveBuilding(buildingId, distance, ItemClass.Service.Commercial);
- if (IsBuildingNoiseRestricted(foundBuilding))
+ if (!CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods))
{
- Log.Debug($"Citizen {citizenId} won't go to the commercial building {foundBuilding}, it has a NIMBY policy");
- return 0;
+ return;
}
- if (StartMovingToVisitBuilding(instance, citizenId, ref citizen, foundBuilding))
+ ushort shop = CitizenProxy.GetVisitBuilding(ref citizen);
+ if (shop == 0)
{
- ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
- uint homeUnit = BuildingMgr.GetCitizenUnit(homeBuilding);
- uint citizenUnit = CitizenProxy.GetContainingUnit(ref citizen, citizenId, homeUnit, CitizenUnit.Flags.Home);
- if (citizenUnit != 0)
- {
- CitizenMgr.ModifyUnitGoods(citizenUnit, ShoppingGoodsAmount);
- }
+ return;
}
- return foundBuilding;
+ BuildingMgr.ModifyMaterialBuffer(shop, TransferManager.TransferReason.Shopping, -ShoppingGoodsAmount);
+ CitizenProxy.RemoveFlags(ref citizen, Citizen.Flags.NeedGoods);
}
- private ushort MoveToLeisure(TAI instance, uint citizenId, ref TCitizen citizen, ushort buildingId)
+ private void ProcessCitizenVisit(ref CitizenSchedule schedule, ref TCitizen citizen)
{
- ushort leisureBuilding = BuildingMgr.FindActiveBuilding(
- buildingId,
- LeisureSearchDistance,
- ItemClass.Service.Commercial,
- ItemClass.SubService.CommercialLeisure);
+ ushort visitBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
- if (IsBuildingNoiseRestricted(leisureBuilding))
+ if (schedule.Hint == ScheduleHint.OnTour
+ || Random.ShouldOccur(ReturnFromVisitChance)
+ || (BuildingMgr.GetBuildingSubService(visitBuilding) == ItemClass.SubService.CommercialLeisure
+ && TimeInfo.IsNightTime
+ && BuildingMgr.IsBuildingNoiseRestricted(visitBuilding)))
{
- Log.Debug($"Citizen {citizenId} won't go to the leisure building {leisureBuilding}, it has a NIMBY policy");
- return 0;
+ schedule.Schedule(ResidentState.Unknown, default);
+ }
+ else
+ {
+ schedule.Schedule(ResidentState.Visiting, default);
}
-
- StartMovingToVisitBuilding(instance, citizenId, ref citizen, leisureBuilding);
- return leisureBuilding;
}
private bool IsBuildingNoiseRestricted(ushort building)
{
- float arriveHour = (float)TimeInfo.Now.AddHours(MaxHoursOnTheWay).TimeOfDay.TotalHours;
+ float arriveHour = (float)TimeInfo.Now.AddHours(MaxTravelTime).TimeOfDay.TotalHours;
return (arriveHour >= Config.GoToSleepUpHour || TimeInfo.CurrentHour >= Config.GoToSleepUpHour
|| arriveHour <= Config.WakeupHour || TimeInfo.CurrentHour <= Config.WakeupHour)
&& BuildingMgr.IsBuildingNoiseRestricted(building);
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.cs b/src/RealTime/CustomAI/RealTimeResidentAI.cs
index 6504c6cf..a6ff2761 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.cs
@@ -17,7 +17,9 @@ internal sealed partial class RealTimeResidentAI : RealTimeHumanA
where TCitizen : struct
{
private readonly ResidentAIConnection residentAI;
- private readonly ResidentStateDescriptor[] residentStates;
+ private readonly WorkBehavior workBehavior;
+ private readonly CitizenSchedule[] residentSchedules;
+ private float simulationCycle;
/// Initializes a new instance of the class.
/// Thrown when any argument is null.
@@ -33,7 +35,8 @@ public RealTimeResidentAI(
: base(config, connections, eventManager)
{
this.residentAI = residentAI ?? throw new ArgumentNullException(nameof(residentAI));
- residentStates = new ResidentStateDescriptor[CitizenMgr.GetMaxCitizensCount()];
+ residentSchedules = new CitizenSchedule[CitizenMgr.GetMaxCitizensCount()];
+ workBehavior = new WorkBehavior(config, connections.Random, connections.BuildingManager, connections.TimeInfo, GetEstimatedTravelTime);
}
/// The entry method of the custom AI.
@@ -44,99 +47,99 @@ public void UpdateLocation(TAI instance, uint citizenId, ref TCitizen citizen)
{
if (!EnsureCitizenCanBeProcessed(citizenId, ref citizen))
{
+ residentSchedules[citizenId] = default;
return;
}
+ ref CitizenSchedule schedule = ref residentSchedules[citizenId];
if (CitizenProxy.IsDead(ref citizen))
{
ProcessCitizenDead(instance, citizenId, ref citizen);
+ schedule.Schedule(ResidentState.Unknown, default);
return;
}
if ((CitizenProxy.IsSick(ref citizen) && ProcessCitizenSick(instance, citizenId, ref citizen))
|| (CitizenProxy.IsArrested(ref citizen) && ProcessCitizenArrested(ref citizen)))
{
+ schedule.Schedule(ResidentState.Unknown, default);
return;
}
- ResidentState residentState = GetResidentState(citizenId, ref citizen);
- switch (residentState)
+ ScheduleAction actionType = UpdateCitizenState(citizenId, ref citizen, ref schedule);
+ switch (actionType)
{
- case ResidentState.MovingHome:
- ProcessCitizenMoving(instance, citizenId, ref citizen, false);
- break;
-
- case ResidentState.AtHome when !IsCitizenVirtual(instance, ref citizen, ShouldRealizeCitizen):
- ProcessCitizenAtHome(instance, citizenId, ref citizen);
- break;
-
- case ResidentState.MovingToTarget:
- ProcessCitizenMoving(instance, citizenId, ref citizen, true);
- break;
-
- case ResidentState.AtSchoolOrWork:
- ProcessCitizenAtSchoolOrWork(instance, citizenId, ref citizen);
- break;
-
- case ResidentState.AtLunch:
- case ResidentState.Shopping:
- case ResidentState.AtLeisureArea:
- case ResidentState.Visiting:
- ProcessCitizenVisit(instance, residentState, citizenId, ref citizen);
- break;
-
- case ResidentState.OnTour:
- ProcessCitizenOnTour(instance, citizenId, ref citizen);
- break;
-
- case ResidentState.Evacuating:
- ProcessCitizenEvacuation(instance, citizenId, ref citizen);
- break;
+ case ScheduleAction.Ignore:
+ return;
- case ResidentState.InShelter:
- CitizenReturnsFromShelter(instance, citizenId, ref citizen);
- break;
+ case ScheduleAction.ProcessTransition:
+ ProcessCitizenMoving(instance, citizenId, ref citizen);
+ return;
+ }
- case ResidentState.Unknown:
- Log.Debug(TimeInfo.Now, $"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is in an UNKNOWN state! Teleporting back home");
- if (CitizenProxy.GetHomeBuilding(ref citizen) != 0)
- {
- CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
- }
+ if (schedule.CurrentState == ResidentState.Unknown)
+ {
+ Log.Debug(TimeInfo.Now, $"WARNING: {GetCitizenDesc(citizenId, ref citizen)} is in an UNKNOWN state! Changing to 'moving'");
+ CitizenProxy.SetLocation(ref citizen, Citizen.Location.Moving);
+ return;
+ }
- break;
+ if (TimeInfo.Now < schedule.ScheduledStateTime)
+ {
+ return;
}
- residentStates[citizenId].State = residentState;
+ UpdateCitizenSchedule(ref schedule, citizenId, ref citizen);
+ ExecuteCitizenSchedule(ref schedule, instance, citizenId, ref citizen);
}
/// Notifies that a citizen has arrived their destination.
/// The citizen ID to process.
public void RegisterCitizenArrival(uint citizenId)
{
- if (citizenId == 0 || citizenId >= residentStates.Length)
+ if (citizenId == 0 || citizenId >= residentSchedules.Length)
{
return;
}
- ref ResidentStateDescriptor citizenState = ref residentStates[citizenId];
+ ref CitizenSchedule citizenState = ref residentSchedules[citizenId];
switch (CitizenMgr.GetCitizenLocation(citizenId))
{
case Citizen.Location.Work:
citizenState.UpdateTravelTimeToWork(TimeInfo.Now);
- Log.Debug($"The citizen {citizenId} arrived at work at {TimeInfo.Now} and needs {residentStates[citizenId].TravelTimeToWork} hours to get to work");
+ Log.Debug($"The citizen {citizenId} arrived at work at {TimeInfo.Now} and needs {residentSchedules[citizenId].TravelTimeToWork} hours to get to work");
break;
case Citizen.Location.Moving:
return;
}
- citizenState.DepartureTime = default;
+ citizenState.DepartureToWorkTime = default;
+ }
+
+ /// Performs simulation for starting a new day for all citizens.
+ public void BeginNewDay()
+ {
+ workBehavior.UpdateLunchTime();
+ }
+
+ /// Performs simulation for starting a new day for a citizen with specified ID.
+ /// The citizen ID to process.
+ public void BeginNewDayForCitizen(uint citizenId)
+ {
+ if (citizenId == 0)
+ {
+ return;
+ }
}
- private bool ShouldRealizeCitizen(TAI ai)
+ /// 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.
+ /// The citizens simulation cycle period, in game hours.
+ public void SetSimulationCyclePeriod(float cyclePeriod)
{
- return residentAI.DoRandomMove(ai);
+ simulationCycle = cyclePeriod;
+ Log.Debug($"SIMULATION CYCLE PERIOD: {cyclePeriod} hours");
}
}
}
\ No newline at end of file
diff --git a/src/RealTime/CustomAI/RealTimeTouristAI.cs b/src/RealTime/CustomAI/RealTimeTouristAI.cs
index e5f00a4e..8f95e801 100644
--- a/src/RealTime/CustomAI/RealTimeTouristAI.cs
+++ b/src/RealTime/CustomAI/RealTimeTouristAI.cs
@@ -102,7 +102,7 @@ private void ProcessMoving(TAI instance, uint citizenId, ref TCitizen citizen)
return;
}
- bool badWeather = IsBadWeather(citizenId);
+ bool badWeather = IsBadWeather();
if (CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour, true))
{
Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} exits the guided tour.");
@@ -149,13 +149,15 @@ private void ProcessVisit(TAI instance, uint citizenId, ref TCitizen citizen)
return;
}
- if (Random.ShouldOccur(TouristEventChance)
- && !IsBadWeather(citizenId)
- && AttendUpcomingEvent(citizenId, ref citizen, out ushort eventBuilding))
+ if (Random.ShouldOccur(TouristEventChance) && !IsBadWeather())
{
- StartMovingToVisitBuilding(instance, citizenId, ref citizen, CitizenProxy.GetCurrentBuilding(ref citizen), eventBuilding);
- Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} attending an event at {eventBuilding}");
- return;
+ ICityEvent cityEvent = GetUpcomingEventToAttend(citizenId, ref citizen);
+ if (cityEvent != null)
+ {
+ StartMovingToVisitBuilding(instance, citizenId, ref citizen, CitizenProxy.GetCurrentBuilding(ref citizen), cityEvent.BuildingId);
+ Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} attending an event at {cityEvent.BuildingId}");
+ return;
+ }
}
int doNothingChance;
@@ -191,7 +193,7 @@ private void FindRandomVisitPlace(TAI instance, uint citizenId, ref TCitizen cit
return;
}
- if (!Random.ShouldOccur(GetGoOutChance(CitizenProxy.GetAge(ref citizen))) || IsBadWeather(citizenId))
+ if (!Random.ShouldOccur(GetGoOutChance(CitizenProxy.GetAge(ref citizen))) || IsBadWeather())
{
FindHotel(instance, citizenId, ref citizen);
return;
diff --git a/src/RealTime/CustomAI/ResidentState.cs b/src/RealTime/CustomAI/ResidentState.cs
index 345f47e9..3613a135 100644
--- a/src/RealTime/CustomAI/ResidentState.cs
+++ b/src/RealTime/CustomAI/ResidentState.cs
@@ -2,44 +2,34 @@
namespace RealTime.CustomAI
{
- /// Possible citizen's states.
+ ///
+ /// Possible citizen's target states. While moving to the target building, citizen will already have the target state.
+ ///
internal enum ResidentState : byte
{
- /// The state could not be determined.
+ /// The state is not defined. A good time to make a decision.
Unknown,
- /// The citizen should be ignored, just a dummy traffic.
+ /// The citizen should be ignored, just dummy traffic.
Ignored,
- /// The citizen is moving to the home building.
- MovingHome,
-
/// The citizen is in the home building.
AtHome,
- /// The citizen is moving to a target that is not their home building.
- MovingToTarget,
-
/// The citizen is in the school or work building.
AtSchoolOrWork,
- /// The citizen has lunch time.
- AtLunch,
-
- /// The citizen is shopping in a commercial building.
+ /// The citizen is shopping or having lunch time in a commercial building.
Shopping,
- /// The citizen is in a commercial leisure building or in a beautification building.
- AtLeisureArea,
+ /// The citizen is in a leisure building or in a beautification building.
+ Relaxing,
/// The citizen visits a building.
Visiting,
- /// The citizen is on a guided tour.
- OnTour,
-
- /// The citizen is evacuating.
- Evacuating,
+ /// The citizen has to evacuate the current building (or area).
+ Evacuation,
/// The citizen is in a shelter building.
InShelter
diff --git a/src/RealTime/CustomAI/ResidentStateDescriptor.cs b/src/RealTime/CustomAI/ResidentStateDescriptor.cs
deleted file mode 100644
index 44060e98..00000000
--- a/src/RealTime/CustomAI/ResidentStateDescriptor.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) dymanoid. All rights reserved.
-
-namespace RealTime.CustomAI
-{
- using System;
- using RealTime.Tools;
- using static Constants;
-
- /// A container struct that holds information about the detailed resident citizen state.
- internal struct ResidentStateDescriptor
- {
- /// The citizen's last known state.
- public ResidentState State;
-
- /// The citizen's current work status.
- public WorkStatus WorkStatus;
-
- /// The time when citizen started their last movement in the city.
- public DateTime DepartureTime;
-
- ///
- /// Gets or sets the travel time (in hours) from citizen's home to the work building. The maximum value is
- /// determined by the constant.
- ///
- public float TravelTimeToWork;
-
- /// The number of days the citizen will be on vacation (as of current game date).
- public byte VacationDaysLeft;
-
- /// Updates the travel time that the citizen needs to read the work building or school/university.
- ///
- /// The arrival time at the work building or school/university. Must be great than .
- ///
- public void UpdateTravelTimeToWork(DateTime arrivalTime)
- {
- if (arrivalTime < DepartureTime || DepartureTime == default)
- {
- return;
- }
-
- float onTheWayHours = (float)(arrivalTime - DepartureTime).TotalHours;
- if (onTheWayHours > MaxHoursOnTheWay)
- {
- onTheWayHours = MaxHoursOnTheWay;
- }
-
- TravelTimeToWork = TravelTimeToWork == 0
- ? onTheWayHours
- : (TravelTimeToWork + onTheWayHours) / 2;
- }
- }
-}
\ No newline at end of file
diff --git a/src/RealTime/CustomAI/ScheduleHint.cs b/src/RealTime/CustomAI/ScheduleHint.cs
new file mode 100644
index 00000000..15aea66b
--- /dev/null
+++ b/src/RealTime/CustomAI/ScheduleHint.cs
@@ -0,0 +1,25 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.CustomAI
+{
+ /// Describes various citizen schedule hints.
+ internal enum ScheduleHint : byte
+ {
+ /// No hint.
+ None,
+
+ /// The citizen can shop only locally.
+ LocalShoppingOnly,
+
+ /// The citizen should find a leisure building.
+ RelaxAtLeisureBuilding,
+
+ /// The citizen is on a guided tour.
+ OnTour,
+
+ /// The citizen is attending an event.
+ AttendingEvent
+ }
+}
\ No newline at end of file
diff --git a/src/RealTime/CustomAI/WorkBehavior.cs b/src/RealTime/CustomAI/WorkBehavior.cs
new file mode 100644
index 00000000..b74549d1
--- /dev/null
+++ b/src/RealTime/CustomAI/WorkBehavior.cs
@@ -0,0 +1,329 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.CustomAI
+{
+ using System;
+ using RealTime.Config;
+ using RealTime.GameConnection;
+ using RealTime.Simulation;
+ using RealTime.Tools;
+ using static Constants;
+
+ ///
+ /// A class containing methods for managing the citizens' work behavior.
+ ///
+ internal sealed class WorkBehavior
+ {
+ private readonly RealTimeConfig config;
+ private readonly IRandomizer randomizer;
+ private readonly IBuildingManagerConnection buildingManager;
+ private readonly ITimeInfo timeInfo;
+ private readonly Func travelTimeCalculator;
+
+ private DateTime lunchBegin;
+ private DateTime lunchEnd;
+
+ /// Initializes a new instance of the class.
+ /// The configuration to run with.
+ /// The randomizer implementation.
+ /// The building manager implementation.
+ /// The time information source.
+ /// A method accepting two building IDs and returning the estimated travel time
+ /// between those buildings (in hours).
+ /// Thrown when any argument is null.
+ public WorkBehavior(
+ RealTimeConfig config,
+ IRandomizer randomizer,
+ IBuildingManagerConnection buildingManager,
+ ITimeInfo timeInfo,
+ Func travelTimeCalculator)
+ {
+ this.config = config ?? throw new ArgumentNullException(nameof(config));
+ this.randomizer = randomizer ?? throw new ArgumentNullException(nameof(randomizer));
+ this.buildingManager = buildingManager ?? throw new ArgumentNullException(nameof(buildingManager));
+ this.timeInfo = timeInfo ?? throw new ArgumentNullException(nameof(timeInfo));
+ this.travelTimeCalculator = travelTimeCalculator ?? throw new ArgumentNullException(nameof(travelTimeCalculator));
+ }
+
+ /// Updates the lunch time according to current date and configuration.
+ public void UpdateLunchTime()
+ {
+ DateTime today = timeInfo.Now.Date;
+ lunchBegin = today.AddHours(config.LunchBegin);
+ lunchEnd = today.AddHours(config.LunchEnd);
+ }
+
+ /// Updates the citizen's work shift parameters in the specified citizen's .
+ /// The citizen's schedule to update the work shift in.
+ /// The age of the citizen.
+ public void UpdateWorkShift(ref CitizenSchedule schedule, Citizen.AgeGroup citizenAge)
+ {
+ if (schedule.WorkBuilding == 0 || citizenAge == Citizen.AgeGroup.Senior)
+ {
+ schedule.UpdateWorkShift(WorkShift.Unemployed, 0, 0, false);
+ return;
+ }
+
+ ItemClass.Service buildingSevice = buildingManager.GetBuildingService(schedule.WorkBuilding);
+ float workBegin, workEnd;
+ WorkShift workShift;
+
+ switch (citizenAge)
+ {
+ case Citizen.AgeGroup.Child:
+ case Citizen.AgeGroup.Teen:
+ workShift = WorkShift.First;
+ workBegin = config.SchoolBegin;
+ workEnd = config.SchoolEnd;
+ break;
+
+ case Citizen.AgeGroup.Young:
+ case Citizen.AgeGroup.Adult:
+ workShift = GetWorkShift(GetBuildingWorkShiftCount(buildingSevice));
+ workBegin = config.WorkBegin;
+ workEnd = config.WorkEnd;
+ break;
+
+ default:
+ return;
+ }
+
+ ItemClass.SubService buildingSubService = buildingManager.GetBuildingSubService(schedule.WorkBuilding);
+ switch (schedule.WorkShift)
+ {
+ case WorkShift.First when HasExtendedFirstWorkShift(buildingSevice, buildingSubService):
+ workBegin = Math.Min(config.WakeupHour, EarliestWakeUp);
+ break;
+
+ case WorkShift.Second:
+ workBegin = workEnd;
+ workEnd = 0;
+ break;
+
+ case WorkShift.Night:
+ workEnd = workBegin;
+ workBegin = 0;
+ break;
+ }
+
+ schedule.UpdateWorkShift(workShift, workBegin, workEnd, IsBuildingActiveOnWeekend(buildingSevice, buildingSubService));
+ }
+
+ /// Updates the citizen's work schedule by determining the time for going to work.
+ /// The citizen's schedule to update.
+ /// The ID of the building where the citizen is currently located.
+ /// The duration (in hours) of a full citizens simulation cycle.
+ /// true if work was scheduled; otherwise, false.
+ public bool ScheduleGoToWork(ref CitizenSchedule schedule, ushort currentBuilding, float simulationCycle)
+ {
+ if (schedule.CurrentState == ResidentState.AtSchoolOrWork)
+ {
+ return false;
+ }
+
+ if (config.IsWeekendEnabled && timeInfo.Now.IsWeekend() && !schedule.WorksOnWeekends)
+ {
+ return false;
+ }
+
+ float travelTime = GetTravelTimeToWork(ref schedule, currentBuilding);
+ float departureHour = schedule.WorkShiftStartHour - travelTime - simulationCycle;
+ schedule.Schedule(ResidentState.AtSchoolOrWork, timeInfo.Now.FutureHour(departureHour));
+ return true;
+ }
+
+ /// Updates the citizen's work schedule by determining the lunch time.
+ /// The citizen's schedule to update.
+ /// The citizen's age.
+ /// true if a lunch time was scheduled; otherwise, false.
+ public bool ScheduleLunch(ref CitizenSchedule schedule, Citizen.AgeGroup citizenAge)
+ {
+ if (schedule.WorkStatus == WorkStatus.Working
+ && schedule.CurrentState == ResidentState.AtSchoolOrWork
+ && schedule.WorkShift == WorkShift.First
+ && WillGoToLunch(citizenAge))
+ {
+ schedule.Schedule(ResidentState.Shopping, lunchBegin);
+ return true;
+ }
+
+ return false;
+ }
+
+ /// Updates the citizen's work schedule by determining the returning from lunch time.
+ /// The citizen's schedule to update.
+ public void ScheduleReturnFromLunch(ref CitizenSchedule schedule)
+ {
+ if (schedule.WorkStatus != WorkStatus.Working || schedule.CurrentState != ResidentState.Shopping)
+ {
+ return;
+ }
+
+ schedule.Schedule(ResidentState.AtSchoolOrWork, lunchEnd);
+ }
+
+ /// Updates the citizen's work schedule by determining the time for returning from work.
+ /// The citizen's schedule to update.
+ /// The age of the citizen.
+ public void ScheduleReturnFromWork(ref CitizenSchedule schedule, Citizen.AgeGroup citizenAge)
+ {
+ if (schedule.WorkStatus != WorkStatus.Working)
+ {
+ return;
+ }
+
+ float departureHour = schedule.WorkShiftEndHour + GetOvertime(citizenAge);
+ schedule.Schedule(ResidentState.Unknown, timeInfo.Now.FutureHour(departureHour));
+ }
+
+ private static bool IsBuildingActiveOnWeekend(ItemClass.Service service, ItemClass.SubService subService)
+ {
+ switch (service)
+ {
+ case ItemClass.Service.Commercial
+ when subService != ItemClass.SubService.CommercialHigh && subService != ItemClass.SubService.CommercialEco:
+ case ItemClass.Service.Tourism:
+ case ItemClass.Service.Electricity:
+ case ItemClass.Service.Water:
+ case ItemClass.Service.Beautification:
+ case ItemClass.Service.HealthCare:
+ case ItemClass.Service.PoliceDepartment:
+ case ItemClass.Service.FireDepartment:
+ case ItemClass.Service.PublicTransport:
+ case ItemClass.Service.Disaster:
+ case ItemClass.Service.Monument:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ private static int GetBuildingWorkShiftCount(ItemClass.Service service)
+ {
+ switch (service)
+ {
+ case ItemClass.Service.Office:
+ case ItemClass.Service.Garbage:
+ case ItemClass.Service.Education:
+ return 1;
+
+ case ItemClass.Service.Road:
+ case ItemClass.Service.Beautification:
+ case ItemClass.Service.Monument:
+ case ItemClass.Service.Citizen:
+ return 2;
+
+ case ItemClass.Service.Commercial:
+ case ItemClass.Service.Industrial:
+ case ItemClass.Service.Tourism:
+ case ItemClass.Service.Electricity:
+ case ItemClass.Service.Water:
+ case ItemClass.Service.HealthCare:
+ case ItemClass.Service.PoliceDepartment:
+ case ItemClass.Service.FireDepartment:
+ case ItemClass.Service.PublicTransport:
+ case ItemClass.Service.Disaster:
+ case ItemClass.Service.Natural:
+ return 3;
+
+ default:
+ return 1;
+ }
+ }
+
+ private static bool HasExtendedFirstWorkShift(ItemClass.Service service, ItemClass.SubService subService)
+ {
+ switch (service)
+ {
+ case ItemClass.Service.Commercial when subService == ItemClass.SubService.CommercialLow:
+ case ItemClass.Service.Beautification:
+ case ItemClass.Service.Garbage:
+ case ItemClass.Service.Road:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ private WorkShift GetWorkShift(int workShiftCount)
+ {
+ switch (workShiftCount)
+ {
+ case 1:
+ return WorkShift.First;
+
+ case 2:
+ return randomizer.ShouldOccur(config.SecondShiftQuota)
+ ? WorkShift.Second
+ : WorkShift.First;
+
+ case 3:
+ int random = randomizer.GetRandomValue(100u);
+ if (random < config.NightShiftQuota)
+ {
+ return WorkShift.Night;
+ }
+ else if (random < config.SecondShiftQuota + config.NightShiftQuota)
+ {
+ return WorkShift.Second;
+ }
+
+ return WorkShift.First;
+
+ default:
+ return WorkShift.Unemployed;
+ }
+ }
+
+ private float GetTravelTimeToWork(ref CitizenSchedule schedule, ushort buildingId)
+ {
+ float result = schedule.CurrentState == ResidentState.AtHome
+ ? schedule.TravelTimeToWork
+ : 0;
+
+ if (result <= 0)
+ {
+ result = travelTimeCalculator(buildingId, schedule.WorkBuilding);
+ }
+
+ return result;
+ }
+
+ private bool WillGoToLunch(Citizen.AgeGroup citizenAge)
+ {
+ if (!config.IsLunchtimeEnabled)
+ {
+ return false;
+ }
+
+ switch (citizenAge)
+ {
+ case Citizen.AgeGroup.Child:
+ case Citizen.AgeGroup.Teen:
+ case Citizen.AgeGroup.Senior:
+ return false;
+ }
+
+ return randomizer.ShouldOccur(config.LunchQuota);
+ }
+
+ private float GetOvertime(Citizen.AgeGroup citizenAge)
+ {
+ switch (citizenAge)
+ {
+ case Citizen.AgeGroup.Young:
+ case Citizen.AgeGroup.Adult:
+ return randomizer.ShouldOccur(config.OnTimeQuota)
+ ? 0
+ : config.MaxOvertime * randomizer.GetRandomValue(100u) / 100f;
+
+ default:
+ return 0;
+ }
+ }
+ }
+}
diff --git a/src/RealTime/CustomAI/WorkShift.cs b/src/RealTime/CustomAI/WorkShift.cs
new file mode 100644
index 00000000..c22795fd
--- /dev/null
+++ b/src/RealTime/CustomAI/WorkShift.cs
@@ -0,0 +1,24 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.CustomAI
+{
+ ///
+ /// An enumeration that describes the citizen's work shift.
+ ///
+ internal enum WorkShift : byte
+ {
+ /// The citizen will not go to work or school.
+ Unemployed,
+
+ /// The citizen will not work first (or default) shift.
+ First,
+
+ /// The citizen will not work second shift.
+ Second,
+
+ /// The citizen will not work night shift.
+ Night
+ }
+}
diff --git a/src/RealTime/CustomAI/WorkStatus.cs b/src/RealTime/CustomAI/WorkStatus.cs
index 0721549e..5c0ae73a 100644
--- a/src/RealTime/CustomAI/WorkStatus.cs
+++ b/src/RealTime/CustomAI/WorkStatus.cs
@@ -10,15 +10,9 @@ namespace RealTime.CustomAI
internal enum WorkStatus : byte
{
/// No special handling.
- Default,
+ None,
- /// The citizen is at work or is heading to the work building.
- AtWork,
-
- /// The citizen takes a break and goes out for lunch.
- AtLunch,
-
- /// The citizen is on vacation or has a day off work.
- OnVacation
+ /// The citizen has working hours.
+ Working
}
}
diff --git a/src/RealTime/Events/RealTimeEventManager.cs b/src/RealTime/Events/RealTimeEventManager.cs
index f79ccdb6..5c109c72 100644
--- a/src/RealTime/Events/RealTimeEventManager.cs
+++ b/src/RealTime/Events/RealTimeEventManager.cs
@@ -172,6 +172,33 @@ public ICityEvent GetUpcomingCityEvent(DateTime earliestStartTime, DateTime late
: null;
}
+ ///
+ /// Gets the instance of an upcoming city event that takes place in a building
+ /// with specified ID.
+ ///
+ /// The ID of a building to search events for.
+ /// An instance of the first matching city event, or null if none found.
+ public ICityEvent GetUpcomingCityEvent(ushort buildingId)
+ {
+ if (buildingId == 0 || upcomingEvents.Count == 0)
+ {
+ return null;
+ }
+
+ LinkedListNode upcomingEvent = upcomingEvents.First;
+ while (upcomingEvent != null)
+ {
+ if (upcomingEvent.Value.BuildingId == buildingId)
+ {
+ return upcomingEvent.Value;
+ }
+
+ upcomingEvent = upcomingEvent.Next;
+ }
+
+ return null;
+ }
+
///
/// Processes the city events simulation step. The method can be called frequently, but the processing occurs periodically
/// at an interval specified by .
diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj
index 74be79a5..f249d1d2 100644
--- a/src/RealTime/RealTime.csproj
+++ b/src/RealTime/RealTime.csproj
@@ -64,8 +64,11 @@
-
+
+
+
+
@@ -124,6 +127,7 @@
+
diff --git a/src/RealTime/Simulation/CitizenProcessor.cs b/src/RealTime/Simulation/CitizenProcessor.cs
new file mode 100644
index 00000000..e19db6ed
--- /dev/null
+++ b/src/RealTime/Simulation/CitizenProcessor.cs
@@ -0,0 +1,78 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.Simulation
+{
+ using System;
+ using RealTime.CustomAI;
+
+ ///
+ /// A class that executes various citizen simulation logic that is not related to movement.
+ ///
+ internal sealed class CitizenProcessor
+ {
+ private const uint StepSize = 256;
+ private const uint StepMask = 0xFFF;
+
+ private readonly RealTimeResidentAI residentAI;
+ private int dayStartFrame;
+
+ /// Initializes a new instance of the class.
+ /// The custom resident AI implementation.
+ /// Thrown when the argument is null.
+ public CitizenProcessor(RealTimeResidentAI residentAI)
+ {
+ this.residentAI = residentAI ?? throw new ArgumentNullException(nameof(residentAI));
+ dayStartFrame = int.MinValue;
+ }
+
+ /// Notifies this simulation object that a new game day begins.
+ public void StartNewDay()
+ {
+ dayStartFrame = int.MinValue;
+ residentAI.BeginNewDay();
+ }
+
+ /// Applies the duration of a simulation frame to this simulation object.
+ /// Duration of a simulation frame in hours.
+ public void SetFrameDuration(float frameDuration)
+ {
+ residentAI.SetSimulationCyclePeriod(frameDuration * (StepMask + 1));
+ }
+
+ /// Processes the simulation frame.
+ /// The index of the simulation frame to process.
+ public void ProcessFrame(uint frameIndex)
+ {
+ if (dayStartFrame == -1)
+ {
+ return;
+ }
+
+ uint step = frameIndex & StepMask;
+ if (dayStartFrame == int.MinValue)
+ {
+ dayStartFrame = (int)step;
+ }
+ else if (step == dayStartFrame)
+ {
+ dayStartFrame = -1;
+ return;
+ }
+
+ uint idFrom = step * StepSize;
+ uint idTo = ((step + 1) * StepSize) - 1;
+
+ for (uint i = idFrom; i <= idTo; i++)
+ {
+ if ((CitizenManager.instance.m_citizens.m_buffer[i].m_flags & Citizen.Flags.Created) == 0)
+ {
+ continue;
+ }
+
+ residentAI.BeginNewDayForCitizen(i);
+ }
+ }
+ }
+}
diff --git a/src/RealTime/Simulation/SimulationHandler.cs b/src/RealTime/Simulation/SimulationHandler.cs
index 1c68c27d..30eb8de1 100644
--- a/src/RealTime/Simulation/SimulationHandler.cs
+++ b/src/RealTime/Simulation/SimulationHandler.cs
@@ -43,6 +43,9 @@ public sealed class SimulationHandler : ThreadingExtensionBase
/// Gets or sets the weather information class instance.
internal static WeatherInfo WeatherInfo { get; set; }
+ /// Gets or sets the citizen processing class instance.
+ internal static CitizenProcessor CitizenProcessor { get; set; }
+
///
/// Called before each game simulation tick. A tick contains multiple frames.
/// Performs the dispatching for this simulation phase.
@@ -50,22 +53,18 @@ public sealed class SimulationHandler : ThreadingExtensionBase
public override void OnBeforeSimulationTick()
{
WeatherInfo?.Update();
- }
-
- ///
- /// Called after each game simulation tick. A tick contains multiple frames.
- /// Performs the dispatching for this simulation phase.
- ///
- public override void OnAfterSimulationTick()
- {
EventManager?.ProcessEvents();
- TimeAdjustment?.Update();
+ if (TimeAdjustment != null && TimeAdjustment.Update())
+ {
+ CitizenProcessor?.SetFrameDuration(TimeAdjustment.HoursPerFrame);
+ }
DateTime currentDate = SimulationManager.instance.m_currentGameTime.Date;
if (currentDate != lastHandledDate)
{
lastHandledDate = currentDate;
DayTimeSimulation?.Process(currentDate);
+ CitizenProcessor?.StartNewDay();
OnNewDay(this);
}
}
@@ -77,6 +76,7 @@ public override void OnBeforeSimulationFrame()
{
uint currentFrame = SimulationManager.instance.m_currentFrameIndex;
Buildings?.ProcessFrame(currentFrame);
+ CitizenProcessor?.ProcessFrame(currentFrame);
}
private static void OnNewDay(SimulationHandler sender)
diff --git a/src/RealTime/Simulation/TimeAdjustment.cs b/src/RealTime/Simulation/TimeAdjustment.cs
index a14d3446..f46c8e39 100644
--- a/src/RealTime/Simulation/TimeAdjustment.cs
+++ b/src/RealTime/Simulation/TimeAdjustment.cs
@@ -28,6 +28,9 @@ public TimeAdjustment(RealTimeConfig config)
vanillaFramesPerDay = SimulationManager.DAYTIME_FRAMES;
}
+ /// Gets the number of hours that fit into one simulation frame.
+ public float HoursPerFrame => SimulationManager.DAYTIME_FRAME_TO_HOUR;
+
/// Enables the customized time adjustment.
/// The current game date and time.
public DateTime Enable()
@@ -40,7 +43,8 @@ public DateTime Enable()
}
/// Updates the time adjustment to be synchronized with the configuration and the daytime.
- public void Update()
+ /// true if the time adjustment was updated; otherwise, false.
+ public bool Update()
{
if (SimulationManager.instance.m_enableDayNight != isNightEnabled)
{
@@ -51,7 +55,7 @@ public void Update()
&& dayTimeSpeed == config.DayTimeSpeed
&& nightTimeSpeed == config.NightTimeSpeed))
{
- return;
+ return false;
}
isNightTime = SimulationManager.instance.m_isNightTime;
@@ -59,6 +63,7 @@ public void Update()
nightTimeSpeed = config.NightTimeSpeed;
UpdateTimeSimulationValues(CalculateFramesPerDay());
+ return true;
}
/// Disables the customized time adjustment restoring the default vanilla values.
From 43193c9a7229d6d32a477d59da2f933afdb4282f Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 19:12:20 +0200
Subject: [PATCH 16/42] Improve performance by consolidating frame-common
calculations (#57)
---
src/RealTime/Core/RealTimeCore.cs | 11 ++-
src/RealTime/CustomAI/RealTimeHumanAIBase.cs | 60 -----------
.../CustomAI/RealTimeResidentAI.Visit.cs | 4 +-
src/RealTime/CustomAI/RealTimeResidentAI.cs | 7 +-
src/RealTime/CustomAI/RealTimeTouristAI.cs | 9 +-
src/RealTime/CustomAI/SpareTimeBehavior.cs | 99 +++++++++++++++++++
src/RealTime/RealTime.csproj | 1 +
src/RealTime/Simulation/CitizenProcessor.cs | 13 ++-
8 files changed, 133 insertions(+), 71 deletions(-)
create mode 100644 src/RealTime/CustomAI/SpareTimeBehavior.cs
diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs
index c7fc25b6..9908ccef 100644
--- a/src/RealTime/Core/RealTimeCore.cs
+++ b/src/RealTime/Core/RealTimeCore.cs
@@ -127,6 +127,7 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, ILocaliza
var timeAdjustment = new TimeAdjustment(config);
DateTime gameDate = timeAdjustment.Enable();
+ SimulationHandler.CitizenProcessor.SetFrameDuration(timeAdjustment.HoursPerFrame);
CityEventsLoader.Instance.ReloadEvents(rootPath);
@@ -223,14 +224,17 @@ private static bool SetupCustomAI(
return false;
}
+ var spareTimeBehavior = new SpareTimeBehavior(config, timeInfo);
+
var realTimeResidentAI = new RealTimeResidentAI(
config,
gameConnections,
residentAIConnection,
- eventManager);
+ eventManager,
+ spareTimeBehavior);
ResidentAIPatch.RealTimeAI = realTimeResidentAI;
- SimulationHandler.CitizenProcessor = new CitizenProcessor(realTimeResidentAI);
+ SimulationHandler.CitizenProcessor = new CitizenProcessor(realTimeResidentAI, spareTimeBehavior);
TouristAIConnection touristAIConnection = TouristAIPatch.GetTouristAIConnection();
if (touristAIConnection == null)
@@ -242,7 +246,8 @@ private static bool SetupCustomAI(
config,
gameConnections,
touristAIConnection,
- eventManager);
+ eventManager,
+ spareTimeBehavior);
TouristAIPatch.RealTimeAI = realTimeTouristAI;
diff --git a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
index 530cf14c..9c42fba7 100644
--- a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
+++ b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
@@ -106,66 +106,6 @@ protected RealTimeHumanAIBase(RealTimeConfig config, GameConnections c
/// Gets the maximum count of the citizen instances.
protected uint CitizenInstancesMaxCount { get; }
- ///
- /// Gets the probability whether a citizen with provided age would go out on current time.
- ///
- ///
- /// The citizen age to check.
- ///
- /// A percentage value in range of 0..100 that describes the probability whether
- /// a citizen with provided age would go out on current time.
- // TODO: make this method to a part of time simulation (no need to calculate for each citizen)
- protected uint GetGoOutChance(Citizen.AgeGroup citizenAge)
- {
- float currentHour = TimeInfo.CurrentHour;
-
- uint weekdayModifier;
- if (Config.IsWeekendEnabled)
- {
- weekdayModifier = TimeInfo.Now.IsWeekendTime(12f, Config.GoToSleepUpHour)
- ? 11u
- : 1u;
- }
- else
- {
- weekdayModifier = 1u;
- }
-
- float latestGoOutHour = Config.GoToSleepUpHour - 2f;
- bool isDayTime = currentHour >= Config.WakeupHour && currentHour < latestGoOutHour;
- float timeModifier;
- if (isDayTime)
- {
- timeModifier = 4f;
- }
- else
- {
- float nightDuration = 24f - (latestGoOutHour - Config.WakeupHour);
- float relativeHour = currentHour - latestGoOutHour;
- if (relativeHour < 0)
- {
- relativeHour += 24f;
- }
-
- timeModifier = 3f / nightDuration * (nightDuration - relativeHour);
- }
-
- switch (citizenAge)
- {
- case Citizen.AgeGroup.Child when isDayTime:
- case Citizen.AgeGroup.Teen when isDayTime:
- case Citizen.AgeGroup.Young:
- case Citizen.AgeGroup.Adult:
- return (uint)((timeModifier + weekdayModifier) * timeModifier);
-
- case Citizen.AgeGroup.Senior when isDayTime:
- return 30 + weekdayModifier;
-
- default:
- return 0;
- }
- }
-
///
/// Ensures that the provided citizen is in a valid state and can be processed.
///
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
index a3142a93..bdbd6d47 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
@@ -14,7 +14,7 @@ internal sealed partial class RealTimeResidentAI
private bool ScheduleRelaxing(ref CitizenSchedule schedule, uint citizenId, ref TCitizen citizen)
{
Citizen.AgeGroup citizenAge = CitizenProxy.GetAge(ref citizen);
- if (!Random.ShouldOccur(GetGoOutChance(citizenAge)) || IsBadWeather())
+ if (!Random.ShouldOccur(spareTimeBehavior.GetGoOutChance(citizenAge)) || IsBadWeather())
{
return false;
}
@@ -101,7 +101,7 @@ private bool ScheduleShopping(ref CitizenSchedule schedule, ref TCitizen citizen
if (TimeInfo.IsNightTime)
{
- if (Random.ShouldOccur(GetGoOutChance(CitizenProxy.GetAge(ref citizen))))
+ if (Random.ShouldOccur(spareTimeBehavior.GetGoOutChance(CitizenProxy.GetAge(ref citizen))))
{
schedule.Hint = ScheduleHint.LocalShoppingOnly;
schedule.Schedule(ResidentState.Shopping, default);
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.cs b/src/RealTime/CustomAI/RealTimeResidentAI.cs
index a6ff2761..dafcb00c 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.cs
@@ -18,6 +18,7 @@ internal sealed partial class RealTimeResidentAI : RealTimeHumanA
{
private readonly ResidentAIConnection residentAI;
private readonly WorkBehavior workBehavior;
+ private readonly SpareTimeBehavior spareTimeBehavior;
private readonly CitizenSchedule[] residentSchedules;
private float simulationCycle;
@@ -27,14 +28,17 @@ internal sealed partial class RealTimeResidentAI : RealTimeHumanA
/// A instance that provides the game connection implementation.
/// A connection to the game's resident AI.
/// A instance.
+ /// A behavior that provides simulation info for the citizens spare time.
public RealTimeResidentAI(
RealTimeConfig config,
GameConnections connections,
ResidentAIConnection residentAI,
- RealTimeEventManager eventManager)
+ RealTimeEventManager eventManager,
+ SpareTimeBehavior spareTimeBehavior)
: base(config, connections, eventManager)
{
this.residentAI = residentAI ?? throw new ArgumentNullException(nameof(residentAI));
+ this.spareTimeBehavior = spareTimeBehavior ?? throw new ArgumentNullException(nameof(spareTimeBehavior));
residentSchedules = new CitizenSchedule[CitizenMgr.GetMaxCitizensCount()];
workBehavior = new WorkBehavior(config, connections.Random, connections.BuildingManager, connections.TimeInfo, GetEstimatedTravelTime);
}
@@ -127,6 +131,7 @@ public void BeginNewDay()
/// The citizen ID to process.
public void BeginNewDayForCitizen(uint citizenId)
{
+ // TODO: use this method
if (citizenId == 0)
{
return;
diff --git a/src/RealTime/CustomAI/RealTimeTouristAI.cs b/src/RealTime/CustomAI/RealTimeTouristAI.cs
index 8f95e801..36aa72ad 100644
--- a/src/RealTime/CustomAI/RealTimeTouristAI.cs
+++ b/src/RealTime/CustomAI/RealTimeTouristAI.cs
@@ -17,11 +17,13 @@ namespace RealTime.CustomAI
/// The type of the tourist AI.
/// The type of the citizen objects.
///
+ // TODO: tourist AI should be unified with resident AI where possible
internal sealed class RealTimeTouristAI : RealTimeHumanAIBase
where TAI : class
where TCitizen : struct
{
private readonly TouristAIConnection touristAI;
+ private readonly SpareTimeBehavior spareTimeBehavior;
///
/// Initializes a new instance of the class.
@@ -31,16 +33,19 @@ internal sealed class RealTimeTouristAI : RealTimeHumanAIBaseA instance that provides the game connection implementation.
/// A connection to game's tourist AI.
/// The custom event manager.
+ /// A behavior that provides simulation info for the citizens spare time.
///
/// Thrown when any argument is null.
public RealTimeTouristAI(
RealTimeConfig config,
GameConnections connections,
TouristAIConnection touristAI,
- RealTimeEventManager eventManager)
+ RealTimeEventManager eventManager,
+ SpareTimeBehavior spareTimeBehavior)
: base(config, connections, eventManager)
{
this.touristAI = touristAI ?? throw new ArgumentNullException(nameof(touristAI));
+ this.spareTimeBehavior = spareTimeBehavior ?? throw new ArgumentNullException(nameof(spareTimeBehavior));
}
///
@@ -193,7 +198,7 @@ private void FindRandomVisitPlace(TAI instance, uint citizenId, ref TCitizen cit
return;
}
- if (!Random.ShouldOccur(GetGoOutChance(CitizenProxy.GetAge(ref citizen))) || IsBadWeather())
+ if (!Random.ShouldOccur(spareTimeBehavior.GetGoOutChance(CitizenProxy.GetAge(ref citizen))) || IsBadWeather())
{
FindHotel(instance, citizenId, ref citizen);
return;
diff --git a/src/RealTime/CustomAI/SpareTimeBehavior.cs b/src/RealTime/CustomAI/SpareTimeBehavior.cs
new file mode 100644
index 00000000..2ce11f1b
--- /dev/null
+++ b/src/RealTime/CustomAI/SpareTimeBehavior.cs
@@ -0,0 +1,99 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.CustomAI
+{
+ using System;
+ using RealTime.Config;
+ using RealTime.Simulation;
+ using RealTime.Tools;
+
+ ///
+ /// A class that provides custom logic for the spare time simulation.
+ ///
+ internal sealed class SpareTimeBehavior
+ {
+ private readonly RealTimeConfig config;
+ private readonly ITimeInfo timeInfo;
+ private readonly uint[] chances;
+ private float simulationCycle;
+
+ /// Initializes a new instance of the class.
+ /// The configuration to run with.
+ /// The object providing the game time information.
+ /// Thrown when any argument is null.
+ public SpareTimeBehavior(RealTimeConfig config, ITimeInfo timeInfo)
+ {
+ this.config = config ?? throw new ArgumentNullException(nameof(config));
+ this.timeInfo = timeInfo ?? throw new ArgumentNullException(nameof(timeInfo));
+ chances = new uint[Enum.GetValues(typeof(Citizen.AgeGroup)).Length];
+ }
+
+ /// 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.
+ /// The citizens simulation cycle period, in game hours.
+ public void SetSimulationCyclePeriod(float cyclePeriod)
+ {
+ simulationCycle = cyclePeriod;
+ }
+
+ /// Calculates the chances for the citizens to go out based on the current game time.
+ public void RefreshGoOutChances()
+ {
+ uint weekdayModifier;
+ if (config.IsWeekendEnabled)
+ {
+ weekdayModifier = timeInfo.Now.IsWeekendTime(12f, config.GoToSleepUpHour)
+ ? 11u
+ : 1u;
+ }
+ else
+ {
+ weekdayModifier = 1u;
+ }
+
+ float currentHour = timeInfo.CurrentHour;
+
+ float latestGoOutHour = config.GoToSleepUpHour - simulationCycle;
+ bool isDayTime = currentHour >= config.WakeupHour && currentHour < latestGoOutHour;
+ float timeModifier;
+ if (isDayTime)
+ {
+ timeModifier = RealTimeMath.Clamp(currentHour - config.WakeupHour - simulationCycle, 0, 4f);
+ }
+ else
+ {
+ float nightDuration = 24f - (latestGoOutHour - config.WakeupHour);
+ float relativeHour = currentHour - latestGoOutHour;
+ if (relativeHour < 0)
+ {
+ relativeHour += 24f;
+ }
+
+ timeModifier = 3f / nightDuration * (nightDuration - relativeHour);
+ }
+
+ uint defaultChance = (uint)((timeModifier + weekdayModifier) * timeModifier);
+
+ chances[(int)Citizen.AgeGroup.Child] = isDayTime ? defaultChance : 0;
+ chances[(int)Citizen.AgeGroup.Teen] = isDayTime ? defaultChance : 0;
+ chances[(int)Citizen.AgeGroup.Young] = defaultChance;
+ chances[(int)Citizen.AgeGroup.Adult] = defaultChance;
+ chances[(int)Citizen.AgeGroup.Senior] = defaultChance;
+ }
+
+ ///
+ /// Gets the probability whether a citizen with provided age would go out on current time.
+ ///
+ ///
+ /// The citizen age to check.
+ ///
+ /// A percentage value in range of 0..100 that describes the probability whether
+ /// a citizen with provided age would go out on current time.
+ public uint GetGoOutChance(Citizen.AgeGroup citizenAge)
+ {
+ return chances[(int)citizenAge];
+ }
+ }
+}
diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj
index f249d1d2..54594bdb 100644
--- a/src/RealTime/RealTime.csproj
+++ b/src/RealTime/RealTime.csproj
@@ -66,6 +66,7 @@
+
diff --git a/src/RealTime/Simulation/CitizenProcessor.cs b/src/RealTime/Simulation/CitizenProcessor.cs
index e19db6ed..c7fd79e2 100644
--- a/src/RealTime/Simulation/CitizenProcessor.cs
+++ b/src/RealTime/Simulation/CitizenProcessor.cs
@@ -16,14 +16,17 @@ internal sealed class CitizenProcessor
private const uint StepMask = 0xFFF;
private readonly RealTimeResidentAI residentAI;
+ private readonly SpareTimeBehavior spareTimeBehavior;
private int dayStartFrame;
/// Initializes a new instance of the class.
/// The custom resident AI implementation.
- /// Thrown when the argument is null.
- public CitizenProcessor(RealTimeResidentAI residentAI)
+ /// A behavior that provides simulation info for the citizens spare time.
+ /// Thrown when any argument is null.
+ public CitizenProcessor(RealTimeResidentAI residentAI, SpareTimeBehavior spareTimeBehavior)
{
this.residentAI = residentAI ?? throw new ArgumentNullException(nameof(residentAI));
+ this.spareTimeBehavior = spareTimeBehavior ?? throw new ArgumentNullException(nameof(spareTimeBehavior));
dayStartFrame = int.MinValue;
}
@@ -38,13 +41,17 @@ public void StartNewDay()
/// Duration of a simulation frame in hours.
public void SetFrameDuration(float frameDuration)
{
- residentAI.SetSimulationCyclePeriod(frameDuration * (StepMask + 1));
+ float cyclePeriod = frameDuration * (StepMask + 1);
+ residentAI.SetSimulationCyclePeriod(cyclePeriod);
+ spareTimeBehavior.SetSimulationCyclePeriod(cyclePeriod);
}
/// Processes the simulation frame.
/// The index of the simulation frame to process.
public void ProcessFrame(uint frameIndex)
{
+ spareTimeBehavior.RefreshGoOutChances();
+
if (dayStartFrame == -1)
{
return;
From b6cd03602d7c5f5c99a0b45b145601ba3af2fe7b Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 19:12:40 +0200
Subject: [PATCH 17/42] Improve debug logging
---
.../CustomAI/RealTimeResidentAI.Common.cs | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
index e8e1f0a2..1feaae49 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
@@ -234,12 +234,15 @@ when BuildingMgr.GetBuildingSubService(currentBuilding) == ItemClass.SubService.
private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId, ref TCitizen citizen)
{
+ Log.Debug(TimeInfo.Now, $"Calculating schedule for {GetCitizenDesc(citizenId, ref citizen)}...");
+
// If the game changed the work building, we have to update the work shifts first
ushort workBuilding = CitizenProxy.GetWorkBuilding(ref citizen);
if (schedule.WorkBuilding != workBuilding)
{
schedule.WorkBuilding = workBuilding;
workBehavior.UpdateWorkShift(ref schedule, CitizenProxy.GetAge(ref citizen));
+ Log.Debug($" - Updated work shifts: work shift {schedule.WorkShift}, {schedule.WorkShiftStartHour} - {schedule.WorkShiftEndHour}, weekends: {schedule.WorksOnWeekends}");
}
if (schedule.ScheduledState != ResidentState.Unknown)
@@ -247,8 +250,6 @@ private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId,
return;
}
- Log.Debug($"Calculating schedule for citizen instance {CitizenProxy.GetInstance(ref citizen)}...");
-
if (schedule.WorkStatus == WorkStatus.Working)
{
schedule.WorkStatus = WorkStatus.None;
@@ -274,8 +275,15 @@ private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId,
if (timeLeft <= MaxTravelTime)
{
// If we have some time, try to shop locally.
- Log.Debug($" - Worktime in {timeLeft} hours, trying local shop");
- ScheduleShopping(ref schedule, ref citizen, true);
+ if (ScheduleShopping(ref schedule, ref citizen, true))
+ {
+ Log.Debug($" - Worktime in {timeLeft} hours, trying local shop");
+ }
+ else
+ {
+ Log.Debug($" - Worktime in {timeLeft} hours, doing nothing");
+ }
+
return;
}
}
From 06a45441afa4d9fb457965a98ee31878a83b34b6 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 19:22:54 +0200
Subject: [PATCH 18/42] Change the work shift settings value format
---
src/RealTime/Config/ConfigurationProvider.cs | 2 +-
src/RealTime/Config/RealTimeConfig.cs | 37 ++++++++++++++------
2 files changed, 28 insertions(+), 11 deletions(-)
diff --git a/src/RealTime/Config/ConfigurationProvider.cs b/src/RealTime/Config/ConfigurationProvider.cs
index 36ab31a1..9a12d122 100644
--- a/src/RealTime/Config/ConfigurationProvider.cs
+++ b/src/RealTime/Config/ConfigurationProvider.cs
@@ -70,7 +70,7 @@ private static RealTimeConfig Deserialize()
var serializer = new XmlSerializer(typeof(RealTimeConfig));
using (var sr = new StreamReader(SettingsFileName))
{
- return ((RealTimeConfig)serializer.Deserialize(sr)).Validate();
+ return ((RealTimeConfig)serializer.Deserialize(sr)).MigrateWhenNecessary().Validate();
}
}
diff --git a/src/RealTime/Config/RealTimeConfig.cs b/src/RealTime/Config/RealTimeConfig.cs
index 041191da..dc902b1c 100644
--- a/src/RealTime/Config/RealTimeConfig.cs
+++ b/src/RealTime/Config/RealTimeConfig.cs
@@ -18,6 +18,9 @@ public RealTimeConfig()
ResetToDefaults();
}
+ /// Gets or sets the version number of this configuration.
+ public int Version { get; set; }
+
///
/// Gets or sets the daytime hour when the city wakes up.
///
@@ -96,7 +99,7 @@ public RealTimeConfig()
/// Valid values are 1..8.
///
[ConfigItem("2Quotas", 0)]
- [ConfigItemSlider(1, 8, DisplayMultiplier = 3.125f)]
+ [ConfigItemSlider(1, 25)]
public uint SecondShiftQuota { get; set; }
///
@@ -104,15 +107,15 @@ public RealTimeConfig()
/// Valid values are 1..8.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "NightShift", Justification = "Reviewed")]
- [ConfigItem("2Quotas", 0)]
- [ConfigItemSlider(1, 8, DisplayMultiplier = 3.125f)]
+ [ConfigItem("2Quotas", 1)]
+ [ConfigItemSlider(1, 25)]
public uint NightShiftQuota { get; set; }
///
/// Gets or sets the percentage of the Cims that will go out for lunch.
/// Valid values are 0..100.
///
- [ConfigItem("2Quotas", 0)]
+ [ConfigItem("2Quotas", 2)]
[ConfigItemSlider(0, 100)]
public uint LunchQuota { get; set; }
@@ -120,7 +123,7 @@ public RealTimeConfig()
/// Gets or sets the percentage of the population that will search locally for buildings.
/// Valid values are 0..100.
///
- [ConfigItem("2Quotas", 1)]
+ [ConfigItem("2Quotas", 3)]
[ConfigItemSlider(0, 100)]
public uint LocalBuildingSearchQuota { get; set; }
@@ -129,7 +132,7 @@ public RealTimeConfig()
/// on time (no overtime!).
/// Valid values are 0..100.
///
- [ConfigItem("2Quotas", 2)]
+ [ConfigItem("2Quotas", 4)]
[ConfigItemSlider(0, 100)]
public uint OnTimeQuota { get; set; }
@@ -212,6 +215,20 @@ public RealTimeConfig()
[ConfigItemSlider(11, 16, 0.25f, SliderValueType.Time)]
public float SchoolEnd { get; set; }
+ /// Checks the version of the deserialized object and migrates it to the latest version when necessary.
+ /// This instance.
+ public RealTimeConfig MigrateWhenNecessary()
+ {
+ if (Version == 0)
+ {
+ SecondShiftQuota = (uint)(SecondShiftQuota * 3.125f);
+ NightShiftQuota = (uint)(NightShiftQuota * 3.125f);
+ }
+
+ Version = 1;
+ return this;
+ }
+
/// Validates this instance and corrects possible invalid property values.
/// This instance.
public RealTimeConfig Validate()
@@ -225,8 +242,8 @@ public RealTimeConfig Validate()
VirtualCitizens = (VirtualCitizensLevel)RealTimeMath.Clamp((int)VirtualCitizens, (int)VirtualCitizensLevel.None, (int)VirtualCitizensLevel.Many);
ConstructionSpeed = RealTimeMath.Clamp(ConstructionSpeed, 0u, 100u);
- SecondShiftQuota = RealTimeMath.Clamp(SecondShiftQuota, 1u, 8u);
- NightShiftQuota = RealTimeMath.Clamp(NightShiftQuota, 1u, 8u);
+ SecondShiftQuota = RealTimeMath.Clamp(SecondShiftQuota, 1u, 25u);
+ NightShiftQuota = RealTimeMath.Clamp(NightShiftQuota, 1u, 25u);
LunchQuota = RealTimeMath.Clamp(LunchQuota, 0u, 100u);
LocalBuildingSearchQuota = RealTimeMath.Clamp(LocalBuildingSearchQuota, 0u, 100u);
OnTimeQuota = RealTimeMath.Clamp(OnTimeQuota, 0u, 100u);
@@ -273,8 +290,8 @@ public void ResetToDefaults()
StopConstructionAtNight = true;
ConstructionSpeed = 50;
- SecondShiftQuota = 4;
- NightShiftQuota = 2;
+ SecondShiftQuota = 13;
+ NightShiftQuota = 6;
LunchQuota = 80;
LocalBuildingSearchQuota = 60;
From 8a894d37ee67ec6713b5647bd604935260f88a8f Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 21:10:33 +0200
Subject: [PATCH 19/42] Improve performance by reducing method call frequency
---
src/RealTime/Simulation/CitizenProcessor.cs | 8 ++++++--
src/RealTime/Simulation/SimulationHandler.cs | 9 +++++++--
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/RealTime/Simulation/CitizenProcessor.cs b/src/RealTime/Simulation/CitizenProcessor.cs
index c7fd79e2..ff5bf8e6 100644
--- a/src/RealTime/Simulation/CitizenProcessor.cs
+++ b/src/RealTime/Simulation/CitizenProcessor.cs
@@ -46,12 +46,16 @@ public void SetFrameDuration(float frameDuration)
spareTimeBehavior.SetSimulationCyclePeriod(cyclePeriod);
}
+ /// Processes the simulation tick.
+ public void ProcessTick()
+ {
+ spareTimeBehavior.RefreshGoOutChances();
+ }
+
/// Processes the simulation frame.
/// The index of the simulation frame to process.
public void ProcessFrame(uint frameIndex)
{
- spareTimeBehavior.RefreshGoOutChances();
-
if (dayStartFrame == -1)
{
return;
diff --git a/src/RealTime/Simulation/SimulationHandler.cs b/src/RealTime/Simulation/SimulationHandler.cs
index 30eb8de1..b56e408b 100644
--- a/src/RealTime/Simulation/SimulationHandler.cs
+++ b/src/RealTime/Simulation/SimulationHandler.cs
@@ -54,9 +54,14 @@ public override void OnBeforeSimulationTick()
{
WeatherInfo?.Update();
EventManager?.ProcessEvents();
- if (TimeAdjustment != null && TimeAdjustment.Update())
+
+ if (CitizenProcessor != null)
{
- CitizenProcessor?.SetFrameDuration(TimeAdjustment.HoursPerFrame);
+ CitizenProcessor.ProcessTick();
+ if (TimeAdjustment != null && TimeAdjustment.Update())
+ {
+ CitizenProcessor.SetFrameDuration(TimeAdjustment.HoursPerFrame);
+ }
}
DateTime currentDate = SimulationManager.instance.m_currentGameTime.Date;
From 1fe62a6fb9010c8f6fe17f23f42c4d47213b9dea Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 21:10:51 +0200
Subject: [PATCH 20/42] Clean up code
---
src/RealTime/Events/RealTimeEventManager.cs | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/RealTime/Events/RealTimeEventManager.cs b/src/RealTime/Events/RealTimeEventManager.cs
index 5c109c72..627a4feb 100644
--- a/src/RealTime/Events/RealTimeEventManager.cs
+++ b/src/RealTime/Events/RealTimeEventManager.cs
@@ -272,9 +272,10 @@ void IStorageData.ReadData(Stream source)
void IStorageData.StoreData(Stream target)
{
var serializer = new XmlSerializer(typeof(RealTimeEventStorageContainer));
- var data = new RealTimeEventStorageContainer();
-
- data.EarliestEvent = earliestEvent.Ticks;
+ var data = new RealTimeEventStorageContainer
+ {
+ EarliestEvent = earliestEvent.Ticks
+ };
AddEventToStorage(lastActiveEvent);
AddEventToStorage(activeEvent);
From 16b9e938e214ccc91611054196dfd38f9a281e1e Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 21:12:36 +0200
Subject: [PATCH 21/42] Improve visit and shopping logic
---
.../CustomAI/RealTimeResidentAI.Visit.cs | 43 ++++++++-----------
1 file changed, 19 insertions(+), 24 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
index bdbd6d47..3b905188 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
@@ -99,31 +99,23 @@ private bool ScheduleShopping(ref CitizenSchedule schedule, ref TCitizen citizen
return false;
}
- if (TimeInfo.IsNightTime)
+ if (!Random.ShouldOccur(spareTimeBehavior.GetGoOutChance(CitizenProxy.GetAge(ref citizen)))
+ || !Random.ShouldOccur(GoShoppingChance))
{
- if (Random.ShouldOccur(spareTimeBehavior.GetGoOutChance(CitizenProxy.GetAge(ref citizen))))
- {
- schedule.Hint = ScheduleHint.LocalShoppingOnly;
- schedule.Schedule(ResidentState.Shopping, default);
- return true;
- }
-
return false;
}
- schedule.Hint = ScheduleHint.None;
- if (Random.ShouldOccur(GoShoppingChance))
+ if (TimeInfo.IsNightTime || localOnly || Random.ShouldOccur(Config.LocalBuildingSearchQuota))
{
- if (localOnly || Random.ShouldOccur(Config.LocalBuildingSearchQuota))
- {
- schedule.Hint = ScheduleHint.LocalShoppingOnly;
- }
-
- schedule.Schedule(ResidentState.Shopping, default);
- return true;
+ schedule.Hint = ScheduleHint.LocalShoppingOnly;
+ }
+ else
+ {
+ schedule.Hint = ScheduleHint.None;
}
- return false;
+ schedule.Schedule(ResidentState.Shopping, default);
+ return true;
}
private void DoScheduledShopping(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
@@ -173,10 +165,14 @@ private void ProcessCitizenShopping(ref TCitizen citizen)
CitizenProxy.RemoveFlags(ref citizen, Citizen.Flags.NeedGoods);
}
- private void ProcessCitizenVisit(ref CitizenSchedule schedule, ref TCitizen citizen)
+ private bool ProcessCitizenVisit(ref CitizenSchedule schedule, ref TCitizen citizen)
{
- ushort visitBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
+ if (schedule.ScheduledState != ResidentState.Unknown)
+ {
+ return false;
+ }
+ ushort visitBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
if (schedule.Hint == ScheduleHint.OnTour
|| Random.ShouldOccur(ReturnFromVisitChance)
|| (BuildingMgr.GetBuildingSubService(visitBuilding) == ItemClass.SubService.CommercialLeisure
@@ -184,11 +180,10 @@ private void ProcessCitizenVisit(ref CitizenSchedule schedule, ref TCitizen citi
&& BuildingMgr.IsBuildingNoiseRestricted(visitBuilding)))
{
schedule.Schedule(ResidentState.Unknown, default);
+ return true;
}
- else
- {
- schedule.Schedule(ResidentState.Visiting, default);
- }
+
+ return false;
}
private bool IsBuildingNoiseRestricted(ushort building)
From 3967b2d28e8c882a7c488969502a805ac3b7e36b Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 21:13:37 +0200
Subject: [PATCH 22/42] Improve go to work logic
---
src/RealTime/CustomAI/Constants.cs | 3 +
.../CustomAI/RealTimeResidentAI.Common.cs | 68 +++++++++----------
.../CustomAI/RealTimeResidentAI.SchoolWork.cs | 45 ++++++++++--
src/RealTime/CustomAI/RealTimeResidentAI.cs | 9 +--
src/RealTime/Tools/DateTimeExtensions.cs | 2 +-
5 files changed, 80 insertions(+), 47 deletions(-)
diff --git a/src/RealTime/CustomAI/Constants.cs b/src/RealTime/CustomAI/Constants.cs
index 11b722fd..60b24959 100644
--- a/src/RealTime/CustomAI/Constants.cs
+++ b/src/RealTime/CustomAI/Constants.cs
@@ -17,6 +17,9 @@ internal static class Constants
/// A distance in game units that corresponds to the complete map.
public const float FullSearchDistance = BuildingManager.BUILDINGGRID_RESOLUTION * BuildingManager.BUILDINGGRID_CELL_SIZE / 2f;
+ /// A chance in percent for an unemployed citizen to stay home until next morning.
+ public const uint StayHomeAllDayChance = 15;
+
/// A chance in percent for a citizen to go shopping.
public const uint GoShoppingChance = 80;
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
index 1feaae49..3374260d 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
@@ -10,6 +10,8 @@ namespace RealTime.CustomAI
internal sealed partial class RealTimeResidentAI
{
+ private DateTime todayWakeup;
+
private enum ScheduleAction
{
Ignore,
@@ -128,13 +130,16 @@ private void DoScheduledEvacuation(ref CitizenSchedule schedule, TAI instance, u
}
}
- private void ProcessCitizenInShelter(ref CitizenSchedule schedule, ref TCitizen citizen)
+ private bool ProcessCitizenInShelter(ref CitizenSchedule schedule, ref TCitizen citizen)
{
ushort shelter = CitizenProxy.GetVisitBuilding(ref citizen);
if (BuildingMgr.BuildingHasFlags(shelter, Building.Flags.Downgrading))
{
schedule.Schedule(ResidentState.Unknown, default);
+ return true;
}
+
+ return false;
}
private ScheduleAction UpdateCitizenState(uint citizenId, ref TCitizen citizen, ref CitizenSchedule schedule)
@@ -255,37 +260,17 @@ private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId,
schedule.WorkStatus = WorkStatus.None;
}
- DateTime nextActivityTime = TimeInfo.Now.FutureHour(Config.WakeupHour);
+ DateTime nextActivityTime = todayWakeup;
if (schedule.CurrentState != ResidentState.AtSchoolOrWork && workBuilding != 0)
{
- DateTime workTime = ScheduleWork(ref schedule, CitizenProxy.GetCurrentBuilding(ref citizen));
- if (schedule.ScheduledState == ResidentState.AtSchoolOrWork)
+ if (ScheduleWork(ref schedule, ref citizen))
{
- Log.Debug($" - Schedule work at {workTime}");
- nextActivityTime = workTime;
-
- float timeLeft = (float)(nextActivityTime - TimeInfo.Now).TotalHours;
- if (timeLeft <= PrepareToWorkHours)
- {
- // Just sit at home if the work time will come soon
- Log.Debug($" - Worktime in {timeLeft} hours, doing nothing");
- return;
- }
+ return;
+ }
- if (timeLeft <= MaxTravelTime)
- {
- // If we have some time, try to shop locally.
- if (ScheduleShopping(ref schedule, ref citizen, true))
- {
- Log.Debug($" - Worktime in {timeLeft} hours, trying local shop");
- }
- else
- {
- Log.Debug($" - Worktime in {timeLeft} hours, doing nothing");
- }
-
- return;
- }
+ if (schedule.ScheduledStateTime > nextActivityTime)
+ {
+ nextActivityTime = schedule.ScheduledStateTime;
}
}
@@ -303,7 +288,12 @@ private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId,
if (schedule.CurrentState == ResidentState.AtHome)
{
- Log.Debug($" - Scheduled sleeping at home until {nextActivityTime}");
+ if (Random.ShouldOccur(StayHomeAllDayChance))
+ {
+ nextActivityTime = todayWakeup.FutureHour(Config.WakeupHour);
+ }
+
+ Log.Debug($" - Schedule sleeping at home until {nextActivityTime}");
schedule.Schedule(ResidentState.Unknown, nextActivityTime);
}
else
@@ -315,7 +305,11 @@ private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId,
private void ExecuteCitizenSchedule(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
{
- ProcessCurrentState(ref schedule, ref citizen);
+ if (ProcessCurrentState(ref schedule, ref citizen) && schedule.ScheduledState == ResidentState.Unknown)
+ {
+ // If the state processing changed the schedule, we need to update it
+ UpdateCitizenSchedule(ref schedule, citizenId, ref citizen);
+ }
if (TimeInfo.Now < schedule.ScheduledStateTime)
{
@@ -357,26 +351,26 @@ private void ExecuteCitizenSchedule(ref CitizenSchedule schedule, TAI instance,
}
}
- private void ProcessCurrentState(ref CitizenSchedule schedule, ref TCitizen citizen)
+ private bool ProcessCurrentState(ref CitizenSchedule schedule, ref TCitizen citizen)
{
switch (schedule.CurrentState)
{
case ResidentState.Shopping:
ProcessCitizenShopping(ref citizen);
- break;
+ return false;
case ResidentState.Relaxing:
ProcessCitizenRelaxing(ref citizen);
- break;
+ return false;
case ResidentState.Visiting:
- ProcessCitizenVisit(ref schedule, ref citizen);
- break;
+ return ProcessCitizenVisit(ref schedule, ref citizen);
case ResidentState.InShelter:
- ProcessCitizenInShelter(ref schedule, ref citizen);
- break;
+ return ProcessCitizenInShelter(ref schedule, ref citizen);
}
+
+ return false;
}
private bool ShouldRealizeCitizen(TAI ai)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index d58635b8..463c5d46 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -4,17 +4,52 @@
namespace RealTime.CustomAI
{
- using System;
using RealTime.Tools;
using static Constants;
internal sealed partial class RealTimeResidentAI
{
- private DateTime ScheduleWork(ref CitizenSchedule schedule, ushort currentBuilding)
+ private bool ScheduleWork(ref CitizenSchedule schedule, ref TCitizen citizen)
{
- return workBehavior.ScheduleGoToWork(ref schedule, currentBuilding, simulationCycle)
- ? schedule.ScheduledStateTime
- : default;
+ ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
+ if (!workBehavior.ScheduleGoToWork(ref schedule, currentBuilding, simulationCycle))
+ {
+ return false;
+ }
+
+ Log.Debug($" - Schedule work at {schedule.ScheduledStateTime}");
+
+ float timeLeft = (float)(schedule.ScheduledStateTime - TimeInfo.Now).TotalHours;
+ if (timeLeft <= PrepareToWorkHours)
+ {
+ // Just sit at home if the work time will come soon
+ Log.Debug($" - Worktime in {timeLeft} hours, doing nothing");
+ return true;
+ }
+
+ if (timeLeft <= MaxTravelTime)
+ {
+ if (schedule.CurrentState != ResidentState.AtHome)
+ {
+ Log.Debug($" - Worktime in {timeLeft} hours, returning home");
+ schedule.Schedule(ResidentState.AtHome, default);
+ return true;
+ }
+
+ // If we have some time, try to shop locally.
+ if (ScheduleShopping(ref schedule, ref citizen, true))
+ {
+ Log.Debug($" - Worktime in {timeLeft} hours, trying local shop");
+ }
+ else
+ {
+ Log.Debug($" - Worktime in {timeLeft} hours, doing nothing");
+ }
+
+ return true;
+ }
+
+ return false;
}
private void DoScheduledWork(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.cs b/src/RealTime/CustomAI/RealTimeResidentAI.cs
index dafcb00c..047d560a 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.cs
@@ -106,25 +106,26 @@ public void RegisterCitizenArrival(uint citizenId)
return;
}
- ref CitizenSchedule citizenState = ref residentSchedules[citizenId];
+ ref CitizenSchedule schedule = ref residentSchedules[citizenId];
switch (CitizenMgr.GetCitizenLocation(citizenId))
{
case Citizen.Location.Work:
- citizenState.UpdateTravelTimeToWork(TimeInfo.Now);
- Log.Debug($"The citizen {citizenId} arrived at work at {TimeInfo.Now} and needs {residentSchedules[citizenId].TravelTimeToWork} hours to get to work");
+ schedule.UpdateTravelTimeToWork(TimeInfo.Now);
+ Log.Debug($"The citizen {citizenId} arrived at work at {TimeInfo.Now} and needs {schedule.TravelTimeToWork} hours to get to work");
break;
case Citizen.Location.Moving:
return;
}
- citizenState.DepartureToWorkTime = default;
+ schedule.DepartureToWorkTime = default;
}
/// Performs simulation for starting a new day for all citizens.
public void BeginNewDay()
{
workBehavior.UpdateLunchTime();
+ todayWakeup = TimeInfo.Now.Date.AddHours(Config.WakeupHour);
}
/// Performs simulation for starting a new day for a citizen with specified ID.
diff --git a/src/RealTime/Tools/DateTimeExtensions.cs b/src/RealTime/Tools/DateTimeExtensions.cs
index bdaad519..e4109c4c 100644
--- a/src/RealTime/Tools/DateTimeExtensions.cs
+++ b/src/RealTime/Tools/DateTimeExtensions.cs
@@ -78,7 +78,7 @@ public static DateTime FutureHour(this DateTime dateTime, float hour)
}
float delta = hour - (float)dateTime.TimeOfDay.TotalHours;
- return delta >= 0
+ return delta > 0
? dateTime.AddHours(delta)
: dateTime.AddHours(24f + delta);
}
From ff080c7cb9eb614910809b54adb1dec662127807 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 21:22:10 +0200
Subject: [PATCH 23/42] Fix issue caused wrong work shift times calculation
---
src/RealTime/CustomAI/WorkBehavior.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/RealTime/CustomAI/WorkBehavior.cs b/src/RealTime/CustomAI/WorkBehavior.cs
index b74549d1..6e18dc4f 100644
--- a/src/RealTime/CustomAI/WorkBehavior.cs
+++ b/src/RealTime/CustomAI/WorkBehavior.cs
@@ -91,7 +91,7 @@ public void UpdateWorkShift(ref CitizenSchedule schedule, Citizen.AgeGroup citiz
}
ItemClass.SubService buildingSubService = buildingManager.GetBuildingSubService(schedule.WorkBuilding);
- switch (schedule.WorkShift)
+ switch (workShift)
{
case WorkShift.First when HasExtendedFirstWorkShift(buildingSevice, buildingSubService):
workBegin = Math.Min(config.WakeupHour, EarliestWakeUp);
From 750c14ef4ccb886b5a51d1d9adf151662dac107e Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 21:51:08 +0200
Subject: [PATCH 24/42] Allow citizens going to school/work if they missed the
beginning hour
---
src/RealTime/CustomAI/RealTimeResidentAI.Common.cs | 6 +++---
src/RealTime/CustomAI/WorkBehavior.cs | 12 +++++++++---
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
index 3374260d..3cf59a40 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
@@ -239,15 +239,13 @@ when BuildingMgr.GetBuildingSubService(currentBuilding) == ItemClass.SubService.
private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId, ref TCitizen citizen)
{
- Log.Debug(TimeInfo.Now, $"Calculating schedule for {GetCitizenDesc(citizenId, ref citizen)}...");
-
// If the game changed the work building, we have to update the work shifts first
ushort workBuilding = CitizenProxy.GetWorkBuilding(ref citizen);
if (schedule.WorkBuilding != workBuilding)
{
schedule.WorkBuilding = workBuilding;
workBehavior.UpdateWorkShift(ref schedule, CitizenProxy.GetAge(ref citizen));
- Log.Debug($" - Updated work shifts: work shift {schedule.WorkShift}, {schedule.WorkShiftStartHour} - {schedule.WorkShiftEndHour}, weekends: {schedule.WorksOnWeekends}");
+ Log.Debug($"Updated work shifts for citizen {citizenId}: work shift {schedule.WorkShift}, {schedule.WorkShiftStartHour} - {schedule.WorkShiftEndHour}, weekends: {schedule.WorksOnWeekends}");
}
if (schedule.ScheduledState != ResidentState.Unknown)
@@ -255,6 +253,8 @@ private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId,
return;
}
+ Log.Debug(TimeInfo.Now, $"Calculating schedule for {GetCitizenDesc(citizenId, ref citizen)}...");
+
if (schedule.WorkStatus == WorkStatus.Working)
{
schedule.WorkStatus = WorkStatus.None;
diff --git a/src/RealTime/CustomAI/WorkBehavior.cs b/src/RealTime/CustomAI/WorkBehavior.cs
index 6e18dc4f..46ae1255 100644
--- a/src/RealTime/CustomAI/WorkBehavior.cs
+++ b/src/RealTime/CustomAI/WorkBehavior.cs
@@ -123,14 +123,20 @@ public bool ScheduleGoToWork(ref CitizenSchedule schedule, ushort currentBuildin
return false;
}
- if (config.IsWeekendEnabled && timeInfo.Now.IsWeekend() && !schedule.WorksOnWeekends)
+ DateTime now = timeInfo.Now;
+ if (config.IsWeekendEnabled && now.IsWeekend() && !schedule.WorksOnWeekends)
{
return false;
}
float travelTime = GetTravelTimeToWork(ref schedule, currentBuilding);
- float departureHour = schedule.WorkShiftStartHour - travelTime - simulationCycle;
- schedule.Schedule(ResidentState.AtSchoolOrWork, timeInfo.Now.FutureHour(departureHour));
+
+ DateTime workEndTime = now.FutureHour(schedule.WorkShiftEndHour);
+ DateTime departureTime = now.AddHours(travelTime + simulationCycle) < workEndTime
+ ? default
+ : now.FutureHour(schedule.WorkShiftStartHour - travelTime - simulationCycle);
+
+ schedule.Schedule(ResidentState.AtSchoolOrWork, departureTime);
return true;
}
From 52624ad0fe88b0f1f1a9921fd16eaca9fcf92aa5 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 21:51:42 +0200
Subject: [PATCH 25/42] Improve go out chance calculation
---
src/RealTime/CustomAI/SpareTimeBehavior.cs | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/RealTime/CustomAI/SpareTimeBehavior.cs b/src/RealTime/CustomAI/SpareTimeBehavior.cs
index 2ce11f1b..060052ba 100644
--- a/src/RealTime/CustomAI/SpareTimeBehavior.cs
+++ b/src/RealTime/CustomAI/SpareTimeBehavior.cs
@@ -60,7 +60,7 @@ public void RefreshGoOutChances()
float timeModifier;
if (isDayTime)
{
- timeModifier = RealTimeMath.Clamp(currentHour - config.WakeupHour - simulationCycle, 0, 4f);
+ timeModifier = RealTimeMath.Clamp((currentHour - config.WakeupHour) * 2f, 0, 4f);
}
else
{
@@ -76,11 +76,18 @@ public void RefreshGoOutChances()
uint defaultChance = (uint)((timeModifier + weekdayModifier) * timeModifier);
+ bool dump = chances[(int)Citizen.AgeGroup.Young] != defaultChance;
+
chances[(int)Citizen.AgeGroup.Child] = isDayTime ? defaultChance : 0;
chances[(int)Citizen.AgeGroup.Teen] = isDayTime ? defaultChance : 0;
chances[(int)Citizen.AgeGroup.Young] = defaultChance;
chances[(int)Citizen.AgeGroup.Adult] = defaultChance;
chances[(int)Citizen.AgeGroup.Senior] = defaultChance;
+
+ if (dump)
+ {
+ Log.Debug($"GO OUT CHANCES for {timeInfo.Now}: child = {chances[0]}, teen = {chances[1]}, young = {chances[2]}, adult = {chances[3]}, senior = {chances[4]}");
+ }
}
///
From 14ccb9dccdc483b82012d55769d6b5585cf3286a Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 21:54:52 +0200
Subject: [PATCH 26/42] Fix issue caused wrong departure to work time
calculation
---
src/RealTime/CustomAI/WorkBehavior.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/RealTime/CustomAI/WorkBehavior.cs b/src/RealTime/CustomAI/WorkBehavior.cs
index 46ae1255..1fc300bd 100644
--- a/src/RealTime/CustomAI/WorkBehavior.cs
+++ b/src/RealTime/CustomAI/WorkBehavior.cs
@@ -133,7 +133,7 @@ public bool ScheduleGoToWork(ref CitizenSchedule schedule, ushort currentBuildin
DateTime workEndTime = now.FutureHour(schedule.WorkShiftEndHour);
DateTime departureTime = now.AddHours(travelTime + simulationCycle) < workEndTime
- ? default
+ ? now
: now.FutureHour(schedule.WorkShiftStartHour - travelTime - simulationCycle);
schedule.Schedule(ResidentState.AtSchoolOrWork, departureTime);
From ecf0c2427f85e137862fb7430ae843c4c9461f7f Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 22:30:08 +0200
Subject: [PATCH 27/42] Improve work and lunch time selection
---
.../CustomAI/RealTimeResidentAI.SchoolWork.cs | 11 +++++++----
src/RealTime/CustomAI/WorkBehavior.cs | 9 +++++----
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index 463c5d46..8779b9a6 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -23,7 +23,7 @@ private bool ScheduleWork(ref CitizenSchedule schedule, ref TCitizen citizen)
if (timeLeft <= PrepareToWorkHours)
{
// Just sit at home if the work time will come soon
- Log.Debug($" - Worktime in {timeLeft} hours, doing nothing");
+ Log.Debug($" - Worktime in {timeLeft} hours, preparing for departure");
return true;
}
@@ -63,11 +63,14 @@ private void DoScheduledWork(ref CitizenSchedule schedule, TAI instance, uint ci
schedule.DepartureToWorkTime = TimeInfo.Now;
}
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going from {currentBuilding} to school/work {schedule.WorkBuilding}");
-
Citizen.AgeGroup citizenAge = CitizenProxy.GetAge(ref citizen);
- if (!workBehavior.ScheduleLunch(ref schedule, citizenAge))
+ if (workBehavior.ScheduleLunch(ref schedule, citizenAge))
+ {
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going from {currentBuilding} to school/work {schedule.WorkBuilding} and will go to lunch at {schedule.ScheduledStateTime}");
+ }
+ else
{
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going from {currentBuilding} to school/work {schedule.WorkBuilding} and will leave work at {schedule.ScheduledStateTime}");
workBehavior.ScheduleReturnFromWork(ref schedule, citizenAge);
}
}
diff --git a/src/RealTime/CustomAI/WorkBehavior.cs b/src/RealTime/CustomAI/WorkBehavior.cs
index 1fc300bd..ae2d6b6a 100644
--- a/src/RealTime/CustomAI/WorkBehavior.cs
+++ b/src/RealTime/CustomAI/WorkBehavior.cs
@@ -132,9 +132,11 @@ public bool ScheduleGoToWork(ref CitizenSchedule schedule, ushort currentBuildin
float travelTime = GetTravelTimeToWork(ref schedule, currentBuilding);
DateTime workEndTime = now.FutureHour(schedule.WorkShiftEndHour);
- DateTime departureTime = now.AddHours(travelTime + simulationCycle) < workEndTime
- ? now
- : now.FutureHour(schedule.WorkShiftStartHour - travelTime - simulationCycle);
+ DateTime departureTime = now.FutureHour(schedule.WorkShiftStartHour - travelTime - simulationCycle);
+ if (departureTime > workEndTime && now.AddHours(travelTime + simulationCycle) < workEndTime)
+ {
+ departureTime = now;
+ }
schedule.Schedule(ResidentState.AtSchoolOrWork, departureTime);
return true;
@@ -147,7 +149,6 @@ public bool ScheduleGoToWork(ref CitizenSchedule schedule, ushort currentBuildin
public bool ScheduleLunch(ref CitizenSchedule schedule, Citizen.AgeGroup citizenAge)
{
if (schedule.WorkStatus == WorkStatus.Working
- && schedule.CurrentState == ResidentState.AtSchoolOrWork
&& schedule.WorkShift == WorkShift.First
&& WillGoToLunch(citizenAge))
{
From 9cddace4d2c9043104843b6d993fd38f2468dbfd Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 22:51:18 +0200
Subject: [PATCH 28/42] Improve debug logging
---
src/RealTime/CustomAI/RealTimeResidentAI.Common.cs | 4 +++-
src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs | 11 ++++++++++-
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
index 3cf59a40..9a48fd4c 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
@@ -253,7 +253,7 @@ private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId,
return;
}
- Log.Debug(TimeInfo.Now, $"Calculating schedule for {GetCitizenDesc(citizenId, ref citizen)}...");
+ Log.Debug(TimeInfo.Now, $"Scheduling for {GetCitizenDesc(citizenId, ref citizen)}...");
if (schedule.WorkStatus == WorkStatus.Working)
{
@@ -307,6 +307,8 @@ private void ExecuteCitizenSchedule(ref CitizenSchedule schedule, TAI instance,
{
if (ProcessCurrentState(ref schedule, ref citizen) && schedule.ScheduledState == ResidentState.Unknown)
{
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} will be rescheduled now");
+
// If the state processing changed the schedule, we need to update it
UpdateCitizenSchedule(ref schedule, citizenId, ref citizen);
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
index 3b905188..b5951332 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
@@ -48,7 +48,11 @@ private void DoScheduledRelaxing(ref CitizenSchedule schedule, TAI instance, uin
schedule.Schedule(ResidentState.Unknown, default);
ushort leisure = MoveToLeisureBuilding(instance, citizenId, ref citizen, buildingId);
- if (leisure != 0)
+ if (leisure == 0)
+ {
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted relax but didn't found a leisure building");
+ }
+ else
{
Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} heading to a leisure building {leisure}");
}
@@ -131,6 +135,10 @@ private void DoScheduledShopping(ref CitizenSchedule schedule, TAI instance, uin
{
Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted go shopping, but didn't find a local shop");
}
+ else
+ {
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} goes shopping at a local shop {shop}");
+ }
}
else
{
@@ -179,6 +187,7 @@ private bool ProcessCitizenVisit(ref CitizenSchedule schedule, ref TCitizen citi
&& TimeInfo.IsNightTime
&& BuildingMgr.IsBuildingNoiseRestricted(visitBuilding)))
{
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(0, ref citizen)} quits a visit (see next line for citizen ID)");
schedule.Schedule(ResidentState.Unknown, default);
return true;
}
From 81027b2323d23407df53e353ea2735ba0a8be68d Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 22:51:55 +0200
Subject: [PATCH 29/42] Fix issues with choosing of shopping and event target
building
---
.../CustomAI/RealTimeResidentAI.Visit.cs | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
index b5951332..f19ef9ae 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
@@ -60,12 +60,19 @@ private void DoScheduledRelaxing(ref CitizenSchedule schedule, TAI instance, uin
return;
case ScheduleHint.AttendingEvent:
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna attend an event at '{schedule.EventBuilding}', on the way now.");
- StartMovingToVisitBuilding(instance, citizenId, ref citizen, schedule.EventBuilding);
+ DateTime returnTime;
ICityEvent cityEvent = EventMgr.GetUpcomingCityEvent(schedule.EventBuilding);
- DateTime returnTime = cityEvent == null
- ? default
- : cityEvent.EndTime;
+ if (cityEvent == null)
+ {
+ returnTime = default;
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted attend an event at '{schedule.EventBuilding}', but there was no event there");
+ }
+ else
+ {
+ StartMovingToVisitBuilding(instance, citizenId, ref citizen, schedule.EventBuilding);
+ returnTime = cityEvent.EndTime;
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna attend an event at '{schedule.EventBuilding}', will return at {returnTime}");
+ }
schedule.Schedule(ResidentState.Unknown, returnTime);
schedule.EventBuilding = 0;
@@ -126,7 +133,7 @@ private void DoScheduledShopping(ref CitizenSchedule schedule, TAI instance, uin
{
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
- if ((schedule.Hint & ScheduleHint.LocalShoppingOnly) != 0)
+ if (schedule.Hint == ScheduleHint.LocalShoppingOnly)
{
schedule.Schedule(ResidentState.Unknown, default);
From 6b4bf094651e1fc46c7280ac3e32c28fc3c50425 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 23:09:00 +0200
Subject: [PATCH 30/42] Fix incorrect debug log value
---
src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index 8779b9a6..2a11b545 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -70,8 +70,8 @@ private void DoScheduledWork(ref CitizenSchedule schedule, TAI instance, uint ci
}
else
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going from {currentBuilding} to school/work {schedule.WorkBuilding} and will leave work at {schedule.ScheduledStateTime}");
workBehavior.ScheduleReturnFromWork(ref schedule, citizenAge);
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going from {currentBuilding} to school/work {schedule.WorkBuilding} and will leave work at {schedule.ScheduledStateTime}");
}
}
From 93ff32d2e0e0bbbdc74eba3351011b927c53a949 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 21 Jul 2018 23:26:13 +0200
Subject: [PATCH 31/42] Update code documentation
---
src/RealTime/Config/ConfigurationProvider.cs | 2 +-
src/RealTime/Core/IStorageData.cs | 4 ++--
src/RealTime/Core/RealTimeCore.cs | 2 +-
src/RealTime/Core/RealTimeStorage.cs | 4 ++--
src/RealTime/CustomAI/RealTimeHumanAIBase.cs | 10 +++++-----
src/RealTime/CustomAI/SpareTimeBehavior.cs | 4 ++--
src/RealTime/Events/CityEventBase.cs | 6 +++---
src/RealTime/Events/ICityEvent.cs | 6 +++---
src/RealTime/Events/ICityEventsProvider.cs | 6 +++---
src/RealTime/Events/RealTimeCityEvent.cs | 4 ++--
src/RealTime/Events/RealTimeEventManager.cs | 6 +++---
src/RealTime/Events/Storage/CityEventsLoader.cs | 6 +++---
src/RealTime/Events/VanillaEvent.cs | 4 ++--
.../GameConnection/BuildingManagerConnection.cs | 8 ++++----
.../GameConnection/CitizenManagerConnection.cs | 4 ++--
src/RealTime/GameConnection/EventManagerConnection.cs | 2 +-
.../GameConnection/IBuildingManagerConnection.cs | 8 ++++----
.../GameConnection/ICitizenManagerConnection.cs | 4 ++--
src/RealTime/GameConnection/IEventManagerConnection.cs | 2 +-
src/RealTime/Patching/FastDelegate.cs | 4 ++--
src/RealTime/Simulation/DayTimeCalculator.cs | 2 +-
src/RealTime/Tools/DateTimeExtensions.cs | 2 +-
src/RealTime/Tools/GitVersion.cs | 4 ++--
src/RealTime/Tools/LinkedListExtensions.cs | 2 +-
src/RealTime/UI/CitiesCheckBoxItem.cs | 2 +-
src/RealTime/UI/CitiesComboBoxItem.cs | 2 +-
src/RealTime/UI/CitiesSliderItem.cs | 2 +-
src/RealTime/UI/CitiesViewItem.cs | 2 +-
src/RealTime/UI/RealTimeUIDateTimeWrapper.cs | 2 +-
29 files changed, 58 insertions(+), 58 deletions(-)
diff --git a/src/RealTime/Config/ConfigurationProvider.cs b/src/RealTime/Config/ConfigurationProvider.cs
index 9a12d122..025c8cba 100644
--- a/src/RealTime/Config/ConfigurationProvider.cs
+++ b/src/RealTime/Config/ConfigurationProvider.cs
@@ -42,7 +42,7 @@ public static RealTimeConfig LoadConfiguration()
}
///
- /// Stores the provided object to the storage.
+ /// Stores the specified object to the storage.
///
///
/// Thrown when the argument is null.
diff --git a/src/RealTime/Core/IStorageData.cs b/src/RealTime/Core/IStorageData.cs
index f44591b5..7d49ab41 100644
--- a/src/RealTime/Core/IStorageData.cs
+++ b/src/RealTime/Core/IStorageData.cs
@@ -17,7 +17,7 @@ internal interface IStorageData
string StorageDataId { get; }
///
- /// Reads the data set from the provided .
+ /// Reads the data set from the specified .
///
///
/// Thrown when the argument is null.
@@ -26,7 +26,7 @@ internal interface IStorageData
void ReadData(Stream source);
///
- /// Reads the data set to the provided .
+ /// Stores the data set to the specified .
///
///
/// Thrown when the argument is null.
diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs
index 9908ccef..2a78176a 100644
--- a/src/RealTime/Core/RealTimeCore.cs
+++ b/src/RealTime/Core/RealTimeCore.cs
@@ -195,7 +195,7 @@ public void Stop()
///
/// Translates all the mod's component to a different language obtained from
- /// the provided .
+ /// the specified .
///
///
/// Thrown when the argument is null.
diff --git a/src/RealTime/Core/RealTimeStorage.cs b/src/RealTime/Core/RealTimeStorage.cs
index 780565c3..0bb7e8cf 100644
--- a/src/RealTime/Core/RealTimeStorage.cs
+++ b/src/RealTime/Core/RealTimeStorage.cs
@@ -56,7 +56,7 @@ public override void OnReleased()
}
///
- /// Serializes the data described by the provided to this level's storage.
+ /// Serializes the data described by the specified to this level's storage.
///
///
/// Thrown when the argument is null.
@@ -86,7 +86,7 @@ internal void Serialize(IStorageData data)
}
///
- /// Deserializes the data described by the provided from this level's storage.
+ /// Deserializes the data described by the specified from this level's storage.
///
///
/// Thrown when the argument is null.
diff --git a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
index 9c42fba7..01ca29fc 100644
--- a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
+++ b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
@@ -107,13 +107,13 @@ protected RealTimeHumanAIBase(RealTimeConfig config, GameConnections c
protected uint CitizenInstancesMaxCount { get; }
///
- /// Ensures that the provided citizen is in a valid state and can be processed.
+ /// Ensures that the specified citizen is in a valid state and can be processed.
///
///
/// The citizen ID to check.
/// The citizen data reference.
///
- /// true if the provided citizen is in a valid state; otherwise, false.
+ /// true if the specified citizen is in a valid state; otherwise, false.
protected bool EnsureCitizenCanBeProcessed(uint citizenId, ref TCitizen citizen)
{
if ((CitizenProxy.GetHomeBuilding(ref citizen) == 0
@@ -167,7 +167,7 @@ protected ICityEvent GetUpcomingEventToAttend(uint citizenId, ref TCitizen citiz
}
///
- /// Finds an evacuation place for the provided citizen.
+ /// Finds an evacuation place for the specified citizen.
///
///
/// The citizen ID to find an evacuation place for.
@@ -179,13 +179,13 @@ protected void FindEvacuationPlace(uint citizenId, TransferManager.TransferReaso
}
///
- /// Gets a string that describes the provided citizen.
+ /// Gets a string that describes the specified citizen.
///
///
/// The citizen ID.
/// The citizen data reference.
///
- /// A short string describing the provided citizen.
+ /// A short string describing the specified citizen.
protected string GetCitizenDesc(uint citizenId, ref TCitizen citizen)
{
ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen);
diff --git a/src/RealTime/CustomAI/SpareTimeBehavior.cs b/src/RealTime/CustomAI/SpareTimeBehavior.cs
index 060052ba..77a1be4c 100644
--- a/src/RealTime/CustomAI/SpareTimeBehavior.cs
+++ b/src/RealTime/CustomAI/SpareTimeBehavior.cs
@@ -91,13 +91,13 @@ public void RefreshGoOutChances()
}
///
- /// Gets the probability whether a citizen with provided age would go out on current time.
+ /// Gets the probability whether a citizen with specified age would go out on current time.
///
///
/// The citizen age to check.
///
/// A percentage value in range of 0..100 that describes the probability whether
- /// a citizen with provided age would go out on current time.
+ /// a citizen with specified age would go out on current time.
public uint GetGoOutChance(Citizen.AgeGroup citizenAge)
{
return chances[(int)citizenAge];
diff --git a/src/RealTime/Events/CityEventBase.cs b/src/RealTime/Events/CityEventBase.cs
index d2adec38..6f5f746d 100644
--- a/src/RealTime/Events/CityEventBase.cs
+++ b/src/RealTime/Events/CityEventBase.cs
@@ -23,7 +23,7 @@ internal abstract class CityEventBase : ICityEvent
/// Gets the localized name of the building this city event takes place in.
public string BuildingName { get; private set; }
- /// Accepts an event attendee with provided properties.
+ /// Accepts an event attendee with specified properties.
/// The attendee age.
/// The attendee gender.
/// The attendee education.
@@ -32,7 +32,7 @@ internal abstract class CityEventBase : ICityEvent
/// The attendee happiness.
/// A reference to the game's randomizer.
///
- /// true if the event attendee with provided properties is accepted and can attend
+ /// true if the event attendee with specified properties is accepted and can attend
/// this city event; otherwise, false.
///
public virtual bool TryAcceptAttendee(
@@ -48,7 +48,7 @@ public virtual bool TryAcceptAttendee(
}
///
- /// Configures this event to take place in the provided building and at the provided start time.
+ /// Configures this event to take place in the specified building and at the specified start time.
///
/// The building ID this city event should take place in.
///
diff --git a/src/RealTime/Events/ICityEvent.cs b/src/RealTime/Events/ICityEvent.cs
index 249683f0..09537b55 100644
--- a/src/RealTime/Events/ICityEvent.cs
+++ b/src/RealTime/Events/ICityEvent.cs
@@ -23,7 +23,7 @@ internal interface ICityEvent
string BuildingName { get; }
///
- /// Configures this event to take place in the provided building and at the provided start time.
+ /// Configures this event to take place in the specified building and at the specified start time.
///
/// ///
/// The building ID this city event should take place in.
@@ -33,7 +33,7 @@ internal interface ICityEvent
/// The city event start time.
void Configure(ushort buildingId, string buildingName, DateTime startTime);
- /// Accepts an event attendee with provided properties.
+ /// Accepts an event attendee with specified properties.
/// The attendee age.
/// The attendee gender.
/// The attendee education.
@@ -42,7 +42,7 @@ internal interface ICityEvent
/// The attendee happiness.
/// A reference to the game's randomizer.
///
- /// true if the event attendee with provided properties is accepted and can attend
+ /// true if the event attendee with specified properties is accepted and can attend
/// this city event; otherwise, false.
///
bool TryAcceptAttendee(
diff --git a/src/RealTime/Events/ICityEventsProvider.cs b/src/RealTime/Events/ICityEventsProvider.cs
index ed0cb0da..094ead1b 100644
--- a/src/RealTime/Events/ICityEventsProvider.cs
+++ b/src/RealTime/Events/ICityEventsProvider.cs
@@ -7,12 +7,12 @@ namespace RealTime.Events
using RealTime.Events.Storage;
///
- /// An interface for a type that can create city event instances for provided building classes.
+ /// An interface for a type that can create city event instances for specified building classes.
///
internal interface ICityEventsProvider
{
///
- /// Gets a randomly created city event for a building of provided class. If no city event
+ /// Gets a randomly created city event for a building of specified class. If no city event
/// could be created, returns null.
///
/// The building class to create a city event for.
@@ -23,7 +23,7 @@ internal interface ICityEventsProvider
ICityEvent GetRandomEvent(string buildingClass);
///
- /// Gets the event template that has the provided name and is configured for the provided
+ /// Gets the event template that has the specified name and is configured for the specified
/// building class.
///
/// The unique name of the city event template.
diff --git a/src/RealTime/Events/RealTimeCityEvent.cs b/src/RealTime/Events/RealTimeCityEvent.cs
index 220ee7ec..0c8fc44e 100644
--- a/src/RealTime/Events/RealTimeCityEvent.cs
+++ b/src/RealTime/Events/RealTimeCityEvent.cs
@@ -44,7 +44,7 @@ public RealTimeCityEvent(CityEventTemplate eventTemplate, int attendeesCount)
this.attendeesCount = attendeesCount;
}
- /// Accepts an event attendee with provided properties.
+ /// Accepts an event attendee with specified properties.
/// The attendee age.
/// The attendee gender.
/// The attendee education.
@@ -53,7 +53,7 @@ public RealTimeCityEvent(CityEventTemplate eventTemplate, int attendeesCount)
/// The attendee happiness.
/// A reference to the game's randomizer.
///
- /// true if the event attendee with provided properties is accepted and can attend this city event;
+ /// true if the event attendee with specified properties is accepted and can attend this city event;
/// otherwise, false.
///
public override bool TryAcceptAttendee(
diff --git a/src/RealTime/Events/RealTimeEventManager.cs b/src/RealTime/Events/RealTimeEventManager.cs
index 627a4feb..af86e707 100644
--- a/src/RealTime/Events/RealTimeEventManager.cs
+++ b/src/RealTime/Events/RealTimeEventManager.cs
@@ -100,7 +100,7 @@ public IEnumerable CityEvents
/// Gets an unique ID of this storage data set.
string IStorageData.StorageDataId => StorageDataId;
- /// Gets the state of a city event in the provided building.
+ /// Gets the state of a city event in the specified building.
/// The building ID to check events in.
/// The latest start time of events to consider.
///
@@ -232,7 +232,7 @@ public void ProcessEvents()
CreateRandomEvent(building);
}
- /// Reads the data set from the provided .
+ /// Reads the data set from the specified .
/// A to read the data set from.
void IStorageData.ReadData(Stream source)
{
@@ -267,7 +267,7 @@ void IStorageData.ReadData(Stream source)
OnEventsChanged();
}
- /// Reads the data set to the provided .
+ /// Reads the data set to the specified .
/// A to write the data set to.
void IStorageData.StoreData(Stream target)
{
diff --git a/src/RealTime/Events/Storage/CityEventsLoader.cs b/src/RealTime/Events/Storage/CityEventsLoader.cs
index 32db2340..872e241f 100644
--- a/src/RealTime/Events/Storage/CityEventsLoader.cs
+++ b/src/RealTime/Events/Storage/CityEventsLoader.cs
@@ -33,7 +33,7 @@ private CityEventsLoader()
///
/// Reloads the event templates from the storage file that is located in a subdirectory of
- /// the provided path.
+ /// the specified path.
///
/// The path where the mod's custom data files are stored.
///
@@ -64,7 +64,7 @@ public void Clear()
}
///
- /// Gets a randomly created city event for a building of provided class. If no city event
+ /// Gets a randomly created city event for a building of specified class. If no city event
/// could be created, returns null.
///
/// The building class to create a city event for.
@@ -90,7 +90,7 @@ ICityEvent ICityEventsProvider.GetRandomEvent(string buildingClass)
}
///
- /// Gets the event template that has the provided name and is configured for the provided
+ /// Gets the event template that has the specified name and is configured for the specified
/// building class.
///
/// The unique name of the city event template.
diff --git a/src/RealTime/Events/VanillaEvent.cs b/src/RealTime/Events/VanillaEvent.cs
index 3c965702..487119b5 100644
--- a/src/RealTime/Events/VanillaEvent.cs
+++ b/src/RealTime/Events/VanillaEvent.cs
@@ -23,7 +23,7 @@ public VanillaEvent(float duration, float ticketPrice)
this.ticketPrice = ticketPrice;
}
- /// Accepts an event attendee with provided properties.
+ /// Accepts an event attendee with specified properties.
/// The attendee age.
/// The attendee gender.
/// The attendee education.
@@ -32,7 +32,7 @@ public VanillaEvent(float duration, float ticketPrice)
/// The attendee happiness.
/// A reference to the game's randomizer.
///
- /// true if the event attendee with provided properties is accepted and can attend
+ /// true if the event attendee with specified properties is accepted and can attend
/// this city event; otherwise, false.
///
public override bool TryAcceptAttendee(
diff --git a/src/RealTime/GameConnection/BuildingManagerConnection.cs b/src/RealTime/GameConnection/BuildingManagerConnection.cs
index d017fe0a..1eb561b9 100644
--- a/src/RealTime/GameConnection/BuildingManagerConnection.cs
+++ b/src/RealTime/GameConnection/BuildingManagerConnection.cs
@@ -58,8 +58,8 @@ public uint GetCitizenUnit(ushort buildingId)
/// The building flags to check.
/// true if a building without any flags can also be considered.
///
- /// true if the building with the specified ID has the
- /// provided; otherwise, false.
+ /// true if the building with the specified ID has the specified ;
+ /// otherwise, false.
///
public bool BuildingHasFlags(ushort buildingId, Building.Flags flags, bool includeZero = false)
{
@@ -137,7 +137,7 @@ public ushort FindActiveBuilding(
restrictedFlags);
}
- /// Gets the ID of an event that takes place in the building with provided ID.
+ /// Gets the ID of an event that takes place in the building with specified ID.
/// The building ID to check.
/// An ID of an event that takes place in the building, or 0 if none.
public ushort GetEvent(ushort buildingId)
@@ -148,7 +148,7 @@ public ushort GetEvent(ushort buildingId)
}
///
- /// Gets an ID of a random building in the city that belongs to any of the provided .
+ /// Gets an ID of a random building in the city that belongs to any of the specified .
///
/// A collection of that specifies in which services to
/// search the random building in.
diff --git a/src/RealTime/GameConnection/CitizenManagerConnection.cs b/src/RealTime/GameConnection/CitizenManagerConnection.cs
index c18fbea0..0393bab0 100644
--- a/src/RealTime/GameConnection/CitizenManagerConnection.cs
+++ b/src/RealTime/GameConnection/CitizenManagerConnection.cs
@@ -32,11 +32,11 @@ public ushort GetTargetBuilding(ushort instanceId)
: (ushort)0;
}
- /// Determines whether the citizen's instance with provided ID has particular flags.
+ /// Determines whether the citizen's instance with specified ID has particular flags.
/// The instance ID to check.
/// The flags to check.
///
- /// true to check all flags from the provided , false to check any flags.
+ /// true to check all flags from the specified , false to check any flags.
///
/// true if the citizen instance has the specified flags; otherwise, false.
public bool InstanceHasFlags(ushort instanceId, CitizenInstance.Flags flags, bool all = false)
diff --git a/src/RealTime/GameConnection/EventManagerConnection.cs b/src/RealTime/GameConnection/EventManagerConnection.cs
index 78e09cba..6b3b06aa 100644
--- a/src/RealTime/GameConnection/EventManagerConnection.cs
+++ b/src/RealTime/GameConnection/EventManagerConnection.cs
@@ -13,7 +13,7 @@ namespace RealTime.GameConnection
///
internal sealed class EventManagerConnection : IEventManagerConnection
{
- /// Gets the flags of an event with provided ID.
+ /// Gets the flags of an event with specified ID.
/// The ID of the event to get flags of.
///
/// The event flags or if none found.
diff --git a/src/RealTime/GameConnection/IBuildingManagerConnection.cs b/src/RealTime/GameConnection/IBuildingManagerConnection.cs
index e3c3ca56..d3dea3f6 100644
--- a/src/RealTime/GameConnection/IBuildingManagerConnection.cs
+++ b/src/RealTime/GameConnection/IBuildingManagerConnection.cs
@@ -39,8 +39,8 @@ internal interface IBuildingManagerConnection
/// The building flags to check.
/// true if a building without any flags can also be considered.
///
- /// true if the building with the specified ID has the
- /// provided; otherwise, false.
+ /// true if the building with the specified ID has the specified ;
+ /// otherwise, false.
///
bool BuildingHasFlags(ushort buildingId, Building.Flags flags, bool includeZero = false);
@@ -72,13 +72,13 @@ ushort FindActiveBuilding(
ItemClass.Service service,
ItemClass.SubService subService = ItemClass.SubService.None);
- /// Gets the ID of an event that takes place in the building with provided ID.
+ /// Gets the ID of an event that takes place in the building with specified ID.
/// The building ID to check.
/// An ID of an event that takes place in the building, or 0 if none.
ushort GetEvent(ushort buildingId);
///
- /// Gets an ID of a random building in the city that belongs to any of the provided .
+ /// Gets an ID of a random building in the city that belongs to any of the specified .
///
/// Thrown when the argument is null.
///
diff --git a/src/RealTime/GameConnection/ICitizenManagerConnection.cs b/src/RealTime/GameConnection/ICitizenManagerConnection.cs
index afdf5a9b..d5a43eae 100644
--- a/src/RealTime/GameConnection/ICitizenManagerConnection.cs
+++ b/src/RealTime/GameConnection/ICitizenManagerConnection.cs
@@ -16,11 +16,11 @@ internal interface ICitizenManagerConnection
/// The ID of the building the citizen is moving to, or 0 if none.
ushort GetTargetBuilding(ushort instanceId);
- /// Determines whether the citizen's instance with provided ID has particular flags.
+ /// Determines whether the citizen's instance with specified ID has particular flags.
/// The instance ID to check.
/// The flags to check.
///
- /// true to check all flags from the provided , false to check any flags.
+ /// true to check all flags from the specified , false to check any flags.
///
/// true if the citizen instance has the specified flags; otherwise, false.
bool InstanceHasFlags(ushort instanceId, CitizenInstance.Flags flags, bool all = false);
diff --git a/src/RealTime/GameConnection/IEventManagerConnection.cs b/src/RealTime/GameConnection/IEventManagerConnection.cs
index d7963bf1..a64b1506 100644
--- a/src/RealTime/GameConnection/IEventManagerConnection.cs
+++ b/src/RealTime/GameConnection/IEventManagerConnection.cs
@@ -10,7 +10,7 @@ namespace RealTime.GameConnection
/// An interface for the game specific logic related to the event management.
internal interface IEventManagerConnection
{
- /// Gets the flags of an event with provided ID.
+ /// Gets the flags of an event with specified ID.
/// The ID of the event to get flags of.
/// The event flags or if none found.
EventData.Flags GetEventFlags(ushort eventId);
diff --git a/src/RealTime/Patching/FastDelegate.cs b/src/RealTime/Patching/FastDelegate.cs
index fe0a0ca4..f9bcee28 100644
--- a/src/RealTime/Patching/FastDelegate.cs
+++ b/src/RealTime/Patching/FastDelegate.cs
@@ -17,7 +17,7 @@ namespace RealTime.Patching
internal static class FastDelegate
{
///
- /// Creates a delegate instance of the provided type that represents a method
+ /// Creates a delegate instance of the specified type that represents a method
/// of the class. If the target method is a 's instance
/// method, the first parameter of the signature must be a reference to a
/// instance.
@@ -67,7 +67,7 @@ private static MethodInfo GetMethodInfo(string name, bool inst
if (methodInfo == null)
{
- throw new MissingMethodException($"The method '{typeof(TType).Name}.{name}' matching the provided signature doesn't exist: {typeof(TDelegate)}");
+ throw new MissingMethodException($"The method '{typeof(TType).Name}.{name}' matching the specified signature doesn't exist: {typeof(TDelegate)}");
}
return methodInfo;
diff --git a/src/RealTime/Simulation/DayTimeCalculator.cs b/src/RealTime/Simulation/DayTimeCalculator.cs
index 1588a084..8e7137f9 100644
--- a/src/RealTime/Simulation/DayTimeCalculator.cs
+++ b/src/RealTime/Simulation/DayTimeCalculator.cs
@@ -35,7 +35,7 @@ public DayTimeCalculator(float latitude)
}
///
- /// Calculates the sunrise and sunset hours for the provided .
+ /// Calculates the sunrise and sunset hours for the specified .
///
///
/// The game date to calculate the sunrise and sunset times for.
diff --git a/src/RealTime/Tools/DateTimeExtensions.cs b/src/RealTime/Tools/DateTimeExtensions.cs
index e4109c4c..9586e310 100644
--- a/src/RealTime/Tools/DateTimeExtensions.cs
+++ b/src/RealTime/Tools/DateTimeExtensions.cs
@@ -50,7 +50,7 @@ public static bool IsWeekendTime(this DateTime dateTime, float fridayStartHour,
}
///
- /// Rounds this to the provided (ceiling).
+ /// Rounds this to the specified (ceiling).
///
///
/// The to round.
diff --git a/src/RealTime/Tools/GitVersion.cs b/src/RealTime/Tools/GitVersion.cs
index 0bd092cd..211442fc 100644
--- a/src/RealTime/Tools/GitVersion.cs
+++ b/src/RealTime/Tools/GitVersion.cs
@@ -16,7 +16,7 @@ internal static class GitVersion
private const string VersionFieldName = "FullSemVer";
///
- /// Gets a string representation of the full semantic assembly version of the provided .
+ /// Gets a string representation of the full semantic assembly version of the specified .
/// This assembly should be built using the GitVersion toolset; otherwise, a "?" version string will
/// be returned.
///
@@ -25,7 +25,7 @@ internal static class GitVersion
///
/// An to get the version of. Should be built using the GitVersion toolset.
///
- /// A string representation of the full semantic version of the provided ,
+ /// A string representation of the full semantic version of the specified ,
/// or "?" if the version could not be determined.
public static string GetAssemblyVersion(Assembly assembly)
{
diff --git a/src/RealTime/Tools/LinkedListExtensions.cs b/src/RealTime/Tools/LinkedListExtensions.cs
index 74f677ad..c922d552 100644
--- a/src/RealTime/Tools/LinkedListExtensions.cs
+++ b/src/RealTime/Tools/LinkedListExtensions.cs
@@ -13,7 +13,7 @@ namespace RealTime.Tools
internal static class LinkedListExtensions
{
///
- /// Gets the linked list's firsts node that matches the provided ,
+ /// Gets the linked list's firsts node that matches the specified ,
/// or null if no matching node was found.
///
///
diff --git a/src/RealTime/UI/CitiesCheckBoxItem.cs b/src/RealTime/UI/CitiesCheckBoxItem.cs
index 08dc1dbc..55a22270 100644
--- a/src/RealTime/UI/CitiesCheckBoxItem.cs
+++ b/src/RealTime/UI/CitiesCheckBoxItem.cs
@@ -50,7 +50,7 @@ public override void Refresh()
UIComponent.isChecked = Value;
}
- /// Creates the view item using the provided .
+ /// Creates the view item using the specified .
/// The UI helper to use for item creation.
/// The item's default value.
/// A newly created view item.
diff --git a/src/RealTime/UI/CitiesComboBoxItem.cs b/src/RealTime/UI/CitiesComboBoxItem.cs
index 7893248a..d9f8777d 100644
--- a/src/RealTime/UI/CitiesComboBoxItem.cs
+++ b/src/RealTime/UI/CitiesComboBoxItem.cs
@@ -80,7 +80,7 @@ public override void Refresh()
UIComponent.selectedIndex = Value;
}
- /// Creates the view item using the provided .
+ /// Creates the view item using the specified .
/// The UI helper to use for item creation.
/// The item's default value.
/// A newly created view item.
diff --git a/src/RealTime/UI/CitiesSliderItem.cs b/src/RealTime/UI/CitiesSliderItem.cs
index fc8fcf49..6de687aa 100644
--- a/src/RealTime/UI/CitiesSliderItem.cs
+++ b/src/RealTime/UI/CitiesSliderItem.cs
@@ -107,7 +107,7 @@ public override void Refresh()
UIComponent.value = Value;
}
- /// Creates the view item using the provided .
+ /// Creates the view item using the specified .
/// The UI helper to use for item creation.
/// The item's default value.
/// A newly created view item.
diff --git a/src/RealTime/UI/CitiesViewItem.cs b/src/RealTime/UI/CitiesViewItem.cs
index 6c3bebc8..9916b406 100644
--- a/src/RealTime/UI/CitiesViewItem.cs
+++ b/src/RealTime/UI/CitiesViewItem.cs
@@ -87,7 +87,7 @@ private set
/// Thrown when the argument is null.
public abstract void Translate(ILocalizationProvider localizationProvider);
- /// Creates the view item using the provided .
+ /// Creates the view item using the specified .
/// The UI helper to use for item creation.
/// The item's default value.
/// A newly created view item.
diff --git a/src/RealTime/UI/RealTimeUIDateTimeWrapper.cs b/src/RealTime/UI/RealTimeUIDateTimeWrapper.cs
index e5570f00..6bdccded 100644
--- a/src/RealTime/UI/RealTimeUIDateTimeWrapper.cs
+++ b/src/RealTime/UI/RealTimeUIDateTimeWrapper.cs
@@ -31,7 +31,7 @@ internal RealTimeUIDateTimeWrapper(DateTime initial)
public DateTime CurrentValue => m_Value;
///
- /// Checks the provided value whether it should be converted to a
+ /// Checks the specified value whether it should be converted to a
/// string representation. Converts the value when necessary.
///
/// ///
From b6dd250efba808e3d668f017cd0b86444588643a Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 22 Jul 2018 01:46:44 +0200
Subject: [PATCH 32/42] Improve the lunch behavior
---
.../CustomAI/RealTimeResidentAI.SchoolWork.cs | 16 +++++++++++++---
src/RealTime/CustomAI/WorkBehavior.cs | 9 ++++-----
2 files changed, 17 insertions(+), 8 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index 2a11b545..6ccb09f9 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -57,7 +57,12 @@ private void DoScheduledWork(ref CitizenSchedule schedule, TAI instance, uint ci
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
schedule.WorkStatus = WorkStatus.Working;
- if (residentAI.StartMoving(instance, citizenId, ref citizen, currentBuilding, schedule.WorkBuilding)
+ if (currentBuilding == schedule.WorkBuilding && schedule.CurrentState != ResidentState.AtSchoolOrWork)
+ {
+ CitizenProxy.SetVisitPlace(ref citizen, citizenId, 0);
+ CitizenProxy.SetLocation(ref citizen, Citizen.Location.Work);
+ }
+ else if (residentAI.StartMoving(instance, citizenId, ref citizen, currentBuilding, schedule.WorkBuilding)
&& schedule.CurrentState == ResidentState.AtHome)
{
schedule.DepartureToWorkTime = TimeInfo.Now;
@@ -78,15 +83,20 @@ private void DoScheduledWork(ref CitizenSchedule schedule, TAI instance, uint ci
private void DoScheduledLunch(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
{
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
+#if DEBUG
+ string citizenDesc = GetCitizenDesc(citizenId, ref citizen);
+#else
+ string citizenDesc = null;
+#endif
ushort lunchPlace = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance);
if (lunchPlace != 0)
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} is going for lunch from {currentBuilding} to {lunchPlace}");
+ Log.Debug(TimeInfo.Now, $"{citizenDesc} is going for lunch from {currentBuilding} to {lunchPlace}");
workBehavior.ScheduleReturnFromLunch(ref schedule);
}
else
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted to go for lunch from {currentBuilding}, but there were no buildings close enough");
+ Log.Debug(TimeInfo.Now, $"{citizenDesc} wanted to go for lunch from {currentBuilding}, but there were no buildings close enough");
workBehavior.ScheduleReturnFromWork(ref schedule, CitizenProxy.GetAge(ref citizen));
}
}
diff --git a/src/RealTime/CustomAI/WorkBehavior.cs b/src/RealTime/CustomAI/WorkBehavior.cs
index ae2d6b6a..c4d3b548 100644
--- a/src/RealTime/CustomAI/WorkBehavior.cs
+++ b/src/RealTime/CustomAI/WorkBehavior.cs
@@ -148,7 +148,8 @@ public bool ScheduleGoToWork(ref CitizenSchedule schedule, ushort currentBuildin
/// true if a lunch time was scheduled; otherwise, false.
public bool ScheduleLunch(ref CitizenSchedule schedule, Citizen.AgeGroup citizenAge)
{
- if (schedule.WorkStatus == WorkStatus.Working
+ if (timeInfo.Now < lunchBegin
+ && schedule.WorkStatus == WorkStatus.Working
&& schedule.WorkShift == WorkShift.First
&& WillGoToLunch(citizenAge))
{
@@ -163,12 +164,10 @@ public bool ScheduleLunch(ref CitizenSchedule schedule, Citizen.AgeGroup citizen
/// The citizen's schedule to update.
public void ScheduleReturnFromLunch(ref CitizenSchedule schedule)
{
- if (schedule.WorkStatus != WorkStatus.Working || schedule.CurrentState != ResidentState.Shopping)
+ if (schedule.WorkStatus == WorkStatus.Working)
{
- return;
+ schedule.Schedule(ResidentState.AtSchoolOrWork, lunchEnd);
}
-
- schedule.Schedule(ResidentState.AtSchoolOrWork, lunchEnd);
}
/// Updates the citizen's work schedule by determining the time for returning from work.
From 9107613f057beaf5d50f9d3ad627825b23050788 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 22 Jul 2018 01:47:02 +0200
Subject: [PATCH 33/42] Implement the custom schedule storage
---
src/RealTime/Core/RealTimeCore.cs | 1 +
src/RealTime/CustomAI/CitizenSchedule.cs | 38 ++++++++
.../CustomAI/CitizenScheduleStorage.cs | 88 +++++++++++++++++++
.../CustomAI/RealTimeResidentAI.Common.cs | 6 ++
src/RealTime/CustomAI/RealTimeResidentAI.cs | 9 ++
src/RealTime/CustomAI/WorkBehavior.cs | 8 +-
.../CitizenManagerConnection.cs | 7 ++
.../ICitizenManagerConnection.cs | 4 +
src/RealTime/RealTime.csproj | 1 +
9 files changed, 160 insertions(+), 2 deletions(-)
create mode 100644 src/RealTime/CustomAI/CitizenScheduleStorage.cs
diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs
index 2a78176a..54354c4c 100644
--- a/src/RealTime/Core/RealTimeCore.cs
+++ b/src/RealTime/Core/RealTimeCore.cs
@@ -149,6 +149,7 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, ILocaliza
RealTimeStorage.CurrentLevelStorage.GameSaving += result.GameSaving;
result.storageData.Add(eventManager);
+ result.storageData.Add(ResidentAIPatch.RealTimeAI.GetStorageService());
result.LoadStorageData();
result.Translate(localizationProvider);
diff --git a/src/RealTime/CustomAI/CitizenSchedule.cs b/src/RealTime/CustomAI/CitizenSchedule.cs
index 9d8d63e1..6fbbdbaf 100644
--- a/src/RealTime/CustomAI/CitizenSchedule.cs
+++ b/src/RealTime/CustomAI/CitizenSchedule.cs
@@ -11,6 +11,9 @@ namespace RealTime.CustomAI
/// Note that this struct is intentionally made mutable to increase performance.
internal struct CitizenSchedule
{
+ /// The size of the buffer in bytes to store the data.
+ public const int DataRecordSize = 6;
+
/// The citizen's current state.
public ResidentState CurrentState;
@@ -29,6 +32,8 @@ internal struct CitizenSchedule
/// The time when citizen started their last ride to the work building.
public DateTime DepartureToWorkTime;
+ private const float TravelTimeMultiplier = ushort.MaxValue / MaxTravelTime;
+
/// Gets the citizen's next scheduled state.
public ResidentState ScheduledState { get; private set; }
@@ -96,5 +101,38 @@ public void Schedule(ResidentState nextState, DateTime nextStateTime)
ScheduledState = nextState;
ScheduledStateTime = nextStateTime;
}
+
+ /// Writes this instance to the specified target buffer.
+ /// The target buffer. Must have length of elements.
+ /// The reference time (in ticks) to use for time serialization.
+ public void Write(byte[] target, long referenceTime)
+ {
+ target[0] = (byte)(((int)WorkShift & 0xF) + ((int)WorkStatus << 4));
+ target[1] = (byte)ScheduledState;
+
+ ushort minutes = (ushort)((ScheduledStateTime.Ticks - referenceTime) / TimeSpan.TicksPerMinute);
+ target[2] = (byte)(minutes & 0xFF);
+ target[3] = (byte)(minutes >> 8);
+
+ ushort travelTime = (ushort)(TravelTimeToWork * TravelTimeMultiplier);
+ target[4] = (byte)(travelTime & 0xFF);
+ target[5] = (byte)(travelTime >> 8);
+ }
+
+ /// Reads this instance from the specified source buffer.
+ /// The source buffer. Must have length of elements.
+ /// The reference time (in ticks) to use for time deserialization.
+ public void Read(byte[] source, long referenceTime)
+ {
+ WorkShift = (WorkShift)(source[0] & 0xF);
+ WorkStatus = (WorkStatus)(source[0] >> 4);
+ ScheduledState = (ResidentState)source[1];
+
+ int minutes = source[2] + (source[3] << 8);
+ ScheduledStateTime = new DateTime((minutes * TimeSpan.TicksPerMinute) + referenceTime);
+
+ int travelTime = source[4] + (source[5] << 8);
+ TravelTimeToWork = travelTime / TravelTimeMultiplier;
+ }
}
}
\ No newline at end of file
diff --git a/src/RealTime/CustomAI/CitizenScheduleStorage.cs b/src/RealTime/CustomAI/CitizenScheduleStorage.cs
new file mode 100644
index 00000000..227902de
--- /dev/null
+++ b/src/RealTime/CustomAI/CitizenScheduleStorage.cs
@@ -0,0 +1,88 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.CustomAI
+{
+ using System;
+ using System.IO;
+ using RealTime.Core;
+ using RealTime.Simulation;
+
+ ///
+ /// A helper class that enables loading and saving of the custom citizen schedules.
+ /// This class accesses the directly for better performance.
+ ///
+ ///
+ internal sealed class CitizenScheduleStorage : IStorageData
+ {
+ private const string StorageDataId = "RealTimeCitizenSchedule";
+
+ private readonly CitizenSchedule[] residentSchedules;
+ private readonly Citizen[] citizens;
+ private readonly ITimeInfo timeInfo;
+
+ /// Initializes a new instance of the class.
+ /// The resident schedules to store or load.
+ /// The game's citizens array.
+ /// An object that provides the game time information.
+ /// Thrown when any argument is null.
+ /// Thrown when and
+ /// have different length.
+ public CitizenScheduleStorage(CitizenSchedule[] residentSchedules, Citizen[] citizens, ITimeInfo timeInfo)
+ {
+ this.residentSchedules = residentSchedules ?? throw new System.ArgumentNullException(nameof(residentSchedules));
+ this.citizens = citizens ?? throw new ArgumentNullException(nameof(citizens));
+ this.timeInfo = timeInfo ?? throw new ArgumentNullException(nameof(timeInfo));
+ if (residentSchedules.Length != citizens.Length)
+ {
+ throw new ArgumentException($"{nameof(residentSchedules)} and {nameof(citizens)} arrays must have equal length");
+ }
+ }
+
+ /// Gets an unique ID of this storage data set.
+ string IStorageData.StorageDataId => StorageDataId;
+
+ /// Reads the data set from the specified .
+ /// A to read the data set from.
+ void IStorageData.ReadData(Stream source)
+ {
+ byte[] buffer = new byte[CitizenSchedule.DataRecordSize];
+ long referenceTime = timeInfo.Now.Date.Ticks;
+
+ for (int i = 0; i < citizens.Length; ++i)
+ {
+ Citizen.Flags flags = citizens[i].m_flags;
+ if ((flags & Citizen.Flags.Created) == 0
+ || (flags & Citizen.Flags.DummyTraffic) != 0)
+ {
+ continue;
+ }
+
+ source.Read(buffer, 0, buffer.Length);
+ residentSchedules[i].Read(buffer, referenceTime);
+ }
+ }
+
+ /// Reads the data set to the specified .
+ /// A to write the data set to.
+ void IStorageData.StoreData(Stream target)
+ {
+ byte[] buffer = new byte[CitizenSchedule.DataRecordSize];
+ long referenceTime = timeInfo.Now.Date.Ticks;
+
+ for (int i = 0; i < citizens.Length; ++i)
+ {
+ Citizen.Flags flags = citizens[i].m_flags;
+ if ((flags & Citizen.Flags.Created) == 0
+ || (flags & Citizen.Flags.DummyTraffic) != 0)
+ {
+ continue;
+ }
+
+ residentSchedules[i].Write(buffer, referenceTime);
+ target.Write(buffer, 0, buffer.Length);
+ }
+ }
+ }
+}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
index 9a48fd4c..5ad3ff01 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
@@ -245,6 +245,12 @@ private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId,
{
schedule.WorkBuilding = workBuilding;
workBehavior.UpdateWorkShift(ref schedule, CitizenProxy.GetAge(ref citizen));
+ if (schedule.CurrentState == ResidentState.AtSchoolOrWork && schedule.ScheduledStateTime == default)
+ {
+ // When enabling for an existing game, the citizens that are working have no schedule yet
+ schedule.Schedule(ResidentState.Unknown, TimeInfo.Now.FutureHour(schedule.WorkShiftEndHour));
+ }
+
Log.Debug($"Updated work shifts for citizen {citizenId}: work shift {schedule.WorkShift}, {schedule.WorkShiftStartHour} - {schedule.WorkShiftEndHour}, weekends: {schedule.WorksOnWeekends}");
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.cs b/src/RealTime/CustomAI/RealTimeResidentAI.cs
index 047d560a..19a622ca 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.cs
@@ -3,7 +3,9 @@
namespace RealTime.CustomAI
{
using System;
+ using System.IO;
using RealTime.Config;
+ using RealTime.Core;
using RealTime.Events;
using RealTime.GameConnection;
using RealTime.Tools;
@@ -147,5 +149,12 @@ public void SetSimulationCyclePeriod(float cyclePeriod)
simulationCycle = cyclePeriod;
Log.Debug($"SIMULATION CYCLE PERIOD: {cyclePeriod} hours");
}
+
+ /// Gets an instance of the storage service that can read and write the custom schedule data.
+ /// An object that implements the interface.
+ public IStorageData GetStorageService()
+ {
+ return new CitizenScheduleStorage(residentSchedules, CitizenMgr.GetCitizensArray(), TimeInfo);
+ }
}
}
\ No newline at end of file
diff --git a/src/RealTime/CustomAI/WorkBehavior.cs b/src/RealTime/CustomAI/WorkBehavior.cs
index c4d3b548..bac4b3f2 100644
--- a/src/RealTime/CustomAI/WorkBehavior.cs
+++ b/src/RealTime/CustomAI/WorkBehavior.cs
@@ -68,7 +68,7 @@ public void UpdateWorkShift(ref CitizenSchedule schedule, Citizen.AgeGroup citiz
ItemClass.Service buildingSevice = buildingManager.GetBuildingService(schedule.WorkBuilding);
float workBegin, workEnd;
- WorkShift workShift;
+ WorkShift workShift = schedule.WorkShift;
switch (citizenAge)
{
@@ -81,7 +81,11 @@ public void UpdateWorkShift(ref CitizenSchedule schedule, Citizen.AgeGroup citiz
case Citizen.AgeGroup.Young:
case Citizen.AgeGroup.Adult:
- workShift = GetWorkShift(GetBuildingWorkShiftCount(buildingSevice));
+ if (workShift == WorkShift.Unemployed)
+ {
+ workShift = GetWorkShift(GetBuildingWorkShiftCount(buildingSevice));
+ }
+
workBegin = config.WorkBegin;
workEnd = config.WorkEnd;
break;
diff --git a/src/RealTime/GameConnection/CitizenManagerConnection.cs b/src/RealTime/GameConnection/CitizenManagerConnection.cs
index 0393bab0..9a794868 100644
--- a/src/RealTime/GameConnection/CitizenManagerConnection.cs
+++ b/src/RealTime/GameConnection/CitizenManagerConnection.cs
@@ -127,5 +127,12 @@ public Citizen.Location GetCitizenLocation(uint citizenId)
return CitizenManager.instance.m_citizens.m_buffer[citizenId].CurrentLocation;
}
+
+ /// Gets the game's citizens array (direct reference).
+ /// The reference to the game's array containing the items.
+ public Citizen[] GetCitizensArray()
+ {
+ return CitizenManager.instance.m_citizens.m_buffer;
+ }
}
}
\ No newline at end of file
diff --git a/src/RealTime/GameConnection/ICitizenManagerConnection.cs b/src/RealTime/GameConnection/ICitizenManagerConnection.cs
index d5a43eae..37d78815 100644
--- a/src/RealTime/GameConnection/ICitizenManagerConnection.cs
+++ b/src/RealTime/GameConnection/ICitizenManagerConnection.cs
@@ -62,5 +62,9 @@ internal interface ICitizenManagerConnection
/// A value that describes the citizen's current location.
/// Thrown when the argument is 0.
Citizen.Location GetCitizenLocation(uint citizenId);
+
+ /// Gets the game's citizens array (direct reference).
+ /// The reference to the game's array containing the items.
+ Citizen[] GetCitizensArray();
}
}
\ No newline at end of file
diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj
index 54594bdb..0c5672fa 100644
--- a/src/RealTime/RealTime.csproj
+++ b/src/RealTime/RealTime.csproj
@@ -57,6 +57,7 @@
+
From 84ed61dd5349e0ccc4e6899a2e3f4b4135dfb7bc Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 22 Jul 2018 03:45:01 +0200
Subject: [PATCH 34/42] Avoid going out at night for senior citizens
---
src/RealTime/CustomAI/SpareTimeBehavior.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/RealTime/CustomAI/SpareTimeBehavior.cs b/src/RealTime/CustomAI/SpareTimeBehavior.cs
index 77a1be4c..261a6764 100644
--- a/src/RealTime/CustomAI/SpareTimeBehavior.cs
+++ b/src/RealTime/CustomAI/SpareTimeBehavior.cs
@@ -82,7 +82,7 @@ public void RefreshGoOutChances()
chances[(int)Citizen.AgeGroup.Teen] = isDayTime ? defaultChance : 0;
chances[(int)Citizen.AgeGroup.Young] = defaultChance;
chances[(int)Citizen.AgeGroup.Adult] = defaultChance;
- chances[(int)Citizen.AgeGroup.Senior] = defaultChance;
+ chances[(int)Citizen.AgeGroup.Senior] = isDayTime ? defaultChance : 0;
if (dump)
{
From 5746c910c6c27a4ad13c211ba8e36525bf3e98de Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 22 Jul 2018 03:45:58 +0200
Subject: [PATCH 35/42] Improve work scheduling (allow unemployment while at
work)
---
src/RealTime/CustomAI/RealTimeResidentAI.Common.cs | 5 +++++
src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs | 1 +
2 files changed, 6 insertions(+)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
index 5ad3ff01..afbe642c 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
@@ -250,6 +250,11 @@ private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId,
// When enabling for an existing game, the citizens that are working have no schedule yet
schedule.Schedule(ResidentState.Unknown, TimeInfo.Now.FutureHour(schedule.WorkShiftEndHour));
}
+ else if (schedule.WorkBuilding == 0 && schedule.WorkStatus == WorkStatus.Working)
+ {
+ // This is for the case when the citizen becomes unemployed while at work
+ schedule.Schedule(ResidentState.Unknown, default);
+ }
Log.Debug($"Updated work shifts for citizen {citizenId}: work shift {schedule.WorkShift}, {schedule.WorkShiftStartHour} - {schedule.WorkShiftEndHour}, weekends: {schedule.WorksOnWeekends}");
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
index 6ccb09f9..3f1f962d 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.SchoolWork.cs
@@ -56,6 +56,7 @@ private void DoScheduledWork(ref CitizenSchedule schedule, TAI instance, uint ci
{
ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
schedule.WorkStatus = WorkStatus.Working;
+ schedule.DepartureToWorkTime = default;
if (currentBuilding == schedule.WorkBuilding && schedule.CurrentState != ResidentState.AtSchoolOrWork)
{
From 783e33b4df54bc82576091b0a73199902d2443dd Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 22 Jul 2018 05:14:15 +0200
Subject: [PATCH 36/42] Fix issues with endless visiting and going to undefined
work buildings
---
src/RealTime/CustomAI/RealTimeResidentAI.Common.cs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
index afbe642c..49d46a08 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
@@ -231,7 +231,7 @@ when BuildingMgr.GetBuildingSubService(currentBuilding) == ItemClass.SubService.
}
schedule.CurrentState = ResidentState.Visiting;
- break;
+ return ScheduleAction.ProcessState;
}
return ScheduleAction.Ignore;
@@ -250,7 +250,8 @@ private void UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId,
// When enabling for an existing game, the citizens that are working have no schedule yet
schedule.Schedule(ResidentState.Unknown, TimeInfo.Now.FutureHour(schedule.WorkShiftEndHour));
}
- else if (schedule.WorkBuilding == 0 && schedule.WorkStatus == WorkStatus.Working)
+ else if (schedule.WorkBuilding == 0
+ && (schedule.ScheduledState == ResidentState.AtSchoolOrWork || schedule.WorkStatus == WorkStatus.Working))
{
// This is for the case when the citizen becomes unemployed while at work
schedule.Schedule(ResidentState.Unknown, default);
From 0809dc54f14dd64e919ca9a065f6a9974e3f510f Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 22 Jul 2018 05:17:15 +0200
Subject: [PATCH 37/42] Improve event attending logic
---
src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
index f19ef9ae..fce55127 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
@@ -41,7 +41,6 @@ private bool ScheduleRelaxing(ref CitizenSchedule schedule, uint citizenId, ref
private void DoScheduledRelaxing(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
{
ushort buildingId = CitizenProxy.GetCurrentBuilding(ref citizen);
-
switch (schedule.Hint)
{
case ScheduleHint.RelaxAtLeisureBuilding:
@@ -60,16 +59,14 @@ private void DoScheduledRelaxing(ref CitizenSchedule schedule, TAI instance, uin
return;
case ScheduleHint.AttendingEvent:
- DateTime returnTime;
+ DateTime returnTime = default;
ICityEvent cityEvent = EventMgr.GetUpcomingCityEvent(schedule.EventBuilding);
if (cityEvent == null)
{
- returnTime = default;
Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted attend an event at '{schedule.EventBuilding}', but there was no event there");
}
- else
+ else if (StartMovingToVisitBuilding(instance, citizenId, ref citizen, schedule.EventBuilding))
{
- StartMovingToVisitBuilding(instance, citizenId, ref citizen, schedule.EventBuilding);
returnTime = cityEvent.EndTime;
Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna attend an event at '{schedule.EventBuilding}', will return at {returnTime}");
}
From f1a7935ea696c38d06d95a24e000d1e9c5e23f23 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 22 Jul 2018 05:37:15 +0200
Subject: [PATCH 38/42] Allow citizens attend already ongoing events
---
.../CustomAI/RealTimeResidentAI.Visit.cs | 2 +-
src/RealTime/Events/RealTimeEventManager.cs | 16 +++++++++++++---
2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
index fce55127..40a4a25f 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
@@ -60,7 +60,7 @@ private void DoScheduledRelaxing(ref CitizenSchedule schedule, TAI instance, uin
case ScheduleHint.AttendingEvent:
DateTime returnTime = default;
- ICityEvent cityEvent = EventMgr.GetUpcomingCityEvent(schedule.EventBuilding);
+ ICityEvent cityEvent = EventMgr.GetCityEvent(schedule.EventBuilding);
if (cityEvent == null)
{
Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted attend an event at '{schedule.EventBuilding}', but there was no event there");
diff --git a/src/RealTime/Events/RealTimeEventManager.cs b/src/RealTime/Events/RealTimeEventManager.cs
index af86e707..484f49bb 100644
--- a/src/RealTime/Events/RealTimeEventManager.cs
+++ b/src/RealTime/Events/RealTimeEventManager.cs
@@ -173,14 +173,24 @@ public ICityEvent GetUpcomingCityEvent(DateTime earliestStartTime, DateTime late
}
///
- /// Gets the instance of an upcoming city event that takes place in a building
+ /// Gets the instance of an ongoing or upcoming city event that takes place in a building
/// with specified ID.
///
/// The ID of a building to search events for.
/// An instance of the first matching city event, or null if none found.
- public ICityEvent GetUpcomingCityEvent(ushort buildingId)
+ public ICityEvent GetCityEvent(ushort buildingId)
{
- if (buildingId == 0 || upcomingEvents.Count == 0)
+ if (buildingId == 0)
+ {
+ return null;
+ }
+
+ if (activeEvent != null && activeEvent.BuildingId == buildingId)
+ {
+ return activeEvent;
+ }
+
+ if (upcomingEvents.Count == 0)
{
return null;
}
From 78ba4a9ab46e60714ea4c4a898dca8f768dce015 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 22 Jul 2018 12:02:22 +0200
Subject: [PATCH 39/42] Fix issue caused wrong storage of the scheduled time
---
src/RealTime/CustomAI/CitizenSchedule.cs | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/RealTime/CustomAI/CitizenSchedule.cs b/src/RealTime/CustomAI/CitizenSchedule.cs
index 6fbbdbaf..f75796d9 100644
--- a/src/RealTime/CustomAI/CitizenSchedule.cs
+++ b/src/RealTime/CustomAI/CitizenSchedule.cs
@@ -110,7 +110,10 @@ public void Write(byte[] target, long referenceTime)
target[0] = (byte)(((int)WorkShift & 0xF) + ((int)WorkStatus << 4));
target[1] = (byte)ScheduledState;
- ushort minutes = (ushort)((ScheduledStateTime.Ticks - referenceTime) / TimeSpan.TicksPerMinute);
+ ushort minutes = ScheduledStateTime == default
+ ? (ushort)0
+ : (ushort)((ScheduledStateTime.Ticks - referenceTime) / TimeSpan.TicksPerMinute);
+
target[2] = (byte)(minutes & 0xFF);
target[3] = (byte)(minutes >> 8);
@@ -129,7 +132,9 @@ public void Read(byte[] source, long referenceTime)
ScheduledState = (ResidentState)source[1];
int minutes = source[2] + (source[3] << 8);
- ScheduledStateTime = new DateTime((minutes * TimeSpan.TicksPerMinute) + referenceTime);
+ ScheduledStateTime = minutes == 0
+ ? default
+ : new DateTime((minutes * TimeSpan.TicksPerMinute) + referenceTime);
int travelTime = source[4] + (source[5] << 8);
TravelTimeToWork = travelTime / TravelTimeMultiplier;
From ce0b8d077353c08229fb8d8bf20db0282014f6a3 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 22 Jul 2018 13:35:25 +0200
Subject: [PATCH 40/42] Improve rescheduling of citizen's plans due to weather,
time, policies
---
src/RealTime/CustomAI/Constants.cs | 6 --
src/RealTime/CustomAI/RealTimeHumanAIBase.cs | 2 +-
.../CustomAI/RealTimeResidentAI.Common.cs | 8 +-
.../CustomAI/RealTimeResidentAI.Moving.cs | 35 +++---
.../CustomAI/RealTimeResidentAI.Visit.cs | 101 +++++++++++++-----
src/RealTime/CustomAI/RealTimeResidentAI.cs | 3 +-
6 files changed, 96 insertions(+), 59 deletions(-)
diff --git a/src/RealTime/CustomAI/Constants.cs b/src/RealTime/CustomAI/Constants.cs
index 60b24959..cc006e68 100644
--- a/src/RealTime/CustomAI/Constants.cs
+++ b/src/RealTime/CustomAI/Constants.cs
@@ -23,12 +23,6 @@ internal static class Constants
/// A chance in percent for a citizen to go shopping.
public const uint GoShoppingChance = 80;
- /// A chance in percent for a citizen to return shopping.
- public const uint ReturnFromShoppingChance = 75;
-
- /// A chance in percent for a citizen to return from a visited building.
- public const uint ReturnFromVisitChance = 50;
-
/// A chance in percent for a citizen to go to sleep when he or she is at home and doesn't go out.
public const uint GoSleepingChance = 75;
diff --git a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
index 01ca29fc..945123ba 100644
--- a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
+++ b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
@@ -254,7 +254,7 @@ protected bool IsBadWeather()
/// An estimated travel time in hours.
protected float GetEstimatedTravelTime(ushort building1, ushort building2)
{
- if (building1 == 0 || building2 == 0)
+ if (building1 == 0 || building2 == 0 || building1 == building2)
{
return 0;
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
index 49d46a08..74d9c714 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
@@ -214,6 +214,8 @@ private ScheduleAction UpdateCitizenState(uint citizenId, ref TCitizen citizen,
switch (buildingService)
{
case ItemClass.Service.Beautification:
+ case ItemClass.Service.Monument:
+ case ItemClass.Service.Tourism:
case ItemClass.Service.Commercial
when BuildingMgr.GetBuildingSubService(currentBuilding) == ItemClass.SubService.CommercialLeisure
&& schedule.WorkStatus != WorkStatus.Working:
@@ -370,12 +372,10 @@ private bool ProcessCurrentState(ref CitizenSchedule schedule, ref TCitizen citi
switch (schedule.CurrentState)
{
case ResidentState.Shopping:
- ProcessCitizenShopping(ref citizen);
- return false;
+ return ProcessCitizenShopping(ref schedule, ref citizen);
case ResidentState.Relaxing:
- ProcessCitizenRelaxing(ref citizen);
- return false;
+ return ProcessCitizenRelaxing(ref schedule, ref citizen);
case ResidentState.Visiting:
return ProcessCitizenVisit(ref schedule, ref citizen);
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
index 333718eb..cdfe4e5e 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Moving.cs
@@ -9,7 +9,7 @@ namespace RealTime.CustomAI
internal sealed partial class RealTimeResidentAI
{
- private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen citizen)
+ private bool ProcessCitizenMoving(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen)
{
ushort instanceId = CitizenProxy.GetInstance(ref citizen);
ushort vehicleId = CitizenProxy.GetVehicle(ref citizen);
@@ -24,52 +24,53 @@ private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen cit
if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.MovingIn))
{
CitizenMgr.ReleaseCitizen(citizenId);
- residentSchedules[citizenId] = default;
+ schedule = default;
}
else
{
CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home);
CitizenProxy.SetArrested(ref citizen, false);
+ schedule.Schedule(ResidentState.Unknown, default);
}
- return;
+ return true;
}
if (vehicleId == 0 && CitizenMgr.IsAreaEvacuating(instanceId) && !CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating))
{
Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} was on the way, but the area evacuates. Finding an evacuation place.");
+ schedule.Schedule(ResidentState.Unknown, default);
TransferMgr.AddOutgoingOfferFromCurrentPosition(citizenId, residentAI.GetEvacuationReason(instance, 0));
- return;
+ return true;
}
ushort targetBuilding = CitizenMgr.GetTargetBuilding(instanceId);
if (targetBuilding == CitizenProxy.GetWorkBuilding(ref citizen))
{
- return;
+ return true;
}
ItemClass.Service targetService = BuildingMgr.GetBuildingService(targetBuilding);
if (targetService == ItemClass.Service.Beautification && IsBadWeather())
{
Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} cancels the trip to a park due to bad weather");
- ushort home = CitizenProxy.GetHomeBuilding(ref citizen);
- if (home != 0)
- {
- residentAI.StartMoving(instance, citizenId, ref citizen, 0, home);
- }
+ schedule.Schedule(ResidentState.AtHome, default);
+ return false;
}
+
+ return true;
}
private ushort MoveToCommercialBuilding(TAI instance, uint citizenId, ref TCitizen citizen, float distance)
{
- ushort buildingId = CitizenProxy.GetCurrentBuilding(ref citizen);
- if (buildingId == 0)
+ ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen);
+ if (currentBuilding == 0)
{
return 0;
}
- ushort foundBuilding = BuildingMgr.FindActiveBuilding(buildingId, distance, ItemClass.Service.Commercial);
- if (IsBuildingNoiseRestricted(foundBuilding))
+ ushort foundBuilding = BuildingMgr.FindActiveBuilding(currentBuilding, distance, ItemClass.Service.Commercial);
+ if (IsBuildingNoiseRestricted(foundBuilding, currentBuilding))
{
Log.Debug($"Citizen {citizenId} won't go to the commercial building {foundBuilding}, it has a NIMBY policy");
return 0;
@@ -89,15 +90,15 @@ private ushort MoveToCommercialBuilding(TAI instance, uint citizenId, ref TCitiz
return foundBuilding;
}
- private ushort MoveToLeisureBuilding(TAI instance, uint citizenId, ref TCitizen citizen, ushort buildingId)
+ private ushort MoveToLeisureBuilding(TAI instance, uint citizenId, ref TCitizen citizen, ushort currentBuilding)
{
ushort leisureBuilding = BuildingMgr.FindActiveBuilding(
- buildingId,
+ currentBuilding,
LeisureSearchDistance,
ItemClass.Service.Commercial,
ItemClass.SubService.CommercialLeisure);
- if (IsBuildingNoiseRestricted(leisureBuilding))
+ if (IsBuildingNoiseRestricted(leisureBuilding, currentBuilding))
{
Log.Debug($"Citizen {citizenId} won't go to the leisure building {leisureBuilding}, it has a NIMBY policy");
return 0;
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
index 40a4a25f..1433647b 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.Visit.cs
@@ -76,7 +76,8 @@ private void DoScheduledRelaxing(ref CitizenSchedule schedule, TAI instance, uin
return;
}
- ResidentState nextState = Random.ShouldOccur(ReturnFromVisitChance)
+ uint relaxChance = spareTimeBehavior.GetGoOutChance(CitizenProxy.GetAge(ref citizen));
+ ResidentState nextState = Random.ShouldOccur(relaxChance)
? ResidentState.Unknown
: ResidentState.Relaxing;
@@ -84,12 +85,12 @@ private void DoScheduledRelaxing(ref CitizenSchedule schedule, TAI instance, uin
if (schedule.CurrentState != ResidentState.Relaxing)
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna relax, heading to an entertainment building.");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} in state {schedule.CurrentState} wanna relax and then schedules {nextState}, heading to an entertainment building.");
residentAI.FindVisitPlace(instance, citizenId, buildingId, residentAI.GetEntertainmentReason(instance));
}
}
- private void ProcessCitizenRelaxing(ref TCitizen citizen)
+ private bool ProcessCitizenRelaxing(ref CitizenSchedule schedule, ref TCitizen citizen)
{
ushort currentBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods)
@@ -98,6 +99,8 @@ private void ProcessCitizenRelaxing(ref TCitizen citizen)
// No Citizen.Flags.NeedGoods flag reset here, because we only bought 'beer' or 'champagne' in a leisure building.
BuildingMgr.ModifyMaterialBuffer(currentBuilding, TransferManager.TransferReason.Shopping, -ShoppingGoodsAmount);
}
+
+ return RescheduleVisit(ref schedule, ref citizen, currentBuilding);
}
private bool ScheduleShopping(ref CitizenSchedule schedule, ref TCitizen citizen, bool localOnly)
@@ -146,7 +149,8 @@ private void DoScheduledShopping(ref CitizenSchedule schedule, TAI instance, uin
}
else
{
- ResidentState nextState = Random.ShouldOccur(ReturnFromShoppingChance)
+ uint moreShoppingChance = spareTimeBehavior.GetGoOutChance(CitizenProxy.GetAge(ref citizen));
+ ResidentState nextState = Random.ShouldOccur(moreShoppingChance)
? ResidentState.Unknown
: ResidentState.Shopping;
@@ -154,57 +158,96 @@ private void DoScheduledShopping(ref CitizenSchedule schedule, TAI instance, uin
if (schedule.CurrentState != ResidentState.Shopping)
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna go shopping, heading to a random shop");
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} in state {schedule.CurrentState} wanna go shopping and schedules {nextState}, heading to a random shop");
residentAI.FindVisitPlace(instance, citizenId, currentBuilding, residentAI.GetShoppingReason(instance));
}
}
}
- private void ProcessCitizenShopping(ref TCitizen citizen)
+ private bool ProcessCitizenShopping(ref CitizenSchedule schedule, ref TCitizen citizen)
{
- if (!CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods))
+ ushort currentBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
+ if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods) && currentBuilding != 0)
{
- return;
+ BuildingMgr.ModifyMaterialBuffer(currentBuilding, TransferManager.TransferReason.Shopping, -ShoppingGoodsAmount);
+ CitizenProxy.RemoveFlags(ref citizen, Citizen.Flags.NeedGoods);
}
- ushort shop = CitizenProxy.GetVisitBuilding(ref citizen);
- if (shop == 0)
+ return RescheduleVisit(ref schedule, ref citizen, currentBuilding);
+ }
+
+ private bool ProcessCitizenVisit(ref CitizenSchedule schedule, ref TCitizen citizen)
+ {
+ if (schedule.Hint == ScheduleHint.OnTour)
{
- return;
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(0, ref citizen)} quits a tour (see next line for citizen ID)");
+ schedule.Schedule(ResidentState.Unknown, default);
+ return true;
}
- BuildingMgr.ModifyMaterialBuffer(shop, TransferManager.TransferReason.Shopping, -ShoppingGoodsAmount);
- CitizenProxy.RemoveFlags(ref citizen, Citizen.Flags.NeedGoods);
+ return RescheduleVisit(ref schedule, ref citizen, CitizenProxy.GetVisitBuilding(ref citizen));
}
- private bool ProcessCitizenVisit(ref CitizenSchedule schedule, ref TCitizen citizen)
+ private bool IsBuildingNoiseRestricted(ushort targetBuilding, ushort currentBuilding)
{
- if (schedule.ScheduledState != ResidentState.Unknown)
+ if (BuildingMgr.GetBuildingSubService(targetBuilding) != ItemClass.SubService.CommercialLeisure)
{
return false;
}
- ushort visitBuilding = CitizenProxy.GetVisitBuilding(ref citizen);
- if (schedule.Hint == ScheduleHint.OnTour
- || Random.ShouldOccur(ReturnFromVisitChance)
- || (BuildingMgr.GetBuildingSubService(visitBuilding) == ItemClass.SubService.CommercialLeisure
- && TimeInfo.IsNightTime
- && BuildingMgr.IsBuildingNoiseRestricted(visitBuilding)))
+ float currentHour = TimeInfo.CurrentHour;
+ if (currentHour >= Config.GoToSleepUpHour || currentHour <= Config.WakeupHour)
{
- Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(0, ref citizen)} quits a visit (see next line for citizen ID)");
- schedule.Schedule(ResidentState.Unknown, default);
- return true;
+ return BuildingMgr.IsBuildingNoiseRestricted(targetBuilding);
+ }
+
+ float travelTime = GetEstimatedTravelTime(currentBuilding, targetBuilding);
+ if (travelTime == 0)
+ {
+ return false;
+ }
+
+ float arriveHour = (float)TimeInfo.Now.AddHours(travelTime).TimeOfDay.TotalHours;
+ if (arriveHour >= Config.GoToSleepUpHour || arriveHour <= Config.WakeupHour)
+ {
+ return BuildingMgr.IsBuildingNoiseRestricted(targetBuilding);
}
return false;
}
- private bool IsBuildingNoiseRestricted(ushort building)
+ private bool RescheduleVisit(ref CitizenSchedule schedule, ref TCitizen citizen, ushort currentBuilding)
{
- float arriveHour = (float)TimeInfo.Now.AddHours(MaxTravelTime).TimeOfDay.TotalHours;
- return (arriveHour >= Config.GoToSleepUpHour || TimeInfo.CurrentHour >= Config.GoToSleepUpHour
- || arriveHour <= Config.WakeupHour || TimeInfo.CurrentHour <= Config.WakeupHour)
- && BuildingMgr.IsBuildingNoiseRestricted(building);
+ if (schedule.ScheduledState != ResidentState.Relaxing
+ && schedule.ScheduledState != ResidentState.Shopping
+ && schedule.ScheduledState != ResidentState.Visiting)
+ {
+ return false;
+ }
+
+ if (IsBadWeather())
+ {
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(0, ref citizen)} quits a visit because of bad weather (see next line for citizen ID)");
+ schedule.Schedule(ResidentState.AtHome, default);
+ return true;
+ }
+
+ if (IsBuildingNoiseRestricted(currentBuilding, currentBuilding))
+ {
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(0, ref citizen)} quits a visit because of NIMBY policy (see next line for citizen ID)");
+ schedule.Schedule(ResidentState.Unknown, default);
+ return true;
+ }
+
+ uint stayChance = spareTimeBehavior.GetGoOutChance(CitizenProxy.GetAge(ref citizen));
+ if (!Random.ShouldOccur(stayChance))
+ {
+ Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(0, ref citizen)} quits a visit because of time (see next line for citizen ID)");
+ schedule.Schedule(ResidentState.AtHome, default);
+ return true;
+ }
+
+ return false;
}
}
}
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.cs b/src/RealTime/CustomAI/RealTimeResidentAI.cs
index 19a622ca..f8c5e015 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.cs
@@ -78,8 +78,7 @@ public void UpdateLocation(TAI instance, uint citizenId, ref TCitizen citizen)
case ScheduleAction.Ignore:
return;
- case ScheduleAction.ProcessTransition:
- ProcessCitizenMoving(instance, citizenId, ref citizen);
+ case ScheduleAction.ProcessTransition when ProcessCitizenMoving(ref schedule, instance, citizenId, ref citizen):
return;
}
From d932849f21a8c93f4fa5a8c3ab51dbe74e0a4101 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 22 Jul 2018 13:51:10 +0200
Subject: [PATCH 41/42] Tweak the going out chances for citizens (closes #57)
---
src/RealTime/CustomAI/SpareTimeBehavior.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/RealTime/CustomAI/SpareTimeBehavior.cs b/src/RealTime/CustomAI/SpareTimeBehavior.cs
index 261a6764..f79ba8c4 100644
--- a/src/RealTime/CustomAI/SpareTimeBehavior.cs
+++ b/src/RealTime/CustomAI/SpareTimeBehavior.cs
@@ -60,7 +60,7 @@ public void RefreshGoOutChances()
float timeModifier;
if (isDayTime)
{
- timeModifier = RealTimeMath.Clamp((currentHour - config.WakeupHour) * 2f, 0, 4f);
+ timeModifier = RealTimeMath.Clamp(currentHour - config.WakeupHour, 0, 4f);
}
else
{
From 60a7b76f0f3acdec3104e7f3edfcb53b12b789ce Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sun, 22 Jul 2018 14:03:05 +0200
Subject: [PATCH 42/42] Improve industry working hours (closes #68)
- generic industry has 3 shifts with a 5-day week
- forestry and agriculture industries have 1 extended shift 7/7
- oil and ore industries have 3 shifts 7/7 (365/24/7)
---
src/RealTime/CustomAI/WorkBehavior.cs | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/RealTime/CustomAI/WorkBehavior.cs b/src/RealTime/CustomAI/WorkBehavior.cs
index bac4b3f2..8a2e5797 100644
--- a/src/RealTime/CustomAI/WorkBehavior.cs
+++ b/src/RealTime/CustomAI/WorkBehavior.cs
@@ -67,6 +67,8 @@ public void UpdateWorkShift(ref CitizenSchedule schedule, Citizen.AgeGroup citiz
}
ItemClass.Service buildingSevice = buildingManager.GetBuildingService(schedule.WorkBuilding);
+ ItemClass.SubService buildingSubService = buildingManager.GetBuildingSubService(schedule.WorkBuilding);
+
float workBegin, workEnd;
WorkShift workShift = schedule.WorkShift;
@@ -83,7 +85,7 @@ public void UpdateWorkShift(ref CitizenSchedule schedule, Citizen.AgeGroup citiz
case Citizen.AgeGroup.Adult:
if (workShift == WorkShift.Unemployed)
{
- workShift = GetWorkShift(GetBuildingWorkShiftCount(buildingSevice));
+ workShift = GetWorkShift(GetBuildingWorkShiftCount(buildingSevice, buildingSubService));
}
workBegin = config.WorkBegin;
@@ -94,7 +96,6 @@ public void UpdateWorkShift(ref CitizenSchedule schedule, Citizen.AgeGroup citiz
return;
}
- ItemClass.SubService buildingSubService = buildingManager.GetBuildingSubService(schedule.WorkBuilding);
switch (workShift)
{
case WorkShift.First when HasExtendedFirstWorkShift(buildingSevice, buildingSubService):
@@ -194,6 +195,7 @@ private static bool IsBuildingActiveOnWeekend(ItemClass.Service service, ItemCla
{
case ItemClass.Service.Commercial
when subService != ItemClass.SubService.CommercialHigh && subService != ItemClass.SubService.CommercialEco:
+ case ItemClass.Service.Industrial when subService != ItemClass.SubService.IndustrialGeneric:
case ItemClass.Service.Tourism:
case ItemClass.Service.Electricity:
case ItemClass.Service.Water:
@@ -211,13 +213,15 @@ private static bool IsBuildingActiveOnWeekend(ItemClass.Service service, ItemCla
}
}
- private static int GetBuildingWorkShiftCount(ItemClass.Service service)
+ private static int GetBuildingWorkShiftCount(ItemClass.Service service, ItemClass.SubService subService)
{
switch (service)
{
case ItemClass.Service.Office:
case ItemClass.Service.Garbage:
case ItemClass.Service.Education:
+ case ItemClass.Service.Industrial
+ when subService == ItemClass.SubService.IndustrialForestry || subService == ItemClass.SubService.IndustrialFarming:
return 1;
case ItemClass.Service.Road:
@@ -252,6 +256,8 @@ private static bool HasExtendedFirstWorkShift(ItemClass.Service service, ItemCla
case ItemClass.Service.Beautification:
case ItemClass.Service.Garbage:
case ItemClass.Service.Road:
+ case ItemClass.Service.Industrial
+ when subService == ItemClass.SubService.IndustrialFarming || subService == ItemClass.SubService.IndustrialForestry:
return true;
default: