Tutorial: Adding a Fruit Tree with CP

From Stardew Modding Wiki
Jump to navigation Jump to search

Introduction

Hello! Rokugin here with the first of hopefully at least a few tutorials on how to make some simple stuff with Content Patcher. If you need any help with this or any of the stuff on my Git, feel free to try to reach me on the Discord server.


If all you need is to see the code and assets you can check my git here.


The focus of this guide will be adding a fruit tree to the game using Content Patcher with the aim to be a simple but hopefully informative tutorial.


Pathoschild has a great author's guide on CP that goes into greater detail on the functionality.

I'll try to provide some basic info about what is happening and why, but nothing beats getting the info from the source.


I'm not an artist, so hopefully what I've provided is good enough to get the point across.


For SDV 1.6

Getting Started

I'll assume you've already installed SMAPI and Content Patcher.

The first thing you'll want to do is create a new folder to contain your mod, located inside your mods folder or a folder inside your mods folder.

Naming convention for the folder is to start it with [CP] to indicate that it's a Content Patcher mod.


Fruit Tree Tutorial Mod Folder.png


I've created a new folder called [CP] New Fruit Tree directly inside the mods folder.


File Name Extensions

At this point I have the first of two suggestions, this one is closer to a requirement and that is to turn on being able to see file name extensions.


FruitTreeTutorialFileNameExtensions.png


This will allow you to actually create json files without having to do anything weird with save as.


Creating and Editing the Manifest

Inside this folder you'll need to create at least two files, we'll start with the manifest.


Simply create a new text document and rename it to "manifest.json" without the quotes.


Fruit Tree Tutorial Mod Folder Manifest.png


Now that you have a manifest, you can open it with any text editor.

My second suggestion is that you use VS Code, as it's fairly light and provides a lot of useful functionality.


{
    "Name": "Your Mod Name",
    "Author": "Your Name",
    "Version": "1.0.0",
    "Description": "One or two sentences about the mod.",
    "UniqueID": "YourName.YourModName",
    "UpdateKeys": [], // when you release the mod, see https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Update_checks
    "ContentPackFor": {
        "UniqueID": "Pathoschild.ContentPatcher"
    }
}


Basic Manifest Explanation

Name should be the name of your mod, spaces are fine here.

Author should be your name, username, or whatever you prefer.

Version is what version of your mod this is, for a first release I typically leave it as 1.0.0.

Description is a one or two sentence description of your mod.

UniqueID is typically a combination of author and name without any spaces. You may have to type this often later while debugging so I suggest something simple.

UpdateKeys is used to tell others when there's an update to your mod.

ContentPackFor should be left alone as this tells SMAPI that this is a Content Patcher mod.

Worth mentioning is that VS Code will complain about comments by default, in the bottom right you can change the language to json with comments.

Comments will not stop SMAPI from being able to use a json.


{
    "Name": "New Fruit Tree",
    "Author": "Rokugin",
    "Version": "1.0.0",
    "Description": "This mod adds a new fruit tree and fruit.",
    "UniqueID": "rokugin.newtree",
    "UpdateKeys": [], // when you release the mod, see https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Update_checks
    "ContentPackFor": {
        "UniqueID": "Pathoschild.ContentPatcher"
    }
}


With the manifest out of the way, it's time to move onto the content.json.


Contents of Content

Inside the same folder where your manifest is, create a content.json file.


FruitTreeTutorialContentInFolder.png


Initial Setup

Open content.json and add the following:


{
    "Format": "2.0.0",
    "Changes": [
        {
            
        }
    ]
}


Format is required in the content.json and for the version as of the writing of this guide on April 28th, 2024, it should be "2.0.0".

Changes is required in any file you're making changes in, since this guide's goal is something simple I won't be going into using Include to load extra files.


Fruit Tree Sprites

In order to add a new fruit tree to the game, we'll need some sprites of the tree.


By looking in the unpacked content files, we can see that fruit tree sprites are stored in TileSheet/fruitTrees.png.

Using this image as a guide, we can see what sizes the different tree stages need to be and the size of image that's being used.

