Tutorial: Adding New Machines (Or New Rules To Existing Machines)

From Stardew Modding Wiki
Jump to navigation Jump to search

Last edited by AtlasVBot on 2025-09-13 18:56:04

Stardew Valley 1.6 now natively supports machine rules, so you (for the most part) no longer need another framework like PFM, only Content Patcher. With that being said, machine data could be somewhat complicated, but once you know how things are formatted and how certain CP concept works it should be a breeze!

First, set up a sample Content Patcher mod. You can follow instructions here. Once you have a CP mod set up, you’d put patches in the Changes entry of your content.json file, each patch corresponding to one change.

Adding a new machine

To add a new machine that can start processing input (optional) and produce outputs, you’ll need to do the following:

Define a new big craftable

Big craftables are objects that are 1x2 in size and placeable on the ground. They don’t necessarily have to be machines, but machines can only(*) be big craftables.

(*in certain situations and/or with the addition of framework mods you can make non-BC machines, but that’s a more advanced topic…)

To define a big craftable, you need to do two things:

Load its sprite

As mentioned, BCs are 1x2 in size; combined with Stardew’s sprite size being 16x16 px means that a BC is 16 pixels wide and 32 pixels tall. Let’s use this sprite of a stone mill to get started!


If you want multiple machines, you can add them to the same sprite sheet as long as they’re neatly organized in 1x2 rectangles. In that case the first sprite will have index 0, and the second sprite will have index 1 and so on.

Once you have a sprite image, save it to a folder named assets in your mod. Let’s name it BigCraftables.png, and load it into an asset named Mods/YourModId/BigCraftables with this CP patch:

{
  "LogName": "Load BC sprite sheet",
  "Action": "Load",
  "Target": "Mods/{{ModId}}/BigCraftables",
  // I recommend using {{ModId}} wherever unique IDs are involved; this turns into your mod's unique ID by CP and helps avoids name collisions with other mods
  "FromFile": "assets/{{TargetWithoutPath}}.png", // {{TargetWithoutPath}} turns into the last part of the asset name; in this case it turns into BigCraftables
}


Define its entry

With its sprite loaded, we next add the BC to the game via a patch that adds a new entry to Data/BigCraftables. This defines the new BC's name, appearance and other non-machine properties. See the page on the main wiki for more info on all the fields you can set.

{
  "LogName": "Add the stone mill",
  "Action": "EditData",
  "Target": "Data/BigCraftables",
  "Entries": {
    "{{ModId}}_StoneMill": {
      "Name": "{{ModId}}_StoneMill",
      // Both the DisplayName and Description field can (and should) use i18n tokens so your mod can
      // be translated into different languages; see
      // https://github.com/Pathoschild/StardewMods/blob/stable/ContentPatcher/docs/author-guide/translations.md
      // for more info!
      "DisplayName": "Stone Mill",
      "Description": "This stone mill mills stuff like the big mill!",
      "Texture": "Mods/{{ModId}}/BigCraftables", // This is the texture we loaded above!
      "SpriteIndex": 0, // Use the first sprite in the texture. If you want the second sprite, set this to 1 and so on
    },
  }
}

Now you should be able to spawn the stone mill into the game and place it down on the ground! It doesn’t do anything fancy yet however; that’s where the next step comes in:

Add machine rules to the big craftable

Machine processing rules are defined in Data/Machines, and you can see the reference page on the main wiki here. There’s a lot to go through, but don’t worry, there are only a couple concepts that are truly mandatory to get started:

  • Each machine has a list of OutputRules objects, defining its rules.
  • Each output rule has:
    • Triggers, defining the conditions for it to start producing.
    • OutputItem, defining the outputs it will produce if any condition in Triggers is satisfied.
    • MinutesUntilReady or DaysUntilReady, how long the rule takes to process.

Got it? Let’s get started!

Add a new output rule

Let’s add a rule for our stone mill that turns wheat into wheat flour just like the large mill.

{
  "LogName": "Add stone mill rules",
  "Action": "EditData",
  "Target": "Data/Machines",
  "Entries": {
    // IMPORTANT!!! The key of Data/Machines must be identical to Data/BigCraftables, *but* with the
    // (BC) identifier added.
    "(BC){{ModId}}_StoneMill": {
      "OutputRules": [
        // Our first rule! If you need more rules add another entry to the OutputRules list
        {
          // It's not important what the ID is, as long as it's unique
          "Id": "{{ModId}}_WheatToFlour",
          "Triggers": [],
          "OutputItem": [],
          // You only need 1 of the below, they're both included here for completeness
          "MinutesUntilReady": 30,
          "DaysUntilReady": 1,
        },
      ],
    },
  }
}

