From b793f77cebbed4212f9824fd2ee19f44aa5e0019 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Tue, 26 Jun 2018 19:28:13 +0200 Subject: [PATCH 1/6] Simplify the flags checks for citizen, buildings, and citizen instances --- .../CustomResidentAI/RealTimeResidentAI.Common.cs | 6 +++--- .../CustomResidentAI/RealTimeResidentAI.Moving.cs | 9 +++------ .../CustomResidentAI/RealTimeResidentAI.Visit.cs | 6 +++--- src/RealTime/CustomTouristAI/RealTimeTouristAI.cs | 10 ++++------ src/RealTime/Events/RealTimeEventManager.cs | 2 +- .../GameConnection/BuildingManagerConnection.cs | 6 +++--- src/RealTime/GameConnection/CitizenConnection.cs | 4 ++-- .../GameConnection/CitizenManagerConnection.cs | 14 ++++++++++---- .../GameConnection/IBuildingManagerConnection.cs | 2 +- src/RealTime/GameConnection/ICitizenConnection.cs | 2 +- .../GameConnection/ICitizenManagerConnection.cs | 2 +- 11 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/RealTime/CustomResidentAI/RealTimeResidentAI.Common.cs b/src/RealTime/CustomResidentAI/RealTimeResidentAI.Common.cs index 0ad488df..91f36e0f 100644 --- a/src/RealTime/CustomResidentAI/RealTimeResidentAI.Common.cs +++ b/src/RealTime/CustomResidentAI/RealTimeResidentAI.Common.cs @@ -132,7 +132,7 @@ private ResidentState GetResidentState(ref TCitizen citizen) ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen); ItemClass.Service buildingService = BuildingMgr.GetBuildingService(currentBuilding); - if ((BuildingMgr.GetBuildingFlags(currentBuilding) & Building.Flags.Evacuating) != 0 + if (BuildingMgr.BuildingHasFlags(currentBuilding, Building.Flags.Evacuating) && buildingService != ItemClass.Service.Disaster) { return ResidentState.Evacuating; @@ -141,7 +141,7 @@ private ResidentState GetResidentState(ref TCitizen citizen) switch (CitizenProxy.GetLocation(ref citizen)) { case Citizen.Location.Home: - if ((CitizenProxy.GetFlags(ref citizen) & Citizen.Flags.MovingIn) != 0) + if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.MovingIn)) { return ResidentState.LeftCity; } @@ -154,7 +154,7 @@ private ResidentState GetResidentState(ref TCitizen citizen) return ResidentState.Unknown; case Citizen.Location.Work: - if (buildingService == ItemClass.Service.Disaster && CitizenProxy.GetFlags(ref citizen) == Citizen.Flags.Evacuating) + if (buildingService == ItemClass.Service.Disaster && CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating)) { return ResidentState.InShelter; } diff --git a/src/RealTime/CustomResidentAI/RealTimeResidentAI.Moving.cs b/src/RealTime/CustomResidentAI/RealTimeResidentAI.Moving.cs index 9e104f87..19cc6185 100644 --- a/src/RealTime/CustomResidentAI/RealTimeResidentAI.Moving.cs +++ b/src/RealTime/CustomResidentAI/RealTimeResidentAI.Moving.cs @@ -29,17 +29,14 @@ private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen cit return; } - if (vehicleId == 0 && CitizenMgr.IsAreaEvacuating(instanceId) && (CitizenProxy.GetFlags(ref citizen) & Citizen.Flags.Evacuating) == 0) + 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."); TransferMgr.AddOutgoingOfferFromCurrentPosition(citizenId, residentAI.GetEvacuationReason(instance, 0)); return; } - CitizenInstance.Flags instanceFlags = CitizenMgr.GetInstanceFlags(instanceId); - CitizenInstance.Flags onTourFlags = CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour; - - if ((instanceFlags & onTourFlags) == onTourFlags) + if (CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour, true)) { ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen); if (IsChance(AbandonTourChance) && homeBuilding != 0) @@ -48,7 +45,7 @@ private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen cit residentAI.StartMoving(instance, citizenId, ref citizen, 0, homeBuilding); } } - else if ((instanceFlags & (CitizenInstance.Flags.WaitingTransport | CitizenInstance.Flags.WaitingTaxi)) != 0) + else if (CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.WaitingTransport | CitizenInstance.Flags.WaitingTaxi)) { if (mayCancel && CitizenMgr.GetInstanceWaitCounter(instanceId) == 255 && IsChance(AbandonTransportWaitChance)) { diff --git a/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs index 2691f536..a199b26a 100644 --- a/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs +++ b/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs @@ -37,7 +37,7 @@ private void ProcessCitizenVisit(TAI instance, ResidentState citizenState, uint return; case ResidentState.Shopping: - if ((CitizenProxy.GetFlags(ref citizen) & Citizen.Flags.NeedGoods) != 0) + 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); @@ -66,7 +66,7 @@ private bool CitzenReturnsFromShelter(TAI instance, uint citizenId, ref TCitizen return true; } - if ((BuildingMgr.GetBuildingFlags(visitBuilding) & Building.Flags.Downgrading) == 0) + if (!BuildingMgr.BuildingHasFlags(visitBuilding, Building.Flags.Downgrading)) { return false; } @@ -127,7 +127,7 @@ private void ReturnFromVisit(TAI instance, uint citizenId, ref TCitizen citizen, private bool CitizenGoesShopping(TAI instance, uint citizenId, ref TCitizen citizen) { - if ((CitizenProxy.GetFlags(ref citizen) & Citizen.Flags.NeedGoods) == 0) + if (!CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods)) { return false; } diff --git a/src/RealTime/CustomTouristAI/RealTimeTouristAI.cs b/src/RealTime/CustomTouristAI/RealTimeTouristAI.cs index ce6b387b..5a987bde 100644 --- a/src/RealTime/CustomTouristAI/RealTimeTouristAI.cs +++ b/src/RealTime/CustomTouristAI/RealTimeTouristAI.cs @@ -72,15 +72,14 @@ private void ProcessMoving(TAI instance, uint citizenId, ref TCitizen citizen) return; } - if (vehicleId == 0 && CitizenMgr.IsAreaEvacuating(instanceId) && (CitizenProxy.GetFlags(ref citizen) & Citizen.Flags.Evacuating) == 0) + if (vehicleId == 0 && CitizenMgr.IsAreaEvacuating(instanceId) && !CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating)) { 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; } - CitizenInstance.Flags flags = CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour; - if ((CitizenMgr.GetInstanceFlags(instanceId) & flags) == flags) + if (CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour, true)) { FindRandomVisitPlace(instance, citizenId, ref citizen, TouristDoNothingProbability, 0); } @@ -95,8 +94,7 @@ private void ProcessVisit(TAI instance, uint citizenId, ref TCitizen citizen) return; } - Building.Flags buildingFlags = BuildingMgr.GetBuildingFlags(visitBuilding); - if ((buildingFlags & Building.Flags.Evacuating) != 0) + if (BuildingMgr.BuildingHasFlags(visitBuilding, Building.Flags.Evacuating)) { touristAI.FindEvacuationPlace(instance, citizenId, visitBuilding, touristAI.GetEvacuationReason(instance, visitBuilding)); return; @@ -105,7 +103,7 @@ private void ProcessVisit(TAI instance, uint citizenId, ref TCitizen citizen) switch (BuildingMgr.GetBuildingService(visitBuilding)) { case ItemClass.Service.Disaster: - if ((buildingFlags & Building.Flags.Downgrading) != 0) + if (BuildingMgr.BuildingHasFlags(visitBuilding, Building.Flags.Downgrading)) { FindRandomVisitPlace(instance, citizenId, ref citizen, 0, visitBuilding); } diff --git a/src/RealTime/Events/RealTimeEventManager.cs b/src/RealTime/Events/RealTimeEventManager.cs index 5edde16a..f977f807 100644 --- a/src/RealTime/Events/RealTimeEventManager.cs +++ b/src/RealTime/Events/RealTimeEventManager.cs @@ -159,7 +159,7 @@ public void ProcessEvents() } ushort building = buildingManager.GetRandomBuilding(EventBuildingServices); - if ((buildingManager.GetBuildingFlags(building) & Building.Flags.Active) == 0) + if (!buildingManager.BuildingHasFlags(building, Building.Flags.Active)) { return; } diff --git a/src/RealTime/GameConnection/BuildingManagerConnection.cs b/src/RealTime/GameConnection/BuildingManagerConnection.cs index c8c83657..22131623 100644 --- a/src/RealTime/GameConnection/BuildingManagerConnection.cs +++ b/src/RealTime/GameConnection/BuildingManagerConnection.cs @@ -25,11 +25,11 @@ public ItemClass.SubService GetBuildingSubService(ushort buildingId) : BuildingManager.instance.m_buildings.m_buffer[buildingId].Info.m_class.m_subService; } - public Building.Flags GetBuildingFlags(ushort buildingId) + public bool BuildingHasFlags(ushort buildingId, Building.Flags flags) { return buildingId == 0 - ? Building.Flags.None - : BuildingManager.instance.m_buildings.m_buffer[buildingId].m_flags; + ? false + : (BuildingManager.instance.m_buildings.m_buffer[buildingId].m_flags & flags) != 0; } public float GetDistanceBetweenBuildings(ushort building1, ushort building2) diff --git a/src/RealTime/GameConnection/CitizenConnection.cs b/src/RealTime/GameConnection/CitizenConnection.cs index 85930dbb..61b162db 100644 --- a/src/RealTime/GameConnection/CitizenConnection.cs +++ b/src/RealTime/GameConnection/CitizenConnection.cs @@ -48,9 +48,9 @@ public ushort GetCurrentBuilding(ref Citizen citizen) return citizen.GetBuildingByLocation(); } - public Citizen.Flags GetFlags(ref Citizen citizen) + public bool HasFlags(ref Citizen citizen, Citizen.Flags flags) { - return citizen.m_flags; + return (citizen.m_flags & flags) != 0; } public ushort GetHomeBuilding(ref Citizen citizen) diff --git a/src/RealTime/GameConnection/CitizenManagerConnection.cs b/src/RealTime/GameConnection/CitizenManagerConnection.cs index f805ca5c..5cdc02a7 100644 --- a/src/RealTime/GameConnection/CitizenManagerConnection.cs +++ b/src/RealTime/GameConnection/CitizenManagerConnection.cs @@ -26,11 +26,17 @@ public ushort GetTargetBuilding(ushort instanceId) : (ushort)0; } - public CitizenInstance.Flags GetInstanceFlags(ushort instanceId) + public bool InstanceHasFlags(ushort instanceId, CitizenInstance.Flags flags, bool exact = false) { - return instanceId == 0 - ? CitizenInstance.Flags.None - : CitizenManager.instance.m_instances.m_buffer[instanceId].m_flags; + if (instanceId == 0) + { + return false; + } + + CitizenInstance.Flags currentFlags = CitizenManager.instance.m_instances.m_buffer[instanceId].m_flags; + return exact + ? currentFlags == flags + : currentFlags != 0; } public byte GetInstanceWaitCounter(ushort instanceId) diff --git a/src/RealTime/GameConnection/IBuildingManagerConnection.cs b/src/RealTime/GameConnection/IBuildingManagerConnection.cs index 8ac4e541..11257a27 100644 --- a/src/RealTime/GameConnection/IBuildingManagerConnection.cs +++ b/src/RealTime/GameConnection/IBuildingManagerConnection.cs @@ -13,7 +13,7 @@ internal interface IBuildingManagerConnection ItemClass.SubService GetBuildingSubService(ushort buildingId); - Building.Flags GetBuildingFlags(ushort buildingId); + bool BuildingHasFlags(ushort buildingId, Building.Flags flags); float GetDistanceBetweenBuildings(ushort building1, ushort building2); diff --git a/src/RealTime/GameConnection/ICitizenConnection.cs b/src/RealTime/GameConnection/ICitizenConnection.cs index 9626195d..39b26ff0 100644 --- a/src/RealTime/GameConnection/ICitizenConnection.cs +++ b/src/RealTime/GameConnection/ICitizenConnection.cs @@ -47,7 +47,7 @@ internal interface ICitizenConnection Citizen.Wellbeing GetWellbeingLevel(ref T citizen); - Citizen.Flags GetFlags(ref T citizen); + bool HasFlags(ref T citizen, Citizen.Flags flags); void SetHome(ref T citizen, uint citizenId, ushort buildingId); diff --git a/src/RealTime/GameConnection/ICitizenManagerConnection.cs b/src/RealTime/GameConnection/ICitizenManagerConnection.cs index e470aa07..59c24c88 100644 --- a/src/RealTime/GameConnection/ICitizenManagerConnection.cs +++ b/src/RealTime/GameConnection/ICitizenManagerConnection.cs @@ -10,7 +10,7 @@ internal interface ICitizenManagerConnection ushort GetTargetBuilding(ushort instanceId); - CitizenInstance.Flags GetInstanceFlags(ushort instanceId); + bool InstanceHasFlags(ushort instanceId, CitizenInstance.Flags flags, bool exact = false); byte GetInstanceWaitCounter(ushort instanceId); From ba6a3482442887e67ba2f84629346f52501d2cd1 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Tue, 26 Jun 2018 20:09:05 +0200 Subject: [PATCH 2/6] Fix the casing in the CityEventState enum --- src/RealTime/CustomAI/RealTimeHumanAIBase.cs | 2 +- src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs | 2 +- src/RealTime/CustomTouristAI/RealTimeTouristAI.cs | 2 +- src/RealTime/Events/CityEventState.cs | 2 +- src/RealTime/Events/RealTimeEventManager.cs | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs index bbbcbf83..4814d394 100644 --- a/src/RealTime/CustomAI/RealTimeHumanAIBase.cs +++ b/src/RealTime/CustomAI/RealTimeHumanAIBase.cs @@ -188,7 +188,7 @@ protected bool AttendUpcomingEvent(uint citizenId, ref TCitizen citizen, out ush eventBuildingId = default; ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen); - if (EventMgr.GetEventState(currentBuilding, DateTime.MaxValue) == CityEventState.OnGoing) + if (EventMgr.GetEventState(currentBuilding, DateTime.MaxValue) == CityEventState.Ongoing) { return false; } diff --git a/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs index a199b26a..82a39840 100644 --- a/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs +++ b/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs @@ -96,7 +96,7 @@ private bool CitizenReturnsHomeFromVisit(TAI instance, uint citizenId, ref TCiti switch (EventMgr.GetEventState(visitBuilding, TimeInfo.Now.AddHours(MaxHoursOnTheWay))) { case CityEventState.Upcoming: - case CityEventState.OnGoing: + case CityEventState.Ongoing: return false; case CityEventState.Finished: diff --git a/src/RealTime/CustomTouristAI/RealTimeTouristAI.cs b/src/RealTime/CustomTouristAI/RealTimeTouristAI.cs index 5a987bde..7dac0733 100644 --- a/src/RealTime/CustomTouristAI/RealTimeTouristAI.cs +++ b/src/RealTime/CustomTouristAI/RealTimeTouristAI.cs @@ -127,7 +127,7 @@ private void ProcessVisit(TAI instance, uint citizenId, ref TCitizen citizen) bool doShopping; switch (EventMgr.GetEventState(visitBuilding, DateTime.MaxValue)) { - case CityEventState.OnGoing: + case CityEventState.Ongoing: doShopping = IsChance(TouristShoppingChance); break; diff --git a/src/RealTime/Events/CityEventState.cs b/src/RealTime/Events/CityEventState.cs index fa7b5856..a8763c43 100644 --- a/src/RealTime/Events/CityEventState.cs +++ b/src/RealTime/Events/CityEventState.cs @@ -8,7 +8,7 @@ internal enum CityEventState { None, Upcoming, - OnGoing, + Ongoing, Finished } } diff --git a/src/RealTime/Events/RealTimeEventManager.cs b/src/RealTime/Events/RealTimeEventManager.cs index f977f807..c1ea8643 100644 --- a/src/RealTime/Events/RealTimeEventManager.cs +++ b/src/RealTime/Events/RealTimeEventManager.cs @@ -105,7 +105,7 @@ public CityEventState GetEventState(ushort buildingId, DateTime latestStart) } else if ((vanillaEventState & EventData.Flags.Active) != 0) { - return CityEventState.OnGoing; + return CityEventState.Ongoing; } else if (vanillaEventState != EventData.Flags.None) { @@ -115,7 +115,7 @@ public CityEventState GetEventState(ushort buildingId, DateTime latestStart) if (activeEvent != null && activeEvent.BuildingId == buildingId) { - return CityEventState.OnGoing; + return CityEventState.Ongoing; } else if (lastActiveEvent != null && lastActiveEvent.BuildingId == buildingId) { From a49a5cd092f24be87992ef56c4ceaf253f0dbb8f Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Tue, 26 Jun 2018 20:11:13 +0200 Subject: [PATCH 3/6] Join all simulation handlers into one root SimulationHandler --- src/RealTime/Core/RealTimeCore.cs | 11 ++- .../Events/RealTimeEventSimulation.cs | 18 ----- src/RealTime/RealTime.csproj | 6 +- .../{DayTime.cs => DayTimeCalculator.cs} | 44 ++++------- src/RealTime/Simulation/DayTimeSimulation.cs | 30 ++++++++ .../Simulation/DaylightTimeSimulation.cs | 73 ------------------- src/RealTime/Simulation/SimulationHandler.cs | 48 ++++++++++++ 7 files changed, 102 insertions(+), 128 deletions(-) delete mode 100644 src/RealTime/Events/RealTimeEventSimulation.cs rename src/RealTime/Simulation/{DayTime.cs => DayTimeCalculator.cs} (59%) create mode 100644 src/RealTime/Simulation/DayTimeSimulation.cs delete mode 100644 src/RealTime/Simulation/DaylightTimeSimulation.cs create mode 100644 src/RealTime/Simulation/SimulationHandler.cs diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs index fe85730c..d7a7cf36 100644 --- a/src/RealTime/Core/RealTimeCore.cs +++ b/src/RealTime/Core/RealTimeCore.cs @@ -82,6 +82,8 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, Localizat var timeAdjustment = new TimeAdjustment(); DateTime gameDate = timeAdjustment.Enable(); + SimulationHandler.DayTimeSimulation = new DayTimeSimulation(); + var timeInfo = new TimeInfo(); var buildingManager = new BuildingManagerConnection(); var simulationManager = new SimulationManagerConnection(); @@ -104,7 +106,7 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, Localizat SetupCustomAI(timeInfo, config, gameConnections, eventManager); - RealTimeEventSimulation.EventManager = eventManager; + SimulationHandler.EventManager = eventManager; CityEventsLoader.Istance.ReloadEvents(rootPath); var customTimeBar = new CustomTimeBar(); @@ -113,7 +115,7 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, Localizat var result = new RealTimeCore(timeAdjustment, customTimeBar, eventManager); eventManager.EventsChanged += result.CityEventsChanged; - DaylightTimeSimulation.NewDay += result.CityEventsChanged; + SimulationHandler.NewDay += result.CityEventsChanged; RealTimeStorage.Instance.GameSaving += result.GameSaving; result.storageData.Add(eventManager); @@ -139,7 +141,7 @@ public void Stop() timeBar.CityEventClick -= CustomTimeBarCityEventClick; timeBar.Disable(); eventManager.EventsChanged -= CityEventsChanged; - DaylightTimeSimulation.NewDay -= CityEventsChanged; + SimulationHandler.NewDay -= CityEventsChanged; CityEventsLoader.Istance.Clear(); @@ -148,7 +150,8 @@ public void Stop() ResidentAIHook.RealTimeAI = null; TouristAIHook.RealTimeAI = null; PrivateBuildingAIHook.RealTimeAI = null; - RealTimeEventSimulation.EventManager = null; + SimulationHandler.EventManager = null; + SimulationHandler.DayTimeSimulation = null; try { diff --git a/src/RealTime/Events/RealTimeEventSimulation.cs b/src/RealTime/Events/RealTimeEventSimulation.cs deleted file mode 100644 index 3a2650f3..00000000 --- a/src/RealTime/Events/RealTimeEventSimulation.cs +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) dymanoid. All rights reserved. -// - -namespace RealTime.Events -{ - using ICities; - - public sealed class RealTimeEventSimulation : ThreadingExtensionBase - { - internal static RealTimeEventManager EventManager { get; set; } - - public override void OnAfterSimulationTick() - { - EventManager?.ProcessEvents(); - } - } -} diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj index 2ab71189..dca21d89 100644 --- a/src/RealTime/RealTime.csproj +++ b/src/RealTime/RealTime.csproj @@ -69,7 +69,6 @@ - @@ -109,8 +108,9 @@ - - + + + diff --git a/src/RealTime/Simulation/DayTime.cs b/src/RealTime/Simulation/DayTimeCalculator.cs similarity index 59% rename from src/RealTime/Simulation/DayTime.cs rename to src/RealTime/Simulation/DayTimeCalculator.cs index 045e2dab..b8ae3eed 100644 --- a/src/RealTime/Simulation/DayTime.cs +++ b/src/RealTime/Simulation/DayTimeCalculator.cs @@ -1,43 +1,37 @@ -// +// // Copyright (c) dymanoid. All rights reserved. // namespace RealTime.Simulation { using System; - using UnityEngine; /// /// Calculates the sunrise and sunset time based on the map latitude and current date. /// - internal sealed class DayTime + internal sealed class DayTimeCalculator { - private float phase; - private float halfAmplitude; + private readonly float phase; + private readonly float halfAmplitude; /// - /// Gets a value indicating whether this object has been properly set up - /// and can be used for the calculations. - /// - public bool IsReady { get; private set; } - - /// - /// Sets up this object so that it can correctly perform the sunrise and sunset time - /// calculations. + /// Initializes a new instance of the class. /// /// /// The latitude coordinate to assume for the city. - /// Valid values are -80° to 80°. - public void Setup(float latitude) + /// Valid values are -80° to 80°. + public DayTimeCalculator(float latitude) { bool southSemisphere = latitude < 0; - latitude = Mathf.Clamp(Mathf.Abs(latitude), 0f, 80f); - halfAmplitude = (0.5f + (latitude / 15f)) / 2f; + latitude = Math.Abs(latitude); + if (latitude > 80f) + { + latitude = 80f; + } + halfAmplitude = (0.5f + (latitude / 15f)) / 2f; phase = southSemisphere ? 0f : (float)Math.PI; - - IsReady = true; } /// @@ -49,21 +43,11 @@ public void Setup(float latitude) /// The game date to calculate the sunrise and sunset times for. /// The calculated sunrise hour (relative to the midnight). /// The calculated sunset hour (relative to the midnight). - /// - /// True when the values are successfully calculated; otherwise, false. - public bool Calculate(DateTime date, out float sunriseHour, out float sunsetHour) + public void Calculate(DateTime date, out float sunriseHour, out float sunsetHour) { - if (!IsReady) - { - sunriseHour = default; - sunsetHour = default; - return false; - } - float modifier = (float)Math.Cos((2 * Math.PI * (date.DayOfYear + 10) / 365.25) + phase); sunriseHour = 6f - (halfAmplitude * modifier); sunsetHour = 18f + (halfAmplitude * modifier); - return true; } } } diff --git a/src/RealTime/Simulation/DayTimeSimulation.cs b/src/RealTime/Simulation/DayTimeSimulation.cs new file mode 100644 index 00000000..7e9b1400 --- /dev/null +++ b/src/RealTime/Simulation/DayTimeSimulation.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Simulation +{ + using System; + + internal sealed class DayTimeSimulation + { + private DayTimeCalculator calculator; + + public void Process(DateTime date) + { + if (calculator == null && DayNightProperties.instance != null) + { + calculator = new DayTimeCalculator(DayNightProperties.instance.m_Latitude); + } + + if (calculator == null) + { + return; + } + + calculator.Calculate(date, out float sunriseHour, out float sunsetHour); + SimulationManager.SUNRISE_HOUR = sunriseHour; + SimulationManager.SUNSET_HOUR = sunsetHour; + } + } +} diff --git a/src/RealTime/Simulation/DaylightTimeSimulation.cs b/src/RealTime/Simulation/DaylightTimeSimulation.cs deleted file mode 100644 index 84e477fe..00000000 --- a/src/RealTime/Simulation/DaylightTimeSimulation.cs +++ /dev/null @@ -1,73 +0,0 @@ -// -// Copyright (c) dymanoid. All rights reserved. -// - -namespace RealTime.Simulation -{ - using System; - using ICities; - - /// - /// A simulation extension that manages the daytime calculation (sunrise and sunset). - /// - public sealed class DaylightTimeSimulation : ThreadingExtensionBase - { - private readonly DayTime dayTime; - private DateTime lastCalculation; - - /// - /// Initializes a new instance of the class. - /// - public DaylightTimeSimulation() - { - dayTime = new DayTime(); - } - - /// - /// Occurs whent a new day in the game begins. - /// - internal static event EventHandler NewDay; - - /// - /// Called after each game simulation tick. Performs the actual work. - /// - public override void OnAfterSimulationTick() - { - if (!dayTime.IsReady) - { - if (DayNightProperties.instance != null) - { - dayTime.Setup(DayNightProperties.instance.m_Latitude); - } - else - { - return; - } - } - - DateTime currentDate = SimulationManager.instance.m_currentGameTime.Date; - if (currentDate != lastCalculation) - { - CalculateDaylight(currentDate); - OnNewDay(this); - } - } - - private static void OnNewDay(DaylightTimeSimulation sender) - { - NewDay?.Invoke(sender, EventArgs.Empty); - } - - private void CalculateDaylight(DateTime date) - { - if (!dayTime.Calculate(date, out float sunriseHour, out float sunsetHour)) - { - return; - } - - SimulationManager.SUNRISE_HOUR = sunriseHour; - SimulationManager.SUNSET_HOUR = sunsetHour; - lastCalculation = date.Date; - } - } -} diff --git a/src/RealTime/Simulation/SimulationHandler.cs b/src/RealTime/Simulation/SimulationHandler.cs new file mode 100644 index 00000000..a89c87bc --- /dev/null +++ b/src/RealTime/Simulation/SimulationHandler.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.Simulation +{ + using System; + using ICities; + using RealTime.Events; + + /// + /// A simulation extension that manages the daytime calculation (sunrise and sunset). + /// + public sealed class SimulationHandler : ThreadingExtensionBase + { + private DateTime lastHandledDate; + + /// + /// Occurs whent a new day in the game begins. + /// + internal static event EventHandler NewDay; + + internal static RealTimeEventManager EventManager { get; set; } + + internal static DayTimeSimulation DayTimeSimulation { get; set; } + + /// + /// Called after each game simulation tick. Performs the actual work. + /// + public override void OnAfterSimulationTick() + { + EventManager?.ProcessEvents(); + + DateTime currentDate = SimulationManager.instance.m_currentGameTime.Date; + if (currentDate != lastHandledDate) + { + lastHandledDate = currentDate; + DayTimeSimulation?.Process(currentDate); + OnNewDay(this); + } + } + + private static void OnNewDay(SimulationHandler sender) + { + NewDay?.Invoke(sender, EventArgs.Empty); + } + } +} From bf3796b4c9ccafcf252e5e2ec15d32adfaa8e2e4 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Tue, 26 Jun 2018 21:52:10 +0200 Subject: [PATCH 4/6] Pause the commercial building 'no customers' problem timer at night --- src/RealTime/BuildingAI/CommercialAI.cs | 38 +++++++++++++++++++ src/RealTime/Core/RealTimeCore.cs | 9 +++-- .../BuildingManagerConnection.cs | 23 ++++++++++- .../IBuildingManagerConnection.cs | 2 + src/RealTime/RealTime.csproj | 1 + src/RealTime/Simulation/SimulationHandler.cs | 8 ++++ 6 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 src/RealTime/BuildingAI/CommercialAI.cs diff --git a/src/RealTime/BuildingAI/CommercialAI.cs b/src/RealTime/BuildingAI/CommercialAI.cs new file mode 100644 index 00000000..c7b0d44e --- /dev/null +++ b/src/RealTime/BuildingAI/CommercialAI.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) dymanoid. All rights reserved. +// + +namespace RealTime.BuildingAI +{ + using RealTime.GameConnection; + using RealTime.Simulation; + + internal sealed class CommercialAI + { + private readonly ITimeInfo timeInfo; + + public CommercialAI(ITimeInfo timeInfo, IBuildingManagerConnection buildingManager) + { + this.timeInfo = timeInfo ?? throw new System.ArgumentNullException(nameof(timeInfo)); + BuildingMgr = buildingManager ?? throw new System.ArgumentNullException(nameof(buildingManager)); + } + + public IBuildingManagerConnection BuildingMgr { get; } + + public void Process(uint frameId) + { + frameId &= 0xFF; + + uint buildingFrom = frameId * 192u; + uint buildingTo = ((frameId + 1u) * 192u) - 1u; + + // We have only few customers at night - that's an inteded behavior. + // To avoid commercial building from collapsing due to lack of customers, + // we force the problem timer to pause at night time. + if (timeInfo.IsNightTime) + { + BuildingMgr.DecrementOutgoingProblemTimer((ushort)buildingFrom, (ushort)buildingTo, ItemClass.Service.Commercial); + } + } + } +} diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs index d7a7cf36..524ea007 100644 --- a/src/RealTime/Core/RealTimeCore.cs +++ b/src/RealTime/Core/RealTimeCore.cs @@ -7,6 +7,7 @@ namespace RealTime.Core using System; using System.Collections.Generic; using System.Security.Permissions; + using RealTime.BuildingAI; using RealTime.Config; using RealTime.CustomAI; using RealTime.Events; @@ -82,8 +83,6 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, Localizat var timeAdjustment = new TimeAdjustment(); DateTime gameDate = timeAdjustment.Enable(); - SimulationHandler.DayTimeSimulation = new DayTimeSimulation(); - var timeInfo = new TimeInfo(); var buildingManager = new BuildingManagerConnection(); var simulationManager = new SimulationManagerConnection(); @@ -106,7 +105,6 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, Localizat SetupCustomAI(timeInfo, config, gameConnections, eventManager); - SimulationHandler.EventManager = eventManager; CityEventsLoader.Istance.ReloadEvents(rootPath); var customTimeBar = new CustomTimeBar(); @@ -117,6 +115,10 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, Localizat eventManager.EventsChanged += result.CityEventsChanged; SimulationHandler.NewDay += result.CityEventsChanged; + SimulationHandler.DayTimeSimulation = new DayTimeSimulation(); + SimulationHandler.EventManager = eventManager; + SimulationHandler.CommercialAI = new CommercialAI(timeInfo, buildingManager); + RealTimeStorage.Instance.GameSaving += result.GameSaving; result.storageData.Add(eventManager); result.LoadStorageData(); @@ -152,6 +154,7 @@ public void Stop() PrivateBuildingAIHook.RealTimeAI = null; SimulationHandler.EventManager = null; SimulationHandler.DayTimeSimulation = null; + SimulationHandler.CommercialAI = null; try { diff --git a/src/RealTime/GameConnection/BuildingManagerConnection.cs b/src/RealTime/GameConnection/BuildingManagerConnection.cs index 22131623..0824220c 100644 --- a/src/RealTime/GameConnection/BuildingManagerConnection.cs +++ b/src/RealTime/GameConnection/BuildingManagerConnection.cs @@ -4,7 +4,6 @@ namespace RealTime.GameConnection { - using System; using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -134,6 +133,28 @@ public ushort GetRandomBuilding(IEnumerable services) return 0; } + public void DecrementOutgoingProblemTimer(ushort buildingIdFrom, ushort buildingIdTo, ItemClass.Service service) + { + if (service == ItemClass.Service.None) + { + return; + } + + for (ushort buildingId = buildingIdFrom; buildingId <= buildingIdTo; buildingId++) + { + ref Building building = ref BuildingManager.instance.m_buildings.m_buffer[buildingId]; + if ((building.m_flags & Building.Flags.Created) == 0 || building.Info.m_class.m_service != service) + { + continue; + } + + if (building.m_outgoingProblemTimer > 0) + { + building.m_outgoingProblemTimer -= 1; + } + } + } + public string GetBuildingClassName(ushort buildingId) { return buildingId == 0 diff --git a/src/RealTime/GameConnection/IBuildingManagerConnection.cs b/src/RealTime/GameConnection/IBuildingManagerConnection.cs index 11257a27..75552da2 100644 --- a/src/RealTime/GameConnection/IBuildingManagerConnection.cs +++ b/src/RealTime/GameConnection/IBuildingManagerConnection.cs @@ -43,6 +43,8 @@ ushort FindActiveBuilding( /// don't call it on every simulation step. ushort GetRandomBuilding(IEnumerable services); + void DecrementOutgoingProblemTimer(ushort buildingIdFrom, ushort buildingIdTo, ItemClass.Service service); + string GetBuildingClassName(ushort buildingId); string GetBuildingName(ushort buildingId); diff --git a/src/RealTime/RealTime.csproj b/src/RealTime/RealTime.csproj index dca21d89..cb68874c 100644 --- a/src/RealTime/RealTime.csproj +++ b/src/RealTime/RealTime.csproj @@ -53,6 +53,7 @@ + diff --git a/src/RealTime/Simulation/SimulationHandler.cs b/src/RealTime/Simulation/SimulationHandler.cs index a89c87bc..f333908d 100644 --- a/src/RealTime/Simulation/SimulationHandler.cs +++ b/src/RealTime/Simulation/SimulationHandler.cs @@ -6,6 +6,7 @@ namespace RealTime.Simulation { using System; using ICities; + using RealTime.BuildingAI; using RealTime.Events; /// @@ -24,6 +25,8 @@ public sealed class SimulationHandler : ThreadingExtensionBase internal static DayTimeSimulation DayTimeSimulation { get; set; } + internal static CommercialAI CommercialAI { get; set; } + /// /// Called after each game simulation tick. Performs the actual work. /// @@ -40,6 +43,11 @@ public override void OnAfterSimulationTick() } } + public override void OnAfterSimulationFrame() + { + CommercialAI?.Process(SimulationManager.instance.m_currentFrameIndex); + } + private static void OnNewDay(SimulationHandler sender) { NewDay?.Invoke(sender, EventArgs.Empty); From fa3e8ec55504495e92473998c1c6c1b1630736e8 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Tue, 26 Jun 2018 21:56:28 +0200 Subject: [PATCH 5/6] Allow the citizens to buy goods in at leisure buildings --- .../CustomResidentAI/RealTimeResidentAI.Visit.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs index 82a39840..097abde4 100644 --- a/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs +++ b/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs @@ -13,7 +13,8 @@ internal sealed partial class RealTimeResidentAI { private void ProcessCitizenVisit(TAI instance, ResidentState citizenState, uint citizenId, ref TCitizen citizen) { - if (CitizenProxy.GetVisitBuilding(ref citizen) == 0) + 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); @@ -28,6 +29,15 @@ private void ProcessCitizenVisit(TAI instance, ResidentState citizenState, uint 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)) { From bdc33fb9b4638677d9481b9a2c6b980e33ba3bf8 Mon Sep 17 00:00:00 2001 From: Dmitry Balabanov Date: Tue, 26 Jun 2018 22:03:16 +0200 Subject: [PATCH 6/6] Reduce the leisure buildings search distance --- src/RealTime/CustomAI/Constants.cs | 3 ++- src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/RealTime/CustomAI/Constants.cs b/src/RealTime/CustomAI/Constants.cs index 43ce10a4..89f5e9ac 100644 --- a/src/RealTime/CustomAI/Constants.cs +++ b/src/RealTime/CustomAI/Constants.cs @@ -8,7 +8,8 @@ internal static class Constants { public const int ShoppingGoodsAmount = 100; - public const float LocalSearchDistance = 500f; + public const float LocalSearchDistance = 270f * 2; + public const float LeisureSearchDistance = 270f * 3; public const float FullSearchDistance = 270f * 64f / 2f; public const uint AbandonTourChance = 25; diff --git a/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs b/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs index 097abde4..c86526e5 100644 --- a/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs +++ b/src/RealTime/CustomResidentAI/RealTimeResidentAI.Visit.cs @@ -252,7 +252,7 @@ private ushort MoveToLeisure(TAI instance, uint citizenId, ref TCitizen citizen, { ushort leisureBuilding = BuildingMgr.FindActiveBuilding( buildingId, - FullSearchDistance, + LeisureSearchDistance, ItemClass.Service.Commercial, ItemClass.SubService.CommercialLeisure);