Minecraft Modpack Making 105: Intermediate Configuration - katubug/HowToModpack GitHub Wiki

Minecraft Modpack Making 105: Intermediate Configuration

How to ensure compatibility through the use of scripts and datapacks.

Note: This page is still being written.

In the last chapter, we went through all our modded configuration files, as well as our options.txt, and discussed how to make sure all your changes ship out with your modpack. In this chapter we’ll be chatting about how to configure our mods using datapacks and KubeJS scripts.

So what sorts of things can you do with these tools? Honestly, with few exceptions, your imagination is the limit, but here are a few examples we’ll cover:

  • add, remove, and edit recipes of basically all types
  • add and remove blocks and items
  • change the way things generate in the world
  • set certain effects to happen when a player triggers them (by right clicking, by hitting certain coordinates, or so on)
  • set certain effects to happen when the server triggers them (on world creation, on startup, on player login, etc)
  • and more!

Before we get into how we do this, let’s talk about why.

Preventing Overlap

One of the most common compatibility issues is when two mods add the same type of items. As an example, let’s use tomatoes from the mod Let’s Do Candlelight, and Farmer’s Delight. Now, could we just ignore the duplicate items and let players figure it out? Sure. But you didn’t come here because you wanted to make a low-effort modpack. You want to make a polished one, and that means streamlining things.

Firstly, pick the item you want to keep. In this case, I’ve chosen Farmer’s Delight Tomatoes because they are used more often than Candlelight's. Also, I like the item sprite (image) better. So what we’ll need to do is make sure that anytime a player receives a tomato or tomato seeds, they’re for Farmer’s Delight. We also want to make sure that the FD tomato can be used in all of Candlelight’s recipes. Then, we want to remove Candlelight’s tomatoes from JEI and creative so players don’t try to get them. Finally, we want to add some failsafes just in case somehow a player gets their hands on a Candlelight tomato.

Making sure players receive the right items

So where do tomatoes/tomato seeds come from? Well, let’s find out. In-game, you can select a tomato in JEI and press “r” to see what recipes there are. If you’re using Just Enough Resources, it should also tell you what blocks and mobs the item drops from. The other way is to check inside the jar file, which we’ll discuss in detail later. For now, let’s just work on these:

  • Tomato seeds come from wild tomato plants, which spawn in the world.
  • Tomato seeds can sometimes be found in chests.

Firstly, both mods add wild tomatoes which spawn in the world. We want to make sure that players only receive Farmer’s Delight tomatoes - so we’ll need to either a) prevent the Candlelight ones from spawning, or b) make it so that they drop FD tomatoes/seeds when broken. Which of these you choose can depend on several factors: how difficult the worldgen is to disable, how many tomatoes you want players to have access to, and whether you like or dislike the look of the Candlelight tomato plant. Another example could be if your modpack has multiple types of deer and venison - perhaps you like both Deer entities but want only one type of venison. In this case, you’d edit their drops via their loot table.

Changing Loot Tables

If you’re not worried about the rarity of an item becoming too common, and you like both types of Deer/Tomato Plants, or if getting them not to spawn is a pain - then you’ll want to edit the drop table. You’ll look into the mod jar (explained in detail later in this chapter), find the data for their “loot_tables”, and then make a copy in your /kubejs/data/ folder and edit the items which drop when you break the block/kill the entity.

Example: Making Wild Tomato Plants drop different Tomatoes

Here's my datapack to change what Candlelight's Wild Tomatoes drop, it's located in /kubejs/data/candlelight/loot_tables/blocks/wild_tomatoes.json:

