[EventPriority(EventPriority.Low)] //use low priority to run after most asset updates
        private static void DayStarted_SetupIslandSchedules(object sender, StardewModdingAPI.Events.DayStartedEventArgs e)
        {
            if (!Context.IsMainPlayer)                                                                                   //if this is NOT the main player
            {
                return;                                                                                                  //do nothing
            }
            ExcludedVisitors = new HashSet <string>(StringComparer.OrdinalIgnoreCase);                                   //create a new case-insensitive set and enable scheduling

            foreach (KeyValuePair <string, List <string> > data in ModEntry.GetAllNPCExclusions(forceCacheUpdate: true)) //for each NPC's set of exclusion data
            {
                if (data.Value.Exists(entry =>
                                      entry.StartsWith("All", StringComparison.OrdinalIgnoreCase) || //if this NPC is excluded from everything
                                      entry.StartsWith("IslandEvent", StringComparison.OrdinalIgnoreCase) || //OR if this NPC is excluded from island events
                                      entry.StartsWith("IslandVisit", StringComparison.OrdinalIgnoreCase) //OR if this NPC is excluded from visting the island resort
                                      ))
                {
                    ExcludedVisitors.Add(data.Key); //add this NPC's name to the excluded set
                }
            }

            if (ExcludedVisitors.Count > 0 && ModEntry.Instance.Monitor.IsVerbose) //if any NPCs were excluded
            {
                string logMessage = string.Join(", ", ExcludedVisitors);
                ModEntry.Instance.Monitor.Log($"Excluded NPCs from possible island visit: {logMessage}", LogLevel.Trace);
            }

            IslandSouth.SetupIslandSchedules(); //set up visitors' schedules

            ExcludedVisitors = null;            //clear the set and disable scheduling
        }
 private static bool IslandSouth_checkAction_Prefix(IslandSouth __instance, Location tileLocation)
 {
     if (!Config.EnableMod)
     {
         return(true);
     }
     if (tileLocation.X == 14 && tileLocation.Y == 22)
     {
         Dictionary <ISalable, int[]> stock = new Dictionary <ISalable, int[]>();
         Utility.AddStock(stock, new Object(Vector2.Zero, 873, int.MaxValue), 300, -1);
         Utility.AddStock(stock, new Object(Vector2.Zero, 346, int.MaxValue), 250, -1);
         Utility.AddStock(stock, new Object(Vector2.Zero, 303, int.MaxValue), 500, -1);
         Utility.AddStock(stock, new Object(Vector2.Zero, 459, int.MaxValue), 400, -1);
         Utility.AddStock(stock, new Object(Vector2.Zero, 612, int.MaxValue), 200, -1);
         Object wine  = new Object(Vector2.Zero, 348, int.MaxValue);
         Object mango = new Object(834, 1, false, -1, 0);
         wine.Price          = mango.Price * 3;
         wine.Name           = mango.Name + " Wine";
         wine.preserve.Value = new Object.PreserveType?(Object.PreserveType.Wine);
         wine.preservedParentSheetIndex.Value = mango.ParentSheetIndex;
         wine.Quality = 2;
         Utility.AddStock(stock, wine, 2500, -1);
         if (!Game1.player.cookingRecipes.ContainsKey("Tropical Curry"))
         {
             Utility.AddStock(stock, new Object(907, 1, true, -1, 0), 1000, -1);
         }
         string name = null;
         foreach (var c in __instance.characters)
         {
             if (c.Name == "Gus")
             {
                 name = "Gus";
                 break;
             }
         }
         Game1.activeClickableMenu = new ShopMenu(stock, 0, name, null, null, "ResortBar");
     }
     return(true);
 }
    /// <summary>
    /// Takes a list of activities and renders them as proper schedules.
    /// </summary>
    /// <param name="random">Sedded random.</param>
    /// <param name="visitors">List of visitors.</param>
    /// <param name="activities">List of activities.</param>
    /// <returns>Dictionary of NPC->raw schedule strings.</returns>
    private static Dictionary <NPC, string> RenderIslandSchedules(Random random, List <NPC> visitors, List <GingerIslandTimeSlot> activities)
    {
        Dictionary <NPC, string> completedSchedules = new();

        foreach (NPC visitor in visitors)
        {
            bool should_dress = IslandSouth.HasIslandAttire(visitor);
            List <SchedulePoint> scheduleList = new();

            if (should_dress)
            {
                scheduleList.Add(new SchedulePoint(
                                     random: random,
                                     npc: visitor,
                                     map: "IslandSouth",
                                     time: 1150,
                                     point: IslandSouth.GetDressingRoomPoint(visitor),
                                     animation: "change_beach",
                                     isarrivaltime: true));
            }

            foreach (GingerIslandTimeSlot activity in activities)
            {
                if (activity.Assignments.TryGetValue(visitor, out SchedulePoint? schedulePoint))
                {
                    scheduleList.Add(schedulePoint);
                }
            }

            if (should_dress)
            {
                scheduleList.Add(new SchedulePoint(
                                     random: random,
                                     npc: visitor,
                                     map: "IslandSouth",
                                     time: 1730,
                                     point: IslandSouth.GetDressingRoomPoint(visitor),
                                     animation: "change_normal",
                                     isarrivaltime: true));
            }

            scheduleList[0].IsArrivalTime = true; // set the first slot, whatever it is, to be the arrival time.

            // render the schedule points to strings before appending the remainder schedules
            // which are already strings.
            List <string> schedPointString = scheduleList.Select((SchedulePoint pt) => pt.ToString()).ToList();
            if (visitor.Name.Equals("Gus", StringComparison.OrdinalIgnoreCase))
            {
                // Gus needs to tend bar. Hardcoded same as vanilla.
                schedPointString.Add("1800 Saloon 10 18 2/2430 bed");
            }
            else
            {
                // Try to find a GI remainder schedule, if any.
                schedPointString.Add(ScheduleUtilities.FindProperGISchedule(visitor, SDate.Now())
                                     // Child2NPC NPCs don't understand "bed", must send them to the bus stop spouse dropoff.
                                     ?? (Globals.IsChildToNPC?.Invoke(visitor) == true ? "1800 BusStop -1 23 3" : "1800 bed"));
            }
            completedSchedules[visitor] = string.Join("/", schedPointString);
            Globals.ModMonitor.DebugOnlyLog($"For {visitor.Name}, created island schedule {completedSchedules[visitor]}");
        }
        return(completedSchedules);
    }
    /// <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);
    }
 /// <summary>
 /// Yields a group of valid explorers.
 /// </summary>
 /// <param name="random">Seeded random.</param>
 /// <returns>An explorer group (of up to three explorers), or an empty hashset if there's no group today.</returns>
 private static HashSet <NPC> GenerateExplorerGroup(Random random)
 {
     if (random.NextDouble() <= Globals.Config.ExplorerChance)
     {
         List <string> explorerGroups = ExplorerGroups.Keys.ToList();
         if (explorerGroups.Count > 0)
         {
             CurrentAdventureGroup = explorerGroups[random.Next(explorerGroups.Count)];
             CurrentAdventurers    = ExplorerGroups[CurrentAdventureGroup].Where((NPC npc) => IslandSouth.CanVisitIslandToday(npc)).Take(3).ToHashSet();
             return(CurrentAdventurers);
         }
     }
     return(new HashSet <NPC>()); // just return an empty hashset.
 }
