예제 #1
0
        /// <summary>Get the locale codes (like <c>ja-JP</c>) used in asset keys.</summary>
        /// <param name="reflection">Simplifies access to private game code.</param>
        private IDictionary <string, LanguageCode> GetKeyLocales(Reflector reflection)
        {
            // get the private code field directly to avoid changed-code logic
            IReflectedField <LanguageCode> codeField = reflection.GetField <LanguageCode>(typeof(LocalizedContentManager), "_currentLangCode");

            // remember previous settings
            LanguageCode previousCode     = codeField.GetValue();
            string       previousOverride = this.LanguageCodeOverride;

            // create locale => code map
            IDictionary <string, LanguageCode> map = new Dictionary <string, LanguageCode>(StringComparer.InvariantCultureIgnoreCase);

            this.LanguageCodeOverride = null;
            foreach (LanguageCode code in Enum.GetValues(typeof(LanguageCode)))
            {
                codeField.SetValue(code);
                map[this.GetKeyLocale.Invoke <string>()] = code;
            }

            // restore previous settings
            codeField.SetValue(previousCode);
            this.LanguageCodeOverride = previousOverride;

            return(map);
        }
예제 #2
0
        public void MenuEvents_MenuChanged(object sender, EventArgsClickableMenuChanged e)
        {
            System.Console.Write($"Type of new menu is {e.NewMenu.GetType().ToString()}");

            if (e.NewMenu is CraftingPage page && !(e.NewMenu is ListenerCraftingPage))
            {
                ListenerCraftingPage replacement = new ListenerCraftingPage(page, this.replacer);
                Game1.activeClickableMenu = replacement;
            }

            if (e.NewMenu is GameMenu)
            {
                IReflectedField <List <IClickableMenu> > pages = this.Helper.Reflection.GetField <List <IClickableMenu> >(e.NewMenu, "pages");
                var menuPages = pages.GetValue();
                for (int i = 0; i < menuPages.Count; i++)
                {
                    if (menuPages[i] is CraftingPage craftingPage)
                    {
                        menuPages[i] = new ListenerCraftingPage(craftingPage, this.replacer);
                        break;
                    }
                }

                pages.SetValue(menuPages);
            }
        }
예제 #3
0
        /// <summary>
        /// Adds in a junimo actor at the current location. Allows for multiple.
        /// </summary>
        /// <param name="EventManager"></param>
        /// <param name="EventData"></param>
        public static void AddInJumimoActorForEvent(EventManager EventManager, string EventData)
        {
            string[] splits = EventData.Split(' ');
            string   name   = splits[0];

            string actorName = splits[1];
            int    xPos      = Convert.ToInt32(splits[2]);
            int    yPos      = Convert.ToInt32(splits[3]);
            Color  color     = new Color(Convert.ToInt32(splits[4]), Convert.ToInt32(splits[5]), Convert.ToInt32(splits[6]));
            bool   flipped   = Convert.ToBoolean(splits[7]);

            List <NPC> actors = Game1.CurrentEvent.actors;
            Junimo     junimo = new Junimo(new Vector2(xPos * 64, yPos * 64), -1, false);

            junimo.Name       = actorName;
            junimo.EventActor = true;
            junimo.flip       = flipped;

            IReflectedField <NetColor> colorF = StardustCore.ModCore.ModHelper.Reflection.GetField <NetColor>(junimo, "color", true);
            NetColor c = colorF.GetValue();

            c.R = color.R;
            c.G = color.G;
            c.B = color.B;
            colorF.SetValue(c);

            actors.Add((NPC)junimo);
            ++Game1.CurrentEvent.CurrentCommand; //I've been told ++<int> is more efficient than <int>++;
        }
예제 #4
0
        private void PlayerUsedTool(object sender, UpdateTickedEventArgs e)
        {
            if (Game1.player.UsingTool != UsingToolOnPreviousTick)                                                                                                          //if UsingTool has changed since the previous tick
            {
                UsingToolOnPreviousTick = Game1.player.UsingTool;                                                                                                           //update the "last tick" value

                if (Game1.player.UsingTool && !(Game1.player.CurrentTool is Axe))                                                                                           //if the player stopped using a tool on this tick
                {
                    Vector2   targetTile = new Vector2((int)(Game1.player.GetToolLocation().X / Game1.tileSize), (int)(Game1.player.GetToolLocation().Y / Game1.tileSize)); //get the tile on which the tool was used
                    Rectangle targetBox  = new Rectangle(((int)targetTile.X) * 64, ((int)targetTile.Y) * 64, Game1.tileSize, Game1.tileSize);                               //get a rectangle representing the target tile
                    var       ltf        = Game1.currentLocation.largeTerrainFeatures;                                                                                      //alias the current location's large terrain feature list

                    for (int x = ltf.Count - 1; x >= 0; x--)                                                                                                                //for each large terrain feature at the current location (looping backward for removal purposes)
                    {
                        if (ltf[x] is LargeResourceClump clump)                                                                                                             //if this is a large resource clump
                        {
                            if (clump.getBoundingBox(clump.tilePosition.Value).Intersects(targetBox))                                                                       //if this was hit by the tool
                            {
                                //NOTE: the reflection below prevents a non-fatal error when an Axe or Pickaxe is used for the first time to hit a LargeResourceClump containing a GiantCrop;
                                //      this is due to the "lastUser" field being null during the first use of those tool subclasses
                                IReflectedField <Farmer> lastUser = Utility.Helper.Reflection.GetField <Farmer>(Game1.player.CurrentTool, "lastUser", false); //get the protected "lastUser" field of this tool
                                lastUser?.SetValue(Game1.player);                                                                                             //set "lastUser" to the player

                                clump.performToolAction(Game1.player.CurrentTool, 0, targetTile, Game1.currentLocation);                                      //make the inner ResourceClump react to being hit by the tool
                            }
                        }
                    }
                }
            }
        }
예제 #5
0
        private void Junimo_Lives(object sender, EventArgs args)
        {
            if (!Context.IsWorldReady)
            {
                return;
            }

            if (Game1.currentMinigame is MineCart game)
            {
                IReflectedField <int> livesLeft = this.Helper.Reflection.GetField <int>(game, "livesLeft");
                if (livesLeft != null)
                {
                    if (livesLeft.GetValue() < 3)
                    {
                        livesLeft.SetValue(3);
                        //this.Monitor.Log("Set Cart Lives to 3");
                    }
                    else
                    {
                        return;
                    }
                }
            }
            else
            {
                return;
            }
        }
