private void DoScheduledShopping(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen) { ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen); if (schedule.Hint == ScheduleHint.LocalShoppingOnly) { schedule.Schedule(ResidentState.Unknown, default); ushort shop = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance); if (shop == 0) { Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted go shopping, but didn't find a local shop"); } else { Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} goes shopping at a local shop {shop}"); } } else { uint moreShoppingChance = spareTimeBehavior.GetGoOutChance(CitizenProxy.GetAge(ref citizen)); ResidentState nextState = Random.ShouldOccur(moreShoppingChance) ? ResidentState.Unknown : ResidentState.Shopping; schedule.Schedule(nextState, default); if (schedule.CurrentState != ResidentState.Shopping) { Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} in state {schedule.CurrentState} wanna go shopping and schedules {nextState}, heading to a random shop"); residentAI.FindVisitPlace(instance, citizenId, currentBuilding, residentAI.GetShoppingReason(instance)); } } }
/// <summary>Schedules next actions for the citizen with no action time (ASAP).</summary> /// <param name="nextState">The next scheduled citizen's state.</param> public void Schedule(ResidentState nextState) { // Note: not calling the overload to avoid additional method call - this method will be called frequently LastScheduledState = ScheduledState; ScheduledState = nextState; ScheduledStateTime = default; }
/// <summary> /// The main method of the custom AI. /// </summary> /// /// <param name="instance">A reference to an object instance of the original AI.</param> /// <param name="citizenId">The ID of the citizen to process.</param> /// <param name="citizen">A <see cref="Citizen"/> reference to process.</param> public void UpdateLocation(TAI instance, uint citizenId, ref TCitizen citizen) { if (!EnsureCitizenValid(citizenId, ref citizen)) { return; } if (CitizenProxy.IsDead(ref citizen)) { ProcessCitizenDead(instance, citizenId, ref citizen); return; } if ((CitizenProxy.IsSick(ref citizen) && ProcessCitizenSick(instance, citizenId, ref citizen)) || (CitizenProxy.IsArrested(ref citizen) && ProcessCitizenArrested(ref citizen))) { return; } ResidentState residentState = GetResidentState(ref citizen); switch (residentState) { case ResidentState.LeftCity: CitizenMgr.ReleaseCitizen(citizenId); break; case ResidentState.MovingHome: ProcessCitizenMoving(instance, citizenId, ref citizen, false); break; case ResidentState.AtHome: ProcessCitizenAtHome(instance, citizenId, ref citizen); break; case ResidentState.MovingToTarget: ProcessCitizenMoving(instance, citizenId, ref citizen, true); break; case ResidentState.AtSchoolOrWork: ProcessCitizenAtSchoolOrWork(instance, citizenId, ref citizen); break; case ResidentState.AtLunch: case ResidentState.Shopping: case ResidentState.AtLeisureArea: case ResidentState.Visiting: ProcessCitizenVisit(instance, residentState, citizenId, ref citizen); break; case ResidentState.Evacuating: ProcessCitizenEvacuation(instance, citizenId, ref citizen); break; case ResidentState.InShelter: CitzenReturnsFromShelter(instance, citizenId, ref citizen); return; } }
private void ProcessCitizenVisit(TAI instance, ResidentState citizenState, uint citizenId, ref TCitizen citizen, bool isVirtual) { ushort currentBuilding = CitizenProxy.GetVisitBuilding(ref citizen); if (currentBuilding == 0) { Log.Debug($"WARNING: {GetCitizenDesc(citizenId, ref citizen, isVirtual)} is in corrupt state: visiting with no visit building. Teleporting home."); CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home); return; } switch (citizenState) { case ResidentState.AtLunch: CitizenReturnsFromLunch(instance, citizenId, ref citizen, isVirtual); 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, isVirtual)) { CitizenReturnsHomeFromVisit(instance, citizenId, ref citizen, isVirtual); } return; case ResidentState.Shopping: if (CitizenProxy.HasFlags(ref citizen, Citizen.Flags.NeedGoods)) { BuildingMgr.ModifyMaterialBuffer(CitizenProxy.GetVisitBuilding(ref citizen), TransferManager.TransferReason.Shopping, -ShoppingGoodsAmount); CitizenProxy.RemoveFlags(ref citizen, Citizen.Flags.NeedGoods); } if (CitizenGoesWorking(instance, citizenId, ref citizen, isVirtual) || CitizenGoesToEvent(instance, citizenId, ref citizen, isVirtual)) { return; } if (Random.ShouldOccur(ReturnFromShoppingChance) || IsWorkDayMorning(CitizenProxy.GetAge(ref citizen))) { Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen, isVirtual)} returning from shopping at {currentBuilding} back home"); ReturnFromVisit(instance, citizenId, ref citizen, CitizenProxy.GetHomeBuilding(ref citizen), Citizen.Location.Home, isVirtual); } return; } }
private bool DoScheduledShopping(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen) { // Shopping 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.Shopping) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted go shopping but is still at work or in shelter. No shopping activity found. Now going home."); return(false); } ushort currentBuilding = CitizenProxy.GetCurrentBuilding(ref citizen); if (schedule.Hint == ScheduleHint.LocalShoppingOnly) { schedule.Schedule(ResidentState.Unknown); ushort shop = MoveToCommercialBuilding(instance, citizenId, ref citizen, LocalSearchDistance); if (shop == 0) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted go shopping, but didn't find a local shop"); return(false); } if (TimeInfo.IsNightTime) { schedule.Hint = ScheduleHint.NoShoppingAnyMore; } Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} goes shopping at a local shop {shop}"); return(true); } uint moreShoppingChance = spareTimeBehavior.GetShoppingChance(CitizenProxy.GetAge(ref citizen)); ResidentState nextState = schedule.Hint != ScheduleHint.NoShoppingAnyMore && Random.ShouldOccur(moreShoppingChance) ? ResidentState.Shopping : ResidentState.Unknown; schedule.Schedule(nextState); if (schedule.CurrentState != ResidentState.Shopping || Random.ShouldOccur(FindAnotherShopOrEntertainmentChance)) { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} in state {schedule.CurrentState} wanna go shopping and schedules {nextState}, heading to a random shop, hint = {schedule.Hint}"); residentAI.FindVisitPlace(instance, citizenId, currentBuilding, residentAI.GetShoppingReason(instance)); } #if DEBUG else { Log.Debug(LogCategory.Movement, TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} continues shopping in the same building."); } #endif return(true); }
private void DoScheduledRelaxing(ref CitizenSchedule schedule, TAI instance, uint citizenId, ref TCitizen citizen) { ushort buildingId = CitizenProxy.GetCurrentBuilding(ref citizen); switch (schedule.Hint) { case ScheduleHint.RelaxAtLeisureBuilding: schedule.Schedule(ResidentState.Unknown, default); ushort leisure = MoveToLeisureBuilding(instance, citizenId, ref citizen, buildingId); if (leisure == 0) { Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted relax but didn't found a leisure building"); } else { Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} heading to a leisure building {leisure}"); } return; case ScheduleHint.AttendingEvent: DateTime returnTime = default; ICityEvent cityEvent = EventMgr.GetCityEvent(schedule.EventBuilding); if (cityEvent == null) { Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanted attend an event at '{schedule.EventBuilding}', but there was no event there"); } else if (StartMovingToVisitBuilding(instance, citizenId, ref citizen, schedule.EventBuilding)) { returnTime = cityEvent.EndTime; Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} wanna attend an event at '{schedule.EventBuilding}', will return at {returnTime}"); } schedule.Schedule(ResidentState.Unknown, returnTime); schedule.EventBuilding = 0; return; } uint relaxChance = spareTimeBehavior.GetGoOutChance(CitizenProxy.GetAge(ref citizen)); ResidentState nextState = Random.ShouldOccur(relaxChance) ? ResidentState.Unknown : ResidentState.Relaxing; schedule.Schedule(nextState, default); if (schedule.CurrentState != ResidentState.Relaxing) { Log.Debug(TimeInfo.Now, $"{GetCitizenDesc(citizenId, ref citizen)} in state {schedule.CurrentState} wanna relax and then schedules {nextState}, heading to an entertainment building."); residentAI.FindVisitPlace(instance, citizenId, buildingId, residentAI.GetEntertainmentReason(instance)); } }
private 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); }
/// <summary>Schedules next actions for the citizen with a specified action time.</summary> /// <param name="nextState">The next scheduled citizen's state.</param> /// <param name="nextStateTime">The time when the scheduled state must change.</param> public void Schedule(ResidentState nextState, DateTime nextStateTime) { LastScheduledState = ScheduledState; ScheduledState = nextState; ScheduledStateTime = nextStateTime; }
/// <summary>The entry method of the custom AI.</summary> /// <param name="instance">A reference to an object instance of the original AI.</param> /// <param name="citizenId">The ID of the citizen to process.</param> /// <param name="citizen">A <typeparamref name="TCitizen"/> reference to process.</param> public void UpdateLocation(TAI instance, uint citizenId, ref TCitizen citizen) { if (!EnsureCitizenCanBeProcessed(citizenId, ref citizen)) { return; } if (CitizenProxy.IsDead(ref citizen)) { ProcessCitizenDead(instance, citizenId, ref citizen); return; } if ((CitizenProxy.IsSick(ref citizen) && ProcessCitizenSick(instance, citizenId, ref citizen)) || (CitizenProxy.IsArrested(ref citizen) && ProcessCitizenArrested(ref citizen))) { return; } ResidentState residentState = GetResidentState(ref citizen); bool isVirtual; switch (residentState) { case ResidentState.MovingHome: ProcessCitizenMoving(instance, citizenId, ref citizen, false); break; case ResidentState.AtHome: isVirtual = IsCitizenVirtual(instance, ref citizen, ShouldRealizeCitizen); ProcessCitizenAtHome(instance, citizenId, ref citizen, isVirtual); break; case ResidentState.MovingToTarget: ProcessCitizenMoving(instance, citizenId, ref citizen, true); break; case ResidentState.AtSchoolOrWork: isVirtual = IsCitizenVirtual(instance, ref citizen, ShouldRealizeCitizen); ProcessCitizenAtSchoolOrWork(instance, citizenId, ref citizen, isVirtual); break; case ResidentState.AtLunch: case ResidentState.Shopping: case ResidentState.AtLeisureArea: case ResidentState.Visiting: isVirtual = IsCitizenVirtual(instance, ref citizen, ShouldRealizeCitizen); ProcessCitizenVisit(instance, residentState, citizenId, ref citizen, isVirtual); break; case ResidentState.OnTour: ProcessCitizenOnTour(instance, citizenId, ref citizen); break; case ResidentState.Evacuating: ProcessCitizenEvacuation(instance, citizenId, ref citizen); break; case ResidentState.InShelter: isVirtual = IsCitizenVirtual(instance, ref citizen, ShouldRealizeCitizen); CitizenReturnsFromShelter(instance, citizenId, ref citizen, isVirtual); break; case ResidentState.Unknown: Log.Debug(TimeInfo.Now, $"WARNING: {GetCitizenDesc(citizenId, ref citizen, null)} is in an UNKNOWN state! Teleporting back home"); if (CitizenProxy.GetHomeBuilding(ref citizen) != 0) { CitizenProxy.SetLocation(ref citizen, Citizen.Location.Home); } break; } }