/// <summary>Generates a list of all valid tiles for object spawning in the provided SpawnArea.</summary>
            /// <param name="area">A SpawnArea listing an in-game map name and the valid regions/terrain within it that may be valid spawn points.</param>
            /// <param name="quarryTileIndex">The list of quarry tile indices for this spawn process.</param>
            /// <param name="customTileIndex">The list of custom tile indices for this spawn process.</param>
            /// <param name="isLarge">True if the objects to be spawned are 2x2 tiles in size, otherwise false (1 tile).</param>
            /// <returns>A completed list of all valid tile coordinates for this spawn process in this SpawnArea.</returns>
            public static List <Vector2> GenerateTileList(SpawnArea area, InternalSaveData save, int[] quarryTileIndex, int[] customTileIndex, bool isLarge)
            {
                List <Vector2> validTiles = new List <Vector2>();                  //list of all open, valid tiles for new spawns on the current map

                foreach (string type in area.AutoSpawnTerrainTypes)                //loop to auto-detect valid tiles based on various types of terrain
                {
                    if (type.Equals("quarry", StringComparison.OrdinalIgnoreCase)) //add tiles matching the "quarry" tile index list
                    {
                        validTiles.AddRange(Utility.GetTilesByIndex(area, quarryTileIndex, isLarge));
                    }
                    else if (type.Equals("custom", StringComparison.OrdinalIgnoreCase)) //add tiles matching the "custom" tile index list
                    {
                        validTiles.AddRange(Utility.GetTilesByIndex(area, customTileIndex, isLarge));
                    }
                    else  //add any tiles with properties matching "type" (e.g. tiles with the "Diggable" property, "Grass" type, etc; if the "type" is "All", this will just add every valid tile)
                    {
                        validTiles.AddRange(Utility.GetTilesByProperty(area, type, isLarge));
                    }
                }
                foreach (string include in area.IncludeAreas) //check for valid tiles in each "include" zone for the area
                {
                    validTiles.AddRange(Utility.GetTilesByVectorString(area, include, isLarge));
                }

                if (area is LargeObjectSpawnArea objArea && objArea.FindExistingObjectLocations)    //if this area is the large object type and is set to use existing object locations
                {
                    if (save.ExistingObjectLocations.ContainsKey(area.UniqueAreaID))                //if this area has save data for existing locations
                    {
                        foreach (string include in save.ExistingObjectLocations[area.UniqueAreaID]) //check each saved "include" string for the area
                        {
                            validTiles.AddRange(Utility.GetTilesByVectorString(area, include, isLarge));
                        }
                    }
                    else //if this area has not generated any save dat for existing locations yet (note: this *shouldn't* be reachable)
                    {
                        Monitor.Log($"Issue: This area never saved its object location data: {area.UniqueAreaID}", LogLevel.Info);
                        Monitor.Log($"FindExistingObjectLocations will not function for this area. Please report this to the mod's author.", LogLevel.Info);
                    }
                }

                validTiles = validTiles.Distinct().ToList();                                               //remove any duplicate tiles from the list

                foreach (string exclude in area.ExcludeAreas)                                              //check for valid tiles in each "exclude" zone for the area (validity isn't technically relevant here, but simpler to code, and tiles' validity cannot currently change during this process)
                {
                    List <Vector2> excludedTiles = Utility.GetTilesByVectorString(area, exclude, isLarge); //get list of valid tiles in the excluded area
                    validTiles.RemoveAll(excludedTiles.Contains);                                          //remove any previously valid tiles that match the excluded area
                }

                return(validTiles);
            }
示例#2
0
        public FarmConfig()
        {
            //basic on/off toggles
            ForageSpawnEnabled      = false;
            LargeObjectSpawnEnabled = false;
            OreSpawnEnabled         = false;

            //settings for each generation type (assigned in the constructor for each of these "Settings" objects; see those for details)
            Forage_Spawn_Settings       = new ForageSettings();
            Large_Object_Spawn_Settings = new LargeObjectSettings();
            Ore_Spawn_Settings          = new OreSettings();

            //a list of every tilesheet index commonly used by "quarry" tiles on maps, e.g. the vanilla hilltop (mining) farm
            //these should be compared to [an instance of GameLocation].getTileIndexAt(x, y, "Back")
            QuarryTileIndex = new int[] { 556, 558, 583, 606, 607, 608, 630, 635, 636, 680, 681, 685 };
            //NOTE: swap in the following code to cover more tiles, e.g. the grassy edges of the "quarry" dirt; this tends to cover too much ground and weird spots, though, such as the farm's cave entrance
            //{ 556, 558, 583, 606, 607, 608, 630, 631, 632, 633, 634, 635, 636, 654, 655, 656, 657, 658, 659, 679, 680, 681, 682, 683, 684, 685, 704, 705, 706, 707 };

            //internal save data for things not normally tracked by Stardew
            Internal_Save_Data = new InternalSaveData();
        }
示例#3
0
            /// <summary>Check each saved object with an expiration setting, respawning them if they were removed overnight (e.g. by the weekly forage removal process).</summary>
            /// <param name="save">The save data to the checked.</param>
            public static void ReplaceProtectedSpawnsOvernight(InternalSaveData save)
            {
                int missing   = 0; //# of objects missing
                int blocked   = 0; //# of objects that could not respawn due to blocked locations
                int respawned = 0; //# of objects respawned
                int unloaded  = 0; //# of objects skipped due to missing (unloaded) or invalid map names

                foreach (SavedObject saved in save.SavedObjects)
                {
                    if (saved.DaysUntilExpire == null) //if the object's expiration setting is null
                    {
                        continue;                      //skip to the next object
                    }

                    if (saved.Type == SavedObject.ObjectType.LargeObject)             //if this is a large object
                    {
                        Farm farm = Game1.getLocationFromName(saved.MapName) as Farm; //get the specified location & treat it as a farm (null otherwise)

                        if (farm == null)                                             //if this isn't a valid map
                        {
                            unloaded++;                                               //increment unloaded tracker
                            //note: don't remove the object's save data; this might be a temporary issue, e.g. a map that didn't load correctly
                            continue;                                                 //skip to the next object
                        }

                        bool stillExists = false; //does this large object still exist?

                        //WARNING: this section accesses SDV "Net" objects; it does not edit them directly, but should be suspected if inconsistent errors occur
                        foreach (ResourceClump clump in farm.resourceClumps)                                                              //for each clump (a.k.a. large object) on this map
                        {
                            if (clump.tile.X == saved.Tile.X && clump.tile.Y == saved.Tile.Y && clump.parentSheetIndex.Value == saved.ID) //if this clump's location & ID match the saved object
                            {
                                stillExists = true;
                                break; //skip the rest of these clumps
                            }
                        }
                        //end of WARNING

                        if (!stillExists) //if the object no longer exists
                        {
                            missing++;    //increment missing tracker

                            //if the object's tiles are not unoccupied
                            if (!farm.isTileOccupiedForPlacement(saved.Tile) && !farm.isTileOccupiedForPlacement(new Vector2(saved.Tile.X + 1, saved.Tile.Y)) && !farm.isTileOccupiedForPlacement(new Vector2(saved.Tile.X, saved.Tile.Y + 1)) && !farm.isTileOccupiedForPlacement(new Vector2(saved.Tile.X + 1, saved.Tile.Y + 1)))
                            {
                                SpawnLargeObject(saved.ID, farm, saved.Tile); //respawn it
                                respawned++;                                  //increment respawn tracker
                            }
                            else //the tiles are occupied
                            {
                                blocked++; //increment obstruction tracker
                            }
                        }
                    }
                    else //if this is forage or ore
                    {
                        GameLocation location = Game1.getLocationFromName(saved.MapName); //get the object's location

                        if (location == null) //if the map wasn't found
                        {
                            unloaded++;       //increment unloaded tracker
                            //note: don't remove the object's save data; this might be a temporary issue, e.g. a map that didn't load correctly
                            continue;         //skip to the next object
                        }

                        StardewValley.Object realObject = location.getObjectAtTile((int)saved.Tile.X, (int)saved.Tile.Y); //get the object at the saved location

                        if (realObject == null)                                                                           //if the object no longer exists
                        {
                            missing++;                                                                                    //increment missing object tracker

                            if (!location.isTileOccupiedForPlacement(saved.Tile))                                         //if the object's tile is not occupied
                            {
                                if (saved.Type == SavedObject.ObjectType.Forage)                                          //if this is forage
                                {
                                    SpawnForage(saved.ID, location, saved.Tile);                                          //respawn it
                                }
                                else //if this is ore
                                {
                                    SpawnOre(saved.Name, location, saved.Tile); //respawn it
                                }
                                respawned++;                                    //increment respawn tracker
                            }
                            else //if the object's tile is occupied
                            {
                                blocked++; //increment obstruction tracker
                            }
                        }
                    }
                }

                Monitor.VerboseLog($"Missing objects: {missing}. Respawned: {respawned}. Not respawned due to obstructions: {blocked}. Skipped due to missing maps: {unloaded}.");
            }
