/// <summary> /// Generates schedules for everyone. /// </summary> internal static void GenerateAllSchedules() { Game1.netWorldState.Value.IslandVisitors.Clear(); if (Game1.getLocationFromName("IslandSouth") is not IslandSouth island || !island.resortRestored.Value || !island.resortOpenToday.Value || Game1.IsRainingHere(island) || Utility.isFestivalDay(Game1.Date.DayOfMonth, Game1.Date.Season) || (Game1.Date.DayOfMonth >= 15 && Game1.Date.DayOfMonth <= 17 && Game1.IsWinter)) { return; } Random random = new((int)(Game1.uniqueIDForThisGame * 1.21f) + (int)(Game1.stats.DaysPlayed * 2.5f)); HashSet <NPC> explorers = GenerateExplorerGroup(random); if (explorers.Count > 0) { Globals.ModMonitor.DebugOnlyLog($"Found explorer group: {string.Join(", ", explorers.Select((NPC npc) => npc.Name))}."); IslandNorthScheduler.Schedule(random, explorers); } // Resort capacity set to zero, can skip everything else. if (Globals.Config.Capacity == 0 && (Globals.SaveDataModel is null || Globals.SaveDataModel.NPCsForTomorrow.Count == 0)) { IslandSouthPatches.ClearCache(); GIScheduler.ClearCache(); return; } List <NPC> visitors = GenerateVistorList(random, Globals.Config.Capacity, explorers); Dictionary <string, string> animationDescriptions = Globals.ContentHelper.Load <Dictionary <string, string> >("Data/animationDescriptions", ContentSource.GameContent); GIScheduler.Bartender = SetBartender(visitors); GIScheduler.Musician = SetMusician(random, visitors, animationDescriptions); List <GingerIslandTimeSlot> activities = AssignIslandSchedules(random, visitors, animationDescriptions); Dictionary <NPC, string> schedules = RenderIslandSchedules(random, visitors, activities); foreach (NPC visitor in schedules.Keys) { Globals.ModMonitor.Log($"Calculated island schedule for {visitor.Name}"); visitor.islandScheduleName.Value = "island"; ScheduleUtilities.ParseMasterScheduleAdjustedForChild2NPC(visitor, schedules[visitor]); Game1.netWorldState.Value.IslandVisitors[visitor.Name] = true; ConsoleCommands.IslandSchedules[visitor.Name] = schedules[visitor]; } IslandSouthPatches.ClearCache(); GIScheduler.ClearCache(); #if DEBUG Globals.ModMonitor.Log($"Current memory usage {GC.GetTotalMemory(false):N0}", LogLevel.Alert); GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect(); Globals.ModMonitor.Log($"Post-collection memory usage {GC.GetTotalMemory(true):N0}", LogLevel.Alert); #endif }
private void OnTimeChanged(object?sender, TimeChangedEventArgs e) { MidDayScheduleEditor.AttemptAdjustGISchedule(e); if (e.NewTime > 615 && !this.haveFixedSchedulesToday) { // No longer need the exclusions cache. IslandSouthPatches.ClearCache(); ScheduleUtilities.FixUpSchedules(); if (Globals.Config.DebugMode) { ScheduleDebugPatches.FixNPCs(); } this.haveFixedSchedulesToday = true; } }
/// <summary> /// Clear all caches at the end of the day and if the player exits to menu. /// </summary> private void ClearCaches() { DialoguePatches.ClearTalkRecord(); DialogueUtilities.ClearDialogueLog(); if (Context.IsSplitScreen && Context.ScreenId != 0) { return; } this.haveFixedSchedulesToday = false; MidDayScheduleEditor.Reset(); IslandSouthPatches.ClearCache(); GIScheduler.ClearCache(); GIScheduler.DayEndReset(); ConsoleCommands.ClearCache(); ScheduleUtilities.ClearCache(); }
/// <summary> /// Gets the visitor list for a specific day. Explorers can't be visitors, so remove them. /// </summary> /// <param name="random">Random to use to select.</param> /// <param name="capacity">Maximum number of people to allow on the island.</param> /// <returns>Visitor List.</returns> /// <remarks>For a deterministic island list, use a Random seeded with the uniqueID + number of days played.</remarks> private static List <NPC> GenerateVistorList(Random random, int capacity, HashSet <NPC> explorers) { CurrentGroup = null; CurrentVisitingGroup = null; CurrentAdventureGroup = null; CurrentAdventurers = null; List <NPC> visitors = new(); HashSet <NPC> valid_visitors = new(); foreach (NPC npc in Utility.getAllCharacters()) { if (IslandSouth.CanVisitIslandToday(npc) && !explorers.Contains(npc)) { valid_visitors.Add(npc); } } if (Globals.SaveDataModel is not null) { foreach (string npcname in Globals.SaveDataModel.NPCsForTomorrow) { NPC npc = Game1.getCharacterFromName(npcname); visitors.Add(npc); if (!valid_visitors.Contains(npc)) { Globals.ModMonitor.Log($"{npcname} queued for Island DESPITE exclusion!", LogLevel.Warn); } } Globals.SaveDataModel.NPCsForTomorrow.Clear(); } if (random.NextDouble() < Globals.Config.GroupChance) { List <string> groupkeys = new(); foreach (string key in IslandGroups.Keys) { // Filter out groups where one member can't make it or are too big if (IslandGroups[key].Count <= capacity - visitors.Count && IslandGroups[key].All((NPC npc) => valid_visitors.Contains(npc))) { groupkeys.Add(key); } } if (groupkeys.Count > 0) { CurrentGroup = Utility.GetRandom(groupkeys, random); #if DEBUG Globals.ModMonitor.Log($"Group {CurrentGroup} headed to Island.", LogLevel.Debug); #endif HashSet <NPC> possiblegroup = IslandGroups[CurrentGroup]; visitors.AddRange(possiblegroup.Where((npc) => !visitors.Contains(npc))); // limit group size if there's too many people... CurrentVisitingGroup = possiblegroup; valid_visitors.ExceptWith(visitors); } } if (Game1.getCharacterFromName("Gus") is NPC gus && !visitors.Contains(gus) && valid_visitors.Contains(gus) && Globals.Config.GusDayAsShortString().Equals(Game1.shortDayNameFromDayOfSeason(Game1.dayOfMonth), StringComparison.OrdinalIgnoreCase) && Globals.Config.GusChance > random.NextDouble()) { Globals.ModMonitor.DebugOnlyLog($"Forcibly adding Gus."); visitors.Add(gus); valid_visitors.Remove(gus); } // Prevent children and anyone with the neveralone exclusion from going alone. int kidsremoved = valid_visitors.RemoveWhere((NPC npc) => npc.Age == NPC.child && (!IslandSouthPatches.Exclusions.TryGetValue(npc, out string[]? exclusions) || !exclusions.Contains("freerange"))); int neveralone = valid_visitors.RemoveWhere((NPC npc) => IslandSouthPatches.Exclusions.TryGetValue(npc, out string[]? exclusions) && exclusions.Contains("neveralone")); if (Globals.Config.DebugMode) { Globals.ModMonitor.Log($"Excluded {kidsremoved} kids and {neveralone} never alone villagers from the valid villagers list"); } if (visitors.Count < capacity) { #if DEBUG Globals.ModMonitor.Log($"{capacity} not yet reached, attempting to add more.", LogLevel.Debug); #endif visitors.AddRange(valid_visitors.OrderBy(a => random.Next()).Take(capacity - visitors.Count)); } // If George in visitors, add Evelyn. if (visitors.Any((NPC npc) => npc.Name.Equals("George", StringComparison.OrdinalIgnoreCase)) && visitors.All((NPC npc) => !npc.Name.Equals("Evelyn", StringComparison.OrdinalIgnoreCase)) && Game1.getCharacterFromName("Evelyn") is NPC evelyn) { // counting backwards to avoid kicking out a group member. for (int i = visitors.Count - 1; i >= 0; i--) { if (!visitors[i].Name.Equals("Gus", StringComparison.OrdinalIgnoreCase) && !visitors[i].Name.Equals("George", StringComparison.OrdinalIgnoreCase)) { Globals.ModMonitor.DebugOnlyLog($"Replacing one visitor {visitors[i].Name} with Evelyn"); visitors[i] = evelyn; break; } } } for (int i = 0; i < visitors.Count; i++) { visitors[i].scheduleDelaySeconds = Math.Min(i * 0.4f, 7f); } // set schedule Delay for George and Evelyn so they arrive together (in theory)? if (visitors.FirstOrDefault((NPC npc) => npc.Name.Equals("George", StringComparison.OrdinalIgnoreCase)) is NPC george && visitors.FirstOrDefault((NPC npc) => npc.Name.Equals("Evelyn", StringComparison.OrdinalIgnoreCase)) is NPC evelyn2) { george.scheduleDelaySeconds = 7f; evelyn2.scheduleDelaySeconds = 6.8f; } Globals.ModMonitor.DebugOnlyLog($"{visitors.Count} vistors: {string.Join(", ", visitors.Select((NPC npc) => npc.Name))}"); IslandSouthPatches.ClearCache(); return(visitors); }