Skip to content

Listening To Events ​

Event listeners can be added and removed using the listener package, each event needs to be defined before they can be listened to, the fabric-api-events package defines all events provided by the Fabric API.

Add Dependencies ​

Include listener and fabric-api-events as dependencies in package.json.

js
// package.json
{
  // ...
  dependencies: {
    "listener": "0.1.0",
    "fabric-api-events": "0.1.0",
    // ...
  }
}

If listener or fabric-api-events are not installed, you can run /pully insall to install any missing dependencies.

Register a Listener ​

To see a list of events, visit the fabric-api-events repository.

Register by Name ​

The code prints a message in chat when a player starts/stops sneaking.

js
// init.js

// Holds the previous state
// If the current state is different to the previous state,
// then we know the player has started/stopped sneaking.
let previouslySneaking = false;

addEventListener("startClientTickEvent", (client) => {
  // do nothing if the player is not in a world
  if (client.player == null) return;

  if (client.player.isSneaking() && !previouslySneaking) {
    console.log("You are now sneaking.");
    previouslySneaking = true;
  } else if (!client.player.isSneaking() && previouslySneaking) {
    console.log("You are no longer sneaking.");
    previouslySneaking = false;
  }
});

By searching the fabric-api-events repository, we know StartClientTickEvent aliases to ClientTIckEvents.START_CLIENT_TICK in Fabric API. The listener can take a MinecraftClient as parameter. The name of the event is case insensitive.

Register by Event ​

You can also register to an event by providing the Event object. This only work if the event definition is provided. In the case of Fabric API events, their definitions are provided by the fabric-api-events package.

The syntax for importing Java classes is covered in the Raw Java Imports section.

js
// init.js
let { ClientTickEvents } =
  Packages.net.fabricmc.fabric.api.client.event.lifecycle.v1;

let previouslySneaking = false;

addEventListener(ClientTickEvents.START_CLIENT_TICK, (client) => {
  if (client.player == null) return;

  if (client.player.isSneaking() && !previouslySneaking) {
    console.log("You are now sneaking.");
    previouslySneaking = true;
  } else if (!client.player.isSneaking() && previouslySneaking) {
    console.log("You are no longer sneaking.");
    previouslySneaking = false;
  }
});

An error would occur if that event does not exist.

Register Options ​

addEventListener and EventObject.register accepts an additional parameter. The parameter optionally contains two fields.

Field nameDescription
priorityThe priority of the listener, lower is earlier (default: 10000)
idThe id of the listener (default is randomised)

Only one listener can have a particular ID. If two listeners are registered with the same ID, the first listener is removed when the second listener registers.

Unregister a Listener ​

All events are removed when JSCore stops (including restarts), so the same listener will not be registered multiples times each time init.js runs.

Unregister with Listener Object ​

You can unregister a listener if you have the listener object with cancel().

The code below stops the listener after 2 seconds.

js
// init.js
let counter = 0;

let counterListener = addEventListener("startClientWorldTickEvent", () => {
  counter++;
  console.log(`Current count: ${counter}`);
});

setTimeout(() => {
  counterListener.cancel();
}, 2000);

Unregister with removeEventListener ​

removeEventListener is an alternative to the cancel() method.

js
removeEventListener(counterListener);

Unregister by ID ​

If you know the ID of the listener, you can unregister it by its ID.

js
// init.js
let counter = 0;

addEventListener(
  "startClientWorldTickEvent",
  () => {
    counter++;
    console.log(`Current count: ${counter}`);
  },
  { id: "myNamedListener" },
);

setTimeout(() => {
  removeEventListener("myNamedListener");
}, 2000);

Create Event Definitions ​

Registering Java Events ​

If no packges provide the event definitions you are looking for, can you define your own with addEvent(name, event, interface, method).

For example, if StartClientTickEvent is not previously defined.

js
// init.js
let { ClientTickEvents } =
  Packages.net.fabricmc.fabric.api.client.event.lifecycle.v1;

let listener = require("listener");

listener.addEvent(
  "StartClientTickEvent",
  ClientTickEvents.START_CLIENT_TICK,
  ClientTickEvents.StartTick,
  "onStartTick",
);

Creating Custom Event Source ​

You can also create a custom channel which can be listened to with createChannel(name). And use dispatchCustomEvent(name, arguments...) to broadcast an event to all listeners.

js
let listener = require("listener");

// ...

listener.createChannel("MyEvent");

addEventListener("myEvent", (arg1, arg2, arg3) => {
  /* do something */
});

listener.dispatchCustomEvent("myEvent", [arg1, arg2, arg3]);

Tail and Continuation ​

The addEvent and createChannel methods each takes two additional parameters.

Tail(output, args) -> newOutput ​

Modifies the output of each listener.

js
// these are examples tail functions

// default tail function if it is not specified
let identity = (output, _) => output;

// if the output of a listener is undefined (no return value)
// then the output defaults to true
let defaultToTrue = (output, _) => output ?? true;

// default to null
let defaultToTrue = (output, _) => output ?? null;

Tail functions must work when output is undefined, as it is also used to get the default value of the listener.

Continuation(output, args) -> [true, newArguments] or [false, finalOutput] ​

Determine whether the current listener should stop event propagation.

js
// these are example continuation function

// default continuation function if it is not specified
let identity = (_, args) => [true, args];

// if event propagation should continue, return [true, newArgs]
// where newArgs is the arguments to the method following the current one
let onlyIfTrue = (output, args) => {
  if (output) {
    return [true, args];
  } else {
    return [false, output];
  }
};

Combining the two, let's define some EntitySleepEvents.

js
// init.js
let { EntitySleepEvents } = Packages.net.fabricmc.fabric.api.entity.event.v1;

let listener = require("listener");

// listeners that returns `void` usually does not require
// tail or continuation functions
listener.addEvent(
  "EntityStartSleepingEvent",
  EntitySleepEvents.START_SLEEPING,
  EntitySleepEvents.StartSleeping,
  "onStartSleeping",
);

// allow bed only returns true if all listeners return true
// in other words, stop propagation and return false if false is returned
// the default value is true
listener.addEvent(
  "EntityStartSleepingEvent",
  EntitySleepEvents.ALLOW_BED,
  EntitySleepEvents.AllowBed,
  "allowBed",
  (output, _) => output ?? true, // default value
  (output, args) => {
    if (output) {
      return [true, args]; // continue propagation
    } else {
      return [false, false]; // stop and return false
    }
  },
);

// returns the original direction if non is specified
// the 3rd argument to the listener is modified each time
// sleeping direction is changed
listener.addEvent(
  "EntityModifySleepingDirectionEvent",
  EntitySleepEvents.MODIFY_SLEEPING_DIRECTION,
  EntitySleepEvents.ModifySleepingDirection,
  "modifySleepingDirection",
  (output, [_, _, originalDirection]) => {
    if (output == undefined || output == null) {
      return originalDirection; // default value
    } else {
      return output;
    }
  },
  (output, args) => {
    args[2] = output; // update argument
    return [true, args]; // continue propagation
  },
);

Pre-listening to Events ​

When listening to an event not yet registered, you will get an error as expected. But this is allowed if you add in the prelisten option.

js
addEventListener(
  "anEventNotYetRegistered",
  () => {
    /* do stuff */
  },
  { prelisten: true },
);

This is useful if you want to handle an event from a package that is loaded in later, which has a good reason to depend on the current package and not the other way round.