예제 #6
0
        private void TemporarilyFakeInteraction(Action action)
        {
            // get references
            // (Note: change net values directly to avoid sync bugs, since the value will be reset when we're done.)
            Farmer                    player             = Game1.player;
            NetRef <Horse>            mountField         = this.Reflection.GetField <NetRef <Horse> >(Game1.player, "netMount").GetValue();
            IReflectedField <Horse>   mountFieldValue    = this.Reflection.GetField <Horse>(mountField, "value");
            IReflectedField <Vector2> mountPositionValue = this.Reflection.GetField <Vector2>(player.mount.position.Field, "value");

            // save current state
            Horse       mount            = mountField.Value;
            Vector2     mountPosition    = mount.Position;
            WateringCan wateringCan      = player.CurrentTool as WateringCan;
            int         waterInCan       = wateringCan?.WaterLeft ?? 0;
            float       stamina          = player.stamina;
            Vector2     position         = player.Position;
            int         facingDirection  = player.FacingDirection;
            int         currentToolIndex = player.CurrentToolIndex;
            bool        canMove          = player.canMove; // fix player frozen due to animations when performing an action

            // move mount out of the way
            mountFieldValue.SetValue(null);
            mountPositionValue.SetValue(new Vector2(-5, -5));

            // perform action
            try
            {
                action();
            }
            finally
            {
                // move mount back
                mountPositionValue.SetValue(mountPosition);
                mountFieldValue.SetValue(mount);

                // restore previous state
                if (wateringCan != null)
                {
                    wateringCan.WaterLeft = waterInCan;
                }
                player.stamina          = stamina;
                player.Position         = position;
                player.FacingDirection  = facingDirection;
                player.CurrentToolIndex = currentToolIndex;
                player.canMove          = canMove;
            }
        }
예제 #7
0
 /*
  * Private Methods
  */
 private void GameLoop_TimeChanged(object sender, TimeChangedEventArgs e)
 {
     if (e.NewTime == 2500)
     {
         IReflectedField <int> timePass = this.Helper.Reflection.GetField <int>(typeof(Game1), "timeOfDay");
         timePass.SetValue(2400);
     }
     //throw new NotImplementedException();
 }
예제 #8
0
        /// <summary>Temporarily dismount and set up the player to interact with a tile, then return it to the previous state afterwards.</summary>
        /// <param name="action">The action to perform.</param>
        private void TemporarilyFakeInteraction(Action action)
        {
            // get references
            SFarmer player = Game1.player;
            IReflectedField <Horse> mountField = this.Reflection.GetField <Horse>(Game1.player, "mount");

            // save current state
            Horse       mount            = mountField.GetValue();
            Vector2     mountPosition    = this.Position;
            WateringCan wateringCan      = player.CurrentTool as WateringCan;
            int         waterInCan       = wateringCan?.WaterLeft ?? 0;
            float       stamina          = player.stamina;
            Vector2     position         = player.Position;
            int         facingDirection  = player.FacingDirection;
            int         currentToolIndex = player.CurrentToolIndex;
            bool        canMove          = Game1.player.canMove; // fix player frozen due to animations when performing an action

            // move mount out of the way
            mountField.SetValue(null);
            this.Position = new Vector2(-5, -5);

            // perform action
            try
            {
                action();
            }
            finally
            {
                // move mount back
                this.Position = mountPosition;
                mountField.SetValue(mount);

                // restore previous state
                if (wateringCan != null)
                {
                    wateringCan.WaterLeft = waterInCan;
                }
                player.stamina          = stamina;
                player.Position         = position;
                player.FacingDirection  = facingDirection;
                player.CurrentToolIndex = currentToolIndex;
                Game1.player.canMove    = canMove;
            }
        }
예제 #9
0
        public static void AutoFishing(BobberBar bar)
        {
            AutoFishingCounter = (AutoFishingCounter + 1) % 3;
            if (AutoFishingCounter > 0)
            {
                return;
            }


            IReflectedField <float> bobberSpeed = Reflection.GetField <float>(bar, "bobberBarSpeed");

            float barPos               = Reflection.GetField <float>(bar, "bobberBarPos").GetValue();
            int   barHeight            = Reflection.GetField <int>(bar, "bobberBarHeight").GetValue();
            float fishPos              = Reflection.GetField <float>(bar, "bobberPosition").GetValue();
            float treasurePos          = Reflection.GetField <float>(bar, "treasurePosition").GetValue();
            float distanceFromCatching = Reflection.GetField <float>(bar, "distanceFromCatching").GetValue();
            bool  treasureCaught       = Reflection.GetField <bool>(bar, "treasureCaught").GetValue();
            bool  treasure             = Reflection.GetField <bool>(bar, "treasure").GetValue();
            float treasureApeearTimer  = Reflection.GetField <float>(bar, "treasureAppearTimer").GetValue();
            float bobberBarSpeed       = bobberSpeed.GetValue();

            float top = barPos;

            if (treasure && treasureApeearTimer <= 0 && !treasureCaught)
            {
                if (!CatchingTreasure && distanceFromCatching > 0.7f)
                {
                    CatchingTreasure = true;
                }
                if (CatchingTreasure && distanceFromCatching < 0.3f)
                {
                    CatchingTreasure = false;
                }
                if (CatchingTreasure)
                {
                    fishPos = treasurePos;
                }
            }

            if (fishPos > barPos + (barHeight / 2f))
            {
                return;
            }

            float strength = (fishPos - (barPos + barHeight / 2f)) / 18f;
            float distance = fishPos - top;

            float threshold = Util.Cap(InstanceHolder.Config.CpuThresholdFishing, 0, 0.5f);

            if (distance < threshold * barHeight || distance > (1 - threshold) * barHeight)
            {
                bobberBarSpeed = strength;
            }

            bobberSpeed.SetValue(bobberBarSpeed);
        }
