private static void Postfix(Vector2 tileLocation, BuildableGameLocation __instance, ref bool __result)
        {
            try
            {
                var playerIsNotOnTile  = !Game1.player.getTileLocation().Equals(tileLocation) || !Game1.player.currentLocation.Equals(__instance);
                var tileIsNotOccupied  = !__instance.isTileOccupiedForPlacement(tileLocation);
                var tileIsPassable     = __instance.isTilePassable(new Location((int)tileLocation.X, (int)tileLocation.Y), Game1.viewport);
                var tileHasNoFurniture = __instance.GetFurnitureAt(tileLocation) == null;

                if (!(playerIsNotOnTile &&
                      tileIsNotOccupied &&
                      tileIsPassable &&
                      tileHasNoFurniture)
                    )
                {
                    return;      // Run original logic.
                }
                __result = true; // Original method will now return true.
            } catch (Exception e) {
                Monitor.Log($"Failed in {nameof(BuildableGameLocationPatches_IsBuildable)}:\n{e}", LogLevel.Error);
            }
        }
Exemplo n.º 2
0
        /// <summary>A Harmony prefix patch that causes <see cref="BuildableGameLocation.isBuildable(Vector2)"/> to return true under more conditions.</summary>
        /// <param name="__instance">The buildable location on which this method is called.</param>
        /// <param name="tileLocation">The tile being checked.</param>
        /// <param name="__result">True if this tile should allow <see cref="Building"/> placement.</param>
        /// <returns>False if the original method (and any other patches) should be skipped.</returns>
        public static bool IsBuildable(BuildableGameLocation __instance, Vector2 tileLocation, ref bool __result)
        {
            try
            {
                if (ModEntry.Config.EverythingEnabled()) //if every feature is enabled
                {
                    __result = true;                     //this tile is buildable
                    return(false);                       //skip the original method
                }

                if (ModEntry.Config.EverythingDisabled())                                                                   //if every feature is disabled
                {
                    return(true);                                                                                           //run the original method (do nothing)
                }
                if (ModEntry.Config.BuildOnAllTerrainFeatures == false)                                                     //if most terrain features should prevent building (based on the original method's behavior)
                {
                    Rectangle tileLocationRect = new Rectangle((int)tileLocation.X * 64, (int)tileLocation.Y * 64, 64, 64); //get a rectangle representing this tile

                    if (__instance.terrainFeatures.TryGetValue(tileLocation, out TerrainFeature feature) && //if this tile has a terrain feature
                        tileLocationRect.Intersects(feature.getBoundingBox(tileLocation)))    //AND the feature's box overlaps with the tile (note: copied from GameLocation.isOccupiedForPlacement)
                    {
                        if (!__instance.terrainFeatures[tileLocation].isPassable() || //if the feature is impassable
                            (feature is HoeDirt dirt && dirt.crop != null)) //OR the feature is a crop
                        {
                            __result = false;                               //this tile is NOT buildable
                            return(false);                                  //skip the original method
                        }
                    }
                }

                if (ModEntry.Config.BuildOnOtherBuildings == false)                  //if collision with other buildings should prevent building
                {
                    foreach (Building building in __instance.buildings)              //for each existing building
                    {
                        if (building.isTileOccupiedForPlacement(tileLocation, null)) //if this building occupies this tile
                        {
                            __result = false;                                        //this tile is NOT buildable
                            return(false);                                           //skip the original method
                        }
                    }
                }

                if (ModEntry.Config.BuildOnWater == false) //if water should prevent building
                {
                    if (__instance.doesTileHaveProperty((int)tileLocation.X, (int)tileLocation.Y, "Water", "Back") != null && //if this tile is water
                        __instance.doesTileHaveProperty((int)tileLocation.X, (int)tileLocation.Y, "Passable", "Buildings") == null) //AND this tile does NOT specifically allow buildings
                    {
                        __result = false;                                                                                           //this tile is NOT buildable
                        return(false);                                                                                              //skip the original method
                    }
                }

                if (ModEntry.Config.BuildOnImpassableTiles == false)                                                                        //if impassable tiles should prevent building
                {
                    if (ModEntry.Config.BuildOnWater == false || __instance.isOpenWater((int)tileLocation.X, (int)tileLocation.Y) == false) //if this tile is NOT specifically allowed by the water setting
                    {
                        if (__instance.isTileOccupiedForPlacement(tileLocation) || //if this tile is occupied
                            __instance.isTilePassable(new Location((int)tileLocation.X, (int)tileLocation.Y), Game1.viewport) == false) //OR if this tile is NOT passable
                        {
                            __result = false;                                                                                           //this tile is NOT buildable
                            return(false);                                                                                              //skip the original method
                        }
                    }
                }

                if (ModEntry.Config.BuildOnNoFurnitureTiles == false)                                                             //if "no furniture" tiles should prevent building
                {
                    if (__instance.doesTileHaveProperty((int)tileLocation.X, (int)tileLocation.Y, "NoFurniture", "Back") != null) //if this tile has a "NoFurniture" property
                    {
                        __result = false;                                                                                         //this tile is NOT buildable
                        return(false);                                                                                            //skip the original method
                    }
                }

                if (ModEntry.Config.BuildOnCaveAndShippingZones == false) //if "no build" zones should prevent building
                {
                    //NOTE: as of SDV 1.5.5, the static preset rectangle "zones" no longer exist, and farm maps use the tile property "Buildable" "f" instead;
                    //that tile property may exist elsewhere too, but checking relevant properties here should be understandable enough

                    //try to get the cave and shipping no-build zones
                    Rectangle?caveNoBuildRect         = ModEntry.Instance.Helper.Reflection.GetField <Rectangle>(__instance, "caveNoBuildRect", false)?.GetValue();
                    Rectangle?shippingAreaNoBuildRect = ModEntry.Instance.Helper.Reflection.GetField <Rectangle>(__instance, "shippingAreaNoBuildRect", false)?.GetValue();

                    if (caveNoBuildRect.HasValue && shippingAreaNoBuildRect.HasValue) //if these fields exist (e.g. SDV v1.5.4 or earlier is in use)
                    {
                        if (caveNoBuildRect.Value.Contains(Utility.Vector2ToPoint(tileLocation)) || //if this tile is within the cave entrance zone
                            shippingAreaNoBuildRect.Value.Contains(Utility.Vector2ToPoint(tileLocation))) //OR this tile is within the shipping bin zone
                        {
                            __result = false;                                                             //this tile is NOT buildable
                            return(false);                                                                //skip the original method
                        }
                    }

                    string buildableValue = __instance.doesTileHavePropertyNoNull((int)tileLocation.X, (int)tileLocation.Y, "Buildable", "Back");             //get the value of this tile's "Buildable" property ("" if null)
                    if (buildableValue.Equals("f", StringComparison.OrdinalIgnoreCase) || buildableValue.Equals("false", StringComparison.OrdinalIgnoreCase)) //if the value is false
                    {
                        __result = false;                                                                                                                     //this tile is NOT buildable
                        return(false);                                                                                                                        //skip the original method
                    }
                }

                //all checks have been successful

                __result = true; //this tile is buildable
                return(false);   //skip the original method
            }
            catch (Exception ex)
            {
                ModEntry.Instance.Monitor.LogOnce($"Encountered an error in Harmony patch \"{nameof(HarmonyPatch_BuildOnAnyTile)}\". The default building rules will be used instead. Full error message:\n-----\n{ex.ToString()}", LogLevel.Error);
                return(true); //run the original method
            }
        }