Exemple #6
0
        private void OnDayStarted(object sender, DayStartedEventArgs e)
        {
            // Set up notification messages
            MESSAGE_EVERYTHING_FAILING = new KeyValuePair <string, int>(i18n.Get("status_message.ship_falling_apart"), 10);
            MESSAGE_LOSING_FISH        = new KeyValuePair <string, int>(i18n.Get("status_message.losing_fish"), 9);
            MESSAGE_MAX_LEAKS          = new KeyValuePair <string, int>(i18n.Get("status_message.taking_on_water"), 8);
            MESSAGE_MULTI_PROBLEMS     = new KeyValuePair <string, int>(i18n.Get("status_message.lots_of_problems"), 7);
            MESSAGE_ENGINE_PROBLEM     = new KeyValuePair <string, int>(i18n.Get("status_message.engine_failing"), 7);
            MESSAGE_NET_PROBLEM        = new KeyValuePair <string, int>(i18n.Get("status_message.nets_torn"), 6);
            MESSAGE_LEAK_PROBLEM       = new KeyValuePair <string, int>(i18n.Get("status_message.leak"), 5);

            todayDayOfWeek = SDate.Now().DayOfWeek.ToString();

            Beach beach = Game1.getLocationFromName("Beach") as Beach;

            beach.modData[MURPHY_ON_TRIP] = "false";

            IslandSouthEast island = Game1.getLocationFromName("IslandSouthEast") as IslandSouthEast;

            island.modData[MURPHY_ON_TRIP] = "false";

            // Set Farmer moddata used for this mod
            EstablishPlayerData();

            if (Context.IsMainPlayer)
            {
                // Must be a user set date (default Wednesday), the player's fishing level >= 3 and the bridge must be fixed on the beach
                if (!Game1.MasterPlayer.mailReceived.Contains("PeacefulEnd.FishingTrawler_WillyIntroducesMurphy") && Game1.MasterPlayer.FishingLevel >= config.minimumFishingLevel && beach.bridgeFixed && todayDayOfWeek == Game1.MasterPlayer.modData[MURPHY_DAY_TO_APPEAR])
                {
                    Monitor.Log($"Sending {Game1.MasterPlayer.Name} intro letter about Murphy!", LogLevel.Trace);
                    Helper.Content.AssetEditors.Add(new CustomMail());
                    Game1.MasterPlayer.mailbox.Add("PeacefulEnd.FishingTrawler_WillyIntroducesMurphy");
                }

                // Must be a user set island date (default Satuday), met Murphy and Ginger Island's resort must be built
                IslandSouth resort = Game1.getLocationFromName("IslandSouth") as IslandSouth;
                if (!Game1.MasterPlayer.mailReceived.Contains("PeacefulEnd.FishingTrawler_MurphyGingerIsland") && Game1.MasterPlayer.mailReceived.Contains("PeacefulEnd.FishingTrawler_WillyIntroducesMurphy") && resort.resortRestored && todayDayOfWeek == Game1.MasterPlayer.modData[MURPHY_DAY_TO_APPEAR_ISLAND])
                {
                    Monitor.Log($"Sending {Game1.MasterPlayer.Name} Ginger Island letter about Murphy!", LogLevel.Trace);
                    Helper.Content.AssetEditors.Add(new CustomMail());
                    Game1.MasterPlayer.mailbox.Add("PeacefulEnd.FishingTrawler_MurphyGingerIsland");
                }
            }

            // Reset ownership of boat, deckhands
            mainDeckhand      = null;
            numberOfDeckhands = 0;

            // Set the reward chest
            Vector2 rewardChestPosition = new Vector2(-100, -100);
            Farm    farm = Game1.getLocationFromName("Farm") as Farm;

            rewardChest = farm.objects.Values.FirstOrDefault(o => o.modData.ContainsKey(REWARD_CHEST_DATA_KEY)) as Chest;
            if (rewardChest is null)
            {
                Monitor.Log($"Creating reward chest {rewardChestPosition}", LogLevel.Trace);
                rewardChest = new Chest(true, rewardChestPosition)
                {
                    Name = "Trawler Rewards"
                };
                rewardChest.modData.Add(REWARD_CHEST_DATA_KEY, "true");

                farm.setObject(rewardChestPosition, rewardChest);
            }

            // Create the trawler object for the beach
            var locationContext = (todayDayOfWeek == Game1.MasterPlayer.modData[MURPHY_DAY_TO_APPEAR_ISLAND] ? GameLocation.LocationContext.Island : GameLocation.LocationContext.Default);

            if (todayDayOfWeek == Game1.MasterPlayer.modData[MURPHY_DAY_TO_APPEAR_ISLAND])
            {
                trawlerObject = new Trawler(island);
            }
            else
            {
                trawlerObject = new Trawler(beach);
            }

            // Create the TrawlerReward class
            _trawlerRewards.Value = new TrawlerRewards(rewardChest);

            // Add the surface location
            TrawlerSurface surfaceLocation = new TrawlerSurface(Path.Combine(ModResources.assetFolderPath, "Maps", "FishingTrawler.tmx"), TRAWLER_SURFACE_LOCATION_NAME)
            {
                IsOutdoors = true, IsFarm = false, locationContext = locationContext
            };

            Game1.locations.Add(surfaceLocation);

            // Add the hull location
            TrawlerHull hullLocation = new TrawlerHull(Path.Combine(ModResources.assetFolderPath, "Maps", "TrawlerHull.tmx"), TRAWLER_HULL_LOCATION_NAME)
            {
                IsOutdoors = false, IsFarm = false, locationContext = locationContext
            };

            Game1.locations.Add(hullLocation);

            // Add the cabin location
            TrawlerCabin cabinLocation = new TrawlerCabin(Path.Combine(ModResources.assetFolderPath, "Maps", "TrawlerCabin.tmx"), TRAWLER_CABIN_LOCATION_NAME)
            {
                IsOutdoors = false, IsFarm = false, locationContext = locationContext
            };

            Game1.locations.Add(cabinLocation);

            // Verify our locations were added and establish our location variables
            _trawlerHull.Value    = Game1.getLocationFromName(TRAWLER_HULL_LOCATION_NAME) as TrawlerHull;
            _trawlerSurface.Value = Game1.getLocationFromName(TRAWLER_SURFACE_LOCATION_NAME) as TrawlerSurface;
            _trawlerCabin.Value   = Game1.getLocationFromName(TRAWLER_CABIN_LOCATION_NAME) as TrawlerCabin;
        }