Each tree has its own row that depicts the different growth stages. The size of the image is very important for the game to choose the correct sprite. Each different tree you want to add should have its own row.


Here's an example of a single tree on a sheet:


FruitTreeTutorialExampleTree.png


Unfortunately, I'm not an artist and don't have anything to say here. Check out the tutorials for some helpful hints on how to do pixel art for this game.


Once you've created your sprites, create a new folder in your mod folder called assets.


FruitTreeTutorialAssetsInFolder.png


Inside that folder, to keep things organized and on track with the tutorial, create another folder called sprites.


FruitTreeTutorialSpritesInAssetsFolder.png


Inside of the sprites folder you place your fruit tree sprites, I've named mine FruitTrees and suggest you do the same to make following along easier.


FruitTreeTutorialTreesInFolder.png


First Action

Now that our content.json is ready and we have some sprites, we can add our first Action field.


{
    "Format": "2.0.0",
    "Changes": [
        {
            "LogName": "Load textures",
            "Action": "Load",
            "Target": "Mods/{{ModId}}/FruitTrees",
            "FromFile": "assets/sprites/{{TargetWithoutPath}}.png"
        }
    ]
}


LogName replaces the default error name that SMAPI shows when there's a problem with a specific edit, this makes it easier to tell which part of the code is failing in most cases.

Action tells the mod what we want to do here, Load either creates a new asset for us to use like in this case, or can be used to completely overwrite a vanilla asset (this should be avoided if possible).

Target is the name of the asset we want to create or overwrite.

FromFile is the actual file path and name that we are trying to load into the asset. The path starts from your mod folder, where the manifest is.

{{ModId}} is a token, in CP this is automatically converted to the UniqueID you used in your manifest. So for this example the Target would be Mods/rokugin.newtree/FruitTrees.

{{TargetWithoutPath}} is a also token, in CP this takes the Target line of Mods/rokugin.newtree/FruitTrees and removes everything before FruitTrees.

This isn't functionally any different from just writing "assets/sprites/FruitTrees.png" but will prove to be useful later.

In this case, we are loading an image into a texture asset that doesn't already exist, so the asset is being created using the image we're loading.


Fruit and Sapling Sprites

Fruit trees require fruit when they're being set up, so now is a good time to create a fruit sprite to load into the game.


The game stores a lot of sprites on mega sprite sheets and then locates then using coordinates or indexes.

By looking at Maps/springobjects.png, we can find the fruit and sapling images, these sprites are in a 16x16 grid.


Here's an example of a fruit and a sapling on a sprite sheet:


FruitTreeTutorialObjectsSpriteSheet.png


Using the existing art is a good way to get the scale of the things you want to add correct but remember that it's all just an approximation and you'll likely want to do what looks best.


Save these to the same sprite sheet as I've shown in the sprites folder, I've named mine Objects for the tutorial.


Fruit Tree Tutorial Objects Sprite Sheet In Folder.png


Loading the Fruit and Sapling Sprites

Now we can load the fruit and sapling sprites into the assets.


{
    "Format": "2.0.0",
    "Changes": [
        {
            "LogName": "Load textures",
            "Action": "Load",
            "Target": "Mods/{{ModId}}/FruitTrees, Mods/{{ModId}}/Objects",
            "FromFile": "assets/sprites/{{TargetWithoutPath}}.png"
        }
    ]
}


Here we've added Mods/{{ModId}}/Objects to the Target line. Because we're using the TargetWithoutPath token, we don't have to add anything else to the FromFile.

This works because the images we're loading are named the same thing as the assets we're creating, if you decide to use different named source files from your assets then you won't be able to use the TargetWithoutPath token.


Adding the Fruit and Sapling

Now we can create our next Action block, that will edit Data/Objects and add our fruit and sapling to the game.