示例#4
0
            /// <summary>Check each saved object with an expiration setting, respawning them if they were removed after being saved (e.g. by the weekly forage removal process).</summary>
            /// <param name="save">The save data to the checked.</param>
            public static void ReplaceProtectedSpawns(InternalSaveData save)
            {
                int missing     = 0; //# of objects missing
                int blocked     = 0; //# of objects that could not respawn due to blocked locations
                int respawned   = 0; //# of objects respawned
                int unloaded    = 0; //# of objects skipped due to missing (unloaded) or invalid map names
                int uninstalled = 0; //# of objects skipped due to missing object data, generally caused by removed mods

                foreach (SavedObject saved in save.SavedObjects)
                {
                    if (saved.DaysUntilExpire == null) //if the object's expiration setting is null
                    {
                        continue;                      //skip to the next object
                    }

                    GameLocation location = Game1.getLocationFromName(saved.MapName); //get the object's location

                    if (location == null)                                             //if the map wasn't found
                    {
                        unloaded++;                                                   //increment unloaded tracker
                        continue;                                                     //skip to the next object
                    }

                    if (saved.Type == SavedObject.ObjectType.Monster) //if this is a monster
                    {
                        missing++;                                    //increment missing tracker (note: monsters should always be removed overnight)

                        //this mod should remove all of its monsters overnight, so respawn this monster without checking for its existence
                        int?newID = SpawnMonster(saved.MonType, location, saved.Tile, "[No Area ID: Respawning previously saved monster.]"); //respawn the monster and get its new ID (null if spawn failed)
                        if (newID.HasValue)                                                                                                  //if a monster ID was generated
                        {
                            saved.ID = newID.Value;                                                                                          //update this monster's saved ID
                            respawned++;                                                                                                     //increment respawn tracker
                        }
                        else //if spawn failed (presumably due to obstructions)
                        {
                            blocked++; //increment obstruction tracker
                        }
                    }
                    else if (saved.Type == SavedObject.ObjectType.LargeObject) //if this is a large object
                    {
                        IEnumerable <TerrainFeature> resourceClumps = null;    //a list of large objects at this location
                        if (location is Farm farm)
                        {
                            resourceClumps = farm.resourceClumps.ToList(); //use the farm's clump list
                        }
                        else if (location is MineShaft mine)
                        {
                            resourceClumps = mine.resourceClumps.ToList(); //use the mine's clump list
                        }
                        else
                        {
                            resourceClumps = location.largeTerrainFeatures.OfType <LargeResourceClump>(); //use this location's large resource clump list
                        }

                        bool stillExists = false;                        //does this large object still exist?

                        foreach (TerrainFeature clump in resourceClumps) //for each of this location's large objects
                        {
                            if (clump is ResourceClump smallClump)
                            {
                                if (smallClump.tile.X == saved.Tile.X && smallClump.tile.Y == saved.Tile.Y && smallClump.parentSheetIndex.Value == saved.ID) //if this clump's location & ID match the saved object
                                {
                                    stillExists = true;
                                    break; //stop searching the clump list
                                }
                            }
                            else if (clump is LargeResourceClump largeClump)
                            {
                                if (largeClump.Clump.Value.tile.X == saved.Tile.X && largeClump.Clump.Value.tile.Y == saved.Tile.Y && largeClump.Clump.Value.parentSheetIndex.Value == saved.ID) //if this clump's location & ID match the saved object
                                {
                                    stillExists = true;
                                    break; //stop searching the clump list
                                }
                            }
                        }

                        if (!stillExists)                                               //if the object no longer exists
                        {
                            missing++;                                                  //increment missing tracker

                            if (IsTileValid(location, saved.Tile, saved.Size, "High"))  //if the object's tile is valid for large object placement (defaulting to "high" strictness)
                            {
                                SpawnLargeObject(saved.ID.Value, location, saved.Tile); //respawn the object
                                respawned++;                                            //increment respawn tracker
                            }
                            else //if the object's tile is invalid
                            {
                                blocked++; //increment obstruction tracker
                            }
                        }
                    }
                    else if (saved.Type == SavedObject.ObjectType.Item) //if this is a forage item
                    {
                        missing++;                                      //increment missing tracker (note: items should always be removed overnight)

                        //this mod should remove all of its forage items overnight, so respawn this item without checking for its existence
                        if (IsTileValid(location, saved.Tile, new Point(1, 1), "Medium") && !location.terrainFeatures.ContainsKey(saved.Tile)) //if the item's tile is clear enough to respawn
                        {
                            //update this item's ID, in case it changed due to other mods
                            string[] categoryAndName = saved.Name.Split(':');
                            int?     newID           = GetItemID(categoryAndName[0], categoryAndName[1]);

                            if (newID.HasValue)                           //if a new ID was successfully generated
                            {
                                respawned++;                              //increment respawn tracker
                                saved.ID = newID;                         //save the new ID
                                SpawnForage(saved, location, saved.Tile); //respawn the item
                            }
                            else //if a new ID could not be generated
                            {
                                uninstalled++; //increment uninstalled mod tracker
                                Monitor.LogOnce($"Couldn't find a valid ID for a previously saved forage item. Item name: {saved.Name}", LogLevel.Trace);
                            }
                        }
                        else //if this object's tile is obstructed
                        {
                            blocked++; //increment obstruction tracker
                        }
                    }
                    else if (saved.Type == SavedObject.ObjectType.Container) //if this is a container
                    {
                        missing++;                                           //increment missing tracker (note: chests should always be removed overnight)

                        //this mod should remove all of its containers overnight, so respawn this container without checking for its existence
                        if (IsTileValid(location, saved.Tile, new Point(1, 1), "Medium")) //if the container's tile is clear enough to respawn
                        {
                            respawned++;                                                  //increment respawn tracker
                            SpawnForage(saved, location, saved.Tile);                     //respawn the container
                        }
                        else //if this object's tile is obstructed
                        {
                            blocked++; //increment obstruction tracker
                        }
                    }
                    else if (saved.Type == SavedObject.ObjectType.DGA)                                                         //if this is a DGA item
                    {
                        StardewValley.Object realObject    = location.getObjectAtTile((int)saved.Tile.X, (int)saved.Tile.Y);   //get the object at the saved location (if any)
                        Furniture            realFurniture = location.GetFurnitureAt(saved.Tile);                              //get the furniture at the saved location (if any)
                        bool featureExists = location.terrainFeatures.TryGetValue(saved.Tile, out TerrainFeature realFeature); //try to get a terrain feature at this location

                        if (DGAItemAPI == null)                                                                                //if DGA isn't available
                        {
                            uninstalled++;                                                                                     //increment uninstalled mod tracker
                            Monitor.LogOnce($"The interface for Dynamic Game Assets (DGA) is unavailable, so a DGA item couldn't be respawned from save data.", LogLevel.Trace);
                        }
                        else if ((realObject == null || DGAItemAPI.GetDGAItemId(realObject) != saved.Name) && //if a matching DGA object is NOT here
                                 (realFurniture == null || DGAItemAPI.GetDGAItemId(realFurniture) != saved.Name) && //AND a matching DGA furniture is NOT here
                                 (!featureExists || realFeature is not PlacedItem placed || placed.Item == null || DGAItemAPI.GetDGAItemId(placed.Item) != saved.Name)) //AND a matching DGA item is NOT here
                        {
                            missing++;                                                                                                                                  //increment missing object tracker

                            if (IsTileValid(location, saved.Tile, new Point(1, 1), "Medium"))                                                                           //if the item's tile is clear enough to respawn
                            {
                                respawned++;                                                                                                                            //increment respawn tracker
                                SpawnForage(saved, location, saved.Tile);                                                                                               //respawn the DGA item
                            }
                            else //if the object's tile is obstructed
                            {
                                blocked++; //increment obstruction tracker
                            }
                        }
                    }
                    else //if this is forage or ore
                    {
                        StardewValley.Object realObject = location.getObjectAtTile((int)saved.Tile.X, (int)saved.Tile.Y); //get the object at the saved location

                        if (realObject == null)                                               //if the object no longer exists
                        {
                            missing++;                                                        //increment missing object tracker

                            if (IsTileValid(location, saved.Tile, new Point(1, 1), "Medium")) //if the object's tile is clear enough to respawn
                            {
                                if (saved.Type == SavedObject.ObjectType.Object)              //if this is a forage object
                                {
                                    if (saved.Name != null)                                   //if this forage was originally assigned a name
                                    {
                                        //update this forage's ID, in case it changed due to other mods

                                        if (saved.Name.Contains(':')) //if this is "category:name"
                                        {
                                            string[] categoryAndName = saved.Name.Split(':');
                                            saved.ID = GetItemID(categoryAndName[0], categoryAndName[1]);
                                        }
                                        else //if this is just an object name
                                        {
                                            saved.ID = GetItemID("object", saved.Name);
                                        }
                                    }

                                    if (saved.ID.HasValue)                        //if a valid ID was found for this object
                                    {
                                        respawned++;                              //increment respawn tracker
                                        SpawnForage(saved, location, saved.Tile); //respawn it
                                    }
                                    else
                                    {
                                        uninstalled++; //increment uninstalled mod tracker
                                        Monitor.LogOnce($"Couldn't find a valid ID for a previously saved forage object. Object name: {saved.Name}", LogLevel.Trace);
                                    }
                                }
                                else //if this is ore
                                {
                                    respawned++; //increment respawn tracker
                                    SpawnOre(saved.Name, location, saved.Tile); //respawn it
                                }
                            }
                            else //if the object's tile is occupied
                            {
                                blocked++; //increment obstruction tracker
                            }
                        }
                    }
                }

                Monitor.Log($"Missing objects: {missing}. Respawned: {respawned}. Not respawned due to obstructions: {blocked}. Skipped due to missing maps: {unloaded}. Skipped due to missing item types: {uninstalled}.", LogLevel.Trace);
            }
