Ejemplo n.º 1
0
        protected Cropbeast(CropTile cropTile, bool containsPlant,
                            bool containsPrimaryHarvest, bool containsSecondaryHarvest,
                            string beastName = null)
            : base(beastName ?? cropTile.mapping.beastName, cropTile.location,
                   Utility.PointToVector2(cropTile.tileLocation) * 64f +
                   new Vector2(0f, -32f))
        {
            cropTile_          = cropTile;
            harvestIndex.Value = cropTile.harvestIndex;
            giantCrop.Value    = cropTile.giantCrop;
            tileLocation.Value = cropTile.tileLocation;

            this.containsPlant.Value            = containsPlant;
            this.containsPrimaryHarvest.Value   = containsPrimaryHarvest;
            this.containsSecondaryHarvest.Value = containsSecondaryHarvest;

            // Calculate the beast's coloration.
            primaryColor.Value = cropTile.mapping.primaryColor
                                 ?? TailoringMenu.GetDyeColor(cropTile.mapping.harvestObject)
                                 ?? Color.White;
            secondaryColor.Value = cropTile.mapping.secondaryColor
                                   ?? primaryColor.Value;

            // Scale health and damage based on combat skill of players present.
            // Base values in Monsters.json are for level zero.
            MaxHealth      = Health = scaleByCombatSkill(Health, 1.2f);
            DamageToFarmer = scaleByCombatSkill(DamageToFarmer, 0.2f);

            // Calculate the normal gain of farming experience.
            int price = cropTile.mapping.harvestObject.Price;

            experienceGained.Value = (int)Math.Ceiling(8.0 *
                                                       Math.Log(0.018 * (double)price + 1.0, Math.E));
        }
Ejemplo n.º 2
0
        internal bool spawnCropbeast(bool console  = false, bool force = false,
                                     string filter = null)
        {
            // Check preconditions.
            if (!Context.IsWorldReady)
            {
                throw new Exception("The world is not ready.");
            }
            if (!Context.IsMainPlayer)
            {
                throw new InvalidOperationException("Only the host can do that.");
            }

            // Choose a crop tile.
            CropTile tile = chooser.choose(console: console, force: force,
                                           filter: filter);

            if (tile == null)
            {
                return(false);
            }

            // Spawn the cropbeast.
            Spawner.Spawn(tile);
            return(true);
        }
Ejemplo n.º 3
0
        public static void Spawn(CropTile cropTile)
        {
            // Check preconditions.
            if (!Context.IsMainPlayer)
            {
                throw new InvalidOperationException("Only the host can do that.");
            }

            // Find the nature of the beast.
            ConstructorInfo ctor = cropTile.mapping.type?.GetConstructor
                                       (new Type[] { typeof(CropTile), typeof(bool) });

            if (ctor == null)
            {
                throw new Exception($"Invalid cropbeast type '{cropTile.mapping.beastName}'.");
            }

            // Create the beast(s).
            List <Cropbeast> beasts = new List <Cropbeast> ();

            beasts.Add(ctor.Invoke(new object[] { cropTile, true }) as Cropbeast);
            if (cropTile.baseQuantity > 1 && !cropTile.giantCrop)
            {
                for (int i = 1; i < cropTile.baseQuantity; ++i)
                {
                    beasts.Add(ctor.Invoke(new object[] { cropTile, false }) as Cropbeast);
                }
            }

            // Register the beast(s) centrally in advance.
            foreach (Cropbeast beast in beasts)
            {
                ModEntry.Instance.registerMonster(beast);
            }

            // Have the witch fly by only if configured, only outdoors and only
            // on the first real spawn of the day.
            bool showWitchFlyover = Config.WitchFlyovers &&
                                    cropTile.location.IsOutdoors && !cropTile.fake &&
                                    !ModEntry.Instance.chooser.witchFlyoverShown;

            // Create the host spawner here.
            new Spawner(cropTile, cropTile.location, cropTile.baseQuantity,
                        beasts, showWitchFlyover);

            // Signal farmhands to create guest spawners.
            Message message = new Message
            {
                locationName     = cropTile.location.Name,
                tileLocation     = cropTile.tileLocation,
                harvestIndex     = cropTile.harvestIndex,
                giantCrop        = cropTile.giantCrop,
                baseQuantity     = cropTile.baseQuantity,
                showWitchFlyover = showWitchFlyover,
            };

            Helper.Multiplayer.SendMessage(message, "CreateSpawner",
                                           modIDs: new string[] { ModEntry.Instance.ModManifest.UniqueID });
        }