예제 #10
0
        private void MenuChanged(object sender, MenuChangedEventArgs e)
        {
            var shop = e.NewMenu as StardewValley.Menus.ShopMenu;

            if (shop == null || Game1.currentLocation.Name != "Forest" || !shop.potraitPersonDialogue.Contains("hats"))
            {
                return;
            }

            // no bundles for Joja members
            if (Game1.player.hasOrWillReceiveMail("JojaMember"))
            {
                return;
            }
            CommunityCenter communityCenter = Game1.locations.OfType <CommunityCenter>().First();

            if (communityCenter.areAllAreasComplete())
            {
                return;
            }

            IReflectedField <Dictionary <Item, int[]> > inventoryInformation = this.Helper.Reflection.GetField <Dictionary <Item, int[]> >(shop, "itemPriceAndStock");
            Dictionary <Item, int[]>       itemPriceAndStock  = inventoryInformation.GetValue();
            IReflectedField <List <Item> > forSaleInformation = this.Helper.Reflection.GetField <List <Item> >(shop, "forSale");
            List <Item> forSale = forSaleInformation.GetValue();

            foreach (var bundle in GetBundles())
            {
                if (communityCenter.isBundleComplete(bundle.ID))
                {
                    continue;
                }

                foreach (var ing in bundle.Ingredients)
                {
                    if (communityCenter.bundles[bundle.ID][ing.Index])
                    {
                        continue;
                    }
                    int itemId      = ing.ItemID;
                    var objectToAdd = new StardewValley.Object(Vector2.Zero, itemId, ing.Stack);
                    objectToAdd.Quality = ing.Quality;
                    if (objectToAdd.Name.Contains("rror"))
                    {
                        continue;
                    }
                    itemPriceAndStock.Add(objectToAdd, new int[2] {
                        5000, ing.Stack
                    });
                    forSale.Add(objectToAdd);
                }
            }

            inventoryInformation.SetValue(itemPriceAndStock);
            forSaleInformation.SetValue(forSale);
        }
예제 #11
0
        public static void Postfix(Farm __instance)
        {
            IReflectedField <NetRectangle> houseSource      = FarmHouseStates.reflector.GetField <NetRectangle>(__instance, "houseSource");
            IReflectedField <NetRectangle> greenhouseSource = FarmHouseStates.reflector.GetField <NetRectangle>(__instance, "greenhouseSource");

            houseSource.SetValue(new NetRectangle(new Microsoft.Xna.Framework.Rectangle(0, 0, 0, 0)));
            greenhouseSource.SetValue(new NetRectangle(new Microsoft.Xna.Framework.Rectangle(0, 0, 0, 0)));
            FarmState.init();
            FarmState.setUpFarm(__instance);
        }
예제 #12
0
        public void SaveEvents_AfterLoad(object sender, EventArgs e)
        {
            //this.Monitor.Log($"SaveEvents_AfterLoad called");

            IReflectedField <NetEvent0> passOutEventField = this.Helper.Reflection.GetField <NetEvent0>(Game1.player, "passOutEvent");
            NetEvent0 passOutEvent = new NetEvent0();

            passOutEvent.onEvent += new NetEvent0.Event(this.performPassOut);
            passOutEventField.SetValue(passOutEvent);

            //this.Monitor.Log($"SaveEvents_AfterLoad ended");
        }
예제 #13
0
 /// <summary>Build base texture by combining face, nose, bottom, and shoe choice.</summary>
 /// <param name="player">The player to patch.</param>
 /// <param name="config">The config to patch.</param>
 /// <param name="which">The config number to use.</param>
 public void PatchBaseTexture(SFarmer player, LocalConfig config, int which = 0)
 {
     if (config.MutiplayerFix)
     {
         player.FarmerRenderer.textureName.Set("KisekaeBase_" + (player.IsMale ? "male_" : "female_") + config.ChosenFace[which] + "_" + config.ChosenNose[which] + "_" + config.ChosenBottoms[which] + "_" + config.ChosenShoes[which]);
     }
     else
     {
         Texture2D playerTextures = LoadBaseTexture(player.isMale ? "male" : "female", new int[] { config.ChosenFace[which], config.ChosenNose[which], config.ChosenBottoms[which], config.ChosenShoes[which] });
         IReflectedField <Texture2D> curBaseTexture = m_env.Helper.Reflection.GetField <Texture2D>(player.FarmerRenderer, "baseTexture");
         curBaseTexture.SetValue(playerTextures);
     }
 }
 public void TougleMultiplayerFix()
 {
     m_config.MutiplayerFix = !m_config.MutiplayerFix;
     if (!m_config.MutiplayerFix)
     {
         //m_env.Helper.Reflection.GetMethod(m_farmer.FarmerRenderer);
         //m_farmer.FarmerRenderer.textureName.fieldChangeEvent -= ;
         IReflectedField <string> tn = m_env.Helper.Reflection.GetField <string>(m_farmer.FarmerRenderer.textureName, "value");
         tn.SetValue(Path.Combine("Characters", "Farmer", m_farmer.IsMale ? "farmer_base" : "farmer_girl_base"));
         //m_farmer.FarmerRenderer.textureName.MarkClean();
     }
     //FixBase();
 }
예제 #15
0
        public static void JunimoNoteMenu_ctor_Postfix(
            JunimoNoteMenu __instance,
            bool fromGameMenu,
            int area,
            bool fromThisMenu)
        {
            CommunityCenter cc = Bundles.CC;

            if (Bundles.IsAbandonedJojaMartBundleAvailableOrComplete())
            {
                return;
            }

            IReflectedField <int> whichAreaField = Reflection.GetField
                                                   <int>
                                                       (__instance, "whichArea");

            bool isAreaSet       = false;
            bool isNavigationSet = false;

            foreach (string areaName in Bundles.GetAllAreaNames())
            {
                int areaNumber = CommunityCenter.getAreaNumberFromName(areaName);

                // Set default area for menu view with custom areas
                if (!isAreaSet &&
                    fromGameMenu && !fromThisMenu && !isAreaSet &&
                    cc.shouldNoteAppearInArea(areaNumber) && !Bundles.IsAreaComplete(cc: cc, areaNumber: areaNumber))
                {
                    area = areaNumber;
                    whichAreaField.SetValue(area);
                    isAreaSet = true;
                }

                // Show navigation arrows when custom areas
                if (!isNavigationSet &&
                    areaNumber >= 0 && areaNumber != area && cc.shouldNoteAppearInArea(areaNumber))
                {
                    __instance.areaNextButton.visible = true;
                    __instance.areaBackButton.visible = true;
                    isNavigationSet = true;
                }

                if (isAreaSet && isNavigationSet)
                {
                    break;
                }
            }
        }
예제 #16
0
        /// <summary>
        /// Reverts the mushroom stump back into a tree exactly like its done in StardewValley.TerrainFeatures.Tree.dayUpdate, but only if it's not a chopped down tree
        /// </summary>
        /// <param name="tree">current mushroom tree</param>
        private void FixMushroomStump(Tree tree)
        {
            if (tree.treeType.Value == (int)TreeType.mushroomTree)
            {
                IReflectedField <float> shakeRotation = Helper.Reflection.GetField <float>(tree, "shakeRotation");

                // if the value is higher than this the game considers the tree as falling or having fallen
                if (Math.Abs(shakeRotation.GetValue()) < 1.5707963267948966)
                {
                    tree.stump.Set(false);
                    tree.health.Set(10f);
                    shakeRotation.SetValue(0f);
                }
            }
        }
예제 #17
0
        private void GameEvents_UpdateTick(object sender, EventArgs e)
        {
            if (Game1.activeClickableMenu is CarpenterMenu carpenter)
            {
                if (carpenter.CurrentBlueprint.name == "Aquaponics")
                {
                    IReflectedField <Building> cBuilding = Helper.Reflection.GetField <Building>(carpenter, "currentBuilding");

                    if (!(cBuilding.GetValue() is Aquaponics))
                    {
                        cBuilding.SetValue(new Aquaponics(Vector2.Zero, Game1.getFarm()));
                    }
                }
            }
        }
예제 #18
0
        private void Events_Rendering(object sender, EventArgs e)
        {
            if (Game1.activeClickableMenu is Billboard)
            {
                #region accessing Billboard
                Billboard menu          = (Billboard)Game1.activeClickableMenu;
                FieldInfo calendarField = menu.GetType().GetField("calendarDays", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                if (calendarField == null)
                {
                    this.Monitor.Log("Could not find field 'calendarDays' in Billboard!", LogLevel.Error);
                    return;
                }

                List <ClickableTextureComponent> calendarDays = (List <ClickableTextureComponent>)calendarField.GetValue(menu);
                IReflectedField <string>         privateField = this.Helper.Reflection.GetField <string>(menu, "hoverText");
                string hoverText = privateField.GetValue();
                #endregion

                if (calendarDays != null && !(hoverText.Contains("Moon") || hoverText.Contains("moon")))
                {
                    for (int day = 1; day <= 28; day++)
                    {
                        ClickableTextureComponent component = calendarDays[day - 1]; // 0 - 27

                        if (component.bounds.Contains(Game1.getMouseX(), Game1.getMouseY()))
                        {
                            if (moon.LunarCalendar[day] >= 0 && moon.LunarCalendar[day] <= 3)
                            {
                                if (hoverText.Length > 0)
                                {
                                    hoverText += "\n";
                                }

                                hoverText += $"{moon.CalculatePhaseName(moon.LunarCalendar[day])} moon";
                            }
                            else
                            {
                                hoverText += "";
                                break;
                            }
                        }
                    }

                    privateField.SetValue(hoverText);
                }
            }
        }
예제 #19
0
        private void Junimo_Lives(object sender, UpdateTickedEventArgs args)
        {
            if (!Context.IsWorldReady)
            {
                return;
            }

            if (Game1.currentMinigame is MineCart game && args.IsMultipleOf(30))
            {
                IReflectedField <int> livesLeft = Helper.Reflection.GetField <int>(game, "livesLeft");
                if (livesLeft?.GetValue() < 3)
                {
                    livesLeft.SetValue(3);
                    //this.Monitor.Log("Set Cart Lives to 3");
                }
            }
        }
예제 #20
0
 public void Shake(Vector2 tileLocation, bool doEvenIfStillShaking)
 {
     if (!((double)_maxShake.GetValue() == 0.0 | doEvenIfStillShaking))
     {
         return;
     }
     _shakeLeft.SetValue((double)Game1.player.getTileLocation().X > (double)tileLocation.X || (double)Game1.player.getTileLocation().X == (double)tileLocation.X && Game1.random.NextDouble() < 0.5);
     _maxShake.SetValue((float)Math.PI / 128f);
     if (!townBush && tileSheetOffset == 1 && inBloom(Game1.currentSeason, Game1.dayOfMonth))
     {
         int    parentSheetIndex = -1;
         string currentSeason    = Game1.currentSeason;
         if (currentSeason != "spring")
         {
             if (currentSeason == "fall")
             {
                 parentSheetIndex = 410;
             }
         }
         else
         {
             parentSheetIndex = 296;
         }
         if (parentSheetIndex == -1)
         {
             return;
         }
         tileSheetOffset.Value = 0;
         setUpSourceRect();
         int num = new Random((int)tileLocation.X + (int)tileLocation.Y * 5000 + (int)Game1.uniqueIDForThisGame + (int)Game1.stats.DaysPlayed).Next(1, 2) + Game1.player.ForagingLevel / 4;
         for (int index = 0; index < num; ++index)
         {
             Game1.createItemDebris((Item) new StardewValley.Object(parentSheetIndex, 1, false, -1, Game1.player.professions.Contains(16) ? 4 : 0), Utility.PointToVector2(getBoundingBox().Center), Game1.random.Next(1, 4), (GameLocation)null);
         }
         DelayedAction.playSoundAfterDelay("leafrustle", 100);
     }
     else
     {
         if ((double)tileLocation.X != 20.0f || (double)tileLocation.Y != 8.0f || (Game1.dayOfMonth != 28 || Game1.timeOfDay != 1200) || Game1.player.mailReceived.Contains("junimoPlush"))
         {
             return;
         }
         Game1.player.addItemByMenuIfNecessaryElseHoldUp((Item) new Furniture(1733, Vector2.Zero), new ItemGrabMenu.behaviorOnItemSelect(junimoPlushCallback));
     }
 }
        public void FruitTryToHarvestHere()
        {
            if (this.currentLocation == null)
            {
                return;
            }

            if (this.currentLocation.terrainFeatures.ContainsKey(this.getTileLocation()) && this.currentLocation.terrainFeatures[this.getTileLocation()] is HoeDirt && (this.currentLocation.terrainFeatures[this.getTileLocation()] as HoeDirt).readyForHarvest() ||
                IsAdjacentReadyToHarvestFruitTree(this.getTileLocation(), currentLocation))
            {
                fharvestTimer.SetValue(2000);
                hasAttemptedHarvestThisInterval = false;
            }
            else
            {
                this.FruitPokeToHarvest();
            }
        }
예제 #22
0
        public static void setShippingCrateLocation(Vector2 tileLocation)
        {
            Farm farm = Game1.getFarm();

            shippingCrateLocation = new Vector2(tileLocation.X, tileLocation.Y);

            IReflectedField <Rectangle> shippingBinLidOpenArea = FarmHouseStates.reflector.GetField <Rectangle>(farm, "shippingBinLidOpenArea");

            shippingBinLidOpenArea.SetValue(new Rectangle((int)(shippingCrateLocation.X - 1) * 64, (int)(shippingCrateLocation.Y - 1) * 64, 256, 192));

            Map map = farm.map;

            Logger.Log("Moving shipping crate tiles...");
            removeVanillaShippingCrateTiles(map);
            removeEverythingFromTile(farm, shippingCrateLocation);
            removeEverythingFromTile(farm, shippingCrateLocation + new Vector2(1, 0));
            buildShippingCrate(map);
        }
예제 #23
0
        private void StartMinigameEndFunction(FishingRod rod, Farmer user, int fish)
        {
            rod.isReeling = true;
            rod.hit       = false;

            // Animation
            switch (user.FacingDirection)
            {
            case 1:
                user.FarmerSprite.setCurrentSingleFrame(48);
                break;

            case 3:
                user.FarmerSprite.setCurrentSingleFrame(48, 32000, false, true);
                break;

            default:
                break;
            }
            user.FarmerSprite.PauseForSingleAnimation = true;

            // Distance from bobber to land
            IReflectedField <int> clearWaterDistanceField = ModFishing.Instance.Helper.Reflection.GetField <int>(rod, "clearWaterDistance");

            clearWaterDistanceField.SetValue(FishingRod.distanceToLand((int)(rod.bobber.X / 64.0 - 1.0), (int)(rod.bobber.Y / 64.0 - 1.0), user.currentLocation));

            // Calculate size of fish
            float num = 1f * (clearWaterDistanceField.GetValue() / 5f) * (Game1.random.Next(1 + Math.Min(10, user.FishingLevel) / 2, 6) / 5f);

            if (rod.favBait)
            {
                num *= 1.2f;
            }
            float fishSize = Math.Max(0.0f, Math.Min(1f, num * (float)(1.0 + Game1.random.Next(-10, 10) / 100.0)));

            // Check if there should be treasure
            bool treasure = !Game1.isFestival();

            treasure &= user.fishCaught != null && user.fishCaught.Count > 1;
            treasure &= Game1.random.NextDouble() < ModFishing.Instance.Api.GetTreasureChance(user, rod);
            Game1.activeClickableMenu = new CustomBobberBar(user, fish, fishSize, treasure, rod.attachments[1]?.ParentSheetIndex ?? -1);
        }
예제 #24
0
        public static void CollectMailAttachmentsAndQuests(LetterViewerMenu menu)
        {
            IReflectedField <int> questIdField = Reflection.GetField <int>(menu, "questID");
            int questId = questIdField.GetValue();

            if (menu.itemsLeftToGrab())
            {
                foreach (ClickableComponent component in menu.itemsToGrab.ToArray())
                {
                    if (component.item == null || !CanPlayerAcceptsItemPartially(component.item))
                    {
                        continue;
                    }

                    int stack = component.item.Stack;
                    Game1.playSound("coin");
                    int remain = Util.AddItemIntoInventory(component.item);

                    Logger.Log($"You collected {component.item.DisplayName}{(stack - remain > 1 ? " x" + (stack - remain) : "")}.");
                    if (remain == 0)
                    {
                        component.item = null;
                    }
                    else
                    {
                        component.item.Stack = remain;
                    }
                }
            }

            if (questId == -1)
            {
                return;
            }

            Logger.Log($"You started Quest: '{Quest.getQuestFromId(questId).questTitle}'.");
            Game1.player.addQuest(questId);
            Game1.playSound("newArtifact");
            questIdField.SetValue(-1);
        }
예제 #25
0
        /// <summary>Calls <see cref="Fire(GameLocation, int, int, int, StardewValley.Farmer)"/> if this is not an automatic slingshot.</summary>
        public override void DoFunction(GameLocation location, int x, int y, int power, StardewValley.Farmer who)
        {
            this.indexOfMenuItemView = this.initialParentTileIndex;
            who.usingSlingshot       = false;
            who.canReleaseTool       = true;
            who.usingTool            = false;
            who.canMove = true;

            if (!this.baseCanPlaySound.GetValue())
            {
                if (this.GetPullBackRange(x, y, (int)startingMousePos.X, (int)startingMousePos.Y) > 4)
                {
                    if (!this.isAutomatic || (this.couldHaveFired && !this.didFire))
                    {
                        this.Fire(location, x, y, power, who);
                    }
                }
            }

            baseCanPlaySound.SetValue(true);
            who.Halt();
        }
예제 #26
0
        private static bool Prefix(Dialogue __instance, string masterDialogue, NPC speaker)
        {
            // get private members
            bool             nameArraysTranslated              = DialogueErrorPatch.Reflection.GetField <bool>(typeof(Dialogue), "nameArraysTranslated").GetValue();
            IReflectedMethod translateArraysOfStrings          = DialogueErrorPatch.Reflection.GetMethod(typeof(Dialogue), "TranslateArraysOfStrings");
            IReflectedMethod parseDialogueString               = DialogueErrorPatch.Reflection.GetMethod(__instance, "parseDialogueString");
            IReflectedMethod checkForSpecialDialogueAttributes = DialogueErrorPatch.Reflection.GetMethod(__instance, "checkForSpecialDialogueAttributes");
            IReflectedField <List <string> > dialogues         = DialogueErrorPatch.Reflection.GetField <List <string> >(__instance, "dialogues");

            // replicate base constructor
            if (dialogues.GetValue() == null)
            {
                dialogues.SetValue(new List <string>());
            }

            // duplicate code with try..catch
            try
            {
                if (!nameArraysTranslated)
                {
                    translateArraysOfStrings.Invoke();
                }
                __instance.speaker = speaker;
                parseDialogueString.Invoke(masterDialogue);
                checkForSpecialDialogueAttributes.Invoke();
            }
            catch (Exception baseEx) when(baseEx.InnerException is TargetInvocationException invocationEx && invocationEx.InnerException is Exception ex)
            {
                string name = !string.IsNullOrWhiteSpace(speaker?.Name) ? speaker.Name : null;

                DialogueErrorPatch.MonitorForGame.Log($"Failed parsing dialogue string{(name != null ? $" for {name}" : "")}:\n{masterDialogue}\n{ex}", LogLevel.Error);

                parseDialogueString.Invoke("...");
                checkForSpecialDialogueAttributes.Invoke();
            }

            return(false);
        }
    }