示例#5
0
            /// <summary>Generates a list of all valid tiles for object spawning in the provided SpawnArea.</summary>
            /// <param name="area">The SpawnArea that defines which tiles are valid for selection.</param>
            /// <param name="location">The specific game location to be checked for valid tiles.</param>
            /// <param name="quarryTileIndex">The list of valid "quarry" tile indices for this spawn process.</param>
            /// <param name="customTileIndex">The list of valid "custom" tile indices for this spawn process.</param>
            /// <returns>A completed list of all valid tile coordinates for this spawn process in this SpawnArea.</returns>
            public static List <Vector2> GenerateTileList(SpawnArea area, GameLocation location, InternalSaveData save, int[] quarryTileIndex, int[] customTileIndex)
            {
                HashSet <Vector2> validTiles = new HashSet <Vector2>(); //a set of all open, valid tiles for new spawns in the provided area

                //include terrain types
                foreach (string includeType in area.IncludeTerrainTypes)                  //loop to auto-detect valid tiles based on various types of terrain
                {
                    if (includeType == null)                                              //if this terrain type is null
                    {
                        continue;                                                         //skip it
                    }
                    if (includeType.Equals("quarry", StringComparison.OrdinalIgnoreCase)) //add tiles matching the "quarry" tile index list
                    {
                        validTiles.UnionWith(GetTilesByIndex(location, quarryTileIndex));
                    }
                    else if (includeType.Equals("custom", StringComparison.OrdinalIgnoreCase)) //add tiles matching the "custom" tile index list
                    {
                        validTiles.UnionWith(GetTilesByIndex(location, customTileIndex));
                    }
                    else  //add any tiles with properties matching "type" (e.g. tiles with the "Diggable" property, "Grass" type, etc; if the "type" is "All", this will just add every tile)
                    {
                        validTiles.UnionWith(GetTilesByProperty(location, includeType));
                    }
                }

                //include coordinates
                foreach (string includeCoords in area.IncludeCoordinates) //check for tiles in each "include" zone for the area
                {
                    validTiles.UnionWith(GetTilesByVectorString(location, includeCoords));
                }

                //include existing object locations
                if (area is LargeObjectSpawnArea objArea && objArea.FindExistingObjectLocations)    //if this area is the large object type and is set to use existing object locations
                {
                    if (save.ExistingObjectLocations.ContainsKey(area.UniqueAreaID))                //if this area has save data for existing locations
                    {
                        foreach (string include in save.ExistingObjectLocations[area.UniqueAreaID]) //check each saved "include" string for the area
                        {
                            validTiles.UnionWith(GetTilesByVectorString(location, include));
                        }
                    }
                    else //if this area has not generated any save data for existing locations yet (note: this *shouldn't* be reachable)
                    {
                        Monitor.Log($"Issue: This area never saved its object location data: {area.UniqueAreaID}", LogLevel.Info);
                        Monitor.Log($"FindExistingObjectLocations will not function for this area. Please report this to the mod's author.", LogLevel.Info);
                    }
                }

                //exclude terrain types
                foreach (string excludeType in area.ExcludeTerrainTypes)
                {
                    if (excludeType.Equals("quarry", StringComparison.OrdinalIgnoreCase)) //remove tiles matching the "quarry" tile index list
                    {
                        validTiles.ExceptWith(GetTilesByIndex(location, quarryTileIndex));
                    }
                    else if (excludeType.Equals("custom", StringComparison.OrdinalIgnoreCase)) //remove tiles matching the "custom" tile index list
                    {
                        validTiles.ExceptWith(GetTilesByIndex(location, customTileIndex));
                    }
                    else  //remove any tiles with properties matching "type" (e.g. tiles with the "Diggable" property, "Grass" type, etc; if the "type" is "All", this will just remove every tile)
                    {
                        validTiles.ExceptWith(GetTilesByProperty(location, excludeType));
                    }
                }

                //exclude coordinates
                foreach (string excludeCoords in area.ExcludeCoordinates)                   //check for tiles in each "exclude" zone for the area
                {
                    validTiles.ExceptWith(GetTilesByVectorString(location, excludeCoords)); //remove any tiles that match the excluded area
                }

                return(validTiles.ToList()); //convert the set to a list and return it
            }