Ejemplo n.º 4
0
        private static void fakeOne(int harvestIndex, bool giantCrop,
                                    Farmer near = null)
        {
            near ??= Game1.player;
            GameLocation location = near.currentLocation;

            var tiles = Utility.recursiveFindOpenTiles(location,
                                                       near.getTileLocation(), 1, 100);

            if (tiles.Count == 0)
            {
                throw new Exception($"Could not find an open tile in {location.Name}.");
            }
            Vector2 tileLocation = tiles[0];

            Crop crop = Utilities.MakeNonceCrop(harvestIndex,
                                                Utility.Vector2ToPoint(tileLocation));

            TerrainFeature feature;

            if (giantCrop)
            {
                if (location is Farm farm)
                {
                    feature = new GiantCrop(harvestIndex, tileLocation);
                    farm.resourceClumps.Add(feature as ResourceClump);
                }
                else
                {
                    throw new Exception($"Cannot fake a Giant Cropbeast in {location.Name}.");
                }
            }
            else
            {
                feature = new HoeDirt(0, crop);
                location.terrainFeatures.Add(tileLocation, feature);
            }

            CropTile cropTile = new CropTile(feature, crop, giantCrop, location,
                                             Utility.Vector2ToPoint(tileLocation), fake: true);

            if (cropTile.state != CropTile.State.Available)
            {
                cropTile.cleanUpFake();
                throw new Exception($"Faked a {cropTile.logDescription} but it had no available cropbeast mapping.");
            }

            cropTile.choose();
            DelayedAction.functionAfterDelay(() => Spawner.Spawn(cropTile), 0);
        }
Ejemplo n.º 5
0
        // Alters records as if the given crop tile had never been chosen.
        public virtual void unchoose(CropTile tile)
        {
            if (!chosenTiles.ContainsKey(tile.tileLocation))
            {
                return;
            }

            if (tile.location.IsOutdoors)
            {
                --outdoorSpawnCount;
            }
            else
            {
                --indoorSpawnCount;
            }

            chosenTiles.Remove(tile.tileLocation);
        }
Ejemplo n.º 6
0
        public virtual void revert()
        {
            if (reverted)
            {
                return;
            }

            CropTile cropTile = cropTile_ as CropTile;

            if (cropTile == null)
            {
                throw new Exception("Cannot revert cropbeast without true CropTile.");
            }

            reverted = true;

            Monitor.Log($"Reverting a {Name} to a {cropTile.logDescription}.",
                        LogLevel.Debug);

            if (containsPlant.Value)
            {
                ModEntry.Instance.chooser?.unchoose(cropTile);
            }

            if (containsPrimaryHarvest.Value)
            {
                cropTile.restoreFully();
            }
            else if (containsPlant.Value && cropTile.repeatingCrop)
            {
                cropTile.restorePlant();
            }

            if (currentLocation != null)
            {
                currentLocation.characters.Remove(this);
            }
            Health = -500;
        }