{
    "Format": "2.0.0",
    "Changes": [
        {
            "LogName": "Load textures",
            "Action": "Load",
            "Target": "Mods/{{ModId}}/FruitTrees",
            "FromFile": "assets/sprites/{{TargetWithoutPath}}.png"
        },
        {
            "LogName": "Add cacao fruit and sapling to objects",
            "Action": "EditData",
            "Target": "Data/Objects",
            "Entries": {
                "{{ModId}}_CacaoFruit": {
                    "Name": "{{ModId}}_CacaoFruit",
                    "Displayname": "Cacao",
                    "Description": "A tropical, sweet, and acidic flavor with an incredible floral scent.",
                    "Type": "Basic",
                    "Category": -79,
                    "Price": 100,
                    "Texture": "Mods/{{ModId}}/Objects",
                    "SpriteIndex": 0,
                    "Edibility": 15,
                    "ContextTags": [
                        "color_yellow"
                    ]
                },
                "{{ModId}}_CacaoSapling": {
                    "Name": "{{ModId}}_CacaoSapling",
                    "Displayname": "Cacao Sapling",
                    "Description": "Takes 28 days to produce a mature Cacao tree. Bears fruit in the summer, or all year round when planted on Ginger Island.",
                    "Type": "Basic",
                    "Category": -74,
                    "Price": 1000,
                    "Texture": "Mods/{{ModId}}/Objects",
                    "SpriteIndex": 1,
                    "Edibility": -300
                }
            }
        }
    ]
}


To make this a little easier, I'll remove the stuff we aren't focusing on right now:


{
    "LogName": "Add cacao fruit and sapling to objects",
    "Action": "EditData",
    "Target": "Data/Objects",
    "Entries": {
        "{{ModId}}_CacaoFruit": {
            "Name": "{{ModId}}_CacaoFruit",
            "Displayname": "Cacao",
            "Description": "A tropical, sweet, and acidic flavor with an incredible floral scent.",
            "Type": "Basic",
            "Category": -79,
            "Price": 100,
            "Texture": "Mods/{{ModId}}/Objects",
            "SpriteIndex": 0,
            "Edibility": 15,
            "ContextTags": [
                "color_yellow"
            ]
        },
        "{{ModId}}_CacaoSapling": {
            "Name": "{{ModId}}_CacaoSapling",
            "Displayname": "Cacao Sapling",
            "Description": "Takes 28 days to produce a mature Cacao tree. Bears fruit in the summer, or all year round when planted on Ginger Island.",
            "Type": "Basic",
            "Category": -74,
            "Price": 1000,
            "Texture": "Mods/{{ModId}}/Objects",
            "SpriteIndex": 1,
            "Edibility": -300
        }
    }
}


Action: EditData allows us to edit entries and fields inside a data asset.

Entries allows you to add, replace or delete entries in the data.

Each entry in Entries requires an ID or key, the first of which is "{{ModId}}_CacaoFruit" in this example. For objects this becomes your objects ID, so it's important that it gets a unique ID here.

Remember {{ModId}}? So for the case of the example, the ID of my Cacao Fruit is actually rokugin.newtree_CacaoFruit.

Name is the internal name, for consistency this should be the same as the ID.

Displayname is how it will actually appear in game, because fruit names get appended to things like preserves, I don't name it "Fruit" and simply name it as what it is.

Type is the items general type.

Category is the specific category the item belongs to.

Price is the amount you sell it for, before any modifiers.

Texture is the texture asset we created earlier.

SpriteIndex is the location on the sheet where our sprite is found.

Edibility affects how much health and stamina is recovered when eating the object. -300 makes the object inedible.

ContextTags have many uses but for the purposes of this, setting the color will dictate how the object can be used in tailoring.

More information can be found about the different fields here.


This block also showcases that we can have multiple entries per action block. As long as your edits are to the same target, you can make as many edits as you'd like in a block.


Each tilesheet has different SpriteIndex values, depending on how the game is using them. In the case of our Objects asset, each index is 16x16, starts at 0 in the top left and increases by one in left to right, top to bottom format.

Since our fruit is first in our image, it's index is 0 on our asset and the sapling being second has an index of 1 on our asset. If we added a new sprite down a row under the fruit that would be index 2 and the bottom right would then become index 3.

