Skip to content

Adding compatibility with TreeChop

hammertater edited this page Jul 27, 2024 · 8 revisions

Use block tags to tell TreeChop how to detect trees

For TreeChop to detect modded trees, only two things are needed:

  • Log blocks should have the #minecraft:logs or #minecraft:logs_that_burn tag
  • Leaves blocks should have the #mincraft:leaves tag

And that's it!

If for some reason your "tree" blocks shouldn't have the normal logs and leaves tags (things like huge mushrooms etc.), there are two alternative, more specific tags: #treechop:choppables (for blocks that can be chopped) and #treechop:leaves_like (for connected blocks that should automatically break when the "tree" is felled).

Common tree detection problems

  • "TreeChop isn't working" - By default, TreeChop only detects a tree if it is connected to leaves blocks (anything with the #minecraft:leaves or #treechop:leaves_like tags)
  • "Felling a tree leaves behind floating logs" - TreeChop doesn't detect logs that aren't touching each other. This often happens when log blocks are separated by leaves
  • "Felling a tree leaves behind floating leaves" - TreeChop doesn't detect leaves that aren't touching each other

Use loot tables to change what happens when chopping

Special chopping loot tables can be used to drop loot or run functions whenever a block is chopped. The chopping loot table has all the functionality of the vanilla blocks table, but with two added conditions (with more to come in the future, feel free to make suggestions):

  • treechop:count_block_chops: Check the number of chops on the block. Specified as a range in the same way as the minecraft:value_check predicate: it can either be an integer, or a range object with min and max (inclusive) properties. Both cases are shown in the example below.
  • treechop:tree_felled: True if this chop also felled the tree. This only happens for the block that was chopped.

Things to note:

  • The chopping loot table is check for each single chop that is performed. This matters for modded tools that perform more than one chop. For example, if a tool performs 3 chops when breaking a block, the loot table will be checked 3 times, each time incrementing the chop count.
  • If a chopped block is broken normally (e.g., while sneaking) or as part of a felled tree, the regular blocks loot table drops will spawn, if any. This includes whenever the treechop:tree_felled condition is met.

In the example below, chopping will drop a minecraft:coal on the first two chops and a minecraft:diamond when the tree is felled:

data/treechop/loot_tables/chopping/chopped_log.json

{
  "type": "treechop:chopping",
  "pools": [
    {
      "rolls": 1,
      "bonus_rolls": 0,
      "entries": [
        {
          "type": "minecraft:alternatives",
          "children": [
            {
              "type": "minecraft:item",
              "name": "minecraft:coal",
              "conditions": [
                {
                  "condition": "treechop:count_block_chops",
                  "range": {
                    "min": 1,
                    "max": 2
                  }
                }
              ]
            },
            {
              "type": "minecraft:item",
              "name": "minecraft:diamond",
              "conditions": [
                {
                  "condition": "treechop:tree_felled"
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Use the API for everything else

The API enables more advanced control over chopping mechanics. All API-related classes are found in the ht.treechop.api package, and the source files are split across three locations:

Most classes are defined in Shared. The Fabric- and Forge-specific bits of the API are primarily for adding event listeners.

Option 1: get an API instance

The safest way to use the API is to first get a TreeChopAPI instance, which provides the following functionalities:

  • Enable/disable chopping for specific blocks*
  • Mark blocks as leaves (or not)*
  • Enable/disable chopping for specific items*
  • Register handlers for blocks and items to change chopping-related behaviors

Changes to blocks or items via the API will override the settings specified in config/treechop-common.toml. For example, if a user tries to blacklist a block from being choppable, it will still be choppable if it was marked as such using the API. So, use with caution, and only for things that can't be accomplished using block tags (see previous section).

The methods provided by TreeChopAPI are documented in the source code.

Forge

With Forge, an API instance can be retrieved using the InterModEnqueueEvent:

static TreeChopAPI api = null;

@SubscribeEvent
public static void enqueueIMC(InterModEnqueueEvent event) {
    InterModComms.sendTo("treechop", "getTreeChopAPI", () -> (Consumer<TreeChopAPI>) response -> {
        api = response;
    });
}

Fabric

With Fabric, an API instance can be retrieved using the "treechop:api_provider" in the shared object store:

TreeChopAPI api = null;

FabricLoader.getInstance().getObjectShare().whenAvailable("treechop:api_provider", (key, value) -> {
    if (value instanceof ITreeChopAPIProvider provider) {
        api = provider.get("your_mod_id");
    }
});

Example 1: make a block choppable

As mentioned earlier, simply marking a block as "choppable" should be done using block tags. The advantage of using the API is that changes can be made at runtime. Still, avoid doing this unless absolutely necessary. Note that TreeChop does not sync these changes between clients and servers.

public static void makePlanksChoppable(TreeChopAPI api) {
    api.overrideChoppableBlock(Blocks.OAK_PLANKS, true);
}

Example 2: change a block's chopped texture

Normally, when a block is chopped, TreeChop tries to look up a stripped version of the block, then uses textures from the model assigned to the stripped blockstate. So, to change the chopped texture for a block, we can point TreeChop to a different stripped blockstate using the IStrippableBlock interface. Let's create a handler that changes the chopped texture for the minecraft:mushroom_stem block to look like minecraft:gold_block:

public class MushroomStemHandler implements ht.treechop.api.IStrippableBlock{
    @Override
    public BlockState getStrippedState(BlockGetter level, BlockPos pos, BlockState blockState) {
        return Blocks.GOLD_BLOCK.defaultBlockState();
    }
}

and use a TreeChopAPI to register the handler:

public static void registerMushroomStemHandler(TreeChopAPI api) {
    api.registerChoppableBlockBehavior(Blocks.MUSHROOM_STEM, new MushroomStemHandler());
}

And now mushrooms look like gold blocks when chopped:

image

Example 3: change a block's chopped radius

Some mods add logs that are slimmer than full-size blocks. If slim block are treated like normal logs, the chopped block will start bigger than the original block. To fix this, use the ISimpleChoppableBlock interface, which provides default implementations for many of the block-related interfaces in the API. Override the getRadius method to return the radius of the slim log (for reference, full blocks have radius 8, and each chop reduces the radius by 1). Note that this has some implications on how many chops a tree will take to fell, as documented in the source code.

public class SlimLogHandler implements ISimpleChoppableBlock {
    @Override
    public int getRadius(BlockGetter level, BlockPos blockPos, BlockState blockState) {
        return 5;
    }
}

As before, use a TreeChopAPI to register the handler:

public static void registerMushroomStemHandler(TreeChopAPI api) {
    api.registerChoppableBlockBehavior(MyBlocks.SLIM_LOG, new SlimLogHandler());
}

Example 4: make an item chop twice as fast

The IChoppingItem can be used to change when an item can be used to chop (maybe it only chops when powered), and how many chops it performs when breaking a block (the default is 1). Let's make minecraft:diamond_axe perform 2 chops instead of 1:

public class SuperAxeHandler implements IChoppingItem {
    @Override
    public boolean canChop(Player player, ItemStack tool, Level level, BlockPos pos, BlockState target) {
        return true; // Always allow chopping
    }

    @Override
    public int getNumChops(ItemStack tool, BlockState target) {
        return 2; // When breaking a block, add 2 chops instead of 1
    }

A TreeChopAPI can register the handler:

public static void registerSuperAxe(TreeChopAPI api) {
    api.registerChoppingItemBehavior(Items.DIAMOND_AXE, new SuperAxeHandler());
}

Option 2: implement TreeChop interfaces in Block and Item classes (via mixins)

Instead of using handlers, classes that extend Block or Item can implement TreeChop interfaces directly. This can be useful when you want all blocks (or items) of a certain class to have special behaviors, but don't know the list of block (or item) IDs that use that class (for example, when a block class is defined in a library for use by other mods). To avoid creating a required dependency on TreeChop, interfaces can be implemented using mixins. For example:

@Mixin(SomeLogBlock.class)
public class SomeLogBlockMixin implements ISimpleChoppableBlock {
    @Override
    public int getRadius(BlockGetter level, BlockPos blockPos, BlockState blockState) {
        return 5;
    }
}