The rule doesn’t do anything yet; let’s fill in its logic next.

Add triggers to the rule

An output rule can have one or many triggers that activate it, each of which can have multiple types and conditions involved. You can reference the wiki for more info, including making more complex triggers!

For our example, we want to add one trigger for when the user puts in a wheat, so we’ll change Triggers to look like this:

"Triggers": [
  {
    "Id": "{{ModId}}_WheatInput",
    // A machine can have other trigger types, such as `DayUpdate` to make the machine start
    // producing on day start. `ItemPlacedInMachine` is the most common type, meaning it starts
    // producing when an item is put into it.
    "Trigger": "ItemPlacedInMachine",
    // What the input item ID must be. In this case we want Wheat (item ID 262)
    "RequiredItemId": "(O)262",
    // How many of the input is needed. You can change this if you want the user to put in more
    "RequiredCount": 1,
  },
],

Add outputs to the rule

With the input defined, let’s define its output next, which will be wheat flour. Let’s change OutputItem to look like this:

"OutputItem": [
  // An entry in OutputItem can accept common spawn fields at
  // https://stardewvalleywiki.com/Modding:Item_queries#Item_spawn_fields
  // plus other machine-specific fields detailed on the machine wiki page. For
  // now let's just add the basics.
  {
    "Id": "{{ModId}}_WheatFlourOutput",
    // The output item. In this case we want wheat flour (ID 246)
    "ItemId": "(O)246",
    // The output count.
    "MinStack": 1,
  },
],

Define the processing time

Finally, set either MinutesUntilReady or DaysUntilReady to the desired duration. It’s already part of the template above, so we just need to pick one.

The final machine rule should look like this:

{
  "LogName": "Add stone mill rules",
  "Action": "EditData",
  "Target": "Data/Machines",
  "Entries": {
    "(BC){{ModId}}_StoneMill": {
      "OutputRules": [
        {
          "Id": "{{ModId}}_WheatToFlour",
          "Triggers": [
            {
              "Id": "{{ModId}}_WheatInput",
              "Trigger": "ItemPlacedInMachine",
              "RequiredItemId": "(O)262",
              "RequiredCount": 1,
            },
          ],
          "OutputItem": [
            {
              "Id": "{{ModId}}_WheatFlourOutput",
              "ItemId": "(O)246",
              "MinStack": 1,
            },
          ],
          "DaysUntilReady": 1,
        },
      ],
    },
  }
}

Now our stone mill should accept one wheat and spit out one wheat flour the next day! As an exercise you can modify this further to add new rules that turn beets into sugar or unmilled rice into regular rice.

Modify an existing machine

NOTE: If you skipped to this section, it's recommended that you turn back and read the entire page up to this point! The text assumes you know how machine data is formatted.

If you don't feel like adding an entire new BC and just want to add a new rule to an existing machine, there are a few things you need to keep in mind.

First, make sure to unpack your files to see how the vanilla machine data is laid out. This is optional, but will be immensely helpful for your own reference.

Then, you'd format a content patcher patch similar to above, with significant changes:

Use TargetField to perform targeted edits

Because these machines will already have Data/Machines entries added, you can't exactly drop an entire block like the example above into Data/Machines, since that would wipe and replace the entire entry in favor of your own. It goes without saying that that is bad; instead you want to perform targeted edits using TargetField. With TargetField you can:

  • Define a deeply nested field you want to edit
  • Insert new entries into that field without wiping other entries inside it

Let's begin with an example! Say that you want to insert a rule into the preserve jar to turn wheat into wheat flour instead of wheat pickles. You may be tempted to do something like this:

{
  "LogName": "The wrong way to add a rule",
  "Action": "EditData",
  "Target": "Data/Machines",
  "Entries": {
    "(BC)15": {
      "OutputRules": [
        {
          "Id": "{{ModId}}_WheatToFlour",
          "Triggers": [
            {
              "Id": "{{ModId}}_WheatInput",
              "Trigger": "ItemPlacedInMachine",
              "RequiredItemId": "(O)262",
              "RequiredCount": 1,
            },
          ],
          "OutputItem": [
            {
              "Id": "{{ModId}}_WheatFlourOutput",
              "ItemId": "(O)246",
              "MinStack": 1,
            },
          ],
          "DaysUntilReady": 1,
        },
      ],
    },
  }
}

This will make preserve jars accept wheat and turn it into flour... and nothing else. This is because the patch essentially tells CP to "replace OutputRules with this". Instead, we should use TargetField like so:

{
  "LogName": "The right way to add an entry",
  "Action": "EditData",
  "Target": "Data/Machines",
  // Important note 1: This means "insert the entry in Entries into the OutputRules list in the (BC)15 entry"
  "TargetField": ["(BC)15", "OutputRules"],
  // Important note 2: Entries must take the form of a dictionary of string keys ({{ModId}}_WheatToFlour in this case) to values (the actual object we're inserting),
  // regardless of the fact that what we're editing (OutputRules) is a list, making the key redundant as a result. This is just how CP works.
  // That's not to say the key is useless however - if there is already an entry with the Id {{ModId}}_WheatToFlour in the list, this allows us to *replace* that entry
  // while keeping other entries intact.
  "Entries": {
    "{{ModId}}_WheatToFlour": {
      "Id": "{{ModId}}_WheatToFlour",
      "Triggers": [
        {
          "Id": "{{ModId}}_WheatInput",
          "Trigger": "ItemPlacedInMachine",
          "RequiredItemId": "(O)262",
          "RequiredCount": 1,
        },
      ],
      "OutputItem": [
        {
          "Id": "{{ModId}}_WheatFlourOutput",
          "ItemId": "(O)246",
          "MinStack": 1,
        },
      ],
      "DaysUntilReady": 1,
    },
  },
}

With this, we have just inserted a new rule for the preserve jar while keeping the other rules in place!

TargetField can also be used to perform even more nested edits by inserting more entries into it. As another example, if you want to edit the preserve jar's sturgeon roe to caviar rule to give 2 Caviars back instead of one, you'd do the following:

{
  "LogName": "More caviar pls",
  "Action": "EditData",
  "Target": "Data/Machines",
  // This looks intimidating, but let's break it down. This is telling CP to:
  // * Enter the (BC)15 entry
  // * Enter the OutputRules list
  // * Find the entry in OutputRules with "Id": "Default_SturgeonRoe"
  // * Enter the OutputItem list
  // * Find the entry in OutputItem with "Id": "(O)445"
  // * Put whatever edit in Entries in that entry
  "TargetField": ["(BC)15", "OutputRules", "Default_SturgeonRoe", "OutputItem", "(O)445"],
  "Entries": {
    "MinStack": 2,
  },
}

Anyway, you should test out your wheat rule in game now by putting a wheat inside a preserve jar; if you did everything right you should get back... pickled wheat again? Hey, what gives?!

That brings us to the next change we need to make:

Use MoveEntries to reorder entries in a list

You see, what happened was that your new entry was inserted below the vanilla "vegetable to pickles" rule, and machine rule selection works by going down the OutputRules list from top to bottom, and pick out the first entry whose trigger matches. Because the wheat triggers the default vegetable to pickles rule, your rule never got picked!

To fix this, we'll need to use MoveEntries, which allows us to reorder entries after inserting them with Entries. MoveEntries must be on the same level as Entries, and can be part of the same entry.

See below for the modified code block with MoveEntries to move it to the top of the list, ensuring that the list gets picked first if possible:

{
  "LogName": "The (more) right way to add an entry",
  "Action": "EditData",
  "Target": "Data/Machines",
  "TargetField": ["(BC)15", "OutputRules"],
  "Entries": {
    "{{ModId}}_WheatToFlour": {
      "Id": "{{ModId}}_WheatToFlour",
      "Triggers": [
        {
          "Id": "{{ModId}}_WheatInput",
          "Trigger": "ItemPlacedInMachine",
          "RequiredItemId": "(O)262",
          "RequiredCount": 1,
        },
      ],
      "OutputItem": [
        {
          "Id": "{{ModId}}_WheatFlourOutput",
          "ItemId": "(O)246",
          "MinStack": 1,
        },
      ],
      "DaysUntilReady": 1,
    },
  },
  // NEW: This tells CP to move the {{ModId}}_WheatToFlour entry we added above to the top of OutputRules
  "MoveEntries": [
    {
      "Id": "{{ModId}}_WheatToFlour",
      // Instead of this, we can also specify `"BeforeId": "Default_Pickled"` to move it before the default pickled veggies entry.
      // Make sure to unpack the game to see what the vanilla IDs are like!
      "ToPosition": "Top",
    },
  ],
}