Ejemplo n.º 7
0
        // Chooses a crop tile to become a cropbeast.
        public virtual CropTile choose(bool console = false,
                                       bool force   = false, string filter = null)
        {
            // Use a predictable RNG seeded by game, day and time.
            Random rng = new Random((int)Game1.uniqueIDForThisGame / 2 +
                                    (int)Game1.stats.DaysPlayed + Game1.timeOfDay);

            // Choose the location to evaluate.
            GameLocation location = Game1.currentLocation;

            if (!(location.IsFarm && location.IsOutdoors) && !location.IsGreenhouse)
            {
                GameLocation farm       = Game1.getLocationFromName("Farm");
                GameLocation greenhouse = Game1.getLocationFromName("Greenhouse");
                if (farm != null && farm.farmers.Count() > 0)
                {
                    location = farm;
                }
                else if (greenhouse != null && greenhouse.farmers.Count() > 0)
                {
                    location = greenhouse;
                }
            }

            // Check preconditions unless forced.
            if (!force && !shouldChoose(rng, location, console: console))
            {
                return(null);
            }

            // Get the maximum distance on the current map for weighting use.
            var   mapLayer    = location.map.Layers[0];
            float mapDiagonal = Vector2.Distance(new Vector2(0, 0),
                                                 new Vector2(mapLayer.LayerWidth, mapLayer.LayerHeight));

            // Find all candidate crop tiles, sort pseudorandomly with
            // weighting based on type and towards those closer to a farmer.
            SortedList <double, CropTile> candidates =
                new SortedList <double, CropTile>
                    (findCandidateCropTiles(location, filter, console)
                    .ToDictionary((tile) =>
            {
                Utilities.FindNearestFarmer(tile.location, tile.tileLocation,
                                            out float distance);
                return(rng.NextDouble() * 1.5
                       - tile.mapping.choiceWeight
                       + distance / mapDiagonal * 2.0);
            }));

            // If the list is empty, give up.
            if (candidates.Count == 0)
            {
                Monitor.Log($"At {Game1.getTimeOfDayString (Game1.timeOfDay)}, {location.Name} met preconditions but had no candidate crops to become cropbeasts.",
                            console ? LogLevel.Warn : LogLevel.Trace);
                return(null);
            }

            // Choose the top of the list as the winner.
            CropTile winner = candidates.First().Value;

            Monitor.Log($"At {Game1.getTimeOfDayString (Game1.timeOfDay)}, chose {winner.logDescription} to become {winner.mapping.beastName}.",
                        console ? LogLevel.Info : LogLevel.Debug);

            // Record progress towards daily limit for location.
            if (location.IsOutdoors)
            {
                ++outdoorSpawnCount;
            }
            else
            {
                ++indoorSpawnCount;
            }

            // Make sure this tile isn't chosen again.
            chosenTiles[winner.tileLocation] = winner;
            winner.choose();

            return(winner);
        }
Ejemplo n.º 8
0
        // Lists all the tiles in the given location with crops
        // on them that are candidates for becoming cropbeasts.
        protected virtual List <CropTile> findCandidateCropTiles(GameLocation location,
                                                                 string filter = null, bool console = false)
        {
            List <SObject>   wickedStatues = Utilities.FindWickedStatues(location);
            List <JunimoHut> junimoHuts    = Utilities.FindActiveJunimoHuts(location);

            // For IF2R, avoid cropbeast-spawning special giant crops in an area
            // of the map that is initially inaccessible.
            List <Point> exemptTiles = new List <Point> ();

            if (location is Farm && Game1.whichFarm == Farm.default_layout &&
                Helper.ModRegistry.IsLoaded("flashshifter.immersivefarm2remastered"))
            {
                exemptTiles.Add(new Point(79, 99));
            }

            return(CropTile.FindAll(location).Where((tile) =>
            {
                // The crop must be available.
                if (tile.state != CropTile.State.Available)
                {
                    return false;
                }

                // If a filter was supplied, it must match.
                if (filter != null && !tile.mapping.matchesFilter(filter))
                {
                    if (console)
                    {
                        Monitor.Log($"Excluded a {tile.logDescription} because it does not match the filter \"{filter}\".");
                    }
                    return false;
                }

                // The crop's tile must not be on the exempt list for the map.
                Point tileLoc = tile.tileLocation;
                if (exemptTiles.Contains(tileLoc))
                {
                    if (console)
                    {
                        Monitor.Log($"Excluded a {tile.logDescription} because its tile is exempt on this map.");
                    }
                    return false;
                }

                // The crop must not have already been chosen by another
                // in-progress cropbeast spawn.
                if (chosenTiles.ContainsKey(tileLoc))
                {
                    if (console)
                    {
                        Monitor.Log($"Excluded a {tile.logDescription} because its tile has already been chosen.");
                    }
                    return false;
                }

                // A non-giant crop must not be in range of an active Junimo Hut.
                if (!tile.giantCrop)
                {
                    foreach (JunimoHut junimoHut in junimoHuts)
                    {
                        if (tileLoc.X >= junimoHut.tileX.Value + 1 - 8 &&
                            tileLoc.X < junimoHut.tileX.Value + 2 + 8 &&
                            tileLoc.Y >= junimoHut.tileY.Value - 8 + 1 &&
                            tileLoc.Y < junimoHut.tileY.Value + 2 + 8)
                        {
                            if (console)
                            {
                                Monitor.Log($"Excluded a {tile.logDescription} because it is in range of an active Junimo Hut at ({junimoHut.tileX.Value},{junimoHut.tileY.Value}).");
                            }
                            return false;
                        }
                    }
                }

                // The crop must not be in range of a Wicked Statue.
                foreach (SObject wickedStatue in wickedStatues)
                {
                    if (Config.WickedStatueRange == -1 ||                     // infinite range
                        Vector2.Distance(Utility.PointToVector2(tileLoc),
                                         wickedStatue.TileLocation) < (float)Config.WickedStatueRange)
                    {
                        if (console)
                        {
                            Monitor.Log($"Excluded a {tile.logDescription} because it is in range of a Wicked Statue at ({(int) wickedStatue.TileLocation.X},{(int) wickedStatue.TileLocation.Y}).");
                        }
                        return false;
                    }
                }

                return true;
            }).ToList());
        }
