Skip to content

Getting Started

Setup

In your build.gradle:

repositories {
  maven { url = "https://api.modrinth.com/maven" }
}

dependencies {
  // jade_version example: 15.10.0+fabric
  // Visit https://modrinth.com/mod/jade/versions?l=fabric to get the latest version
  modImplementation "maven.modrinth:jade:${project.jade_version}"
}

In your build.gradle:

repositories {
  maven { url = "https://api.modrinth.com/maven" }
}

dependencies {
  // jade_version example: 15.10.0+neoforge
  // Visit https://modrinth.com/mod/jade/versions?l=neoforge to get the latest version
  implementation "maven.modrinth:jade:${project.jade_version}"
}

Visit Modrinth Maven to find more information about how to set up your workspace.

Registering

package snownee.jade.test;

import snownee.jade.api.IWailaClientRegistration;
import snownee.jade.api.IWailaCommonRegistration;
import snownee.jade.api.IWailaPlugin;
import snownee.jade.api.WailaPlugin;

@WailaPlugin
public class ExamplePlugin implements IWailaPlugin {

  @Override
  public void register(IWailaCommonRegistration registration) {
    //TODO register data providers and hiding things here
  }

  @Override
  public void registerClient(IWailaClientRegistration registration) {
    //TODO register component providers, icon providers, callbacks, and config options here
  }
}

Note

For Fabric user, you need to add entrypoints in your fabric.mod.json

{
  "entrypoints": {
    "jade": [
      "full.class.path.to.ExamplePlugin"
    ]
  }
}

Component Provider

Component providers can append information (texts or images) to the tooltip.

Let's create a simple block component provider that adds an extra line to all the furnaces:

package snownee.jade.test;

import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import snownee.jade.api.BlockAccessor;
import snownee.jade.api.IBlockComponentProvider;
import snownee.jade.api.ITooltip;
import snownee.jade.api.config.IPluginConfig;

public class ExampleComponentProvider implements IBlockComponentProvider {
  public static final ExampleComponentProvider INSTANCE = new ExampleComponentProvider();

  @Override
  public void appendTooltip(
    ITooltip tooltip,
    BlockAccessor accessor,
    IPluginConfig config
  ) {
    tooltip.add(Component.translatable("mymod.fuel"));
  }

  @Override
  public ResourceLocation getUid() {
    return ExamplePlugin.FURNACE_FUEL;
  }
}

Here you have the tooltip that you can do many various operations to the tooltip. You can take the tooltip as a list of Component. But here our elements are IElements, to support displaying images, not just texts. In this case we only added a single line.

You also have the accessor, which you can get access to the context. We will use it later.

Then register our ExampleComponentProvider:

@Override
public void registerClient(IWailaClientRegistration registration) {
  registration.registerBlockComponent(ExampleComponentProvider.INSTANCE, AbstractFurnaceBlock.class);
}

AbstractFurnaceBlock.class means we will append our text only when the block is extended from AbstractFurnaceBlock.

Once your component provider is registered, Jade will create a config option for the user to toggle the provider. So don't forget to add translation for your option.

Note

If you want to control the position your elements are inserted, you can override getDefaultPriority method in your component provider. Greater is lower. -5000 ~ 5000 is for normal providers and they will be folded in the Lite mode.

Now launch the game:

Congrats you have implemented your first Jade plugin!

Server Data Provider

IServerDataProvider can help you sync data that is not on client side. In this tutorial it is the remaining burn time of the furnace. It will sync to the client every 250 milliseconds.

This is a chart shows the basic lifecycle:

Now it's time to implement our IServerDataProvider. Usually we will use StreamServerDataProvider for simplicity and allow use to use StreamCodec to encode/decode data:

package snownee.jade.test;

import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
import org.jetbrains.annotations.Nullable;
import snownee.jade.api.BlockAccessor;
import snownee.jade.api.StreamServerDataProvider;

public class ExampleDataProvider
  implements StreamServerDataProvider<BlockAccessor, Integer> {
  public static final ExampleDataProvider INSTANCE = new ExampleDataProvider();

  @Override
  public @Nullable Integer streamData(BlockAccessor accessor) {
    return ((AbstractFurnaceBlockEntity) accessor.getBlockEntity()).litTimeRemaining;
  }

  @Override
  public StreamCodec<RegistryFriendlyByteBuf, Integer> streamCodec() {
    return ByteBufCodecs.VAR_INT.cast();
  }

  @Override
  public ResourceLocation getUid() {
    return ExamplePlugin.FURNACE_FUEL;
  }
}

Here we used Access Transformers or Access Wideners to get access to the protected field.

Register IServerDataProvider:

@Override
public void register(IWailaCommonRegistration registration) {
    registration.registerBlockDataProvider(ExampleDataProvider.INSTANCE, AbstractFurnaceBlockEntity.class);
}

Changes are made on the client side to get the data:

package snownee.jade.test;

import java.util.List;
import java.util.Optional;
import net.minecraft.client.gui.components.Button;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.Style;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import snownee.jade.Jade;
import snownee.jade.api.BlockAccessor;
import snownee.jade.api.IBlockComponentProvider;
import snownee.jade.api.ITooltip;
import snownee.jade.api.JadeIds;
import snownee.jade.api.config.IPluginConfig;
import snownee.jade.api.ui.Element;
import snownee.jade.api.ui.JadeUI;
import snownee.jade.gui.LayoutWithPadding;

public class ExampleComponentProvider implements IBlockComponentProvider {
  public static final ExampleComponentProvider INSTANCE = new ExampleComponentProvider();

  @Override
  public void appendTooltip(
    ITooltip tooltip,
    BlockAccessor accessor,
    IPluginConfig config
  ) {
    Optional<Integer> fuel = ExampleDataProvider.INSTANCE.decodeFromData(accessor);
    if (fuel.isPresent()) {
      tooltip.add(Component.translatable("mymod.fuel", fuel.get()));
    }
  }

  @Override
  public ResourceLocation getUid() {
    return ExamplePlugin.FURNACE_FUEL;
  }
}

At the end, don't forget to add the translations:

{
  "config.jade.plugin_mymod.furnace_fuel": "Test",
  "mymod.fuel": "Fuel: %d ticks"
}

Great!

Showing an Item

Now let's show a clock as a small icon:

@Override
public void appendTooltip(
  ITooltip tooltip,
  BlockAccessor accessor,
  IPluginConfig config
) {
  Optional<Integer> fuel = ExampleDataProvider.INSTANCE.decodeFromData(accessor);
  if (fuel.isPresent()) {
    Element icon = JadeUI.smallItem(new ItemStack(Items.CLOCK));
    tooltip.add(icon);
    tooltip.append(Component.translatable("mymod.fuel", fuel.get()));
  }
}

Result: