/// <summary>Determines whether the specified citizen must be processed as a virtual citizen.</summary> /// <typeparam name="TAI">The type of the citizen's AI.</typeparam> /// <param name="humanAI">The citizen AI reference.</param> /// <param name="citizen">The citizen to check.</param> /// <param name="realizeCitizen">A callback to determine whether a virtual citizen should be realized.</param> /// <returns><c>true</c> if the citizen must be processed as a virtual citizen; otherwise, <c>false</c>.</returns> protected bool IsCitizenVirtual <TAI>(TAI humanAI, ref TCitizen citizen, Func <TAI, bool> realizeCitizen) { if (CitizenProxy.GetInstance(ref citizen) != 0) { return(false); } uint virtualChance; switch (Config.VirtualCitizens) { case VirtualCitizensLevel.None: return(false); case VirtualCitizensLevel.Few: virtualChance = FewVirtualCitizensChance; break; case VirtualCitizensLevel.Vanilla: return(!realizeCitizen(humanAI)); case VirtualCitizensLevel.Many: virtualChance = ManyVirtualCitizensChance; break; default: return(false); } return(CitizenMgr.GetInstancesCount() * 100 / CitizenInstancesMaxCount < virtualChance ? !realizeCitizen(humanAI) : Random.ShouldOccur(virtualChance)); }
private void ProcessMoving(TAI instance, uint citizenId, ref TCitizen citizen) { ushort instanceId = CitizenProxy.GetInstance(ref citizen); ushort vehicleId = CitizenProxy.GetVehicle(ref citizen); if (instanceId == 0) { if (vehicleId == 0) { CitizenMgr.ReleaseCitizen(citizenId); } return; } 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; } if (CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour, true)) { FindRandomVisitPlace(instance, citizenId, ref citizen, TouristDoNothingProbability, 0); } }
/// <summary>Determines whether the specified citizen must be processed as a virtual citizen.</summary> /// <typeparam name="TAI">The type of the citizen's AI.</typeparam> /// <param name="humanAI">The citizen AI reference.</param> /// <param name="citizen">The citizen to check.</param> /// <param name="realizeCitizen">A callback to determine whether a virtual citizen should be realized.</param> /// <returns><c>true</c> if the citizen must be processed as a virtual citizen; otherwise, <c>false</c>.</returns> protected bool IsCitizenVirtual <TAI>(TAI humanAI, ref TCitizen citizen, Func <TAI, bool> realizeCitizen) { uint virtualChance; switch (Config.VirtualCitizens) { case VirtualCitizensLevel.None: return(false); case VirtualCitizensLevel.Few: virtualChance = FewVirtualCitizensChance; break; case VirtualCitizensLevel.Vanilla: return(CitizenProxy.GetInstance(ref citizen) == 0 && !realizeCitizen(humanAI)); case VirtualCitizensLevel.Many: virtualChance = ManyVirtualCitizensChance; break; default: return(false); } return(!Random.ShouldOccur(virtualChance)); }
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); if (vehicleId == 0 && instanceId == 0) { if (CitizenProxy.GetVisitBuilding(ref citizen) != 0) { CitizenProxy.SetVisitPlace(ref citizen, citizenId, 0); } if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.MovingIn)) { CitizenMgr.ReleaseCitizen(citizenId); schedule = default; } else { CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home); CitizenProxy.SetArrested(ref citizen, false); schedule.Schedule(ResidentState.Unknown); } return(true); } if (vehicleId == 0 && CitizenMgr.IsAreaEvacuating(instanceId) && !CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating)) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} was on the way, but the area evacuates. Finding an evacuation place."); schedule.Schedule(ResidentState.Unknown); TransferMgr.AddOutgoingOfferFromCurrentPosition(citizenId, residentAI.GetEvacuationReason(instance, 0)); return(true); } ushort targetBuilding = CitizenMgr.GetTargetBuilding(instanceId); if (targetBuilding == CitizenProxy.GetWorkBuilding(ref citizen)) { return(true); } ItemClass.Service targetService = BuildingMgr.GetBuildingService(targetBuilding); if (targetService == ItemClass.Service.Beautification && WeatherInfo.IsBadWeather) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} cancels the trip to a park due to bad weather"); schedule.Schedule(ResidentState.AtHome); return(false); } return(true); }
private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen citizen, bool mayCancel) { ushort instanceId = CitizenProxy.GetInstance(ref citizen); ushort vehicleId = CitizenProxy.GetVehicle(ref citizen); // TODO: implement bored of traffic jam trip abandon if (vehicleId == 0 && instanceId == 0) { if (CitizenProxy.GetVisitBuilding(ref citizen) != 0) { CitizenProxy.SetVisitPlace(ref citizen, citizenId, 0); } Log.Debug($"Teleporting {GetCitizenDesc(citizenId, ref citizen)} back home because they are moving but no instance is specified"); CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home); CitizenProxy.SetArrested(ref citizen, false); return; } 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; } if (CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour, true)) { ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen); if (IsChance(AbandonTourChance) && homeBuilding != 0) { CitizenProxy.RemoveFlags(ref citizen, Citizen.Flags.Evacuating); residentAI.StartMoving(instance, citizenId, ref citizen, 0, homeBuilding); } } else if (CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.WaitingTransport | CitizenInstance.Flags.WaitingTaxi)) { if (mayCancel && CitizenMgr.GetInstanceWaitCounter(instanceId) == 255 && IsChance(AbandonTransportWaitChance)) { ushort home = CitizenProxy.GetHomeBuilding(ref citizen); if (home == 0) { return; } Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} doesn't want to wait for transport anymore, goes back home"); residentAI.StartMoving(instance, citizenId, ref citizen, 0, home); } } }
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, false)} exits a guided tour and moves back home."); residentAI.StartMoving(instance, citizenId, ref citizen, 0, homeBuilding); } }
/// <summary>Determines whether the specified citizen must be processed as a virtual citizen.</summary> /// <typeparam name="TAI">The type of the citizen's AI.</typeparam> /// <param name="humanAI">The citizen AI reference.</param> /// <param name="citizen">The citizen to check.</param> /// <param name="realizeCitizen">A callback to determine whether a virtual citizen should be realized.</param> /// <returns><c>true</c> if the citizen must be processed as a virtual citizen; otherwise, <c>false</c>.</returns> protected bool IsCitizenVirtual <TAI>(TAI humanAI, ref TCitizen citizen, Func <TAI, bool> realizeCitizen) { if (CitizenProxy.GetInstance(ref citizen) != 0) { return(false); } switch (Config.VirtualCitizens) { case VirtualCitizensLevel.None: return(false); default: return(!realizeCitizen(humanAI)); } }
protected bool EnsureCitizenValid(uint citizenId, ref TCitizen citizen) { if (CitizenProxy.GetHomeBuilding(ref citizen) == 0 && CitizenProxy.GetWorkBuilding(ref citizen) == 0 && CitizenProxy.GetVisitBuilding(ref citizen) == 0 && CitizenProxy.GetInstance(ref citizen) == 0 && CitizenProxy.GetVehicle(ref citizen) == 0) { CitizenMgr.ReleaseCitizen(citizenId); return(false); } if (CitizenProxy.IsCollapsed(ref citizen)) { Log.Debug($"{GetCitizenDesc(citizenId, ref citizen)} is collapsed, doing nothing..."); return(false); } return(true); }
/// <summary> /// Ensures that the provided citizen is in a valid state and can be processed. /// </summary> /// /// <param name="citizenId">The citizen ID to check.</param> /// <param name="citizen">The citizen data reference.</param> /// /// <returns><c>true</c> if the provided citizen is in a valid state; otherwise, <c>false</c>.</returns> protected bool EnsureCitizenCanBeProcessed(uint citizenId, ref TCitizen citizen) { if ((CitizenProxy.GetHomeBuilding(ref citizen) == 0 && CitizenProxy.GetWorkBuilding(ref citizen) == 0 && CitizenProxy.GetVisitBuilding(ref citizen) == 0 && CitizenProxy.GetInstance(ref citizen) == 0 && CitizenProxy.GetVehicle(ref citizen) == 0) || (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.MovingIn) && CitizenProxy.GetLocation(ref citizen) == Citizen.Location.Home)) { CitizenMgr.ReleaseCitizen(citizenId); return(false); } if (CitizenProxy.IsCollapsed(ref citizen)) { Log.Debug($"{GetCitizenDesc(citizenId, ref citizen, false)} is collapsed, doing nothing..."); return(false); } return(true); }
private bool FindRandomVisitPlace(TAI instance, uint citizenId, ref TCitizen citizen, int doNothingProbability, ushort visitBuilding) { int targetType = touristAI.GetRandomTargetType(instance, doNothingProbability); if (targetType == 1) { Log.Debug(TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} decides to leave the city"); touristAI.FindVisitPlace(instance, citizenId, visitBuilding, touristAI.GetLeavingReason(instance, citizenId, ref citizen)); return(true); } if (CitizenProxy.GetInstance(ref citizen) == 0 && !touristAI.DoRandomMove(instance)) { return(false); } if (!IsChance(GetGoOutChance(CitizenProxy.GetAge(ref citizen)))) { FindHotel(instance, citizenId, ref citizen); return(true); } switch (targetType) { case 2: touristAI.FindVisitPlace(instance, citizenId, visitBuilding, touristAI.GetShoppingReason(instance)); 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)} stays in the city, goes relaxing"); touristAI.FindVisitPlace(instance, citizenId, visitBuilding, touristAI.GetEntertainmentReason(instance)); break; } return(true); }
private void ProcessCitizenMoving(TAI instance, uint citizenId, ref TCitizen citizen, bool mayCancel) { ushort instanceId = CitizenProxy.GetInstance(ref citizen); ushort vehicleId = CitizenProxy.GetVehicle(ref citizen); if (vehicleId == 0 && instanceId == 0) { if (CitizenProxy.GetVisitBuilding(ref citizen) != 0) { CitizenProxy.SetVisitPlace(ref citizen, citizenId, 0); } if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.MovingIn)) { CitizenMgr.ReleaseCitizen(citizenId); } 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); } return; } 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."); TransferMgr.AddOutgoingOfferFromCurrentPosition(citizenId, residentAI.GetEvacuationReason(instance, 0)); return; } bool returnHome = false; ushort targetBuilding = CitizenMgr.GetTargetBuilding(instanceId); 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; } } if (!returnHome && CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.WaitingTransport | CitizenInstance.Flags.WaitingTaxi)) { if (mayCancel && CitizenMgr.GetInstanceWaitCounter(instanceId) == 255 && Random.ShouldOccur(AbandonTransportWaitChance)) { Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, false)} goes back home"); returnHome = true; } } if (returnHome) { ushort home = CitizenProxy.GetHomeBuilding(ref citizen); if (home == 0) { return; } residentAI.StartMoving(instance, citizenId, ref citizen, 0, home); } }
private ResidentState GetResidentState(ref TCitizen citizen) { if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.DummyTraffic)) { return(ResidentState.Ignored); } ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen); ItemClass.Service buildingService = BuildingMgr.GetBuildingService(currentBuilding); if (BuildingMgr.BuildingHasFlags(currentBuilding, Building.Flags.Evacuating) && buildingService != ItemClass.Service.Disaster) { return(ResidentState.Evacuating); } switch (CitizenProxy.GetLocation(ref citizen)) { case Citizen.Location.Home: return(currentBuilding != 0 ? ResidentState.AtHome : ResidentState.Unknown); case Citizen.Location.Work: if (buildingService == ItemClass.Service.Disaster && CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating)) { return(ResidentState.InShelter); } if (CitizenProxy.GetVisitBuilding(ref citizen) == currentBuilding) { // A citizen may visit their own work building (e.g. shopping) goto case Citizen.Location.Visit; } return(currentBuilding != 0 ? ResidentState.AtSchoolOrWork : ResidentState.Unknown); case Citizen.Location.Visit: if (currentBuilding == 0) { return(ResidentState.Unknown); } switch (buildingService) { case ItemClass.Service.Commercial: if (CitizenProxy.GetWorkBuilding(ref citizen) != 0 && IsWorkDay && TimeInfo.CurrentHour > Config.LunchBegin && TimeInfo.CurrentHour < GetSpareTimeBeginHour(CitizenProxy.GetAge(ref citizen))) { return(ResidentState.AtLunch); } if (BuildingMgr.GetBuildingSubService(currentBuilding) == ItemClass.SubService.CommercialLeisure) { return(ResidentState.AtLeisureArea); } return(ResidentState.Shopping); case ItemClass.Service.Beautification: return(ResidentState.AtLeisureArea); case ItemClass.Service.Disaster: return(ResidentState.InShelter); } return(ResidentState.Visiting); case Citizen.Location.Moving: ushort instanceId = CitizenProxy.GetInstance(ref citizen); if (CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.OnTour | CitizenInstance.Flags.TargetIsNode, true)) { return(ResidentState.OnTour); } ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen); return(homeBuilding != 0 && CitizenMgr.GetTargetBuilding(instanceId) == homeBuilding ? ResidentState.MovingHome : ResidentState.MovingToTarget); default: return(ResidentState.Unknown); } }
/// <summary> /// Gets a string that describes the specified citizen. /// </summary> /// /// <param name="citizenId">The citizen ID.</param> /// <param name="citizen">The citizen data reference.</param> /// /// <returns>A short string describing the specified citizen.</returns> 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); return($"Citizen {citizenId} ({CitizenProxy.GetAge(ref citizen)}, {home}, {employment}, currently {location} at {CitizenProxy.GetCurrentBuilding(ref citizen)}) / instance {CitizenProxy.GetInstance(ref 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); if (instanceId == 0) { if (vehicleId == 0) { if (CitizenProxy.GetVisitBuilding(ref citizen) != 0) { CitizenProxy.SetVisitPlace(ref citizen, citizenId, 0); } if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.MovingIn)) { CitizenMgr.ReleaseCitizen(citizenId); schedule = default; } else { CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home); CitizenProxy.SetArrested(ref citizen, false); schedule.Schedule(ResidentState.Unknown); } } return(true); } if (vehicleId == 0 && !CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating) && CitizenMgr.IsAreaEvacuating(instanceId)) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} was on the way, but the area evacuates. Finding an evacuation place."); schedule.Schedule(ResidentState.Unknown); schedule.DepartureTime = default; TransferMgr.AddOutgoingOfferFromCurrentPosition(citizenId, residentAI.GetEvacuationReason(instance, 0)); return(true); } ushort targetBuilding = CitizenMgr.GetTargetBuilding(instanceId); bool headingToWork = targetBuilding == CitizenProxy.GetWorkBuilding(ref citizen); if (vehicleId != 0 && schedule.DepartureTime != default) { float maxTravelTime = headingToWork ? abandonCarRideToWorkDurationThreshold : abandonCarRideDurationThreshold; if ((TimeInfo.Now - schedule.DepartureTime).TotalHours > maxTravelTime) { buildingAI.RegisterReachingTrouble(targetBuilding); if (targetBuilding == CitizenProxy.GetHomeBuilding(ref citizen)) { return(true); } Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} cancels the trip because of traffic jam"); schedule.Schedule(ResidentState.Relaxing); schedule.Hint = ScheduleHint.RelaxNearbyOnly; schedule.CurrentState = ResidentState.InTransition; return(false); } } if (headingToWork) { return(true); } ItemClass.Service targetService = BuildingMgr.GetBuildingService(targetBuilding); if (targetService == ItemClass.Service.Beautification && WeatherInfo.IsBadWeather) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} cancels the trip to a park due to bad weather"); schedule.Schedule(ResidentState.AtHome); schedule.CurrentState = ResidentState.InTransition; return(false); } return(true); }
private void ProcessMoving(TAI instance, uint citizenId, ref TCitizen citizen) { ushort instanceId = CitizenProxy.GetInstance(ref citizen); ushort vehicleId = CitizenProxy.GetVehicle(ref citizen); if (instanceId == 0) { if (vehicleId == 0) { CitizenMgr.ReleaseCitizen(citizenId); } return; } bool isEvacuating = CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating); if (vehicleId == 0 && !isEvacuating && CitizenMgr.IsAreaEvacuating(instanceId)) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} was on the way, but the area evacuates. Searching for a shelter."); TransferMgr.AddOutgoingOfferFromCurrentPosition(citizenId, touristAI.GetEvacuationReason(instance, 0)); return; } if (isEvacuating) { return; } if (CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.TargetIsNode | CitizenInstance.Flags.OnTour, all: true)) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} exits the guided tour."); FindRandomVisitPlace(instance, citizenId, ref citizen, TouristDoNothingProbability, 0); return; } ushort targetBuildingId = CitizenProxy.GetVisitBuilding(ref citizen); TouristTarget target; if (CitizenMgr.InstanceHasFlags(instanceId, CitizenInstance.Flags.TargetIsNode)) { if (CitizenMgr.GetTargetNode(instanceId) != 0) { target = TouristTarget.Relaxing; } else { return; } } else { if (targetBuildingId == 0) { targetBuildingId = CitizenMgr.GetTargetBuilding(instanceId); } BuildingMgr.GetBuildingService(targetBuildingId, out ItemClass.Service targetService, out ItemClass.SubService targetSubService); switch (targetService) { // Heading to a hotel, no need to change anything case ItemClass.Service.Commercial when targetSubService == ItemClass.SubService.CommercialTourist: return; case ItemClass.Service.Commercial when targetSubService == ItemClass.SubService.CommercialLeisure: target = TouristTarget.Party; break; case ItemClass.Service.Tourism: case ItemClass.Service.Beautification: case ItemClass.Service.Monument: target = TouristTarget.Relaxing; break; case ItemClass.Service.Commercial: target = TouristTarget.Shopping; break; default: return; } } if (GetTouristGoingOutChance(ref citizen, target) > 0) { return; } ushort hotel = FindHotel(targetBuildingId); if (hotel != 0) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} changes the target and moves to a hotel {hotel} because of time or weather"); StartMovingToVisitBuilding(instance, citizenId, ref citizen, 0, hotel); } else { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"Tourist {GetCitizenDesc(citizenId, ref citizen)} leaves the city because of time or weather"); touristAI.FindVisitPlace(instance, citizenId, 0, touristAI.GetLeavingReason(instance, citizenId, ref citizen)); } }
private ScheduleAction UpdateCitizenState(ref TCitizen citizen, ref CitizenSchedule schedule) { if (schedule.CurrentState == ResidentState.Ignored) { return(ScheduleAction.Ignore); } if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.DummyTraffic)) { schedule.CurrentState = ResidentState.Ignored; return(ScheduleAction.Ignore); } Citizen.Location location = CitizenProxy.GetLocation(ref citizen); if (location == Citizen.Location.Moving) { 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); 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) { schedule.CurrentState = ResidentState.Evacuation; schedule.Schedule(ResidentState.InShelter); return(ScheduleAction.ProcessState); } switch (location) { case Citizen.Location.Home: schedule.CurrentState = ResidentState.AtHome; return(ScheduleAction.ProcessState); case Citizen.Location.Work: if (buildingService == ItemClass.Service.Disaster && CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating)) { schedule.CurrentState = ResidentState.InShelter; return(ScheduleAction.ProcessState); } if (CitizenProxy.GetVisitBuilding(ref citizen) == currentBuilding && schedule.WorkStatus != WorkStatus.Working) { // A citizen may visit their own work building (e.g. shopping), // but the game sets the location to 'work' even if the citizen visits the building. goto case Citizen.Location.Visit; } schedule.CurrentState = ResidentState.AtSchoolOrWork; return(ScheduleAction.ProcessState); case Citizen.Location.Visit: 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: schedule.CurrentState = ResidentState.Relaxing; return(ScheduleAction.ProcessState); case ItemClass.Service.Commercial: schedule.CurrentState = ResidentState.Shopping; return(ScheduleAction.ProcessState); case ItemClass.Service.Disaster when CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating): schedule.CurrentState = ResidentState.InShelter; return(ScheduleAction.ProcessState); } schedule.CurrentState = ResidentState.Visiting; return(ScheduleAction.ProcessState); } return(ScheduleAction.Ignore); }
/// <summary> /// Gets a string that describes the provided citizen. /// </summary> /// /// <param name="citizenId">The citizen ID.</param> /// <param name="citizen">The citizen data reference.</param> /// <param name="isVirtual"><c>true</c> if the citizen is in a virtual mode; otherwise, <c>false</c>.</param> /// /// <returns>A short string describing the provided citizen.</returns> protected string GetCitizenDesc(uint citizenId, ref TCitizen citizen, bool?isVirtual) { 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}"); }
private bool DoScheduledRelaxing(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen) { // Relaxing was already scheduled last time, but the citizen is still at school/work or in shelter. // This can occur when the game's transfer manager can't find any activity for the citizen. // In that case, move back home. if ((schedule.CurrentState == ResidentState.AtSchoolOrWork || schedule.CurrentState == ResidentState.InShelter) && schedule.LastScheduledState == ResidentState.Relaxing) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted relax but is still at work or in shelter. No relaxing activity found. Now going home."); return(false); } ushort buildingId = CitizenProxy.GetCurrentBuilding(ref citizen); switch (schedule.Hint) { case ScheduleHint.RelaxAtLeisureBuilding: schedule.Schedule(ResidentState.Unknown); ushort leisure = MoveToLeisureBuilding(instance, citizenId, ref citizen, buildingId); if (leisure == 0) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted relax but didn't find a leisure building"); return(false); } Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} heading to a leisure building {leisure}"); return(true); case ScheduleHint.AttendingEvent: ushort eventBuilding = schedule.EventBuilding; schedule.EventBuilding = 0; ICityEvent cityEvent = EventMgr.GetCityEvent(eventBuilding); if (cityEvent == null) { Log.Debug(LogCategory.Events, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted attend an event at '{eventBuilding}', but there was no event there"); } else if (StartMovingToVisitBuilding(instance, citizenId, ref citizen, eventBuilding)) { schedule.Schedule(ResidentState.Unknown, cityEvent.EndTime); Log.Debug(LogCategory.Events, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna attend an event at '{eventBuilding}', will return at {cityEvent.EndTime}"); return(true); } schedule.Schedule(ResidentState.Unknown); return(false); case ScheduleHint.RelaxNearbyOnly: Vector3 currentPosition = CitizenMgr.GetCitizenPosition(CitizenProxy.GetInstance(ref citizen)); ushort parkBuildingId = BuildingMgr.FindActiveBuilding(currentPosition, LocalSearchDistance, ItemClass.Service.Beautification); if (StartMovingToVisitBuilding(instance, citizenId, ref citizen, parkBuildingId)) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} heading to a nearby entertainment building {parkBuildingId}"); schedule.Schedule(ResidentState.Unknown); return(true); } schedule.Schedule(ResidentState.Unknown); DoScheduledHome(ref schedule, instance, citizenId, ref citizen); return(true); } uint relaxChance = spareTimeBehavior.GetRelaxingChance( CitizenProxy.GetAge(ref citizen), schedule.WorkShift, schedule.WorkStatus == WorkStatus.OnVacation); relaxChance = AdjustRelaxChance(relaxChance, ref citizen); ResidentState nextState = Random.ShouldOccur(relaxChance) ? ResidentState.Relaxing : ResidentState.Unknown; schedule.Schedule(nextState); if (schedule.CurrentState != ResidentState.Relaxing || Random.ShouldOccur(FindAnotherShopOrEntertainmentChance)) { Log.Debug(LogCategory.Movement, 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)); } #if DEBUG else { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} continues relaxing in the same entertainment building."); } #endif return(true); }
private ResidentState GetResidentState(ref TCitizen citizen) { ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen); ItemClass.Service buildingService = BuildingMgr.GetBuildingService(currentBuilding); if (BuildingMgr.BuildingHasFlags(currentBuilding, Building.Flags.Evacuating) && buildingService != ItemClass.Service.Disaster) { return(ResidentState.Evacuating); } switch (CitizenProxy.GetLocation(ref citizen)) { case Citizen.Location.Home: if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.MovingIn)) { return(ResidentState.LeftCity); } if (currentBuilding != 0) { return(ResidentState.AtHome); } return(ResidentState.Unknown); case Citizen.Location.Work: if (buildingService == ItemClass.Service.Disaster && CitizenProxy.HasFlags(ref citizen, Citizen.Flags.Evacuating)) { return(ResidentState.InShelter); } return(currentBuilding != 0 ? ResidentState.AtSchoolOrWork : ResidentState.Unknown); case Citizen.Location.Visit: if (currentBuilding == 0) { return(ResidentState.Unknown); } switch (buildingService) { case ItemClass.Service.Commercial: if (CitizenProxy.GetWorkBuilding(ref citizen) != 0 && IsWorkDay && TimeInfo.CurrentHour > Config.LunchBegin && TimeInfo.CurrentHour < GetSpareTimeBeginHour(CitizenProxy.GetAge(ref citizen))) { return(ResidentState.AtLunch); } if (BuildingMgr.GetBuildingSubService(currentBuilding) == ItemClass.SubService.CommercialLeisure) { return(ResidentState.AtLeisureArea); } return(ResidentState.Shopping); case ItemClass.Service.Beautification: return(ResidentState.AtLeisureArea); case ItemClass.Service.Disaster: return(ResidentState.InShelter); } return(ResidentState.Visiting); case Citizen.Location.Moving: ushort homeBuilding = CitizenProxy.GetHomeBuilding(ref citizen); return(homeBuilding != 0 && CitizenMgr.GetTargetBuilding(CitizenProxy.GetInstance(ref citizen)) == homeBuilding ? ResidentState.MovingHome : ResidentState.MovingToTarget); default: return(ResidentState.Unknown); } }