示例#6
0
            /// <summary>Loads all available data files for the current farm into FarmDataList. Checks the mod's data folder and any relevant content packs.</summary>
            /// <param name="helper">SMAPI interface, used here to access files.</param>
            /// <returns>True if the files loaded successfully; false otherwise.</returns>
            public static void LoadFarmData(IModHelper helper)
            {
                Monitor.Log("Beginning file loading process...", LogLevel.Trace);

                //clear any existing farm data
                FarmDataList = new List <FarmData>();

                FarmConfig       config;        //temp for the current config as it's loaded
                InternalSaveData save;          //temp for the current save as it's loaded

                if (MConfig.EnableContentPacks) //if content packs are enabled
                {
                    //load data from each relevant content pack
                    foreach (IContentPack pack in helper.ContentPacks.GetOwned())
                    {
                        Monitor.Log($"Loading files from content pack: {pack.Manifest.Name}", LogLevel.Trace);

                        //clear each temp object
                        config = null;
                        save   = null;

                        //attempt to load the farm config from this pack
                        try
                        {
                            config = pack.ReadJsonFile <FarmConfig>($"content.json"); //load the content pack's farm config (null if it doesn't exist)
                        }
                        catch (Exception ex)
                        {
                            Monitor.Log($"Warning: This content pack could not be parsed correctly: {pack.Manifest.Name}", LogLevel.Warn);
                            Monitor.Log($"Please edit the content.json file or reinstall the content pack. The auto-generated error message is displayed below:", LogLevel.Warn);
                            Monitor.Log($"----------", LogLevel.Warn);
                            Monitor.Log($"{ex.Message}", LogLevel.Warn);
                            continue; //skip to the next content pack
                        }

                        if (config == null) //no config file found for this farm
                        {
                            Monitor.Log($"Warning: The content.json file for this content pack could not be found: {pack.Manifest.Name}", LogLevel.Warn);
                            Monitor.Log($"Please reinstall the content pack. If you are its author, please create a config file named content.json in the pack's main folder (not the /data/ folder).", LogLevel.Warn);
                            continue; //skip to the next content pack
                        }

                        //attempt to load the save data for this pack and specific farm
                        try
                        {
                            save = pack.ReadJsonFile <InternalSaveData>($"data/{Constants.SaveFolderName}_SaveData.save"); //load the content pack's save data for this farm (null if it doesn't exist)
                        }
                        catch (Exception ex)
                        {
                            Monitor.Log($"Warning: Your farm's save data for this content pack could not be parsed correctly: {pack.Manifest.Name}", LogLevel.Warn);
                            Monitor.Log($"This file will need to be edited or deleted: data/{Constants.SaveFolderName}_SaveData.save", LogLevel.Warn);
                            Monitor.Log($"The content pack will be skipped until this issue is fixed. The auto-generated error message is displayed below:", LogLevel.Warn);
                            Monitor.Log($"----------", LogLevel.Warn);
                            Monitor.Log($"{ex.Message}", LogLevel.Warn);
                            continue; //skip to the next content pack
                        }

                        if (save == null)                  //no save file found for this farm
                        {
                            save = new InternalSaveData(); //load the (built-in) default save settings
                        }

                        ValidateFarmData(config, pack);                                                              //validate certain data in the current file before using it

                        pack.WriteJsonFile($"content.json", config);                                                 //update the content pack's config file
                        pack.WriteJsonFile(Path.Combine("data", $"{Constants.SaveFolderName}_SaveData.save"), save); //create or update the content pack's save file for the current farm

                        if (CheckFileConditions(config, pack, helper))                                               //check file conditions; only use the current data if this returns true
                        {
                            FarmDataList.Add(new FarmData(config, save, pack));                                      //add the config, save, and content pack to the farm data list
                            Monitor.Log("Content pack loaded successfully.", LogLevel.Trace);
                        }
                    }

                    Monitor.Log("All available content packs checked.", LogLevel.Trace);
                }
                else
                {
                    Monitor.Log("Content packs disabled in config.json. Skipping to local files...", LogLevel.Trace);
                }

                //clear each temp object
                config = null;
                save   = null;

                Monitor.Log("Loading files from FarmTypeManager/data", LogLevel.Trace);

                //attempt to load the farm config from this mod's data folder
                //NOTE: this should always be done *after* content packs, because it will end the loading process if an error occurs
                try
                {
                    config = helper.Data.ReadJsonFile <FarmConfig>(Path.Combine("data", $"{Constants.SaveFolderName}.json")); //load the current save's config file (null if it doesn't exist)
                }
                catch (Exception ex)
                {
                    Monitor.Log($"Warning: This file could not be parsed correctly: FarmTypeManager/data/{Constants.SaveFolderName}.json", LogLevel.Warn);
                    Monitor.Log($"Please edit the file, or delete it and reload your farm to generate a new config file.", LogLevel.Warn);
                    Monitor.Log($"Your config file will be skipped until this issue is fixed. The auto-generated error message is displayed below:", LogLevel.Warn);
                    Monitor.Log($"----------", LogLevel.Warn);
                    Monitor.Log($"{ex.Message}", LogLevel.Warn);
                    return; //end this process without adding this set of farm data
                }

                if (config == null) //no config file found for this farm
                {
                    //attempt to load the default.json config file
                    try
                    {
                        config = helper.Data.ReadJsonFile <FarmConfig>(Path.Combine("data", "default.json")); //load the default.json config file (null if it doesn't exist)
                    }
                    catch (Exception ex)
                    {
                        Monitor.Log($"Warning: This file could not be parsed correctly: FarmTypeManager/data/default.json", LogLevel.Warn);
                        Monitor.Log($"Please edit the file, or delete it and reload your farm to generate a new default config file.", LogLevel.Warn);
                        Monitor.Log($"Your config file will be skipped until this issue is fixed. The auto-generated error message is displayed below:", LogLevel.Warn);
                        Monitor.Log($"----------", LogLevel.Warn);
                        Monitor.Log($"{ex.Message}", LogLevel.Warn);
                        return; //end this process without adding this set of farm data
                    }

                    if (config == null)            //no default.json config file
                    {
                        config = new FarmConfig(); //load the (built-in) default config settings
                    }

                    ValidateFarmData(config, null);                                          //validate certain data in the current file before using it

                    helper.Data.WriteJsonFile(Path.Combine("data", "default.json"), config); //create or update the default.json config file
                }

                //attempt to load the save data for this farm
                try
                {
                    save = helper.Data.ReadJsonFile <InternalSaveData>(Path.Combine("data", $"{Constants.SaveFolderName}_SaveData.save")); //load the mod's save data for this farm (null if it doesn't exist)
                }
                catch (Exception ex)
                {
                    Monitor.Log($"Warning: This file could not be parsed correctly: FarmTypeManager/data/{Constants.SaveFolderName}_SaveData.save", LogLevel.Warn);
                    Monitor.Log($"Please edit the file, or delete it and reload your farm to generate a new savedata file.", LogLevel.Warn);
                    Monitor.Log($"Your config file will be skipped until this issue is fixed. The auto-generated error message is displayed below:", LogLevel.Warn);
                    Monitor.Log($"----------", LogLevel.Warn);
                    Monitor.Log($"{ex.Message}", LogLevel.Warn);
                    return; //end this process without adding this set of farm data
                }

                if (save == null)                  //no save file found for this farm
                {
                    save = new InternalSaveData(); //load the (built-in) default save settings
                }

                ValidateFarmData(config, null);                                                                     //validate certain data in the current file before using it

                helper.Data.WriteJsonFile(Path.Combine("data", $"{Constants.SaveFolderName}.json"), config);        //create or update the config file for the current farm
                helper.Data.WriteJsonFile(Path.Combine("data", $"{Constants.SaveFolderName}_SaveData.save"), save); //create or update this config's save file for the current farm

                if (CheckFileConditions(config, null, helper))                                                      //check file conditions; only use the current data if this returns true
                {
                    FarmDataList.Add(new FarmData(config, save, null));                                             //add the config, save, and a *null* content pack to the farm data list
                    Monitor.Log("FarmTypeManager/data farm data loaded successfully.", LogLevel.Trace);
                }
            }