{
  "type": "minecraft:block",
  "pools": [
    {
      "bonus_rolls": 0.0,
      "entries": [
        {
          "type": "minecraft:alternatives",
          "children": [
            {
              "type": "minecraft:item",
              "conditions": [
                {
                  "condition": "minecraft:match_tool",
                  "predicate": {
                    "items": [
                      "minecraft:shears"
                    ]
                  }
                }
              ],
              "name": "candlelight:wild_tomatoes"     // This is the block that the loot table applies to
            },
            {
              "type": "minecraft:item",               // A lot of this stuff won't make sense, but that's not a big deal if you only
              "conditions": [                         // want to change simple things. For more complex issues, remember to ask for help!
                {
                  "chance": 0.125,
                  "condition": "minecraft:random_chance"
                }
              ],
              "functions": [
                {
                  "enchantment": "minecraft:fortune",
                  "formula": "minecraft:uniform_bonus_count",
                  "function": "minecraft:apply_bonus",
                  "parameters": {
                    "bonusMultiplier": 2
                  }
                },
                {
                  "function": "minecraft:explosion_decay"
                }
              ],
              "name": "farmersdelight:tomato_seeds"       // This is one of 2 lines I changed.
            },
            {
              "type": "minecraft:item",
              "conditions": [
                {
                  "chance": 1.0,
                  "condition": "minecraft:random_chance"
                }
              ],
              "name": "farmersdelight:tomato"             // This is the other line I changed.
            }
          ]
        }
      ],
      "rolls": 1.0
    }
  ],
  "random_sequence": "minecraft:blocks/grass"
}

All I did was copy and paste the existing file from the mod jar, and changed the lines which previously told the game to drop Candlelight's Tomatoes and Tomato Seeds, instructing it to drop the ones from Farmer's Delight instead.

Example: Removing, or Adding New Entries to a Chest Loot Table

Here's an example of how you can change entries in a chest loot table. The file is located in /kubejs/data/aquamirae/loot_tables/chests/maze_common_chest.json:

{
  "type": "minecraft:chest",  // This tells you what kind of loot table it is
  "pools": [                  // This defines the loot pools the table contains. One table can contain multiple loot pools, and one pool can contain multiple entries.
    {
      "rolls": {              // This is how many chances the entries in this pool get to generate.
        "min": 2,
        "max": 4
      },
      "entries": [      // Each loot entry begins and ends with {}, and because VSCode highlights these in different colors, it's
        {               // easier to see what bits of data go with which entry. You can also click one bracket to highlight its mate.
          "type": "minecraft:item",
          "name": "minecraft:snowball",  // This determines what item will generate
          "weight": 6,                   // This determines how likely it is to appear in the chest. It's relative to the highest number in the pool.
          "functions": [
            {
              "function": "set_count",  // This determines how many of an item will appear
              "count": {
                "min": 1,
                "max": 3
              }
            }
          ]
        },
        {                                   // To add a new entry, copy one of the existing entries and paste it back in, so there's two.
          "type": "minecraft:item",         // Then, edit the new one to include the item you want, the weight you want it at, and the min and max counts
          "name": "minecraft:ice",
          "weight": 4,
          "functions": [
            {
              "function": "set_count",
              "count": {
                "min": 1,
                "max": 1
              }
            }
          ]
        },
        {                                      // To remove an entry, simply delete it from the file! When you do this, make sure you set "replace" to
          "type": "minecraft:item",            // true at the beginning of the file. And be sure you have all your commas and brackets correct!
          "name": "minecraft:emerald",
          "weight": 2,
          "functions": [
            {
              "function": "set_count",
              "count": {
                "min": 1,
                "max": 1
              }
            }
          ]
        },
        {
          "type": "minecraft:item",
          "name": "aquamirae:ship_graveyard_echo",
          "weight": 1,
          "functions": [
            {
              "function": "set_count",
              "count": {
                "min": 1,
                "max": 1
              }
            }
          ]
        }
      ]
    },
    {
      "rolls": {
        "min": 0,
        "max": 2
      },
      "entries": [
        {
          "type": "minecraft:item",
          "name": "aquamirae:dead_sea_scroll",
          "weight": 1,
          "functions": [
            {
              "function": "set_count",
              "count": {
                "min": 1,
                "max": 1
              }
            }
          ]
        }
      ]
    }
  ]
}
  • About Tables, Pools, and Entries: Think of these as like nesting dolls, or a gallon of milk. A Table is the gallon jug. It can contain multiple Quarts, and each Quart contains multiple Pints. The table is the totality of what can appear in a chest or drop from a monster. Loot pools exist so that you can get drops from each. A perfect example is Sheep, who have a loot pool for their wool, and another one for their meat. This means that if you kill a sheep, you can get both, instead of just one or the other.
  • About loot weight: Let's say you have 3 items. One has a weight of 10, one a weight of 3, and the last one is weight 1. This is identical to if they were weighted at 100, 30, and 10, because it's all relative to the highest number in the loot pool. So if you want something particularly rare in your loot pool, you can weight the common items at 100, and then weight the rare items at something very small, like 5.

