/// <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
    }
Example #2
0
    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;
        }
    }
Example #3
0
    /// <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);
    }