It's important to remain consistent in the way we expand our sprite sheet.

if we were counting on having 0, 1 on our first row and 2, 3 on our second, but then decided to add a third column to our sheet, our indexes would instead become 0, 1, 2 on the top row and 3, 4, 5 on our bottom row.

Any index we had already set in our json would then point to the wrong location on our sheet.


Adding the Fruit Tree

Now that we have all the other pieces, we can finally add the tree itself to the game. Here's where our content.json should be now:


{
    "Format": "2.0.0",
    "Changes": [
        {
            "LogName": "Load textures",
            "Action": "Load",
            "Target": "Mods/{{ModId}}/FruitTrees, Mods/{{ModId}}/Objects",
            "FromFile": "assets/sprites/{{TargetWithoutPath}}.png"
        },
        {
            "LogName": "Add cacao fruit and sapling to objects",
            "Action": "EditData",
            "Target": "Data/Objects",
            "Entries": {
                "{{ModId}}_CacaoFruit": {
                    "Name": "{{ModId}}_CacaoFruit",
                    "Displayname": "Cacao",
                    "Description": "A tropical, sweet, and acidic flavor with an incredible floral scent.",
                    "Type": "Basic",
                    "Category": -79,
                    "Price": 100,
                    "Texture": "Mods/{{ModId}}/Objects",
                    "SpriteIndex": 0,
                    "Edibility": 15,
                    "ContextTags": [
                        "color_yellow"
                    ]
                },
                "{{ModId}}_CacaoSapling": {
                    "Name": "{{ModId}}_CacaoSapling",
                    "Displayname": "Cacao Sapling",
                    "Description": "Takes 28 days to produce a mature Cacao tree. Bears fruit in the summer, or all year round when planted on Ginger Island.",
                    "Type": "Basic",
                    "Category": -74,
                    "Price": 1000,
                    "Texture": "Mods/{{ModId}}/Objects",
                    "SpriteIndex": 1,
                    "Edibility": -300
                }
            }
        },
        {
            "LogName": "Add cacao to fruit trees",
            "Action": "EditData", 
            "Target": "Data/FruitTrees", 
            "Entries": {
                "{{ModId}}_CacaoSapling": {
                    "DisplayName": "Cacao",
                    "Seasons": [
                        "Summer"
                    ],
                    "Fruit": [
                        {
                            "ID": "CacaoFruit",
                            "ItemId": "{{ModId}}_CacaoFruit"
                        }
                    ],
                    "Texture": "Mods/{{ModId}}/FruitTrees",
                    "TextureSpriteRow": 0
                }
            }
        }
    ]
}


And here's the new portion we're adding by itself:


{
    "LogName": "Add cacao to fruit trees",
    "Action": "EditData",
    "Target": "Data/FruitTrees",
    "Entries": {
        "{{ModId}}_CacaoSapling": {
            "DisplayName": "Cacao",
            "Seasons": [
                "Summer"
            ],
            "Fruit": [
                {
                    "ID": "CacaoFruit",
                    "ItemId": "{{ModId}}_CacaoFruit"
                }
            ],
            "Texture": "Mods/{{ModId}}/FruitTrees",
            "TextureSpriteRow": 0
        }
    }
}


Here in our Entries, the key for the tree is the ID of its sapling.

DisplayName is once again how it will appear in game.

Seasons is an array of seasons the tree can produce fruit in.

Fruit is the fruit the tree can produce.

ID here is just a unique identifier for this fruit entry.

ItemId is the ID of the fruit object.

Texture is from our FruitTrees asset.

TextureSpriteRow is 0 because we only have one tree on our sheet.

If you have more than one tree, your sprite sheet should grow downwards only.


At this point, you should have a fully functioning fruit tree. Hopefully this guide was easy enough to follow and I didn't mess anything up so that you were able to achieve that.

As a bonus, I'll also include how to make the sapling be sold at Pierre's shop so you have a way to get it that isn't only through debugging.


Bonus

Adding Sapling to Seed Shop

Here's the code needed to add the sapling to the seed shop:


{
    "LogName": "Add cacao sapling to seed shop",
    "Action": "EditData",
    "Target": "Data/Shops",
    "TargetField": [
        "SeedShop",
        "Items"
    ],
    "Entries": {
        "{{ModId}}_CacaoSapling": {
            "Id": "{{ModId}}_CacaoSapling",
            "ItemId": "{{ModId}}_CacaoSapling",
            "ObjectInternalName": "{{ModId}}_CacaoSapling",
            "Price": 1000
        }
    },
    "MoveEntries": [
        {
            "ID": "{{ModId}}_CacaoSapling",
            "BeforeID": "(O)630"
        }
    ]
}


The entry key, internal Id and ItemId all being the same makes it easy to insure that we're targeting and getting the correct item.

ObjectInternalName can also be used if you've named your object something different, check out Cookies in Data/Objects to see another example of this. I tend to not rely on this though.

Price is the amount you would pay for the sapling before modifiers, some shops just have price modifiers.

MoveEntries allows you to reorganize some lists, in this case you can use it to move the sapling up with the others.


And here's the final version of the content.json with everything in it:


{
    "Format": "2.0.0",
    "Changes": [
        {
            "LogName": "Load textures",
            "Action": "Load",
            "Target": "Mods/{{ModId}}/FruitTrees, Mods/{{ModId}}/Objects",
            "FromFile": "assets/sprites/{{TargetWithoutPath}}.png"
        },
        {
            "LogName": "Add cacao fruit and sapling to objects",
            "Action": "EditData",
            "Target": "Data/Objects",
            "Entries": {
                "{{ModId}}_CacaoFruit": {
                    "Name": "{{ModId}}_CacaoFruit",
                    "Displayname": "Cacao",
                    "Description": "A tropical, sweet, and acidic flavor with an incredible floral scent.",
                    "Type": "Basic",
                    "Category": -79,
                    "Price": 100,
                    "Texture": "Mods/{{ModId}}/Objects",
                    "SpriteIndex": 0,
                    "Edibility": 15,
                    "ContextTags": [
                        "color_yellow"
                    ]
                },
                "{{ModId}}_CacaoSapling": {
                    "Name": "{{ModId}}_CacaoSapling",
                    "Displayname": "Cacao Sapling",
                    "Description": "Takes 28 days to produce a mature Cacao tree. Bears fruit in the summer, or all year round when planted on Ginger Island.",
                    "Type": "Basic",
                    "Category": -74,
                    "Price": 1000,
                    "Texture": "Mods/{{ModId}}/Objects",
                    "SpriteIndex": 1,
                    "Edibility": -300
                }
            }
        },
        {
            "LogName": "Add cacao to fruit trees",
            "Action": "EditData", // More info:
            "Target": "Data/FruitTrees", // https://stardewvalleywiki.com/Modding:Migrate_to_Stardew_Valley_1.6#Custom_fruit_trees
            "Entries": {
                "{{ModId}}_CacaoSapling": {
                    "DisplayName": "Cacao",
                    "Seasons": [
                        "Summer"
                    ],
                    "Fruit": [
                        {
                            "ID": "CacaoFruit",
                            "ItemId": "{{ModId}}_CacaoFruit"
                        }
                    ],
                    "Texture": "Mods/{{ModId}}/FruitTrees",
                    "TextureSpriteRow": 0
                }
            }
        },
        {
            "LogName": "Add cacao sapling to seed shop",
            "Action": "EditData",
            "Target": "Data/Shops",
            "TargetField": [
                "SeedShop",
                "Items"
            ],
            "Entries": {
                "{{ModId}}_CacaoSapling": {
                    "Id": "{{ModId}}_CacaoSapling",
                    "ItemId": "{{ModId}}_CacaoSapling",
                    "ObjectInternalName": "{{ModId}}_CacaoSapling",
                    "Price": 1000
                }
            },
            "MoveEntries": [
                {
                    "ID": "{{ModId}}_CacaoSapling",
                    "BeforeID": "(O)630"
                }
            ]
        }
    ]
}