If the loot table you want doesn't exist, you can create a new one! More information on this can be found here.

Disabling World Gen/Spawning

If you want the item to be more rare, then you’ll want to remove the overlapping items from spawning at all. For entities, you can use a mod like Spawn Balance Utility or Bad Mobs to prevent it from spawning. We will have a whole section on Spawn Balance Utility later on in the guide.

For worldgen, you’ll need to look into the mod jar and find the files which determine whether/how often the block appears in generation, and make a copy in your /kubejs/data/ folder to overwrite that information with your preferences.

Example: Editing a WorldGen Datapack to Remove Structures

For this example, we'll be disabling the shed from Moog's Voyager Structures. This file goes in /kubejs/data/mvs/worldgen/structure_set/shed.json

{
  "replace":true,       // Don't forget to change "replace" to true when you're overridding default behavior!
  "structures":[        // See the change we made? Of course you don't, because I deleted it. Inside this "structures" entry, there used 
                        // to be a reference to the Shed structure, but we've removed it, leaving the entry empty. This will prevent the
  ],                    // Shed structure from generating. You can ignore everything else
  "placement": {

    "type": "mvs:advanced_random_spread",

    "salt": 532363685,

    "spacing": 38,

    "separation": 23
  }
}

Making Recipes Compatible

So let's say you have two types of venison from 2 different mods, and you want to ensure that all the foods you can cook from each mod will use the version of venison that you prefer.

Example: Replacing Ingredients in KubeJS

You can easily replace all ingredients in all vanilla crafting recipes using this kubejs code, which will go into your /kubejs/server_scripts/ folder:

ServerEvents.recipes(event => {
event.replaceInput({
        input: 'environmental:venison'},     // Arg 1: This tells the script what recipes to target
        'environmental:venison',             // Arg 2: This tells the script what ingredient to replace (it should generally match Arg 1)
        'twilightforest:venison'             // Arg 3: This tells the script what ingredient to use instead. You can do an item or a tag!
    )
})

However, this won't affect other recipes such as ones in the Farmer's Delight Cooking Pot. For those, you'll need to use datapacks. We'll cover this in depth later, but here's an example of what a Farmer's Delight Cooking Pot recipe datapack looks like.

Example: A Farmer's Delight Cooking Pot Recipe Datapack

This goes in /kubejs/data/farmersdelight/recipes/cooking/:

{
  "type": "farmersdelight:cooking",
  "cookingtime": 200,
  "experience": 0.1,
  "ingredients": [
    {
      "tag": "forge:fruits/lemon"
    },
    {
      "item": "farmersdelight:pie_crust"
    },
    {
      "tag": "forge:eggs"
    },
    {
      "item": "minecraft:sugar"
    },
    {
      "tag": "forge:milk/milk_bottle"
    }
  ],
  "result": {
    "count": 2,
    "item": "fruitsdelight:lemon_tart"
  }
}

Removing recipes and/or tags

You might wish to remove access to a certain item entirely, or perhaps you just want to give it as a quest reward or a special drop instead of it being craftable. To remove a recipe to an item, you'll need KubeJS.

Example: Removing Recipes in KubeJS

Here's what it looks like, and it would go in the /kubejs/server_scripts/ folder:

ServerEvents.recipes(event => {
        event.remove({output: 'candlelight:tomato_seeds'})
})

But chances are, you'll want to disable more than one thing, and it's not best practice to do event.remove() for each individual item. Instead, we'll use what's called an array. It will look like this:

ServerEvents.recipes(event => {    // This is the framework of the KubeJS Script. It can contain multiple "events" of the same type "hideItems." We'll delve into KubeJS syntax later on.
    let toRemove = [
        {output: 'candlelight:tomato_seeds'},       // On these lines, you can put each item to disable.
        {mod: 'plushies'},                          // Make sure each line except for the final one has a comma!
        {id: 'fruitfulfun:farmersdelight/honey_pomelo_tea'},
    ];
    
    for (const remove of toRemove) {  // This code says, in English: "Let's call each item in the 'toRemove' array 'remove'. For each of those items, do this:
      event.remove(remove);           // Remove all of the recipes called 'remove'
  }
})

Look intimidating? It's not, especially not once you put it into VSCode where it will be helpfully highlighted and much easier to read. Also, see all the instructions preceded by //? That's called a "comment." Putting // before some text in the document will tell the code parser not to read whatever is behind those symbols. This is helpful for making notes to yourself.

You'll also note that some of the items are preceded by "output," and some by "id" and by "mod." This is because there are different methods to removing recipes, and you may want to use some and not others. You can view the official documentation for more info, but let's quickly go over why I chose each of the removal types I did:

  • output is used to remove all recipes which produce this item, to make it uncraftable, unsmeltable, un-stonecutter-able, etc. This will override all datapacks, even ones you custom-made.
  • mod is used to remove all recipes for each item in a given mod. In my case, I want all plushies to be quest rewards, so I've made them uncraftable.
  • id is used to remove a single recipe for the given item, using the recipe's ID. In this case, it removes the Farmer's Delight Cooking Pot recipe for this tea, so I can make it craftable in HerbalBrew's Tea Kettle instead. This is the option you need to use if you add or change a recipe via datapack.

To find a recipe ID, you'll need to have advanced tooltips enabled (F3+H), and then hover over the recipe output in JEI. Like this:

You'll see at the bottom of the tooltip, it says Recipe ID: dawnoftimebuilders:paper_wall_flowery- entering this into the script will remove only the crafting bench recipe for this item.

Sometimes items will still show up as a potential crafting ingredient (or quest completion task), even if you've done your best to remove them. This is usually because they are tagged, and the recipe or quest systems are allowing anything with that tag to be accepted - so they show it in the preview. Hardly gamebreaking, but it can be annoying. To fix this, you can remove the tags from these items quite easily.

Example: Removing Tags in KubeJS

This file should be in /kubejs/server_scripts/:

//=== Tag Removal ===
    //Dough
    event.removeAllTagsFrom('bakery:dough',
    'candlelight:dough',
    'create:dough',
    'vegandelight:salt')

    //why are plushies wool??
    event.remove('minecraft:wool', [
        '#perfectplushies:village_plushies',
        "#perfectplushies:treasure_plushies",
        "#perfectplushies:rare_treasure_plushies"
    ])