예제 #27
0
        /// <summary>The event called after the list of NPCs in a location changes.</summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void OnNpcListChanged(object sender, NpcListChangedEventArgs e)
        {
            if (!this.IsEnabled)
            {
                return;
            }

            // workaround for mines not spawning a ladder on monster levels
            if (e.Location is MineShaft mine && e.IsCurrentLocation)
            {
                if (mine.mustKillAllMonstersToAdvance() && mine.characters.All(p => p is Horse))
                {
                    IReflectedField <bool> hasLadder = this.Helper.Reflection.GetField <bool>(mine, "ladderHasSpawned");
                    if (!hasLadder.GetValue())
                    {
                        mine.createLadderAt(mine.mineEntrancePosition(Game1.player));
                        hasLadder.SetValue(true);
                    }
                }
            }

            // workaround for instantly-built tractors spawning a horse
            if (Context.IsMainPlayer && e.Location is BuildableGameLocation buildableLocation)
            {
                Horse[] horses = e.Added.OfType <Horse>().ToArray();
                if (horses.Any())
                {
                    HashSet <Guid> tractorIDs = new HashSet <Guid>(this.GetGaragesIn(buildableLocation).Select(p => p.HorseId));
                    foreach (Horse horse in horses)
                    {
                        if (tractorIDs.Contains(horse.HorseId) && !TractorManager.IsTractor(horse))
                        {
                            horse.Name = TractorManager.GetTractorName(horse.HorseId);
                        }
                    }
                }
            }
        }
