Tutorial 1.18 Episode 7

From McJty Modding
Revision as of 09:28, 7 March 2022 by McJty (talk | contribs)
Jump to: navigation, search



In this tutorial we explain various ways that you can store data and also communicate that data to the client. We will cover world data, player capabilities, and networking. In addition we also cover a new way to make render overlays (HUD's)

Key Bindings

In this tutorial we want a key binding that the player can press to gather mana from the chunk. How we will store and make this mana is for later but let's first make the key binding. First add the class to actually define the key binding (KeyBindings.java on Github). In this class we make a new keymapping which by default is assigned to the period key. The player can reconfigure this in the standard Minecraft options screen:

public class KeyBindings {

    public static final String KEY_CATEGORIES_TUTORIAL = "key.categories.tutorial";
    public static final String KEY_GATHER_MANA = "key.gatherMana";

    public static KeyMapping gatherManaKeyMapping;

    public static void init() {
        // Use KeyConflictContext.IN_GAME to indicate this key is meant for usage in-game
        gatherManaKeyMapping = new KeyMapping(KEY_GATHER_MANA, KeyConflictContext.IN_GAME, InputConstants.getKey("key.keyboard.period"), KEY_CATEGORIES_TUTORIAL);

We also need to call this method. We do that in ClientSetup.init() (ClientSetup.java on Github):

    public static void init(FMLClientSetupEvent event) {

In addition to the key binding we also need an input handler. That input handler will be called whenever the key is pressed. To do this we listen to the KeyInputEvent and when that event is received we consume the keypress (so it doesn't get used for something else) and send a message to the server:

18px-OOjs_UI_icon_notice-destructive.svg.png Warning: To also allow the player to bind this action on a mouse button you would also need to listen to InputEvent.MouseInputEvent
public class KeyInputHandler {

    public static void onKeyInput(InputEvent.KeyInputEvent event) {
        if (KeyBindings.gatherManaKeyMapping.consumeClick()) {
            Messages.sendToServer(new PacketGatherMana());

We need to register this event. Again edit ClientSetup.init() for that:

We also need to call this method. We do that in ClientSetup.init():

    public static void init(FMLClientSetupEvent event) {


Whenever a key is pressed on the client we need to send a message to the server. The reason for that is that actual logic and the mana system will live on the server. To support networking add the following class (Messages.java on Github). This class is the main entry point for networking. It basically makes use of a SimpleChannel which is a helper class from Forge:

public class Messages {

    private static SimpleChannel INSTANCE;

    // Every packet needs a unique ID (unique for this channel)
    private static int packetId = 0;
    private static int id() {
        return packetId++;

    public static void register() {
        // Make the channel. If needed you can do version checking here
        SimpleChannel net = NetworkRegistry.ChannelBuilder
                .named(new ResourceLocation(TutorialV3.MODID, "messages"))
                .networkProtocolVersion(() -> "1.0")
                .clientAcceptedVersions(s -> true)
                .serverAcceptedVersions(s -> true)

        INSTANCE = net;

        // Register all our packets. We only have one right now. The new message has a unique ID, an indication
        // of how it is going to be used (from client to server) and ways to encode and decode it. Finally 'handle'
        // will actually execute when the packet is received
        net.messageBuilder(PacketGatherMana.class, id(), NetworkDirection.PLAY_TO_SERVER)

    public static <MSG> void sendToServer(MSG message) {

    public static <MSG> void sendToPlayer(MSG message, ServerPlayer player) {
        INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), message);

And we also need the actual message (PacketGatherMana.java on Github). This message is sent from the client to the server and otherwise contains no data. That's why the toBytes() and constructors are empty. The handle() method is currently empty because we don't have the mana system yet:

public class PacketGatherMana {

    public static final String MESSAGE_NO_MANA = "message.nomana";

    public PacketGatherMana() {

    public PacketGatherMana(FriendlyByteBuf buf) {

    public void toBytes(FriendlyByteBuf buf) {

    public boolean handle(Supplier<NetworkEvent.Context> supplier) {
        NetworkEvent.Context ctx = supplier.get();
        ctx.enqueueWork(() -> {
            // Here we are server side
            ...  TODO for later
        return true;

We also need to register our Messages class. Do that by calling Messages.register() from ModSetup (ModSetup on Github):

    public static void init(FMLCommonSetupEvent event) {