示例#7
0
            /// <summary>Checks whether objects should be spawned in a given SpawnArea based on its ExtraConditions settings.</summary>
            /// <param name="area">The SpawnArea to be checked.</param>
            /// <param name="save">The mod's save data for the current farm and config file.</param>
            /// <returns>True if objects are allowed to spawn. False if any extra conditions should prevent spawning.</returns>
            public static bool CheckExtraConditions(SpawnArea area, InternalSaveData save)
            {
                Monitor.Log($"Checking extra conditions for this area...", LogLevel.Trace);

                //check years
                if (area.ExtraConditions.Years != null && area.ExtraConditions.Years.Length > 0)
                {
                    Monitor.Log("Years condition(s) found. Checking...", LogLevel.Trace);

                    bool validYear = false;

                    foreach (string year in area.ExtraConditions.Years)
                    {
                        try                                                                                                                       //watch for errors related to string parsing
                        {
                            if (year.Equals("All", StringComparison.OrdinalIgnoreCase) || year.Equals("Any", StringComparison.OrdinalIgnoreCase)) //if "all" or "any" is listed
                            {
                                validYear = true;
                                break;                                           //skip the rest of the "year" checks
                            }
                            else if (year.Contains("+"))                         //contains a plus, so parse it as a single year & any years after it, e.g. "2+"
                            {
                                string[] split   = year.Split('+');              //split into separate strings around the plus symbol
                                int      minYear = Int32.Parse(split[0].Trim()); //get the number to the left of the plus (trim whitespace)

                                if (minYear <= Game1.year)                       //if the current year is within the specified range
                                {
                                    validYear = true;
                                    break; //skip the rest of the "year" checks
                                }
                            }
                            else if (year.Contains("-"))                            //contains a delimiter, so parse it as a range of years, e.g. "1-10"
                            {
                                string[] split   = year.Split('-');                 //split into separate strings for each delimiter
                                int      minYear = Int32.Parse(split[0].Trim());    //get the first number (trim whitespace)
                                int      maxYear = Int32.Parse(split[1].Trim());    //get the second number (trim whitespace)

                                if (minYear <= Game1.year && maxYear >= Game1.year) //if the current year is within the specified range
                                {
                                    validYear = true;
                                    break; //skip the rest of the "year" checks
                                }
                            }
                            else //parse as a single year, e.g. "1"
                            {
                                int yearNum = Int32.Parse(year.Trim()); //convert to a number

                                if (yearNum == Game1.year) //if it matches the current year
                                {
                                    validYear = true;
                                    break; //skip the rest of the "year" checks
                                }
                            }
                        }
                        catch (Exception)
                        {
                            Monitor.Log($"Issue: This part of the extra condition \"Years\" for the {area.MapName} map isn't formatted correctly: \"{year}\"", LogLevel.Info);
                        }
                    }

                    if (validYear)
                    {
                        Monitor.Log("The current year matched a setting. Spawn allowed.", LogLevel.Trace);
                    }
                    else
                    {
                        Monitor.Log("The current year did NOT match any settings. Spawn disabled.", LogLevel.Trace);
                        return(false);
                    }
                }

                //check seasons
                if (area.ExtraConditions.Seasons != null && area.ExtraConditions.Seasons.Length > 0)
                {
                    Monitor.Log("Seasons condition(s) found. Checking...", LogLevel.Trace);

                    bool validSeason = false;

                    foreach (string season in area.ExtraConditions.Seasons)
                    {
                        if (season.Equals("All", StringComparison.OrdinalIgnoreCase) || season.Equals("Any", StringComparison.OrdinalIgnoreCase)) //if "all" or "any" is listed
                        {
                            validSeason = true;
                            break;                                                                       //skip the rest of the "season" checks
                        }
                        else if (season.Equals(Game1.currentSeason, StringComparison.OrdinalIgnoreCase)) //if the current season is listed
                        {
                            validSeason = true;
                            break; //skip the rest of the "season" checks
                        }
                    }

                    if (validSeason)
                    {
                        Monitor.Log("The current season matched a setting. Spawn allowed.", LogLevel.Trace);
                    }
                    else
                    {
                        Monitor.Log("The current season did NOT match any settings. Spawn disabled.", LogLevel.Trace);
                        return(false); //prevent spawning
                    }
                }

                //check days
                if (area.ExtraConditions.Days != null && area.ExtraConditions.Days.Length > 0)
                {
                    Monitor.Log("Days condition(s) found. Checking...", LogLevel.Trace);

                    bool validDay = false;

                    foreach (string day in area.ExtraConditions.Days)
                    {
                        try                                                                                                                     //watch for errors related to string parsing
                        {
                            if (day.Equals("All", StringComparison.OrdinalIgnoreCase) || day.Equals("Any", StringComparison.OrdinalIgnoreCase)) //if "all" or "any" is listed
                            {
                                validDay = true;
                                break;                                          //skip the rest of the "day" checks
                            }
                            else if (day.Contains("+"))                         //contains a plus, so parse it as a single day & any days after it, e.g. "2+"
                            {
                                string[] split  = day.Split('+');               //split into separate strings around the plus symbol
                                int      minDay = Int32.Parse(split[0].Trim()); //get the number to the left of the plus (trim whitespace)

                                if (minDay <= Game1.dayOfMonth)                 //if the current day is within the specified range
                                {
                                    validDay = true;
                                    break; //skip the rest of the "day" checks
                                }
                            }
                            else if (day.Contains("-"))                                       //contains a delimiter, so parse it as a range of dates, e.g. "1-10"
                            {
                                string[] split  = day.Split('-');                             //split into separate strings for each delimiter
                                int      minDay = Int32.Parse(split[0].Trim());               //get the first number (trim whitespace)
                                int      maxDay = Int32.Parse(split[1].Trim());               //get the second number (trim whitespace)

                                if (minDay <= Game1.dayOfMonth && maxDay >= Game1.dayOfMonth) //if the current day is within the specified range
                                {
                                    validDay = true;
                                    break; //skip the rest of the "day" checks
                                }
                            }
                            else //parse as a single date, e.g. "1" or "25"
                            {
                                int dayNum = Int32.Parse(day.Trim()); //convert to a number

                                if (dayNum == Game1.dayOfMonth) //if it matches the current day
                                {
                                    validDay = true;
                                    break; //skip the rest of the "day" checks
                                }
                            }
                        }
                        catch (Exception)
                        {
                            Monitor.Log($"Issue: This part of the extra condition \"Days\" for the {area.MapName} map isn't formatted correctly: \"{day}\"", LogLevel.Info);
                        }
                    }

                    if (validDay)
                    {
                        Monitor.Log("The current day matched a setting. Spawn allowed.", LogLevel.Trace);
                    }
                    else
                    {
                        Monitor.Log("The current day did NOT match any settings. Spawn disabled.", LogLevel.Trace);
                        return(false); //prevent spawning
                    }
                }

                //check yesterday's weather
                if (area.ExtraConditions.WeatherYesterday != null && area.ExtraConditions.WeatherYesterday.Length > 0)
                {
                    Monitor.Log("Yesterday's Weather condition(s) found. Checking...", LogLevel.Trace);

                    bool validWeather = false;

                    foreach (string weather in area.ExtraConditions.WeatherYesterday)                                                               //for each listed weather name
                    {
                        if (weather.Equals("All", StringComparison.OrdinalIgnoreCase) || weather.Equals("Any", StringComparison.OrdinalIgnoreCase)) //if "all" or "any" is listed
                        {
                            validWeather = true;
                            break; //skip the rest of these checks
                        }

                        switch (save.WeatherForYesterday) //compare to yesterday's weather
                        {
                        case Utility.Weather.Sunny:
                        case Utility.Weather.Festival:     //festival and wedding = sunny, as far as this mod is concerned
                        case Utility.Weather.Wedding:
                            if (weather.Equals("Sun", StringComparison.OrdinalIgnoreCase) || weather.Equals("Sunny", StringComparison.OrdinalIgnoreCase) || weather.Equals("Clear", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;

                        case Utility.Weather.Rain:
                            if (weather.Equals("Rain", StringComparison.OrdinalIgnoreCase) || weather.Equals("Rainy", StringComparison.OrdinalIgnoreCase) || weather.Equals("Raining", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;

                        case Utility.Weather.Debris:
                            if (weather.Equals("Wind", StringComparison.OrdinalIgnoreCase) || weather.Equals("Windy", StringComparison.OrdinalIgnoreCase) || weather.Equals("Debris", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;

                        case Utility.Weather.Lightning:
                            if (weather.Equals("Storm", StringComparison.OrdinalIgnoreCase) || weather.Equals("Stormy", StringComparison.OrdinalIgnoreCase) || weather.Equals("Storming", StringComparison.OrdinalIgnoreCase) || weather.Equals("Lightning", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;

                        case Utility.Weather.Snow:
                            if (weather.Equals("Snow", StringComparison.OrdinalIgnoreCase) || weather.Equals("Snowy", StringComparison.OrdinalIgnoreCase) || weather.Equals("Snowing", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;
                        }

                        if (validWeather == true) //if a valid weather condition was listed
                        {
                            break;                //skip the rest of these checks
                        }
                    }


                    if (validWeather)
                    {
                        Monitor.Log("Yesterday's weather matched a setting. Spawn allowed.", LogLevel.Trace);
                    }
                    else
                    {
                        Monitor.Log("Yesterday's weather did NOT match any settings. Spawn disabled.", LogLevel.Trace);
                        return(false); //prevent spawning
                    }
                }

                //check today's weather
                if (area.ExtraConditions.WeatherToday != null && area.ExtraConditions.WeatherToday.Length > 0)
                {
                    Monitor.Log("Today's Weather condition(s) found. Checking...", LogLevel.Trace);

                    bool validWeather = false;

                    foreach (string weather in area.ExtraConditions.WeatherToday)                                                                   //for each listed weather name
                    {
                        if (weather.Equals("All", StringComparison.OrdinalIgnoreCase) || weather.Equals("Any", StringComparison.OrdinalIgnoreCase)) //if "all" or "any" is listed
                        {
                            validWeather = true;
                            break; //skip the rest of these checks
                        }

                        switch (Utility.WeatherForToday()) //compare to today's weather
                        {
                        case Utility.Weather.Sunny:
                        case Utility.Weather.Festival:     //festival and wedding = sunny, as far as this mod is concerned
                        case Utility.Weather.Wedding:
                            if (weather.Equals("Sun", StringComparison.OrdinalIgnoreCase) || weather.Equals("Sunny", StringComparison.OrdinalIgnoreCase) || weather.Equals("Clear", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;

                        case Utility.Weather.Rain:
                            if (weather.Equals("Rain", StringComparison.OrdinalIgnoreCase) || weather.Equals("Rainy", StringComparison.OrdinalIgnoreCase) || weather.Equals("Raining", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;

                        case Utility.Weather.Debris:
                            if (weather.Equals("Wind", StringComparison.OrdinalIgnoreCase) || weather.Equals("Windy", StringComparison.OrdinalIgnoreCase) || weather.Equals("Debris", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;

                        case Utility.Weather.Lightning:
                            if (weather.Equals("Storm", StringComparison.OrdinalIgnoreCase) || weather.Equals("Stormy", StringComparison.OrdinalIgnoreCase) || weather.Equals("Storming", StringComparison.OrdinalIgnoreCase) || weather.Equals("Lightning", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;

                        case Utility.Weather.Snow:
                            if (weather.Equals("Snow", StringComparison.OrdinalIgnoreCase) || weather.Equals("Snowy", StringComparison.OrdinalIgnoreCase) || weather.Equals("Snowing", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;
                        }
                        if (validWeather == true) //if a valid weather condition was listed
                        {
                            break;                //skip the rest of these checks
                        }
                    }

                    if (validWeather)
                    {
                        Monitor.Log("Today's weather matched a setting. Spawn allowed.", LogLevel.Trace);
                    }
                    else
                    {
                        Monitor.Log("Today's weather did NOT match any settings. Spawn disabled.", LogLevel.Trace);
                        return(false); //prevent spawning
                    }
                }

                //check tomorrow's weather
                if (area.ExtraConditions.WeatherTomorrow != null && area.ExtraConditions.WeatherTomorrow.Length > 0)
                {
                    Monitor.Log("Tomorrow's Weather condition(s) found. Checking...", LogLevel.Trace);

                    bool validWeather = false;

                    foreach (string weather in area.ExtraConditions.WeatherTomorrow)                                                                //for each listed weather name
                    {
                        if (weather.Equals("All", StringComparison.OrdinalIgnoreCase) || weather.Equals("Any", StringComparison.OrdinalIgnoreCase)) //if "all" or "any" is listed
                        {
                            validWeather = true;
                            break; //skip the rest of these checks
                        }

                        switch (Game1.weatherForTomorrow) //compare to tomorrow's weather
                        {
                        case (int)Utility.Weather.Sunny:
                        case (int)Utility.Weather.Festival:     //festival and wedding = sunny, as far as this mod is concerned
                        case (int)Utility.Weather.Wedding:
                            if (weather.Equals("Sun", StringComparison.OrdinalIgnoreCase) || weather.Equals("Sunny", StringComparison.OrdinalIgnoreCase) || weather.Equals("Clear", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;

                        case (int)Utility.Weather.Rain:
                            if (weather.Equals("Rain", StringComparison.OrdinalIgnoreCase) || weather.Equals("Rainy", StringComparison.OrdinalIgnoreCase) || weather.Equals("Raining", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;

                        case (int)Utility.Weather.Debris:
                            if (weather.Equals("Wind", StringComparison.OrdinalIgnoreCase) || weather.Equals("Windy", StringComparison.OrdinalIgnoreCase) || weather.Equals("Debris", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;

                        case (int)Utility.Weather.Lightning:
                            if (weather.Equals("Storm", StringComparison.OrdinalIgnoreCase) || weather.Equals("Stormy", StringComparison.OrdinalIgnoreCase) || weather.Equals("Storming", StringComparison.OrdinalIgnoreCase) || weather.Equals("Lightning", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;

                        case (int)Utility.Weather.Snow:
                            if (weather.Equals("Snow", StringComparison.OrdinalIgnoreCase) || weather.Equals("Snowy", StringComparison.OrdinalIgnoreCase) || weather.Equals("Snowing", StringComparison.OrdinalIgnoreCase))
                            {
                                validWeather = true;
                            }
                            break;
                        }

                        if (validWeather == true) //if a valid weather condition was listed
                        {
                            break;                //skip the rest of these checks
                        }
                    }

                    if (validWeather)
                    {
                        Monitor.Log("Tomorrow's weather matched a setting. Spawn allowed.", LogLevel.Trace);
                    }
                    else
                    {
                        Monitor.Log("Tomorrow's weather did NOT match any settings. Spawn disabled.", LogLevel.Trace);
                        return(false); //prevent spawning
                    }
                }

                //check EPU preconditions
                if (area.ExtraConditions.EPUPreconditions != null && area.ExtraConditions.EPUPreconditions.Length > 0)
                {
                    Monitor.Log($"EPU Preconditions found. Checking...", LogLevel.Trace);
                    if (EPUConditionsChecker == null) //if EPU's API is not available
                    {
                        Monitor.LogOnce($"FTM cannot currently access the API for Expanded Preconditions Utility (EPU), but at least one spawn area has EPU preconditions. Those areas will be disabled. Please make sure EPU is installed.", LogLevel.Warn);
                        Monitor.Log($"EPU preconditions could not be checked. Spawn disabled.", LogLevel.Trace);
                        return(false); //prevent spawning
                    }
                    else //if EPU's API is available
                    {
                        try
                        {
                            if (EPUConditionsChecker.CheckConditions(area.ExtraConditions.EPUPreconditions) == true) //if ANY of this area's precondition strings are true
                            {
                                Monitor.Log("At least one EPU precondition string was valid. Spawn allowed.", LogLevel.Trace);
                            }
                            else //if ALL of this area's precondition strings are false
                            {
                                Monitor.Log("All EPU precondition strings were invalid. Spawn disabled.", LogLevel.Trace);
                                return(false); //prevent spawning
                            }
                        }
                        catch (Exception ex)
                        {
                            Monitor.Log($"An error occurred while FTM was using the API for Expanded Preconditions Utility (EPU). Please report this to FTM's developer. Auto-generated error message:", LogLevel.Error);
                            Monitor.Log($"----------", LogLevel.Error);
                            Monitor.Log($"{ex.ToString()}", LogLevel.Error);
                            Monitor.Log($"EPU preconditions could not be checked. Spawn disabled.", LogLevel.Trace);
                            return(false);
                        }
                    }
                }

                //check number of spawns
                //NOTE: it's important that this is the last condition checked, because otherwise it might count down while not actually spawning (i.e. while blocked by another condition)
                if (area.ExtraConditions.LimitedNumberOfSpawns != null)
                {
                    Monitor.Log("Limited Number Of Spawns condition found. Checking...", LogLevel.Trace);
                    if (area.ExtraConditions.LimitedNumberOfSpawns > 0) //if there's at least one spawn day for this area
                    {
                        //if save data already exists for this area
                        if (save.LNOSCounter.ContainsKey(area.UniqueAreaID))
                        {
                            Monitor.Log("Sava data found for this area; checking spawn days counter...", LogLevel.Trace);
                            //if there's still at least one spawn day remaining
                            if ((area.ExtraConditions.LimitedNumberOfSpawns - save.LNOSCounter[area.UniqueAreaID]) > 0)
                            {
                                Monitor.Log($"Spawns remaining (including today): {area.ExtraConditions.LimitedNumberOfSpawns - save.LNOSCounter[area.UniqueAreaID]}. Spawn allowed.", LogLevel.Trace);
                                save.LNOSCounter[area.UniqueAreaID]++; //increment (NOTE: this change needs to be saved at the end of the day)
                            }
                            else //no spawn days remaining
                            {
                                Monitor.Log($"Spawns remaining (including today): {area.ExtraConditions.LimitedNumberOfSpawns - save.LNOSCounter[area.UniqueAreaID]}. Spawn disabled.", LogLevel.Trace);
                                return(false); //prevent spawning
                            }
                        }
                        else //no save file exists for this area; behave as if LNOSCounter == 0
                        {
                            Monitor.Log("No save data found for this area; creating new counter.", LogLevel.Trace);
                            save.LNOSCounter.Add(area.UniqueAreaID, 1); //new counter for this area, starting at 1
                            Monitor.Log($"Spawns remaining (including today): {area.ExtraConditions.LimitedNumberOfSpawns}. Spawn allowed.", LogLevel.Trace);
                        }
                    }
                    else //no spawns remaining
                    {
                        Monitor.Log($"Spawns remaining (including today): {area.ExtraConditions.LimitedNumberOfSpawns}. Spawn disabled.", LogLevel.Trace);
                        return(false); //prevent spawning
                    }
                }

                return(true); //all extra conditions allow for spawning
            }
示例#8
0
            /// <summary>Check each saved object's expiration data, updating counters & removing missing/expired objects from the data and custom classes from the game world.</summary>
            /// <param name="save">The save data to the checked.</param>
            /// <param name="endOfDay">If false, expiration dates will be ignored. Used to temporarily remove custom classes during the day.</param>
            public static void ProcessObjectExpiration(InternalSaveData save, bool endOfDay = true)
            {
                List <SavedObject> objectsToRemove = new List <SavedObject>();                         //objects to remove from saved data after processing (note: do not remove them while looping through them)

                foreach (SavedObject saved in save.SavedObjects)                                       //for each saved object & expiration countdown
                {
                    if (saved.DaysUntilExpire == null && saved.Type != SavedObject.ObjectType.Monster) //if the object's expiration setting is null & it's not a monster
                    {
                        Monitor.VerboseLog($"Removing object data saved with a null expiration setting. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                        objectsToRemove.Add(saved); //mark this for removal from save
                        continue;                   //skip to the next object
                    }

                    GameLocation location = Game1.getLocationFromName(saved.MapName); //get the saved object's location

                    if (location == null)                                             //if this isn't a valid map
                    {
                        Monitor.VerboseLog($"Removing object data saved for a missing location. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                        objectsToRemove.Add(saved); //mark this for removal from save
                        continue;                   //skip to the next object
                    }

                    if (saved.Type == SavedObject.ObjectType.Monster)                                //if this is a monster
                    {
                        bool stillExists = false;                                                    //does this monster still exist?

                        for (int x = location.characters.Count - 1; x >= 0; x--)                     //for each character at this location (looping backward for removal purposes)
                        {
                            if (location.characters[x] is Monster monster && monster.id == saved.ID) //if this is a monster with an ID that matches the saved ID
                            {
                                stillExists = true;
                                if (endOfDay)                                                        //if expirations should be processed
                                {
                                    if (saved.DaysUntilExpire == 1 || saved.DaysUntilExpire == null) //if this should expire tonight (including monsters generated without expiration settings)
                                    {
                                        Monitor.VerboseLog($"Removing expired object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                                        objectsToRemove.Add(saved);     //mark this for removal from save
                                    }
                                    else if (saved.DaysUntilExpire > 1) //if the object should expire, but not tonight
                                    {
                                        saved.DaysUntilExpire--;        //decrease counter by 1
                                    }
                                }

                                if (saved.MonType != null && saved.MonType.Settings.ContainsKey("PersistentHP") && (bool)saved.MonType.Settings["PersistentHP"]) //if the PersistentHP setting is enabled for this monster
                                {
                                    saved.MonType.Settings["CurrentHP"] = monster.Health;                                                                        //save this monster's current HP
                                }

                                location.characters.RemoveAt(x); //remove this monster from the location, regardless of expiration
                                break;                           //stop searching the character list
                            }
                        }

                        if (!stillExists) //if this monster no longer exists
                        {
                            Monitor.VerboseLog($"Removing missing object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                            objectsToRemove.Add(saved); //mark this for removal from save
                        }
                    }
                    else if (saved.Type == SavedObject.ObjectType.ResourceClump) //if this is a resource clump
                    {
                        IEnumerable <TerrainFeature> resourceClumps = null;      //a list of resource clumps at this location
                        if (location is Farm farm)
                        {
                            resourceClumps = farm.resourceClumps.ToList(); //use the farm's clump list
                        }
                        else if (location is MineShaft mine)
                        {
                            resourceClumps = mine.resourceClumps.ToList(); //use the mine's clump list
                        }
                        else
                        {
                            resourceClumps = location.largeTerrainFeatures.OfType <LargeResourceClump>(); //use this location's large resource clump list
                        }

                        TerrainFeature existingObject = null;            //the in-game object, if it currently exists

                        foreach (TerrainFeature clump in resourceClumps) //for each of this location's large objects
                        {
                            if (clump is ResourceClump smallClump)
                            {
                                if (smallClump.tile.X == saved.Tile.X && smallClump.tile.Y == saved.Tile.Y && smallClump.parentSheetIndex.Value == saved.ID) //if this clump's location & ID match the saved object
                                {
                                    existingObject = smallClump;
                                    break; //stop searching the clump list
                                }
                            }
                            else if (clump is LargeResourceClump largeClump)
                            {
                                if (largeClump.Clump.Value.tile.X == saved.Tile.X && largeClump.Clump.Value.tile.Y == saved.Tile.Y && largeClump.Clump.Value.parentSheetIndex.Value == saved.ID) //if this clump's location & ID match the saved object
                                {
                                    existingObject = largeClump;
                                    break; //stop searching the clump list
                                }
                            }
                        }

                        if (existingObject != null)             //if the object still exists
                        {
                            if (endOfDay)                       //if expirations should be processed
                            {
                                if (saved.DaysUntilExpire == 1) //if the object should expire tonight
                                {
                                    Monitor.VerboseLog($"Removing expired object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.Tile.X},{saved.Tile.Y} ({saved.MapName}).");

                                    if (existingObject is ResourceClump clump) //if this is NOT a custom class that always needs removal
                                    {
                                        if (location is Farm farmLoc)
                                        {
                                            farmLoc.resourceClumps.Remove(clump); //remove this object from the farm's resource clumps list
                                        }
                                        else if (location is MineShaft mineLoc)
                                        {
                                            mineLoc.resourceClumps.Remove(clump); //remove this object from the mine's resource clumps list
                                        }
                                    }

                                    objectsToRemove.Add(saved);     //mark object for removal from save
                                }
                                else if (saved.DaysUntilExpire > 1) //if the object should expire, but not tonight
                                {
                                    saved.DaysUntilExpire--;        //decrease counter by 1
                                }
                            }

                            if (existingObject is LargeResourceClump largeClump)  //if this is a custom class that always needs removal
                            {
                                location.largeTerrainFeatures.Remove(largeClump); //remove this object from the large terrain features list (NOTE: this must be done even for unexpired LargeResourceClumps to avoid SDV save errors)
                            }
                        }
                        else //if the object no longer exists
                        {
                            Monitor.VerboseLog($"Removing missing object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                            objectsToRemove.Add(saved); //mark object for removal from save
                        }
                    }
                    else if (saved.Type == SavedObject.ObjectType.Item) //if this is a forage item, i.e. "debris" containing an item
                    {
                        bool stillExists = false;                       //does this item still exist?

                        //if a PlacedItem terrain feature exists at the saved tile & contains an item with a matching name
                        if (location.terrainFeatures.ContainsKey(saved.Tile) && location.terrainFeatures[saved.Tile] is PlacedItem placedItem && placedItem.Item?.ParentSheetIndex == saved.ID.Value)
                        {
                            stillExists = true;
                            location.terrainFeatures.Remove(saved.Tile);                         //remove this placed item, regardless of expiration

                            if (endOfDay)                                                        //if expirations should be processed
                            {
                                if (saved.DaysUntilExpire == 1 || saved.DaysUntilExpire == null) //if this should expire tonight
                                {
                                    Monitor.VerboseLog($"Removing expired object. Type: {saved.Type.ToString()}. Name: {placedItem.Item?.Name}. Location: {saved.MapName}.");
                                    objectsToRemove.Add(saved);     //mark this for removal from save
                                }
                                else if (saved.DaysUntilExpire > 1) //if this should expire, but not tonight
                                {
                                    saved.DaysUntilExpire--;        //decrease counter by 1
                                }
                            }
                        }

                        if (!stillExists) //if this item no longer exists
                        {
                            Monitor.VerboseLog($"Removing missing object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                            objectsToRemove.Add(saved); //mark this for removal from save
                        }
                    }
                    else if (saved.Type == SavedObject.ObjectType.Container)                                              //if this is a container
                    {
                        StardewValley.Object realObject = location.getObjectAtTile((int)saved.Tile.X, (int)saved.Tile.Y); //get the object at the saved location

                        if (realObject != null)                                                                           //if an object exists in the saved location
                        {
                            bool sameContainerCategory = false;
                            switch (saved.ConfigItem?.Category.ToLower()) //compare the saved object's category to this object's class
                            {
                            case "barrel":
                            case "barrels":
                            case "breakable":
                            case "breakables":
                            case "crate":
                            case "crates":
                                if (realObject is BreakableContainerFTM)
                                {
                                    sameContainerCategory = true;
                                }
                                break;

                            case "buried":
                            case "burieditem":
                            case "burieditems":
                            case "buried item":
                            case "buried items":
                                if (realObject is BuriedItems)
                                {
                                    sameContainerCategory = true;
                                }
                                break;

                            case "chest":
                            case "chests":
                                if (realObject is Chest)
                                {
                                    sameContainerCategory = true;
                                }
                                break;
                            }

                            if (sameContainerCategory)                                           //if the real object matches the saved object's category
                            {
                                if (realObject is Chest chest)                                   //if this is a chest
                                {
                                    while (chest.items.Count < saved.ConfigItem?.Contents.Count) //while this chest has less items than the saved object's "contents"
                                    {
                                        saved.ConfigItem.Contents.RemoveAt(0);                   //remove a missing item from the ConfigItem's contents (note: chests output the item at index 0 when used)
                                    }
                                }

                                realObject.CanBeGrabbed = true;           //workaround for certain objects being ignored by the removeObject method
                                location.removeObject(saved.Tile, false); //remove this container from the location, regardless of expiration

                                if (endOfDay)                             //if expirations should be processed
                                {
                                    if (saved.DaysUntilExpire == 1)       //if the object should expire tonight
                                    {
                                        Monitor.VerboseLog($"Removing expired container. Type: {saved.Type.ToString()}. Category: {saved.ConfigItem?.Category}. Location: {saved.Tile.X},{saved.Tile.Y} ({saved.MapName}).");

                                        objectsToRemove.Add(saved);     //mark object for removal from save
                                    }
                                    else if (saved.DaysUntilExpire > 1) //if the object should expire, but not tonight
                                    {
                                        saved.DaysUntilExpire--;        //decrease counter by 1
                                    }
                                }
                            }
                            else //if the real object does NOT match the saved object's category
                            {
                                Monitor.VerboseLog($"Removing missing object. Type: {saved.Type.ToString()}. Category: {saved.ConfigItem?.Category}. Location: {saved.MapName}.");
                                objectsToRemove.Add(saved); //mark object for removal from save
                            }
                        }
                        else //if the object no longer exists
                        {
                            Monitor.VerboseLog($"Removing missing object. Type: {saved.Type.ToString()}. Category: {saved.ConfigItem?.Category}. Location: {saved.MapName}.");
                            objectsToRemove.Add(saved); //mark object for removal from save
                        }
                    }
                    else //if this is a StardewValley.Object (e.g. forage or ore)
                    {
                        StardewValley.Object realObject = location.getObjectAtTile((int)saved.Tile.X, (int)saved.Tile.Y); //get the object at the saved location

                        if (realObject != null && realObject.ParentSheetIndex == saved.ID) //if an object exists in the saved location & matches the saved object's ID
                        {
                            if (endOfDay)                                                  //if expirations should be processed
                            {
                                if (saved.DaysUntilExpire == 1)                            //if the object should expire tonight
                                {
                                    Monitor.VerboseLog($"Removing expired object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.Tile.X},{saved.Tile.Y} ({saved.MapName}).");
                                    realObject.CanBeGrabbed = true;           //workaround for certain objects being ignored by the removeObject method
                                    location.removeObject(saved.Tile, false); //remove the object from the game
                                    objectsToRemove.Add(saved);               //mark object for removal from save
                                }
                                else if (saved.DaysUntilExpire > 1)           //if the object should expire, but not tonight
                                {
                                    saved.DaysUntilExpire--;                  //decrease counter by 1
                                }
                            }
                        }
                        else //if the object no longer exists
                        {
                            Monitor.VerboseLog($"Removing missing object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                            objectsToRemove.Add(saved); //mark object for removal from save
                        }
                    }
                }

                Monitor.Log($"Object check complete. Removing {objectsToRemove.Count} missing/expired objects from save data.", LogLevel.Trace);
                foreach (SavedObject saved in objectsToRemove) //for each object that should be removed from the save data
                {
                    save.SavedObjects.Remove(saved);           //remove it
                }

                MonsterTracker.Clear(); //clear all monster IDs and related data, since they've all been removed
            }
示例#9
0
            }                                      //NOTE: this should be null when no content pack was involved, i.e. the data came from files in the mod's own folder

            public FarmData(FarmConfig config, InternalSaveData save, IContentPack pack)
            {
                Config = config;
                Save   = save;
                Pack   = pack;
            }
            /// <summary>Check each saved object's expiration data, updating counters & removing missing/expired objects from the data and game.</summary>
            /// <param name="save">The save data to the checked.</param>
            public static void ProcessObjectExpiration(InternalSaveData save)
            {
                Monitor.Log($"Updating save data for missing/expired objects...", LogLevel.Trace);
                List <SavedObject> objectsToRemove = new List <SavedObject>(); //objects to remove from saved data after processing (note: do not remove them while looping through them)

                foreach (SavedObject saved in save.SavedObjects)               //for each saved object & expiration countdown
                {
                    if (saved.DaysUntilExpire == null)                         //if the object's expiration setting is null
                    {
                        continue;                                              //skip to the next object
                    }

                    if (saved.Type == SavedObject.ObjectType.LargeObject)             //if this is a large object
                    {
                        Farm farm = Game1.getLocationFromName(saved.MapName) as Farm; //get the specified location & treat it as a farm (null otherwise)

                        if (farm == null)                                             //if this isn't a valid map
                        {
                            //note: don't remove the object's save data; this might be a temporary issue, e.g. a map that didn't load correctly
                            continue; //skip to the next object
                        }

                        bool stillExists = false; //does this large object still exist?

                        //WARNING: this section accesses SDV "Net" objects; it does not edit them directly, but should be suspected if inconsistent errors occur
                        foreach (ResourceClump clump in farm.resourceClumps)                                                              //for each clump (a.k.a. large object) on this map
                        {
                            if (clump.tile.X == saved.Tile.X && clump.tile.Y == saved.Tile.Y && clump.parentSheetIndex.Value == saved.ID) //if this clump's location & ID match the saved object
                            {
                                stillExists = true;
                                break; //skip the rest of these clumps
                            }
                        }
                        //end of WARNING

                        if (stillExists)                    //if the object still exists
                        {
                            if (saved.DaysUntilExpire == 1) //if the object should expire tonight
                            {
                                Monitor.Log($"Removing expired object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.Tile.X},{saved.Tile.Y} ({saved.MapName}).", LogLevel.Trace);
                                farm.removeEverythingExceptCharactersFromThisTile((int)saved.Tile.X, (int)saved.Tile.Y); //remove the object from the game
                                objectsToRemove.Add(saved);                                                              //mark object for removal from save
                            }
                            else if (saved.DaysUntilExpire > 1)                                                          //if the object should expire, but not tonight
                            {
                                saved.DaysUntilExpire--;                                                                 //decrease counter by 1
                            }
                        }
                        else //if the object no longer exists
                        {
                            objectsToRemove.Add(saved); //mark object for removal from save
                        }
                    }
                    else //if this is forage or ore
                    {
                        GameLocation location = Game1.getLocationFromName(saved.MapName); //get the object's location

                        if (location == null) //if the map wasn't found
                        {
                            //note: don't remove the object's save data; this might be a temporary issue, e.g. a map that didn't load correctly
                            continue; //skip to the next object
                        }

                        StardewValley.Object realObject = location.getObjectAtTile((int)saved.Tile.X, (int)saved.Tile.Y); //get the object at the saved location

                        if (realObject != null && realObject.ParentSheetIndex == saved.ID)                                //if an object exists in the saved location & matches the saved object's ID
                        {
                            if (saved.DaysUntilExpire == 1)                                                               //if the object should expire tonight
                            {
                                Monitor.Log($"Removing expired object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.Tile.X},{saved.Tile.Y} ({saved.MapName}).", LogLevel.Trace);
                                realObject.CanBeGrabbed = true;           //workaround for certain objects being ignored by the removeObject method
                                location.removeObject(saved.Tile, false); //remove the object from the game
                                objectsToRemove.Add(saved);               //mark object for removal from save
                            }
                            else if (saved.DaysUntilExpire > 1)           //if the object should expire, but not tonight
                            {
                                saved.DaysUntilExpire--;                  //decrease counter by 1
                            }
                        }
                        else //if the object no longer exists
                        {
                            objectsToRemove.Add(saved); //mark object for removal from save
                        }
                    }
                }

                Monitor.Log($"Expiration check complete. Clearing {objectsToRemove.Count} missing/expired objects from save data.", LogLevel.Trace);
                foreach (SavedObject saved in objectsToRemove) //for each object that should be removed from the save data
                {
                    save.SavedObjects.Remove(saved);           //remove it
                }
            }