If all goes well, our wheat should now properly turn into wheat flour! Obviously, MoveEntries can be used elsewhere as well, as long as the top level context (the entity we're editing as specified by Target or TargetField) is a list (denoted by square brackets).

Other advanced stuff

(This section is incomplete, feel free to contribute to it!)

Add fuels to a recipe

Some machines like the furnace require fuels in addition to the main input, and you can add fuels to your own machine via the AdditionalConsumedItems field. This will make every recipe requires that fuel in addition to the main input.

For example, this will make the stone mill need 1 coal in addition to wheat:

{
  "LogName": "Add stone mill rules",
  "Action": "EditData",
  "Target": "Data/Machines",
  "Entries": {
    "(BC){{ModId}}_StoneMill": {
      "OutputRules": [
        // Omitted for brevity
      ],
      "AdditionalConsumedItems": [
        {
          "ItemId": "(O)382",
          "RequiredCount": 1,
          "InvalidCountMessage": "The stone mill needs 1 coal as well!"
        }
      ],
    },
  }
}

Want to make it so that only certain recipes need fuel? You'll need another framework for that, namely Extra Machine Config, which (among many other features) allow per-recipe fuels. See the link in that mod's description for instructions.


Machine animations

By default a machine will only pulse when it's working (you can disable this behavior by setting WobbleWhileWorking to false). There are a couple ways to add fancy working effects to a machine, listed here in orders of increasing complexity:

Show next sprite when processing or ready

You can set either/both ShowNextIndexWhileWorking or ShowNextIndexWhenReady to show the sprite next to the machine's main sprite when the machine is working or when it has input ready respectively. These two fields are top level data fields (ie. on the same level as OutputRules).

Show different sprites for idle, processing and ready

What if you want all three idling, processing and ready sprites to be different? You would need to combine the above fields with the field IncrementMachineParentSheetIndex, which is set on the OutputItem entries themselves. This field is the number amount to increment the sprite sheet index by when that output item is being processed or it has produce from that output rule ready.

For example, suppose you have three sprites on your big craftable's tile sheets: the base/idle sprite (sprite 0), the machine working sprite (sprite 1), and the machine ready sprite (sprite 2). You would need to:

  • Set "IncrementMachineParentSheetIndex": 1 on the output rule itself. This makes processing and ready both use sprite 1.
  • Set "ShowNextIndexWhenReady": true on the machine data. This adds 1 to the ready sprite, making it use sprite 2.

See full example below:

{
  "LogName": "Add stone mill rules - now with different sprites",
  "Action": "EditData",
  "Target": "Data/Machines",
  "Entries": {
    "(BC){{ModId}}_StoneMill": {
      "OutputRules": [
        {
          "Id": "{{ModId}}_WheatToFlour",
          "Triggers": [
            {
              "Id": "{{ModId}}_WheatInput",
              "Trigger": "ItemPlacedInMachine",
              "RequiredItemId": "(O)262",
              "RequiredCount": 1,
            },
          ],
          "OutputItem": [
            {
              "Id": "{{ModId}}_WheatFlourOutput",
              "ItemId": "(O)246",
              "MinStack": 1,
			  // NEW: add 1 to the sprite index when processing or ready
			  "IncrementMachineParentSheetIndex": 1,
            },
          ],
          "DaysUntilReady": 1,
        },
      ],
	  // NEW: add (another) 1 to the sprite index when ready
	  "ShowNextIndexWhenReady": true,
    },
  }
}

Animate a machine while working

Static sprites aren't fancy enough for you? You can set a machine to cycle through different sprites as it works by setting LoadEffects and WorkingEffects, which determines the effects a machine will run after it's being loaded and while it's working respectively. You can check the main wiki for how they're formatted, but there are two fields you want to care about:

  • Frames, which determine the frames to play out. This is a list of numbers corresponding to the sprite index relative to the main sprite.
  • Interval, which determines how many miliseconds a frame should be on screen for.
  • Sounds (optional), which determines the sound to play when this animation plays. You may want to only set this for loading effects, or it will get annoying.

You need to also set WorkingEffectChance to 1 if you want your work effects to play constantly! By default it's only 0.33, which means it only has a 33% chance to repeat every time change.

See full example below:

{
  "LogName": "Make the stone mill cycle between frames 2, 3 and 4 while working, and show sprite 1 when ready",
  "Action": "EditData",
  "Target": "Data/Machines",
  "Entries": {
    "(BC){{ModId}}_StoneMill": {
      "OutputRules": [
        /* Omitted for brevity - note that we won't need IncrementMachineParentSheetIndex here unless you want to do some more advanced stuff*/],
      "LoadEffects": [
        {
          "Sounds": [
            {
              "Id": "woodchipper"
            }
          ],
          "Frames": [
            2,
            3,
            4
          ],
          "Interval": 100
        }
      ],
      "WorkingEffects": [
        {
          "Frames": [
            2,
            3,
            4,
            5
          ],
          "Interval": 80
        }
      ],
      "WorkingEffectChance": 1,
      "ShowNextIndexWhenReady": true
    }
  }
}