예제 #28
0
 public override void performHoverAction(int x, int y)
 {
     base.performHoverAction(x, y);
     if (_movingAnimal.GetValue())
     {
         Vector2  tile             = new Vector2((float)((x + Game1.viewport.X) / Game1.tileSize), (float)((y + Game1.viewport.Y) / Game1.tileSize));
         Farm     locationFromName = Game1.getLocationFromName("Farm") as Farm;
         Building buildingAt       = locationFromName.getBuildingAt(tile);
         if (buildingAt != null)
         {
             if (buildingAt.color.Equals(Color.LightGreen * 0.8f) &&
                 PregnancyController.IsAnimalPregnant(this._farmAnimal.myID.Value) &&
                 PregnancyController.CheckBuildingLimit((buildingAt.indoors.Value as AnimalHouse)))
             {
                 buildingAt.color.Value = Color.Red * 0.8f;
             }
         }
     }
     else
     {
         if (this.meatButton != null)
         {
             if (this.meatButton.containsPoint(x, y))
             {
                 this.meatButton.scale = Math.Min(4.1f, this.meatButton.scale + 0.05f);
                 _hoverText.SetValue(DataLoader.i18n.Get("Menu.AnimalQueryMenu.ExchangeAnimalForMeat"));
             }
             else
             {
                 this.meatButton.scale = Math.Max(4f, this.sellButton.scale - 0.05f);
             }
         }
         if (this.pregnantStatus != null)
         {
             if (this.pregnantStatus.containsPoint(x, y))
             {
                 int daysUtilBirth = PregnancyController.GetPregnancyItem(this._farmAnimal.myID.Value).DaysUntilBirth;
                 if (daysUtilBirth > 1)
                 {
                     _hoverText.SetValue(DataLoader.i18n.Get("Menu.AnimalQueryMenu.DaysUntilBirth", new { numberOfDays = daysUtilBirth }));
                 }
                 else
                 {
                     _hoverText.SetValue(DataLoader.i18n.Get("Menu.AnimalQueryMenu.ReadyForBirth"));
                 }
             }
         }
         if (this.treatStatus != null)
         {
             if (this.treatStatus.containsPoint(x, y))
             {
                 int daysUntilNextTreat = TreatsController.DaysUntilNextTreat(this._farmAnimal);
                 if (daysUntilNextTreat > 1)
                 {
                     _hoverText.SetValue(DataLoader.i18n.Get("Menu.AnimalQueryMenu.WantsTreatInDays", new { numberOfDays = daysUntilNextTreat }));
                 }
                 else if (daysUntilNextTreat == 1)
                 {
                     _hoverText.SetValue(DataLoader.i18n.Get("Menu.AnimalQueryMenu.WantsTreatTomorrow"));
                 }
                 else
                 {
                     _hoverText.SetValue(DataLoader.i18n.Get("Menu.AnimalQueryMenu.WantsTreat"));
                 }
             }
         }
         if (this.animalContestIndicator != null)
         {
             if (this.animalContestIndicator.containsPoint(x, y))
             {
                 if (AnimalContestController.CanChangeParticipant(this._farmAnimal))
                 {
                     _hoverText.SetValue(DataLoader.i18n.Get("Menu.AnimalQueryMenu.ChangeParticipant"));
                 }
                 else if (AnimalContestController.HasParticipated(this._farmAnimal) && AnimalContestController.HasWon(this._farmAnimal))
                 {
                     SDate date = AnimalContestController.GetParticipantDate(this._farmAnimal);
                     _hoverText.SetValue(DataLoader.i18n.Get("Menu.AnimalQueryMenu.Winner", new { contestDate = Utility.getDateStringFor(date.Day, Utility.getSeasonNumber(date.Season), date.Year) }));
                 }
                 else
                 {
                     SDate date = AnimalContestController.GetParticipantDate(this._farmAnimal);
                     _hoverText.SetValue(DataLoader.i18n.Get("Menu.AnimalQueryMenu.ContestParticipant", new { contestDate = Utility.getDateStringFor(date.Day, Utility.getSeasonNumber(date.Season), date.Year) }));
                 }
             }
         }
     }
 }
예제 #29
0
        // Change: Added functionality to show a preview of a lost book.
        public override void performHoverAction(int x, int y)
        {
            IReflectedField <int> secretNoteImageRef = ModEntry.CommonServices.ReflectionHelper.GetField <int>(this, "secretNoteImage");

            int currentTab  = ModEntry.CommonServices.ReflectionHelper.GetField <int>(this, "currentTab").GetValue();
            int currentPage = ModEntry.CommonServices.ReflectionHelper.GetField <int>(this, "currentPage").GetValue();

            descriptionTextRef.SetValue("");
            hoverTextRef.SetValue("");
            valueRef.SetValue(-1);
            secretNoteImageRef.SetValue(-1);

            foreach (ClickableTextureComponent sideTab in this.sideTabs)
            {
                if (sideTab.containsPoint(x, y))
                {
                    hoverTextRef.SetValue(sideTab.hoverText);
                    return;
                }
            }

            foreach (ClickableTextureComponent textureComponent in this.collections[currentTab][currentPage])
            {
                if (textureComponent.containsPoint(x, y))
                {
                    textureComponent.scale = Math.Min(textureComponent.scale + 0.02f, textureComponent.baseScale + 0.1f);

                    if (currentTab != achievementsTab)
                    {
                        // Draw [unknown] tooltip if item hasn't been encountered yet
                        if (!Convert.ToBoolean(textureComponent.name.Split(' ')[1]))
                        {
                            hoverTextRef.SetValue("???");
                            continue;
                        }

                        // Book has already been found -> show book preview
                        if (currentTab == lostBooksTabPageIndex)
                        {
                            string index   = textureComponent.name.Split(' ')[2];
                            string message = Game1.content.LoadString("Strings\\Notes:" + index).Replace('\n', '^');

                            string title = message.Split('^')[0].Trim();
                            if (title.Length > BOOK_PREVIEW_LENGTH)
                            {
                                title = title.Substring(0, BOOK_PREVIEW_LENGTH) + "...";
                            }

                            // Set hover text to book content preview.
                            hoverTextRef.SetValue(title);
                            continue;
                        }
                    }

                    hoverTextRef.SetValue(this.createDescription(Convert.ToInt32(textureComponent.name.Split(' ')[0])));
                }
                else
                {
                    textureComponent.scale = Math.Max(textureComponent.scale - 0.02f, textureComponent.baseScale);
                }
            }

            this.forwardButton.tryHover(x, y, 0.5f);
            this.backButton.tryHover(x, y, 0.5f);
        }
