Modding:Specific Examples

From Stardew Valley Wiki
Jump to: navigation, search

Index

Specific Examples (SMAPI mods)

This page will, as time permits, detail some very specific Stardew Valley (SDV) modding examples using SMAPI and C#. The hope is that you can learn how to use SMAPI for your own project, even if you are not attempting to do the exact same thing the examples are doing. Seeing full, working code, instead of a small snippet, along with detailed explanation, might provide the insight you need to complete your own mod.

"Tagging" the train

This example explains how to "tag" the train cars with your own graffiti image, illustrating how to edit an existing "vanilla" texture using SMAPI. It is helpful to have a basic understanding of how an area functions in the vanilla version of SDV before you begin to modify it, otherwise you might modify an area that has a specific function or you might have accurate code, but not see the result you are expecting in the game. There are many experienced modders on the Discord channel (Get help, ask questions, or discuss with the community) who can assist you, so don't be afraid to ask.

Pro Tip!
This section assumes you are already familiar with unpacking XNB files. If you are not up-to-speed on that topic you should take a few moments to familiarize yourself with that information (Unpacking XNB Files) so you can get the most out of this example.

The image data for the existing "graffiti" used by SDV can be found in the Stardew Valley\Content\LooseSprites folder in the Cursors.xnb file. Unpacking that file will allow you to examine the Cursors.png file in a paint program of your choice. This texture stores many differing images, only a small number of which are related to the train, and an even smaller subset are related specifically to graffiti. For the purposes of illustration a small snippet of the Cursors.png is included below (from 224, 608 to 448, 736). Please note that the image has been enlarged and the brightness increased to show otherwise difficult to see details. There are other graffiti items as well, located a "row" (32 pixels) above what is shown in Figure 1, but some of those, such as the Joja Co sprite, are used specifically by SDV and should not be replaced without a deeper understanding.

Wiki SMAPI example1.png Wiki SMAPI example2.png
Figure 1: Graffiti from vanilla Cursors.png Figure 2: Our Texture

Each sprite that can appear on the train car is 32x32 pixels, therefore your image must also be 32x32 pixels. SDV will randomly select one of these images to adorn a train car with, making it very easy to substitute your own image instead; however; this does mean that whichever vanilla image you replace will not be seen in the game. For this reason the example code will randomly replace a vanilla image rather than always replacing the same image, just so all vanilla graffiti has a chance to be seen by the player. That is not entirely necessary but provides a better experience for the user of your mod.

Pro Tip!
Some textures used by SDV contain an alpha channel for masking the image when rendering it to a surface. You will need to use a paint program that can load and save alpha channels in order to achieve the expected results!

Asset editor class

Take a look at the following code. Using a separate class or implementing this in your ModEntry has already been covered elsewhere, so for this example it will be encapsulated in a separate class. Since we are using a separate class the IModHelper has to be provided to us in the constructor and saved so it can be used.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI;
using StardewValley;

namespace MyModNamespace
{
    class GraffitiExample : IAssetEditor
    {
        private IModHelper modHelper;

        public GraffitiExample(IModHelper helper)
        {
            modHelper = helper;
        }

        /// <summary>
        /// Indicate that our mod wants to edit some content.
        /// </summary>
        /// <returns>True if this is an asset we want to edit, false otherwise</returns>
        public bool CanEdit<T>(IAssetInfo asset)
        {
            return asset.AssetNameEquals("LooseSprites\\cursors");
        }

        /// <summary>
        /// Edit the content that we care about
        /// </summary>
        public void Edit<T>(IAssetData asset)
        {
            if (asset.AssetNameEquals("LooseSprites\\cursors"))
            {
                Texture2D trainGraffiti = modHelper.Content.Load<Texture2D>("assets/MyGraffiti.png", ContentSource.ModFolder);

                // Replace a random vanilla train graffiti image with ours.  There are more than this, but this is just an example of how to
                // accomplish it.  You can replace the ones you dislike the most.  You can also replace multiples if you wanted.  Again this
                // is to show you how to fish, so to speak!
                int random = Game1.random.Next(6);
                int targetX = 0;
                int targetY = 0;

                switch (random)
                {
                    case 0:     // Replace crossbones emojis
                        targetX = 224;
                        targetY = 608;
                        break;
                    case 1:     // Replace shadow creature
                        targetX = 288;
                        targetY = 704;
                        break;
                    case 2:     // Replace Joja Sux
                        targetX = 256;
                        targetY = 640;
                        break;
                    case 3:     // Replace Green blob
                        targetX = 288;
                        targetY = 640;
                        break;
                    case 4:     // Replace Winter tree
                        targetX = 224;
                        targetY = 704;
                        break;
                    case 5:     // Replace snowman
                        targetX = 288;
                        targetY = 608;
                        break;
                }

                // The "source" in this example is a single spirit in the image so no need for the source rectangle (could be null).
                asset.AsImage().PatchImage(trainGraffiti, new Rectangle(0, 0, 32, 32), new Rectangle(targetX, targetY, 32, 32), PatchMode.Overlay);
            }
        }
    }
}

CanEdit() is straightforward for this example. This class could be injecting (editing) changes into multiple content areas where it would need to evaluate multiple assets, in which case you would need to use if conditions rather than a single return. Edit() loads our texture from the mods asset folder, selects a random vanilla sprite to replace and then patches the existing image by copying our image (0, 0, 32, 32) over the target image (random location). That's it! Existing SDV code will grab a random sprite (maybe ours) and apply it to a train car.

ModEntry class

For completeness the ModEntry class is included below in case it is of value. This class is light in this example, so using a separate asset class doesn't appear to be valuable, but in the full mod that does other functions, the ModEntry class can be cluttered enough.

using StardewModdingAPI;
using StardewModdingAPI.Events;
using StardewValley;

namespace MyModNamespace
{
    public class ModEntry : Mod
    {
        private IModHelper modHelper;

        /// <summary>
        /// Entry Method for SMAPI.  Called when mod is loaded
        /// </summary>
        /// <param name="helper">Provides simplified APIs for writing mods.</param>
        public override void Entry(IModHelper helper)
        {
            modHelper = helper;

            helper.Events.GameLoop.GameLaunched += this.OnGameLaunched;
        }

        /// <summary>
        /// Fires after the game is launched, before the first update tick. This happens once per game session (unrelated to loading saves).
        /// All mods are loaded and initialized at this point, so this is a good time to set up mod integrations.
        /// </summary>
        private void OnGameLaunched(object sender, GameLaunchedEventArgs e)
        {
            Helper.Content.AssetEditors.Add(new GraffitiExample(Helper));
        }
    }
}