From f889d23977fe88e5d45b3d7ac623fdeff5e2543c Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Mon, 30 Jul 2018 21:39:03 +0200
Subject: [PATCH] Create interfaces for various classes to enable testability
---
src/RealTime/CustomAI/IRealTimeBuildingAI.cs | 25 +++++++++
src/RealTime/CustomAI/ISpareTimeBehavior.cs | 33 ++++++++++++
src/RealTime/CustomAI/ITravelBehavior.cs | 18 +++++++
src/RealTime/CustomAI/IWorkBehavior.cs | 54 ++++++++++++++++++++
src/RealTime/CustomAI/RealTimeBuildingAI.cs | 10 ++--
src/RealTime/CustomAI/RealTimeHumanAIBase.cs | 6 +--
src/RealTime/CustomAI/RealTimeResidentAI.cs | 24 +++++----
src/RealTime/CustomAI/RealTimeTouristAI.cs | 6 +--
src/RealTime/CustomAI/SpareTimeBehavior.cs | 2 +-
src/RealTime/CustomAI/TravelBehavior.cs | 5 +-
src/RealTime/CustomAI/WorkBehavior.cs | 10 ++--
src/RealTime/Events/IRealTimeEventManager.cs | 36 +++++++++++++
src/RealTime/Events/RealTimeEventManager.cs | 2 +-
src/RealTime/RealTime.csproj | 5 ++
14 files changed, 206 insertions(+), 30 deletions(-)
create mode 100644 src/RealTime/CustomAI/IRealTimeBuildingAI.cs
create mode 100644 src/RealTime/CustomAI/ISpareTimeBehavior.cs
create mode 100644 src/RealTime/CustomAI/ITravelBehavior.cs
create mode 100644 src/RealTime/CustomAI/IWorkBehavior.cs
create mode 100644 src/RealTime/Events/IRealTimeEventManager.cs
diff --git a/src/RealTime/CustomAI/IRealTimeBuildingAI.cs b/src/RealTime/CustomAI/IRealTimeBuildingAI.cs
new file mode 100644
index 0000000..335a7f4
--- /dev/null
+++ b/src/RealTime/CustomAI/IRealTimeBuildingAI.cs
@@ -0,0 +1,25 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.CustomAI
+{
+ ///
+ /// An interface for the custom logic for the private buildings.
+ ///
+ internal interface IRealTimeBuildingAI
+ {
+ ///
+ /// Determines whether the building with the specified is noise restricted
+ /// (has NIMBY policy that is active on current time).
+ ///
+ /// The building ID to check.
+ /// The ID of a building where the citizen starts their journey.
+ /// Specify 0 if there is no journey in schedule.
+ ///
+ /// true if the building with the specified has NIMBY policy
+ /// that is active on current time; otherwise, false.
+ ///
+ bool IsNoiseRestricted(ushort buildingId, ushort currentBuildingId = 0);
+ }
+}
\ No newline at end of file
diff --git a/src/RealTime/CustomAI/ISpareTimeBehavior.cs b/src/RealTime/CustomAI/ISpareTimeBehavior.cs
new file mode 100644
index 0000000..eea4872
--- /dev/null
+++ b/src/RealTime/CustomAI/ISpareTimeBehavior.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.CustomAI
+{
+ ///
+ /// An interface for custom logic for the spare time simulation.
+ ///
+ internal interface ISpareTimeBehavior
+ {
+ ///
+ /// Gets the probability whether a citizen with specified age would go relaxing on current time.
+ ///
+ ///
+ /// The age of the citizen to check.
+ /// The citizen's assigned work shift (default is ).
+ ///
+ /// A percentage value in range of 0..100 that describes the probability whether
+ /// a citizen with specified age would go relaxing on current time.
+ uint GetRelaxingChance(Citizen.AgeGroup citizenAge, WorkShift workShift = WorkShift.Unemployed);
+
+ ///
+ /// Gets the probability whether a citizen with specified age would go shopping on current time.
+ ///
+ ///
+ /// The age of the citizen to check.
+ ///
+ /// A percentage value in range of 0..100 that describes the probability whether
+ /// a citizen with specified age would go shopping on current time.
+ uint GetShoppingChance(Citizen.AgeGroup citizenAge);
+ }
+}
\ No newline at end of file
diff --git a/src/RealTime/CustomAI/ITravelBehavior.cs b/src/RealTime/CustomAI/ITravelBehavior.cs
new file mode 100644
index 0000000..74f64e8
--- /dev/null
+++ b/src/RealTime/CustomAI/ITravelBehavior.cs
@@ -0,0 +1,18 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.CustomAI
+{
+ ///
+ /// An interface for citizens traveling behavior.
+ ///
+ internal interface ITravelBehavior
+ {
+ /// 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.
+ float GetEstimatedTravelTime(ushort building1, ushort building2);
+ }
+}
\ No newline at end of file
diff --git a/src/RealTime/CustomAI/IWorkBehavior.cs b/src/RealTime/CustomAI/IWorkBehavior.cs
new file mode 100644
index 0000000..6150e5a
--- /dev/null
+++ b/src/RealTime/CustomAI/IWorkBehavior.cs
@@ -0,0 +1,54 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.CustomAI
+{
+ ///
+ /// An interface for the citizens work behavior.
+ ///
+ internal interface IWorkBehavior
+ {
+ /// Notifies this object that a new game day starts.
+ void BeginNewDay();
+
+ ///
+ /// Determines whether a building of specified and
+ /// currently has working hours. Note that this method always returns true for residential buildings.
+ ///
+ /// The building service.
+ /// The building sub-service.
+ ///
+ /// true if a building of specified and
+ /// currently has working hours; otherwise, false.
+ ///
+ bool IsBuildingWorking(ItemClass.Service service, ItemClass.SubService subService);
+
+ /// 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.
+ bool ScheduleGoToWork(ref CitizenSchedule schedule, ushort currentBuilding, float simulationCycle);
+
+ /// 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.
+ bool ScheduleLunch(ref CitizenSchedule schedule, Citizen.AgeGroup citizenAge);
+
+ /// Updates the citizen's work schedule by determining the returning from lunch time.
+ /// The citizen's schedule to update.
+ void ScheduleReturnFromLunch(ref CitizenSchedule schedule);
+
+ /// 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.
+ void ScheduleReturnFromWork(ref CitizenSchedule schedule, Citizen.AgeGroup citizenAge);
+
+ /// 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.
+ void UpdateWorkShift(ref CitizenSchedule schedule, Citizen.AgeGroup citizenAge);
+ }
+}
\ No newline at end of file
diff --git a/src/RealTime/CustomAI/RealTimeBuildingAI.cs b/src/RealTime/CustomAI/RealTimeBuildingAI.cs
index 295127c..490f3fb 100644
--- a/src/RealTime/CustomAI/RealTimeBuildingAI.cs
+++ b/src/RealTime/CustomAI/RealTimeBuildingAI.cs
@@ -13,7 +13,7 @@ namespace RealTime.CustomAI
///
/// A class that incorporates the custom logic for the private buildings.
///
- internal sealed class RealTimeBuildingAI
+ internal sealed class RealTimeBuildingAI : IRealTimeBuildingAI
{
private const int ConstructionSpeedPaused = 10880;
private const int ConstructionSpeedMinimum = 1088;
@@ -27,8 +27,8 @@ namespace RealTime.CustomAI
private readonly ITimeInfo timeInfo;
private readonly IBuildingManagerConnection buildingManager;
private readonly IToolManagerConnection toolManager;
- private readonly WorkBehavior workBehavior;
- private readonly TravelBehavior travelBehavior;
+ private readonly IWorkBehavior workBehavior;
+ private readonly ITravelBehavior travelBehavior;
private readonly bool[] lightStates;
@@ -58,8 +58,8 @@ namespace RealTime.CustomAI
ITimeInfo timeInfo,
IBuildingManagerConnection buildingManager,
IToolManagerConnection toolManager,
- WorkBehavior workBehavior,
- TravelBehavior travelBehavior)
+ IWorkBehavior workBehavior,
+ ITravelBehavior travelBehavior)
{
this.config = config ?? throw new ArgumentNullException(nameof(config));
this.timeInfo = timeInfo ?? throw new ArgumentNullException(nameof(timeInfo));
diff --git a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
index d7e4d87..563e8ea 100644
--- a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
+++ b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs
@@ -28,8 +28,8 @@ namespace RealTime.CustomAI
///
/// A configuration to use with this custom logic.
/// An object providing the proxies that connect the method calls to the game methods.
- /// A reference an instance.
- protected RealTimeHumanAIBase(RealTimeConfig config, GameConnections connections, RealTimeEventManager eventManager)
+ /// A reference to an instance.
+ protected RealTimeHumanAIBase(RealTimeConfig config, GameConnections connections, IRealTimeEventManager eventManager)
{
Config = config ?? throw new ArgumentNullException(nameof(config));
EventMgr = eventManager ?? throw new ArgumentNullException(nameof(eventManager));
@@ -68,7 +68,7 @@ namespace RealTime.CustomAI
///
/// Gets a reference to the event manager.
///
- protected RealTimeEventManager EventMgr { get; }
+ protected IRealTimeEventManager EventMgr { get; }
///
/// Gets a reference to the proxy class that provides access to citizen's methods and fields.
diff --git a/src/RealTime/CustomAI/RealTimeResidentAI.cs b/src/RealTime/CustomAI/RealTimeResidentAI.cs
index 3f0ff03..f83167e 100644
--- a/src/RealTime/CustomAI/RealTimeResidentAI.cs
+++ b/src/RealTime/CustomAI/RealTimeResidentAI.cs
@@ -18,11 +18,13 @@ namespace RealTime.CustomAI
where TCitizen : struct
{
private readonly ResidentAIConnection residentAI;
- private readonly RealTimeBuildingAI buildingAI;
- private readonly WorkBehavior workBehavior;
- private readonly SpareTimeBehavior spareTimeBehavior;
- private readonly TravelBehavior travelBehavior;
+ private readonly IRealTimeBuildingAI buildingAI;
+ private readonly IWorkBehavior workBehavior;
+ private readonly ISpareTimeBehavior spareTimeBehavior;
+ private readonly ITravelBehavior travelBehavior;
+
private readonly CitizenSchedule[] residentSchedules;
+
private float simulationCycle;
/// Initializes a new instance of the class.
@@ -30,7 +32,7 @@ namespace RealTime.CustomAI
/// A instance containing the mod's configuration.
/// A instance that provides the game connection implementation.
/// A connection to the game's resident AI.
- /// A instance.
+ /// An instance.
/// The custom building AI.
/// A behavior that provides simulation info for the citizens work time.
/// A behavior that provides simulation info for the citizens spare time.
@@ -39,11 +41,11 @@ namespace RealTime.CustomAI
RealTimeConfig config,
GameConnections connections,
ResidentAIConnection residentAI,
- RealTimeEventManager eventManager,
- RealTimeBuildingAI buildingAI,
- WorkBehavior workBehavior,
- SpareTimeBehavior spareTimeBehavior,
- TravelBehavior travelBehavior)
+ IRealTimeEventManager eventManager,
+ IRealTimeBuildingAI buildingAI,
+ IWorkBehavior workBehavior,
+ ISpareTimeBehavior spareTimeBehavior,
+ ITravelBehavior travelBehavior)
: base(config, connections, eventManager)
{
this.residentAI = residentAI ?? throw new ArgumentNullException(nameof(residentAI));
@@ -135,7 +137,7 @@ namespace RealTime.CustomAI
/// Performs simulation for starting a new day for all citizens.
public void BeginNewDay()
{
- workBehavior.UpdateLunchTime();
+ workBehavior.BeginNewDay();
todayWakeup = TimeInfo.Now.Date.AddHours(Config.WakeUpHour);
}
diff --git a/src/RealTime/CustomAI/RealTimeTouristAI.cs b/src/RealTime/CustomAI/RealTimeTouristAI.cs
index a6e55af..fe3bfbc 100644
--- a/src/RealTime/CustomAI/RealTimeTouristAI.cs
+++ b/src/RealTime/CustomAI/RealTimeTouristAI.cs
@@ -22,7 +22,7 @@ namespace RealTime.CustomAI
where TCitizen : struct
{
private readonly TouristAIConnection touristAI;
- private readonly SpareTimeBehavior spareTimeBehavior;
+ private readonly ISpareTimeBehavior spareTimeBehavior;
///
/// Initializes a new instance of the class.
@@ -39,8 +39,8 @@ namespace RealTime.CustomAI
RealTimeConfig config,
GameConnections connections,
TouristAIConnection touristAI,
- RealTimeEventManager eventManager,
- SpareTimeBehavior spareTimeBehavior)
+ IRealTimeEventManager eventManager,
+ ISpareTimeBehavior spareTimeBehavior)
: base(config, connections, eventManager)
{
this.touristAI = touristAI ?? throw new ArgumentNullException(nameof(touristAI));
diff --git a/src/RealTime/CustomAI/SpareTimeBehavior.cs b/src/RealTime/CustomAI/SpareTimeBehavior.cs
index 5367f12..af9b3eb 100644
--- a/src/RealTime/CustomAI/SpareTimeBehavior.cs
+++ b/src/RealTime/CustomAI/SpareTimeBehavior.cs
@@ -13,7 +13,7 @@ namespace RealTime.CustomAI
///
/// A class that provides custom logic for the spare time simulation.
///
- internal sealed class SpareTimeBehavior
+ internal sealed class SpareTimeBehavior : ISpareTimeBehavior
{
private readonly RealTimeConfig config;
private readonly ITimeInfo timeInfo;
diff --git a/src/RealTime/CustomAI/TravelBehavior.cs b/src/RealTime/CustomAI/TravelBehavior.cs
index 4ca776c..779f6e2 100644
--- a/src/RealTime/CustomAI/TravelBehavior.cs
+++ b/src/RealTime/CustomAI/TravelBehavior.cs
@@ -8,7 +8,10 @@ namespace RealTime.CustomAI
using RealTime.Tools;
using static Constants;
- internal sealed class TravelBehavior
+ ///
+ /// A behavior for citizens traveling.
+ ///
+ internal sealed class TravelBehavior : ITravelBehavior
{
private readonly IBuildingManagerConnection buildingManager;
diff --git a/src/RealTime/CustomAI/WorkBehavior.cs b/src/RealTime/CustomAI/WorkBehavior.cs
index 5779607..ee28e3b 100644
--- a/src/RealTime/CustomAI/WorkBehavior.cs
+++ b/src/RealTime/CustomAI/WorkBehavior.cs
@@ -14,13 +14,13 @@ namespace RealTime.CustomAI
///
/// A class containing methods for managing the citizens' work behavior.
///
- internal sealed class WorkBehavior
+ internal sealed class WorkBehavior : IWorkBehavior
{
private readonly RealTimeConfig config;
private readonly IRandomizer randomizer;
private readonly IBuildingManagerConnection buildingManager;
private readonly ITimeInfo timeInfo;
- private readonly TravelBehavior travelBehavior;
+ private readonly ITravelBehavior travelBehavior;
private DateTime lunchBegin;
private DateTime lunchEnd;
@@ -37,7 +37,7 @@ namespace RealTime.CustomAI
IRandomizer randomizer,
IBuildingManagerConnection buildingManager,
ITimeInfo timeInfo,
- TravelBehavior travelBehavior)
+ ITravelBehavior travelBehavior)
{
this.config = config ?? throw new ArgumentNullException(nameof(config));
this.randomizer = randomizer ?? throw new ArgumentNullException(nameof(randomizer));
@@ -90,8 +90,8 @@ namespace RealTime.CustomAI
HasExtendedFirstWorkShift(service, subService));
}
- /// Updates the lunch time according to current date and configuration.
- public void UpdateLunchTime()
+ /// Notifies this object that a new game day starts.
+ public void BeginNewDay()
{
DateTime today = timeInfo.Now.Date;
lunchBegin = today.AddHours(config.LunchBegin);
diff --git a/src/RealTime/Events/IRealTimeEventManager.cs b/src/RealTime/Events/IRealTimeEventManager.cs
new file mode 100644
index 0000000..21506a8
--- /dev/null
+++ b/src/RealTime/Events/IRealTimeEventManager.cs
@@ -0,0 +1,36 @@
+//
+// Copyright (c) dymanoid. All rights reserved.
+//
+
+namespace RealTime.Events
+{
+ using System;
+
+ /// An interface for the customized city events manager.
+ internal interface IRealTimeEventManager
+ {
+ ///
+ /// 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.
+ ICityEvent GetCityEvent(ushort buildingId);
+
+ ///
+ /// Gets the instance of an upcoming city event whose start time is between the specified values.
+ ///
+ /// The earliest city event start time to consider.
+ /// The latest city event start time to consider.
+ /// An instance of the first matching city event, or null if none found.
+ ICityEvent GetUpcomingCityEvent(DateTime earliestStartTime, DateTime latestStartTime);
+
+ /// 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.
+ ///
+ /// The state of an event that meets the specified criteria, or if none found.
+ ///
+ CityEventState GetEventState(ushort buildingId, DateTime latestStart);
+ }
+}
\ No newline at end of file
diff --git a/src/RealTime/Events/RealTimeEventManager.cs b/src/RealTime/Events/RealTimeEventManager.cs
index 525c828..ea80085 100644
--- a/src/RealTime/Events/RealTimeEventManager.cs
+++ b/src/RealTime/Events/RealTimeEventManager.cs
@@ -16,7 +16,7 @@ namespace RealTime.Events
/// The central class for the custom city events logic.
///
- internal sealed class RealTimeEventManager : IStorageData
+ internal sealed class RealTimeEventManager : IStorageData, IRealTimeEventManager
{
private const int MaximumEventsCount = 5;
private const string StorageDataId = "RealTimeEvents";
diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj
index 27346c6..cb96a92 100644
--- a/src/RealTime/RealTime.csproj
+++ b/src/RealTime/RealTime.csproj
@@ -60,6 +60,10 @@
+
+
+
+
@@ -77,6 +81,7 @@
+
--
GitLab