예제 #30
0
            /// <summary>Generates a monster and places it on the specified map and tile.</summary>
            /// <param name="monsterType">The monster type's name and an optional dictionary of monster-specific settings.</param>
            /// <param name="location">The GameLocation where the monster should be spawned.</param>
            /// <param name="tile">The x/y coordinates of the tile where the monster should be spawned.</param>
            /// <param name="areaID">The UniqueAreaID of the related SpawnArea. Required for log messages.</param>
            /// <returns>Returns the monster's ID value, or null if the spawn process failed.</returns>
            public static int?SpawnMonster(MonsterType monsterType, GameLocation location, Vector2 tile, string areaID = "")
            {
                Monster monster = null;                                                                      //an instatiated monster, to be spawned into the world later

                Color?color = null;                                                                          //the monster's color (used by specific monster types)

                if (monsterType.Settings != null)                                                            //if settings were provided
                {
                    if (monsterType.Settings.ContainsKey("Color"))                                           //if this setting was provided
                    {
                        string[]   colorText    = ((string)monsterType.Settings["Color"]).Trim().Split(' '); //split the setting string into strings for each number
                        List <int> colorNumbers = new List <int>();
                        foreach (string text in colorText)                                                   //for each string
                        {
                            int num = Convert.ToInt32(text);                                                 //convert it to a number
                            if (num < 0)
                            {
                                num = 0;
                            }                         //minimum 0
                            else if (num > 255)
                            {
                                num = 255;
                            }                      //maximum 255
                            colorNumbers.Add(num); //add it to the list
                        }

                        //convert strings into RGBA values
                        int r = Convert.ToInt32(colorNumbers[0]);
                        int g = Convert.ToInt32(colorNumbers[1]);
                        int b = Convert.ToInt32(colorNumbers[2]);
                        int a;
                        if (colorNumbers.Count > 3) //if the setting included an "A" value
                        {
                            a = Convert.ToInt32(colorNumbers[3]);
                        }
                        else //if the setting did not include an "A" value
                        {
                            a = 255; //default to no transparency
                        }

                        color = new Color(r, g, b, a);                                                                     //set the color
                    }
                    else if (monsterType.Settings.ContainsKey("MinColor") && monsterType.Settings.ContainsKey("MaxColor")) //if color wasn't provided, but mincolor & maxcolor were
                    {
                        string[]   minColorText    = ((string)monsterType.Settings["MinColor"]).Trim().Split(' ');         //split the setting string into strings for each number
                        List <int> minColorNumbers = new List <int>();
                        foreach (string text in minColorText)                                                              //for each string
                        {
                            int num = Convert.ToInt32(text);                                                               //convert it to a number
                            if (num < 0)
                            {
                                num = 0;
                            }                         //minimum 0
                            else if (num > 255)
                            {
                                num = 255;
                            }                         //maximum 255
                            minColorNumbers.Add(num); //add it to the list
                        }

                        string[]   maxColorText    = ((string)monsterType.Settings["MaxColor"]).Trim().Split(' '); //split the setting string into strings for each number
                        List <int> maxColorNumbers = new List <int>();
                        foreach (string text in maxColorText)                                                      //for each string
                        {
                            int num = Convert.ToInt32(text);                                                       //convert it to a number
                            if (num < 0)
                            {
                                num = 0;
                            }                         //minimum 0
                            else if (num > 255)
                            {
                                num = 255;
                            }                         //maximum 255
                            maxColorNumbers.Add(num); //convert to number
                        }

                        for (int x = 0; x < minColorNumbers.Count && x < maxColorNumbers.Count; x++) //for each pair of values
                        {
                            if (minColorNumbers[x] > maxColorNumbers[x])                             //if min > max
                            {
                                //swap min and max
                                int temp = minColorNumbers[x];
                                minColorNumbers[x] = maxColorNumbers[x];
                                maxColorNumbers[x] = temp;
                            }
                        }

                        //pick random RGBA values between min and max
                        int r = RNG.Next(minColorNumbers[0], maxColorNumbers[0] + 1);
                        int g = RNG.Next(minColorNumbers[1], maxColorNumbers[1] + 1);
                        int b = RNG.Next(minColorNumbers[2], maxColorNumbers[2] + 1);
                        int a;
                        if (minColorNumbers.Count > 3 && maxColorNumbers.Count > 3) //if both settings included an "A" value
                        {
                            a = RNG.Next(minColorNumbers[3], maxColorNumbers[3] + 1);
                        }
                        else //if one/both of the settings did not include an "A" value
                        {
                            a = 255; //default to no transparency
                        }

                        color = new Color(r, g, b, a); //set the color
                    }
                }

                bool seesPlayers = false;                                               //whether the monster automatically "sees" players at spawn (handled differently by some monster types)

                if (monsterType.Settings != null)                                       //if settings were provided
                {
                    if (monsterType.Settings.ContainsKey("SeesPlayersAtSpawn"))         //if this setting was provided
                    {
                        seesPlayers = (bool)monsterType.Settings["SeesPlayersAtSpawn"]; //use the provided setting
                    }
                }

                //create a new monster based on the provided name & apply type-specific settings
                switch (monsterType.MonsterName.ToLower()) //avoid most casing issues by making this lower-case
                {
                case "bat":
                    monster = new BatFTM(tile, 0);
                    break;

                case "frostbat":
                case "frost bat":
                    monster = new BatFTM(tile, 40);
                    break;

                case "lavabat":
                case "lava bat":
                    monster = new BatFTM(tile, 80);
                    break;

                case "iridiumbat":
                case "iridium bat":
                    monster = new BatFTM(tile, 171);
                    break;

                case "doll":
                case "curseddoll":
                case "cursed doll":
                    monster = new BatFTM(tile, -666);
                    break;

                case "skull":
                case "hauntedskull":
                case "haunted skull":
                    monster = new BatFTM(tile, 77377);
                    break;

                case "bigslime":
                case "big slime":
                case "biggreenslime":
                case "big green slime":
                    monster = new BigSlimeFTM(tile, 0);
                    if (color.HasValue)                               //if color was provided
                    {
                        ((BigSlimeFTM)monster).c.Value = color.Value; //set its color after creation
                    }
                    if (seesPlayers)                                  //if the "SeesPlayersAtSpawn" setting is true
                    {
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "bigblueslime":
                case "big blue slime":
                case "bigfrostjelly":
                case "big frost jelly":
                    monster = new BigSlimeFTM(tile, 40);
                    if (color.HasValue)                               //if color was provided
                    {
                        ((BigSlimeFTM)monster).c.Value = color.Value; //set its color after creation
                    }
                    if (seesPlayers)                                  //if the "SeesPlayersAtSpawn" setting is true
                    {
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "bigredslime":
                case "big red slime":
                case "bigredsludge":
                case "big red sludge":
                    monster = new BigSlimeFTM(tile, 80);
                    if (color.HasValue)                               //if color was provided
                    {
                        ((BigSlimeFTM)monster).c.Value = color.Value; //set its color after creation
                    }
                    if (seesPlayers)                                  //if the "SeesPlayersAtSpawn" setting is true
                    {
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "bigpurpleslime":
                case "big purple slime":
                case "bigpurplesludge":
                case "big purple sludge":
                    monster = new BigSlimeFTM(tile, 121);
                    if (color.HasValue)                               //if color was provided
                    {
                        ((BigSlimeFTM)monster).c.Value = color.Value; //set its color after creation
                    }
                    if (seesPlayers)                                  //if the "SeesPlayersAtSpawn" setting is true
                    {
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "bug":
                    monster = new Bug(tile, 0);
                    break;

                case "armoredbug":
                case "armored bug":
                    monster = new Bug(tile, 121);
                    break;

                case "dino":
                case "dinomonster":
                case "dino monster":
                case "pepper":
                case "pepperrex":
                case "pepper rex":
                case "rex":
                    monster = new DinoMonster(tile);
                    break;

                case "duggy":
                    monster = new DuggyFTM(tile);
                    break;

                case "dust":
                case "sprite":
                case "dustsprite":
                case "dust sprite":
                case "spirit":
                case "dustspirit":
                case "dust spirit":
                    monster = new DustSpirit(tile);
                    break;

                case "ghost":
                    monster = new GhostFTM(tile);
                    break;

                case "carbonghost":
                case "carbon ghost":
                    monster = new GhostFTM(tile, "Carbon Ghost");
                    break;

                case "slime":
                case "greenslime":
                case "green slime":
                    monster = new GreenSlime(tile, 0);
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "blueslime":
                case "blue slime":
                case "frostjelly":
                case "frost jelly":
                    monster = new GreenSlime(tile, 40);
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "redslime":
                case "red slime":
                case "redsludge":
                case "red sludge":
                    monster = new GreenSlime(tile, 80);
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "purpleslime":
                case "purple slime":
                case "purplesludge":
                case "purple sludge":
                    monster = new GreenSlime(tile, 121);
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "grub":
                case "cavegrub":
                case "cave grub":
                    monster = new GrubFTM(tile, false);
                    break;

                case "fly":
                case "cavefly":
                case "cave fly":
                    monster = new FlyFTM(tile, false);
                    break;

                case "mutantgrub":
                case "mutant grub":
                    monster = new GrubFTM(tile, true);
                    break;

                case "mutantfly":
                case "mutant fly":
                    monster = new FlyFTM(tile, true);
                    break;

                case "metalhead":
                case "metal head":
                    monster = new MetalHead(tile, 0);
                    if (color.HasValue)                             //if color was provided
                    {
                        ((MetalHead)monster).c.Value = color.Value; //set its color after creation
                    }
                    break;

                case "mummy":
                    monster = new MummyFTM(tile);
                    break;

                case "rockcrab":
                case "rock crab":
                    monster = new RockCrab(tile);
                    break;

                case "lavacrab":
                case "lava crab":
                    monster = new LavaCrab(tile);
                    break;

                case "iridiumcrab":
                case "iridium crab":
                    monster = new RockCrab(tile, "Iridium Crab");
                    break;

                case "rockgolem":
                case "rock golem":
                case "stonegolem":
                case "stone golem":
                    monster = new RockGolemFTM(tile);
                    break;

                case "wildernessgolem":
                case "wilderness golem":
                    monster = new RockGolemFTM(tile, Game1.player.CombatLevel);
                    break;

                case "serpent":
                    monster = new SerpentFTM(tile);
                    break;

                case "brute":
                case "shadowbrute":
                case "shadow brute":
                    monster = new ShadowBrute(tile);
                    break;

                case "shaman":
                case "shadowshaman":
                case "shadow shaman":
                    monster = new ShadowShaman(tile);
                    break;

                case "skeleton":
                    monster = new Skeleton(tile);
                    if (seesPlayers)                                                                                        //if the "SeesPlayersAtSpawn" setting is true
                    {
                        IReflectedField <bool> spottedPlayer = Helper.Reflection.GetField <bool>(monster, "spottedPlayer"); //try to access this skeleton's private "spottedPlayer" field
                        spottedPlayer.SetValue(true);
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "squid":
                case "kid":
                case "squidkid":
                case "squid kid":
                    monster = new SquidKidFTM(tile);
                    break;

                default:                                                                           //if the name doesn't match any directly known monster types
                    Type externalType = GetTypeFromName(monsterType.MonsterName, typeof(Monster)); //find a monster subclass with a matching name
                    monster = (Monster)Activator.CreateInstance(externalType, tile);               //create a monster with the Vector2 constructor
                    break;
                }

                if (monster == null)
                {
                    Monitor.Log($"The monster to be spawned (\"{monsterType.MonsterName}\") doesn't match any known monster types. Make sure that name isn't misspelled in your config file.", LogLevel.Info);
                    return(null);
                }

                int?ID = MonsterTracker.AddMonster(monster);  //generate an ID for this monster

                if (!ID.HasValue)
                {
                    Monitor.Log("A new monster ID could not be generated. This is may be due to coding issue; please report it to this mod's developer. This monster won't be spawned.", LogLevel.Warn);
                    return(null);
                }
                monster.id = ID.Value;                                       //assign the ID to this monster

                monster.MaxHealth = monster.Health;                          //some monster types set Health on creation and expect MaxHealth to be updated like this

                ApplyMonsterSettings(monster, monsterType.Settings, areaID); //adjust the monster based on any other provided optional settings

                //spawn the completed monster at the target location
                Monitor.VerboseLog($"Spawning monster. Type: {monsterType.MonsterName}. Location: {tile.X},{tile.Y} ({location.Name}).");
                monster.currentLocation = location;
                monster.setTileLocation(tile);
                location.addCharacter(monster);
                return(monster.id);
            }