[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. }
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; }