Ejemplo n.º 9
0
        // Checks whether an attempt to spawn a cropbeast should be made now.
        protected virtual bool shouldChoose(Random rng, GameLocation location,
                                            bool console = false)
        {
            // Only spawn in daytime. Nighttime is for the regular Wilderness
            // Farm monsters. The dusk hour is left as a buffer period.
            if (Game1.timeOfDay >= 1800)
            {
                if (console)
                {
                    Monitor.Log($"Not spawning a cropbeast because the current time {Game1.timeOfDay} is after 1800.", LogLevel.Info);
                }
                return(false);
            }

            // Don't spawn on a festival or wedding day.
            if (Utility.isFestivalDay(Game1.dayOfMonth, Game1.currentSeason) ||
                Game1.weddingToday)
            {
                if (console)
                {
                    Monitor.Log($"Not spawning a cropbeast because it is a festival or wedding day.", LogLevel.Info);
                }
                return(false);
            }

            // Only spawn if all previous cropbeasts have been slain,
            // unless otherwise configured.
            if (ModEntry.Instance.currentBeastCount > 0 && !Config.AllowSimultaneous)
            {
                if (console)
                {
                    Monitor.Log($"Not spawning a cropbeast because there are already {ModEntry.Instance.currentBeastCount} active and we are not configured for simultaneous spawns.", LogLevel.Info);
                }
                return(false);
            }

            // Only spawn on farms with monsters, unless otherwise configured.
            if (!Game1.spawnMonstersAtNight && !Config.SpawnOnAnyFarm)
            {
                if (console)
                {
                    Monitor.Log($"Not spawning a cropbeast because farm monsters are deactivated and we are not configured to ignore that.", LogLevel.Info);
                }
                return(false);
            }

            // Only spawn outdoors on the farm or in the greenhouse.
            if (!(location.IsFarm && location.IsOutdoors) && !location.IsGreenhouse)
            {
                if (console)
                {
                    Monitor.Log($"Not spawning a cropbeast because {location.Name} is not on outdoors on the farm or in the greenhouse.", LogLevel.Info);
                }
                return(false);
            }

            bool outdoors = location.IsOutdoors;

            // Only spawn outdoors if there are at least 16 crops.
            int totalCount = CropTile.CountAll(location);

            if (outdoors && totalCount < 16)
            {
                if (console)
                {
                    Monitor.Log($"Not spawning a cropbeast because there are only {totalCount} crop(s) on {location.Name}.", LogLevel.Info);
                }
                return(false);
            }

            // Only spawn if the applicable daily limit has not been hit.
            int limit = outdoors
                                ? Config.OutdoorSpawnLimit : Config.IndoorSpawnLimit;
            int count = outdoors
                                ? outdoorSpawnCount : indoorSpawnCount;

            if (limit >= 0 && count >= limit)
            {
                if (console)
                {
                    Monitor.Log($"Not spawning a cropbeast because we have already spawned {count} out of a limit of {limit} {(outdoors ? "outdoors" : "indoors")}.", LogLevel.Info);
                }
                return(false);
            }

            // Give a small fixed chance of a cropbeast spawning at each clock
            // tick, scaled based on the total number of spawns expected so that
            // the desired limit fits within the day comfortably.
            double chance = 0.01 * limit;

            // However, don't let too much time pass without a spawn lest the
            // count fall too far short of the desired limit.
            double dayProgress   = (Game1.timeOfDay / 100 - 6) / 12.0;
            double spawnProgress = (count + 1) / (double)limit;

            if (dayProgress > spawnProgress)
            {
                chance *= 5.0;
            }

            // Roll the die. Always succeed for no RNG or the console command.
            if (rng == null)
            {
                return(true);
            }
            else if (rng.NextDouble() < chance)
            {
                return(true);
            }
            else if (console)
            {
                Monitor.Log($"If not by console command, wouldn't have spawned a cropbeast due to the chance roll.", LogLevel.Info);
                return(true);
            }
            else
            {
                return(false);
            }
        }
