Skip to content

Commit

Permalink
Merge branch 'feature-campus'
Browse files Browse the repository at this point in the history
  • Loading branch information
dymanoid committed May 19, 2019
2 parents 82ed4c5 + 85b25f0 commit 63f8be7
Show file tree
Hide file tree
Showing 38 changed files with 739 additions and 112 deletions.
2 changes: 1 addition & 1 deletion src/RealTime/Core/Compatibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public bool IsAnyModActive(ulong modId, params ulong[] furtherModIds)
private List<string> GetIncompatibleModNames()
{
var result = new List<string>();
foreach (ulong modId in IncompatibleModIds)
foreach (var modId in IncompatibleModIds)
{
try
{
Expand Down
4 changes: 4 additions & 0 deletions src/RealTime/Core/RealTimeCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ public static RealTimeCore Run(
{
WorldInfoPanelPatches.CitizenInfoPanel = CustomCitizenInfoPanel.Enable(ResidentAIPatch.RealTimeAI, localizationProvider);
WorldInfoPanelPatches.VehicleInfoPanel = CustomVehicleInfoPanel.Enable(ResidentAIPatch.RealTimeAI, localizationProvider);
WorldInfoPanelPatches.CampusWorldInfoPanel = CustomCampusWorldInfoPanel.Enable(localizationProvider);
}

AwakeSleepSimulation.Install(configProvider.Configuration);
Expand Down Expand Up @@ -266,6 +267,9 @@ public void Stop()
WorldInfoPanelPatches.VehicleInfoPanel?.Disable();
WorldInfoPanelPatches.VehicleInfoPanel = null;

WorldInfoPanelPatches.CampusWorldInfoPanel?.Disable();
WorldInfoPanelPatches.CampusWorldInfoPanel = null;

isEnabled = false;
}

Expand Down
10 changes: 5 additions & 5 deletions src/RealTime/CustomAI/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ internal static class Constants
/// <summary>The amount of hours the citizen will spend preparing to work and not going out.</summary>
public const float PrepareToWorkHours = 1f;

/// <summary>An assumed maximum travel time to a target building.</summary>
public const float MaxTravelTime = 2.5f;
/// <summary>An assumed maximum travel time to a target building (in hours).</summary>
public const float MaxTravelTime = 3.5f;

/// <summary>An assumed minimum travel to a target building.</summary>
public const float MinTravelTime = 0.25f;
/// <summary>An assumed minimum travel time to a target building (in hours).</summary>
public const float MinTravelTime = 0.5f;

/// <summary>An earliest hour when citizens wake up at home.</summary>
public const float EarliestWakeUp = 5.5f;
Expand All @@ -77,7 +77,7 @@ internal static class Constants
/// <summary>The average distance a citizen can move for (walking, by car, by public transport) during a full simulation
/// cycle at maximum time speed (6).
/// This value was determined empirically.</summary>
public const float AverageDistancePerSimulationCycle = 750f;
public const float AverageDistancePerSimulationCycle = 600f;

/// <summary>The maximum number of buildings (of one zone type) that are in construction or upgrading process.</summary>
public const int MaximumBuildingsInConstruction = 50;
Expand Down
37 changes: 36 additions & 1 deletion src/RealTime/CustomAI/RealTimeBuildingAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,13 +295,20 @@ public bool IsEntertainmentTarget(ushort buildingId)
return true;
}

// We override the building's active flag (e.g. at night), so a building still can post outgoing offers while inactive.
// A building still can post outgoing offers while inactive.
// This is to prevent those offers from being dispatched.
if (!buildingManager.BuildingHasFlags(buildingId, Building.Flags.Active))
{
return false;
}

var buildingService = buildingManager.GetBuildingService(buildingId);
if (buildingService == ItemClass.Service.VarsitySports)
{
// Do not visit varsity sport arenas for entertainment when no active events
return false;
}

string className = buildingManager.GetBuildingClassName(buildingId);
if (string.IsNullOrEmpty(className))
{
Expand All @@ -319,6 +326,32 @@ public bool IsEntertainmentTarget(ushort buildingId)
return true;
}

/// <summary>
/// Determines whether the building with the specified ID is a shopping target.
/// </summary>
/// <param name="buildingId">The building ID to check.</param>
/// <returns>
/// <c>true</c> if the building is a shopping target; otherwise, <c>false</c>.
/// </returns>
public bool IsShoppingTarget(ushort buildingId)
{
if (buildingId == 0)
{
return true;
}

// A building still can post outgoing offers while inactive.
// This is to prevent those offers from being dispatched.
if (!buildingManager.BuildingHasFlags(buildingId, Building.Flags.Active))
{
return false;
}

var buildingService = buildingManager.GetBuildingService(buildingId);
return buildingService != ItemClass.Service.VarsitySports
&& buildingManager.IsRealUniqueBuilding(buildingId);
}

/// <summary>Determines whether a building with specified ID is currently active.</summary>
/// <param name="buildingId">The ID of the building to check.</param>
/// <returns>
Expand Down Expand Up @@ -490,6 +523,8 @@ private bool ShouldSwitchBuildingLightsOff(ushort buildingId, ItemClass.Service
return false;

case ItemClass.Service.Monument:
case ItemClass.Service.VarsitySports:
case ItemClass.Service.Museums:
return false;

case ItemClass.Service.Beautification when subService == ItemClass.SubService.BeautificationParks:
Expand Down
2 changes: 1 addition & 1 deletion src/RealTime/CustomAI/RealTimeResidentAI.Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ private bool UpdateCitizenSchedule(ref CitizenSchedule schedule, uint citizenId,
#if DEBUG
if (nextActivityTime <= TimeInfo.Now)
{
Log.Debug(LogCategory.Schedule, $" - Schedule idle until next scheduling run");
Log.Debug(LogCategory.Schedule, " - Schedule idle until next scheduling run");
}
else
{
Expand Down
4 changes: 4 additions & 0 deletions src/RealTime/CustomAI/WorkBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ private static bool IsBuildingActiveOnWeekend(ItemClass.Service service, ItemCla
case ItemClass.Service.Monument:
case ItemClass.Service.Garbage:
case ItemClass.Service.Road:
case ItemClass.Service.Museums:
case ItemClass.Service.VarsitySports:
return true;

default:
Expand All @@ -273,6 +275,7 @@ private static int GetBuildingWorkShiftCount(ItemClass.Service service, ItemClas
{
case ItemClass.Service.Office:
case ItemClass.Service.Education:
case ItemClass.Service.PlayerEducation:
case ItemClass.Service.PlayerIndustry
when subService == ItemClass.SubService.PlayerIndustryForestry || subService == ItemClass.SubService.PlayerIndustryFarming:
case ItemClass.Service.Industrial
Expand Down Expand Up @@ -313,6 +316,7 @@ private static bool HasExtendedFirstWorkShift(ItemClass.Service service, ItemCla
case ItemClass.Service.Beautification:
case ItemClass.Service.Education:
case ItemClass.Service.PlayerIndustry:
case ItemClass.Service.PlayerEducation:
case ItemClass.Service.Industrial
when subService == ItemClass.SubService.IndustrialFarming || subService == ItemClass.SubService.IndustrialForestry:
return true;
Expand Down
46 changes: 33 additions & 13 deletions src/RealTime/Events/RealTimeEventManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ namespace RealTime.Events
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using RealTime.Config;
using RealTime.Events.Storage;
Expand All @@ -26,7 +25,7 @@ internal sealed class RealTimeEventManager : IStorageData, IRealTimeEventManager
private static readonly TimeSpan EventStartTimeGranularity = TimeSpan.FromMinutes(30);
private static readonly TimeSpan EventProcessInterval = TimeSpan.FromMinutes(15);

private static readonly ItemClass.Service[] EventBuildingServices = { ItemClass.Service.Monument, ItemClass.Service.Beautification };
private static readonly ItemClass.Service[] EventBuildingServices = { ItemClass.Service.Monument, ItemClass.Service.Beautification, ItemClass.Service.Museums };

private readonly LinkedList<ICityEvent> upcomingEvents;
private readonly RealTimeConfig config;
Expand All @@ -35,6 +34,8 @@ internal sealed class RealTimeEventManager : IStorageData, IRealTimeEventManager
private readonly IBuildingManagerConnection buildingManager;
private readonly IRandomizer randomizer;
private readonly ITimeInfo timeInfo;
private readonly List<ICityEvent> currentEvents;
private readonly IReadOnlyList<ICityEvent> readonlyCurrentEvents;

private ICityEvent lastActiveEvent;
private ICityEvent activeEvent;
Expand Down Expand Up @@ -70,30 +71,32 @@ public RealTimeEventManager(
this.randomizer = randomizer ?? throw new ArgumentNullException(nameof(randomizer));
this.timeInfo = timeInfo ?? throw new ArgumentNullException(nameof(timeInfo));
upcomingEvents = new LinkedList<ICityEvent>();
currentEvents = new List<ICityEvent>();
readonlyCurrentEvents = new ReadOnlyList<ICityEvent>(currentEvents);
}

/// <summary>Occurs when currently preparing, ready, ongoing, or recently finished events change.</summary>
public event EventHandler EventsChanged;

/// <summary>Gets the currently preparing, ready, ongoing, or recently finished city events.</summary>
public IEnumerable<ICityEvent> CityEvents
public IReadOnlyList<ICityEvent> CityEvents
{
get
{
currentEvents.Clear();

if (lastActiveEvent != null)
{
yield return lastActiveEvent;
currentEvents.Add(lastActiveEvent);
}

if (activeEvent != null)
{
yield return activeEvent;
currentEvents.Add(activeEvent);
}

foreach (var upcomingEvent in upcomingEvents)
{
yield return upcomingEvent;
}
upcomingEvents.CopyTo(currentEvents);
return readonlyCurrentEvents;
}
}

Expand Down Expand Up @@ -315,6 +318,21 @@ void AddEventToStorage(ICityEvent cityEvent)
}
}

private static ICityEvent GetVanillaEvent(IReadOnlyList<ICityEvent> events, ushort eventId, ushort buildingId)
{
for (int i = 0; i < events.Count; ++i)
{
if (events[i] is VanillaEvent vanillaEvent
&& vanillaEvent.EventId == eventId
&& vanillaEvent.BuildingId == buildingId)
{
return vanillaEvent;
}
}

return null;
}

private void Update()
{
if (activeEvent != null && activeEvent.EndTime <= timeInfo.Now)
Expand Down Expand Up @@ -355,8 +373,12 @@ private bool SynchronizeWithVanillaEvents()
bool result = false;

DateTime today = timeInfo.Now.Date;
foreach (ushort eventId in eventManager.GetUpcomingEvents(today, today.AddDays(1)))
var upcomingEventIds = eventManager.GetUpcomingEvents(today, today.AddDays(1));

for (int i = 0; i < upcomingEventIds.Count; ++i)
{
ushort eventId = upcomingEventIds[i];

if (!eventManager.TryGetEventInfo(eventId, out ushort buildingId, out DateTime startTime, out float duration, out float ticketPrice))
{
continue;
Expand All @@ -367,9 +389,7 @@ private bool SynchronizeWithVanillaEvents()
continue;
}

VanillaEvent existingVanillaEvent = CityEvents
.OfType<VanillaEvent>()
.FirstOrDefault(e => e.BuildingId == buildingId && e.EventId == eventId);
var existingVanillaEvent = GetVanillaEvent(CityEvents, eventId, buildingId);

if (existingVanillaEvent != null)
{
Expand Down
4 changes: 2 additions & 2 deletions src/RealTime/Events/Storage/CityEventsLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ private void LoadRushHourEvents(HashSet<string> loadedEvents)

try
{
foreach (string path in buildingPaths)
foreach (var path in buildingPaths)
{
string eventsPath = Path.Combine(path, RushHourEventsDirectoryName);
Log.Debug(LogCategory.Generic, $"Checking directory '{eventsPath}' for Rush Hour events...");
Expand Down Expand Up @@ -165,7 +165,7 @@ private void LoadEvents(IEnumerable<string> files, HashSet<string> loadedEvents)
{
var serializer = new XmlSerializer(typeof(CityEventContainer));

foreach (string file in files)
foreach (var file in files)
{
try
{
Expand Down
59 changes: 34 additions & 25 deletions src/RealTime/Events/VanillaEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static VanillaEvents Customize()
for (uint i = 0; i < PrefabCollection<EventInfo>.PrefabCount(); ++i)
{
EventInfo eventInfo = PrefabCollection<EventInfo>.GetPrefab(i);
if (eventInfo == null || eventInfo.m_eventAI == null)
if (eventInfo?.m_eventAI?.m_info == null)
{
continue;
}
Expand Down Expand Up @@ -90,35 +90,44 @@ public void Revert()

private static void Customize(EventAI eventAI)
{
switch (eventAI)
{
case null:
return;
float eventDuration;
float prepareDuration;
float disorganizeDuration;

case ConcertAI concertAI:
concertAI.m_eventDuration = 4f;
concertAI.m_prepareDuration = 4f;
concertAI.m_disorganizeDuration = 2f;
return;

case SportMatchAI matchAI:
matchAI.m_eventDuration = 2f;
matchAI.m_prepareDuration = 4f;
matchAI.m_disorganizeDuration = 2f;
return;

case RocketLaunchAI rocketLaunchAI:
rocketLaunchAI.m_eventDuration = 4f;
rocketLaunchAI.m_prepareDuration = 12f;
rocketLaunchAI.m_disorganizeDuration = 4f;
return;
switch (eventAI.m_info.m_type)
{
case EventManager.EventType.Concert:
eventDuration = 4f;
prepareDuration = 4f;
disorganizeDuration = 2f;
break;

case EventManager.EventType.VarsitySportsMatch:
case EventManager.EventType.Football:
eventDuration = 2f;
prepareDuration = 4f;
disorganizeDuration = 2f;
break;

case EventManager.EventType.RocketLaunch:
eventDuration = 4f;
prepareDuration = 12f;
disorganizeDuration = 4f;
break;

case EventManager.EventType.AcademicYear:
eventDuration = 7f * 24f;
prepareDuration = 2f;
disorganizeDuration = 2f;
break;

default:
eventAI.m_eventDuration = 2f;
eventAI.m_prepareDuration = 2f;
eventAI.m_disorganizeDuration = 2f;
return;
}

eventAI.m_eventDuration = eventDuration;
eventAI.m_prepareDuration = prepareDuration;
eventAI.m_disorganizeDuration = disorganizeDuration;
}

private readonly struct EventAIData
Expand Down
26 changes: 26 additions & 0 deletions src/RealTime/GameConnection/BuildingManagerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,32 @@ public DistrictPolicies.Park GetParkPolicies(byte parkId)
public bool IsAreaEvacuating(ushort buildingId)
=> buildingId != 0 && DisasterManager.instance.IsEvacuating(BuildingManager.instance.m_buildings.m_buffer[buildingId].m_position);

/// <summary>
/// Determines whether the building with specified ID is a real unique building (not a stadium, not a concert area).
/// </summary>
/// <param name="buildingId">The building ID to check.</param>
/// <returns>
/// <c>true</c> if the building with the specified ID is a real unique building; otherwise, <c>false</c>.
/// </returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("General", "RCS1130", Justification = "The EventType enum has no [Flags] attribute but has values of power of 2")]
public bool IsRealUniqueBuilding(ushort buildingId)
{
if (buildingId == 0)
{
return false;
}

var buildingInfo = BuildingManager.instance.m_buildings.m_buffer[buildingId].Info;
if (buildingInfo?.m_class?.m_service != ItemClass.Service.Monument)
{
return false;
}

var monumentAI = buildingInfo.m_buildingAI as MonumentAI;
return monumentAI != null
&& (monumentAI.m_supportEvents & (EventManager.EventType.Football | EventManager.EventType.Concert)) == 0;
}

private static bool BuildingCanBeVisited(ushort buildingId)
{
uint currentUnitId = BuildingManager.instance.m_buildings.m_buffer[buildingId].m_citizenUnits;
Expand Down
Loading

0 comments on commit 63f8be7

Please sign in to comment.