/// <summary>Generates specific spawn times for a list of objects and adds them to the Utility.TimedSpawns list.</summary> /// <param name="objects">A list of saved objects to be spawned during the current in-game day.</param> /// <param name="data">The FarmData from which these saved objects were generated.</param> /// <param name="area">The SpawnArea for which these saved objects were generated.</param> public static void PopulateTimedSpawnList(List <SavedObject> objects, FarmData data, SpawnArea area) { List <TimedSpawn> timedSpawns = new List <TimedSpawn>(); //the list of fully processed objects and associated data Dictionary <int, int> possibleTimes = new Dictionary <int, int>(); //a dictionary of valid spawn times (keys) and the number of objects assigned to them (values) if (area.SpawnTiming == null) //if the SpawnTiming setting is null { possibleTimes.Add(600, 0); //spawn everything at 6:00AM } else { for (StardewTime x = area.SpawnTiming.StartTime; x <= area.SpawnTiming.EndTime; x++) //for each 10-minute time from StartTime to EndTime { possibleTimes.Add(x, 0); //add this time to the list } } foreach (SavedObject obj in objects) //for each provided object { int index = RNG.Next(0, possibleTimes.Count); //randomly select an index for a valid time obj.SpawnTime = possibleTimes.Keys.ElementAt(index); //assign the time to this object timedSpawns.Add(new TimedSpawn(obj, data, area)); //add this object to the processed list possibleTimes[obj.SpawnTime]++; //increment the number of objects assigned to this time if (area.SpawnTiming.MaximumSimultaneousSpawns.HasValue && area.SpawnTiming.MaximumSimultaneousSpawns.Value <= possibleTimes[obj.SpawnTime]) //if "max spawns" exists and has been reached for this time { possibleTimes.Remove(obj.SpawnTime); //remove this time from the list } else if (area.SpawnTiming.MinimumTimeBetweenSpawns.HasValue && area.SpawnTiming.MinimumTimeBetweenSpawns.Value > 10) //if "time between" exists and is significant { int between = (area.SpawnTiming.MinimumTimeBetweenSpawns.Value - 10) / 10; //get the number of other possible times to remove before/after the selected time StardewTime minTime = obj.SpawnTime; //the earliest time to be removed from the list StardewTime maxTime = obj.SpawnTime; //the latest time to be removed from the list for (int x = 0; x < between; x++) //for each adjacent time to be removed { minTime--; //select the previous time maxTime++; //select the next time } for (int x = possibleTimes.Count - 1; x >= 0; x--) //for each possible time (looping backward for removal purposes) { int time = possibleTimes.Keys.ElementAt(x); if (time != obj.SpawnTime && time >= minTime && time <= maxTime) //if this time isn't the selected time, and is within the range of minTime and maxTime { possibleTimes.Remove(time); //remove it from the list } } } if (possibleTimes.Count <= 0) //if no valid spawn times are left { break; //skip the rest of the objects } } TimedSpawns.Add(timedSpawns); //add the processed list of timed spawns to Utility.TimedSpawns }
/// <summary>Validates a single instance of farm data, correcting obsolete/invalid settings automatically.</summary> /// <param name="config">The contents of a single config file to be validated.</param> /// <param name="pack">The content pack associated with this config data; null if the file was from this mod's own data folder.</param> public static void ValidateFarmData(FarmConfig config, IContentPack pack) { if (pack != null) { Monitor.Log($"Validating data from content pack: {pack.Manifest.Name}", LogLevel.Trace); } else { Monitor.Log("Validating data from FarmTypeManager/data", LogLevel.Trace); } List <SpawnArea[]> allAreas = new List <SpawnArea[]>(); //a unified list of each "Areas" array in this config file //add each group of spawn areas to the list (unless its config section is null) if (config.Forage_Spawn_Settings != null) { allAreas.Add(config.Forage_Spawn_Settings.Areas); } if (config.Large_Object_Spawn_Settings != null) { allAreas.Add(config.Large_Object_Spawn_Settings.Areas); } if (config.Ore_Spawn_Settings != null) { allAreas.Add(config.Ore_Spawn_Settings.Areas); } if (config.Monster_Spawn_Settings != null) { allAreas.Add(config.Monster_Spawn_Settings.Areas); } Monitor.Log("Checking for duplicate UniqueAreaIDs...", LogLevel.Trace); HashSet <string> IDs = new HashSet <string>(); //a record of all unique IDs encountered during this process //erase any duplicate IDs and record the others in the "IDs" hashset foreach (SpawnArea[] areas in allAreas) //for each "Areas" array in allAreas { foreach (SpawnArea area in areas) //for each area in the current array { if (String.IsNullOrWhiteSpace(area.UniqueAreaID) || area.UniqueAreaID.ToLower() == "null") //if the area ID is null, blank, or the string "null" (to account for user confusion) { continue; //this name will be replaced later, so ignore it for now } if (IDs.Contains(area.UniqueAreaID)) //if this area's ID was already encountered { Monitor.Log($"Duplicate UniqueAreaID found: \"{area.UniqueAreaID}\" will be renamed.", LogLevel.Debug); if (pack != null) //if this config is from a content pack { Monitor.Log($"Content pack: {pack.Manifest.Name}", LogLevel.Info); Monitor.Log($"If this happened after updating another mod, it might cause certain conditions (such as one-time-only spawns) to reset in that area.", LogLevel.Debug); } area.UniqueAreaID = ""; //erase this area's ID, marking it for replacement } else //if this ID is unique so far { IDs.Add(area.UniqueAreaID); //add the area to the ID set } } } Monitor.Log("Assigning new UniqueAreaIDs to any blanks or duplicates...", LogLevel.Trace); string newName; //temp storage for a new ID while it's created/tested int newNumber; //temp storage for the numeric part of a new ID //create new IDs for any empty ones foreach (SpawnArea[] areas in allAreas) //for each "Areas" array in allAreas { foreach (SpawnArea area in areas) //for each area in the current array { if (String.IsNullOrWhiteSpace(area.UniqueAreaID) || area.UniqueAreaID.ToLower() == "null") //if the area ID is null, blank, or the string "null" (to account for user confusion) { //create a new name, based on which type of area this is newName = area.MapName; if (area is ForageSpawnArea) { newName += " forage area "; } else if (area is LargeObjectSpawnArea) { newName += " large object area "; } else if (area is OreSpawnArea) { newName += " ore area "; } else if (area is MonsterSpawnArea) { newName += " monster area "; } else { newName += " area "; } newNumber = 1; while (IDs.Contains(newName + newNumber)) //if this ID wouldn't be unique { newNumber++; //increment and try again } area.UniqueAreaID = newName + newNumber; //apply the new unique ID Monitor.Log($"New UniqueAreaID assigned: {area.UniqueAreaID}", LogLevel.Trace); } IDs.Add(area.UniqueAreaID); //the ID is finalized, so add it to the set of encountered IDs } } //confirm that any paired min/max settings are in the correct order foreach (SpawnArea[] areas in allAreas) //for each "Areas" array in allAreas { foreach (SpawnArea area in areas) //for each area in the current array { if (area.MinimumSpawnsPerDay > area.MaximumSpawnsPerDay) //if the min and max are in the wrong order { //swap min and max int temp = area.MinimumSpawnsPerDay; area.MinimumSpawnsPerDay = area.MaximumSpawnsPerDay; area.MaximumSpawnsPerDay = temp; Monitor.Log($"Swapping minimum and maximum spawns per day for this area: {area.UniqueAreaID}", LogLevel.Trace); } if (area.SpawnTiming.StartTime > area.SpawnTiming.EndTime) //if start and end are in the wrong order { //swap start and end StardewTime temp = area.SpawnTiming.StartTime; area.SpawnTiming.StartTime = area.SpawnTiming.EndTime; area.SpawnTiming.EndTime = temp; Monitor.Log($"Swapping StartTime and EndTime in the SpawnTiming settings for this area: {area.UniqueAreaID}", LogLevel.Trace); } } } //detect invalid sound names and warn the user //NOTE: this will not remove the invalid name, in case the problem is related to custom sound loading foreach (SpawnArea[] areas in allAreas) //for each "Areas" array in allAreas { foreach (SpawnArea area in areas) //for each area in the current array { if (area.SpawnTiming.SpawnSound != null && area.SpawnTiming.SpawnSound.Trim() != "") //if a SpawnSound has been provided for this area { try { Game1.soundBank.GetCue(area.SpawnTiming.SpawnSound); //test whether this sound exists by retrieving it from the game's soundbank } catch //if an exception is thrown while retrieving the sound { Monitor.Log($"This spawn sound could not be found: {area.SpawnTiming.SpawnSound}", LogLevel.Debug); Monitor.Log($"Please make sure the sound's name is spelled and capitalized correctly. Sound names are case-sensitive.", LogLevel.Debug); Monitor.Log($"Area: {area.UniqueAreaID}", LogLevel.Debug); if (pack != null) //if this file is from a content pack { Monitor.Log($"Content pack: {pack.Manifest.Name}", LogLevel.Debug); } else //if this file is from FarmTypeManager/data { Monitor.Log($"File: FarmTypeManager/data/{Constants.SaveFolderName}.json", LogLevel.Debug); } } } } } if (pack != null) { Monitor.Log($"Validation complete for content pack: {pack.Manifest.Name}", LogLevel.Trace); } else { Monitor.Log("Validation complete for data from FarmTypeManager/data", LogLevel.Trace); } return; }
/// <summary> /// Gets a more detailed string representation of the time. /// </summary> /// <param name="Minutes"></param> /// <returns></returns> public static string GetVerboseTimeString(int Minutes) { StardewTime s = new StardewTime(Minutes); return(s.GetVerboseString()); }