Ejemplo n.º 10
0
        protected virtual void calculateHarvest(bool recalculate = false)
        {
            // This method is based on GiantCrop.performToolAction and
            // Crop.harvest except as noted. Since the RNG is called in a
            // different order, the results will not match the specific original
            // crop, but should match the stock algorithm on average.

            if (calculatedHarvest && !recalculate)
            {
                return;
            }
            calculatedHarvest = true;

            // This can only be run from the host.
            CropTile cropTile = cropTile_ as CropTile;

            if (cropTile == null)
            {
                throw new Exception("Cannot calculate harvest without true CropTile.");
            }

            // Start with nothing.
            primaryHarvest   = null;
            secondaryHarvest = null;
            tertiaryHarvest  = null;

            // Chance of a quality bump for skilful combat, even to iridium.
            int qualityBonus =
                (cropTile.rng.NextDouble() < rateCombatPerformance())
                                        ? 1 : 0;

            // This should never happen.
            if (cropTile.harvestIndex <= 0)
            {
                return;
            }

            // Calculate any programmatic coloration of the harvest.
            Color?tintColor = null;

            if (cropTile.crop.programColored.Value)
            {
                tintColor = cropTile.crop.tintColor.Value;
            }

            // Secondary harvests always have regular quality.
            if (containsSecondaryHarvest.Value)
            {
                int secondaryQuantity = (cropTile.giantCrop && containsPrimaryHarvest.Value)
                                        ? cropTile.baseQuantity - 1 : 1;
                secondaryHarvest = tintColor.HasValue
                                        ? new ColoredObject(cropTile.harvestIndex, secondaryQuantity,
                                                            tintColor.Value)
                                        : new SObject(cropTile.harvestIndex, secondaryQuantity);
            }

            // Primary harvest goes with tertiary harvest and special drops.
            if (!containsPrimaryHarvest.Value)
            {
                objectsToDrop.Clear();
                return;
            }

            // Add the primary harvest with the determined quality.
            int quality = cropTile.giantCrop
                                ? qualityBonus * 2 // regular or gold
                                : cropTile.baseQuality + qualityBonus;

            if (quality == 3)
            {
                quality = 4;
            }
            primaryHarvest = tintColor.HasValue
                                ? new ColoredObject(cropTile.harvestIndex, 1, tintColor.Value)
            {
                Quality = quality
            }
                                : new SObject(cropTile.harvestIndex, 1, quality: quality);

            // Add any tertiary harvest. The special seed drop for Sunflowers is
            // omitted here since they do not become cropbeasts by default.

            // Wheat sometimes drops Hay.
            if (cropTile.harvestIndex == 262 && cropTile.rng.NextDouble() < 0.4)
            {
                tertiaryHarvest = new SObject(178, 1);
            }
            // Otherwise, cropbeasts rarely drop more seeds. Does not apply
            // to Coffee Beans (which are themselves seeds). Give between one
            // and five seeds, but not more than twice the harvest's worth.
            else if (cropTile.harvestIndex != cropTile.seedIndex &&
                     cropTile.rng.NextDouble() < 0.05)
            {
                tertiaryHarvest       = new SObject(cropTile.seedIndex, 1);
                tertiaryHarvest.Stack = cropTile.rng.Next(1, Math.Min(6,
                                                                      1 + (2 * primaryHarvest.Price * cropTile.baseQuantity)
                                                                      / Math.Max(10, tertiaryHarvest.Price)));
            }
        }