You can see in this example script that I am removing all tags from Candlelight and Create's Dough, and Vegan Delight's Salt. In the second event, I am removing only one tag - minecraft:wool - from three entries (in this case, all three are tags [you can tell by the # in front of them], but it works the same for items as well). All other tags associated with those entries will stay in place.

Hiding in JEI

To remove items from appearing in JEI, you'll use KubeJS. Why would you want to do this? For a number of potential reasons:

  • You have disabled access to the item and don't want to confuse players.
  • The item is naturally unobtainable and cluttering up JEI.
  • You want the item or its recipe to be a secret.

The code for doing this is super easy.

Example: Hiding Items in JEI with KubeJS

This code should go in a .js file in /kubejs/client_scripts/:

JEIEvents.hideItems(event => {
event.hide('candlelight:tomato')
})

But again - chances are, you'll want to disable more than one thing. So we'll do another array:

JEIEvents.hideItems(event => {    
    let toHide = [
        'candlelight:tomato',
        'environmental:venison'
    ];
    
    for (const hide of toHide) {
      event.hide(hide);
  }
})

So! Make a .js file containing the above code (you should replace the example items with your own) and put it in your /kubejs/client_scripts/ folder. To view these changes, you have to reload resources via F3+T. Another thing we'll talk about later is the differences between Client, Server, and Startup scripts. For now, let's move on.

Failsafes

So, now you know how to hopefully prevent players from getting their hands on unwanted items - but never trust that you've blocked every avenue, because undoubtedly there will be something you miss, from time to time. And you don't want anyone to end up stuck with useless items! So it's best practice to add a failsafe. The easiest way of doing this is to make a recipe to convert the unwanted item into the wanted one.

Example: Adding a Conversion Recipe

This would belong with your recipes .js file in /kubejs/server_scripts/:

ServerEvents.recipes(event => {
event.shapeless(
        Item.of('farmersdelight:tomato'),   // The recipe output, the tomato we like
        [
            'candlelight:tomato'            // The disabled item
        ]
    )
})

In this code, you can see we have added a very simple conversion recipe which will allow anyone who somehow obtains Candlelight Tomatoes to put them into their crafting grid, and get the desired Farmer's Delight Tomatoes out. You can do the same with Tomato Seeds.

Overlapping recipes: slab and small shelf for example

Sometimes you'll encounter two recipes which use the same ingredients but have different outputs.

Overlapping Item Types

Now what if there are two items which are added by different mods, but have the same purpose? You can try to make them cross-mod compatible. An example of this is Stoves, added by the Let’s Do series, and Stoves from Farmer’s Delight. In both cases, the Stove is meant to provide heat to cooking pots or other tools. Thankfully, this functionality is data-driven*! The mod defines a stove-like block via a block tag - “farmersdelight:heat_sources” and “candlelight:allows_cooking” in this case. You can use KubeJS to add the Candlelight tag to the Farmer's Delight stoves, and vice versa! You can even extend this functionality to non-stove blocks, such as the Create Blaze Burner. So we don’t need to disable anything, we can simply ensure that each block a player might want to use as a Stove, can be used that way.

*For more information on what "data driven" means and how datapacks work, check out the About Datapacks section of this guide.

Another example could be Goblets, which are added by both Eidolon and Supplementaries. They both have very different purposes and completely different appearances, but are called the same thing. Since one of them is used for progression in Eidolon, we want to ensure that the player doesn’t accidentally make the Supplementaries version and then get confused that they can’t progress in Eidolon. In this case, we want to keep both items enabled, so we need to find another way of making the difference between them clear. For a case like this, I recommend renaming one of the items via the lang file. This is located in the /assets/lang/ folder, and you'll use the file en_us.json.

In my case, I went into Eidolon's lang file and changed all references to "Goblet" into "Arcane Goblet". Make sure you CTRL+F to find every mention, as it will almost always be in multiple places! Then, the lang file goes in /kubejs/assets/lang/en_us.json.

How to Look into Mod Jars, and What to Look For

Checking into a mod’s .jar file is often the fastest and easiest way to get an answer to your questions. What biomes does this mob spawn in? What foods are tagged for entity tempting? How does a mod decide what a stove is?

To open a .jar file, you may need 7zip, although some versions of windows allow you to navigate into jars and other zips through Explorer. For this tutorial, we’ll assume 7zip. Right click on the mod you want and choose “7zip > Open Archive.” Ta-da, it was that easy.

Now, what are you looking for? There are 2 main places: /assets/ and /data/. You want assets if you’re working on a resourcepack, changing a lang file, or something that is visual in nature. Almost everything else is in Data.

Inside the /data/ folder will be a number of other folders. Most commonly, it will include the mod’s namespace* (for example, /farmersdelight/), /forge/, and /minecraft/, and sometimes it will contain other mod namespaces, such as /create/ or /sereneseasons/ - in these cases, this is compatibility that the mod author has already crafted between the listed mods. Most of the time, you’ll ignore these modded compat folders, and focus on the main mod or its compatibility with forge or Minecraft.

  • /namespace/ - This is where most of the mod’s data will be. It will contain custom recipes, tags, loot tables, and much more. You’ll usually check here first for information.
  • /forge/ - This folder generally contains tags which the mod uses to ensure wider compatibility in recipes. It can also contain loot modifiers.
  • /minecraft/ - This folder often contains tags, and information for world gen - such as what biomes X or Y can generate or spawn in. It also contains any changes the mod makes to vanilla behavior, such advancements, or such as custom drops from vanilla mobs, blocks, or other loot tables.

*A “namespace” is the ID of a mod, which the game uses to communicate with its data. It is all lowercase and only contains alphanumeric characters and underscores - so no spaces. An example of this would be “fairylights” or “easy_mob_farm.”