Events
Events are a way to listen to specific actions that happen in the game. They are used to trigger specific actions when something happens in the game.
Listening to events
To listen to an event, you need to register a listener for that event.
This is done by registering an object with the PluginManager Event Bus.
It is recommended to do this in the onEnable method of your plugin.
And unregister the listener in the onDisable method in the entrypoint of
your plugin that inherits from IPlugin.
@Override
public void onEnable() {
PluginManager.getInstance().PLUGIN_EVENT_BUS.register(this);
}
@Override
public void onDisable() {
PluginManager.getInstance().PLUGIN_EVENT_BUS.unregister(this);
}
The way event listening works is that the listener class has methods that are annotated with @Subscribe.
And the parameter of the method is the event class that you want to listen to.
@Subscribe
public void OnEvent(EventClassHere event) {
// Do something
}
Remember to replace EventClassHere with the event class you want to listen to.
ClientTickEvent
The ClientTickEvent is an event that is fired every tick of the client.
This event is useful for doing things that need to be done every tick.
It is located in the com.originmint.plugin.events.ClientTickEvent class.
@Subscribe
public void onClientTick(ClientTickEvent event) {
// Do something every tick
}
It has two phases:
PRE: Fired at the beginning of the tick.POST: Fired at the end of the tick.
@Subscribe
public void onClientTick(ClientTickEvent event) {
if (event.phase == ClientTickEvent.Phase.PRE) {
// Do something at the beginning of the tick
} else if (event.phase == ClientTickEvent.Phase.POST) {
// Do something at the end of the tick
}
}
event.data is empty for this event on every version.
KeyInputEvent
The KeyInputEvent is an event that is fired when a key is pressed or released.
This event is useful for listening to key presses.
It is located in the com.originmint.plugin.events.KeyInputEvent class.
event.data contains the key that triggered the event on every version:
data[0]: the key code as anInteger.data[1]: whether the key was pressed (true) or released (false) as aBoolean.
The key code values differ per Minecraft version:
1.8.9uses LWJGL 2 key codes (e.g.42is left shift). When the key has no LWJGL key code, the event carries the typed character plus256.1.18.2,1.20.4and1.21use GLFW key codes (e.g.340is left shift).
@Subscribe
public void onKeyInput(KeyInputEvent event) {
int keyCode = (Integer) event.data[0]; // LWJGL 2 key code
boolean pressed = (Boolean) event.data[1];
if (pressed && keyCode == 42) {
// Left shift was pressed
}
}
@Subscribe
public void onKeyInput(KeyInputEvent event) {
int keyCode = (Integer) event.data[0]; // GLFW key code
boolean pressed = (Boolean) event.data[1];
if (pressed && keyCode == 340) {
// Left shift was pressed
}
} RenderTickEvent
The RenderTickEvent is an event that is fired every tick of the render loop.
This event is useful for doing things that need to be done every render tick.
It is located in the com.originmint.plugin.events.RenderTickEvent class.
It also has multiple types:
PRE: Fired at the beginning of the render tick.OVERLAY: Fired when the overlay (HUD) is rendered.WORLD: Fired when the world is rendered.POST: Fired at the end of the render tick.GUI: Reserved. This type exists in the enum but is currently never fired on any version.
This can be useful to render something as a HUD overlay, or something in the
world such as tracers or esp. World drawing must happen during the WORLD
type and overlay drawing during the OVERLAY type — see the
Render Manager page for the drawing methods.
@Subscribe
public void onRenderTick(RenderTickEvent event) {
if (event.type == RenderTickEvent.Type.PRE) {
// Do something at the beginning of the render tick
} else if (event.type == RenderTickEvent.Type.POST) {
// Do something at the end of the render tick
} else if (event.type == RenderTickEvent.Type.OVERLAY) {
// Draw HUD overlay shapes here
} else if (event.type == RenderTickEvent.Type.WORLD) {
// Draw world shapes (esp, tracers, nametags) here
}
}
RenderTickEvent data
event.data always starts with the tick delta, but newer versions attach
extra render state. Verified contents per version:
| Type | 1.8.9 | 1.18.2 / 1.20.4 | 1.21 |
|---|---|---|---|
PRE / POST | Float partialTicks, Long nanoTime | Float tickDelta, Long startTime | Float tickProgress, Long nanoTime |
WORLD | Float partialTicks, Long finishTimeNano | Float tickDelta, Long limitTime, MatrixStack, float[3] cameraPos, float[16] modelView, float[16] projection | same as 1.18.2/1.20.4, but limitTime is always 0 |
OVERLAY | Float partialTicks, Long nanoTime | Float tickDelta, MatrixStack, float[16] modelView, float[16] projection | Float tickDelta, float[16] modelView, float[16] projection |
The event also exposes convenience fields, so you usually do not need to dig
through data yourself:
event.modelViewMatrix/event.projectionMatrix(float[16]): set forWORLDandOVERLAYon1.18.2,1.20.4and1.21; alwaysnullon1.8.9. On1.21theOVERLAYmatrices are identity, because the 1.21.5+ HUD renders in plain 2D screen space.event.matrixStack(net.minecraft.client.util.math.MatrixStack): only exists on1.18.2,1.20.4and1.21. Set forWORLDeverywhere, and forOVERLAYon1.18.2/1.20.4(the 1.21 HUD no longer uses aMatrixStack, so it isnullthere). Code that references this field will not compile on1.8.9.
Packet Event
Listening to packets is different than other events, it uses the vanilla
packet classes directly. This is useful for listening to packets that are sent
or received by the client. You have to have two parameters in the method: the
packet class you want to listen to and the PacketCallback class located at
com.originmint.plugin.events.PacketCallback. The packet callback is used to
cancel the packet if needed.
Because the vanilla packet classes are part of Minecraft, their names depend on the version your plugin targets:
| Packet base class | Packet packages | |
|---|---|---|
1.8.9 | net.minecraft.network.Packet | net.minecraft.network.play.client (sent), net.minecraft.network.play.server (received) |
1.18.2 | net.minecraft.network.Packet | net.minecraft.network.packet.c2s.* (sent), net.minecraft.network.packet.s2c.* (received) |
1.20.4 / 1.21 | net.minecraft.network.packet.Packet | same as 1.18.2, but some packets moved to the new c2s.common / s2c.common packages (e.g. KeepAliveS2CPacket) |
Listening to every packet uses the base class:
import net.minecraft.network.Packet;
@Subscribe
public void onPacket(Packet<?> packet, PacketCallback callback) {
// Listen to all packets
}
import net.minecraft.network.Packet;
@Subscribe
public void onPacket(Packet<?> packet, PacketCallback callback) {
// Listen to all packets
}
import net.minecraft.network.packet.Packet;
@Subscribe
public void onPacket(Packet<?> packet, PacketCallback callback) {
// Listen to all packets
} You can also listen to specific packets by specifying the class, for example listening to chat messages from the server:
import net.minecraft.network.play.server.S02PacketChat;
@Subscribe
public void onPacket(S02PacketChat packet, PacketCallback callback) {
// Cancel received chat packets
callback.cancel();
}
import net.minecraft.network.packet.s2c.play.GameMessageS2CPacket;
@Subscribe
public void onPacket(GameMessageS2CPacket packet, PacketCallback callback) {
// Cancel received chat packets
callback.cancel();
} ReplayEvent
The ReplayEvent is an event that is fired when a replay is started, stopped or paused.
This is a custom event that is fired by the replay system.
It is located in the com.originmint.plugin.events.ReplayEvent class.
The different types of the replay event are:
START: Fired when the replay starts.STOP: Fired when the replay stops.FINISH: Fired when the replay finishes.PAUSE: Fired when the replay is paused.RESUME: Fired when the replay is resumed.
It also has two phases:
PRE: Fired at the beginning of the replay event.POST: Fired at the end of the replay event.
This can be useful to do something when the replay starts, stops, or pauses. And you can also cancel starting, stopping, or pausing the replay.
@Subscribe
public void onReplay(ReplayEvent event) {
if (event.type == ReplayEvent.Type.START) {
// Do something when the replay starts
} else if (event.type == ReplayEvent.Type.STOP) {
// Do something when the replay stops
} else if (event.type == ReplayEvent.Type.FINISH) {
// Do something when the replay finishes
} else if (event.type == ReplayEvent.Type.PAUSE) {
// Do something when the replay is paused
} else if (event.type == ReplayEvent.Type.RESUME) {
// Do something when the replay is resumed
}
}
For all replay event types, event.data[0] is the replay id as a String.
@Subscribe
public void onReplay(ReplayEvent event) {
if (event.data == null || event.data.length == 0) {
return;
}
String replayId = (String) event.data[0];
if (event.type == ReplayEvent.Type.START && event.phase == ReplayEvent.Phase.POST) {
LoggingManager.getInstance().log("Started replay " + replayId, LoggingManager.LOG_LEVEL.INFO);
}
}
Example of cancelling the replay start:
@Subscribe
public void onReplay(ReplayEvent event) {
if (event.type == ReplayEvent.Type.START && event.phase == ReplayEvent.Phase.PRE) {
// Cancel starting the replay
event.cancelEvent();
}
}
SyncEvent
The SyncEvent is an event that is fired when the replay goes out of sync.
This is a custom event that is fired by the replay system.
It is located in the com.originmint.plugin.events.SyncEvent class.
This can be used to do something when the replay goes out of sync.
Or cancel the replay from going out of sync.
There are different types of the sync event:
CAMERA: Fired when the camera goes out of sync.POSITION: Fired when the position goes out of sync.LOCK_POSITION: Fired when the client gets moved by the server.INVENTORY: Fired when the inventory goes out of sync.INVENTORY_ERROR: Fired when an error occurs with the inventory.INVENTORY_OPEN: Fired when the inventory is opened.INVENTORY_OPEN_CONTAINER: Fired when the container is opened.INVENTORY_HOTBAR: Fired when the hotbar goes out of sync.INVENTORY_ARMOR: Fired when the armor goes out of sync.INVENTORY_SLOT: Fired when a slot goes out of sync.PLAYER_NEARBY: Fired when a watched player is too close during replay playback.ENTITY_NEARBY: Fired when a watched living entity is too close during replay playback.AREA: Fired when a locked area block changes near the player.
It also has two phases:
PRE: Fired at the beginning of the sync event.POST: Fired at the end of the sync event.
@Subscribe
public void onSync(SyncEvent event) {
if (event.type == SyncEvent.Type.CAMERA) {
// Do something when the camera goes out of sync
} else if (event.type == SyncEvent.Type.POSITION) {
// Do something when the position goes out of sync
} else if (event.type == SyncEvent.Type.LOCK_POSITION) {
// Do something when the client gets moved by the server
} else if (event.type == SyncEvent.Type.INVENTORY) {
// Do something when the inventory goes out of sync
} else if (event.type == SyncEvent.Type.INVENTORY_ERROR) {
// Do something when an error occurs with the inventory
} else if (event.type == SyncEvent.Type.INVENTORY_OPEN) {
// Do something when the inventory is opened
} else if (event.type == SyncEvent.Type.INVENTORY_OPEN_CONTAINER) {
// Do something when the container is opened
} else if (event.type == SyncEvent.Type.INVENTORY_HOTBAR) {
// Do something when the hotbar goes out of sync
} else if (event.type == SyncEvent.Type.INVENTORY_ARMOR) {
// Do something when the armor goes out of sync
} else if (event.type == SyncEvent.Type.INVENTORY_SLOT) {
// Do something when a slot goes out of sync
} else if (event.type == SyncEvent.Type.PLAYER_NEARBY) {
// Do something when a watched player is nearby
} else if (event.type == SyncEvent.Type.ENTITY_NEARBY) {
// Do something when a watched entity is nearby
} else if (event.type == SyncEvent.Type.AREA) {
// Do something when the locked area changes
}
}
SyncEvent.data is an Object[] with type-specific values:
CAMERA:Float yawDifference,Float pitchDifference— all versions.POSITIONandLOCK_POSITION:Double xDifference,Double yDifference,Double zDifference— all versions.- Inventory types:
ItemStack savedItemStack,ItemStack newItemStack. Either value can benull— 1.8.9 only. PLAYER_NEARBYandENTITY_NEARBY: the nearbyEntity— 1.8.9 only.AREA:Integer x,Integer y,Integer zfor the changed block position — 1.8.9 only.
On 1.18.2, 1.20.4 and 1.21 the inventory, entity and area sync events
still fire (and can still be cancelled), but their data array is currently
empty. Always check event.data.length before indexing.
The following data example targets 1.8.9 (the only version that currently
fills the inventory, entity and area data). The camera and position branches
work the same on every version.
import net.minecraft.entity.Entity;
import net.minecraft.item.ItemStack;
private Object data(SyncEvent event, int index) {
if (event.data == null || event.data.length <= index) {
return null;
}
return event.data[index];
}
@Subscribe
public void onSyncData(SyncEvent event) {
switch (event.type) {
case CAMERA: {
Float yawDifference = (Float) data(event, 0);
Float pitchDifference = (Float) data(event, 1);
if (yawDifference == null || pitchDifference == null) {
return;
}
LoggingManager.getInstance().log(
"Camera out of sync: yaw=" + yawDifference + ", pitch=" + pitchDifference,
LoggingManager.LOG_LEVEL.ERROR
);
break;
}
case POSITION:
case LOCK_POSITION: {
Double xDifference = (Double) data(event, 0);
Double yDifference = (Double) data(event, 1);
Double zDifference = (Double) data(event, 2);
if (xDifference == null || yDifference == null || zDifference == null) {
return;
}
LoggingManager.getInstance().log(
"Position out of sync: x=" + xDifference + ", y=" + yDifference + ", z=" + zDifference,
LoggingManager.LOG_LEVEL.ERROR
);
break;
}
case INVENTORY:
case INVENTORY_ERROR:
case INVENTORY_OPEN:
case INVENTORY_OPEN_CONTAINER:
case INVENTORY_HOTBAR:
case INVENTORY_ARMOR:
case INVENTORY_SLOT: {
ItemStack savedItemStack = (ItemStack) data(event, 0);
ItemStack newItemStack = (ItemStack) data(event, 1);
String savedName = savedItemStack == null ? "empty" : savedItemStack.getDisplayName();
String newName = newItemStack == null ? "empty" : newItemStack.getDisplayName();
LoggingManager.getInstance().log(
"Inventory out of sync: saved=" + savedName + ", new=" + newName,
LoggingManager.LOG_LEVEL.ERROR
);
break;
}
case PLAYER_NEARBY:
case ENTITY_NEARBY: {
Entity entity = (Entity) data(event, 0);
if (entity == null) {
return;
}
LoggingManager.getInstance().log(
"Nearby entity caused sync event: " + entity.getName(),
LoggingManager.LOG_LEVEL.ERROR
);
break;
}
case AREA: {
Integer x = (Integer) data(event, 0);
Integer y = (Integer) data(event, 1);
Integer z = (Integer) data(event, 2);
if (x == null || y == null || z == null) {
return;
}
LoggingManager.getInstance().log(
"Area out of sync at " + x + ", " + y + ", " + z,
LoggingManager.LOG_LEVEL.ERROR
);
break;
}
}
}
Example of cancelling the camera going out of sync:
@Subscribe
public void onSync(SyncEvent event) {
if (event.type == SyncEvent.Type.CAMERA && event.phase == SyncEvent.Phase.PRE) {
// Cancel the camera from going out of sync
event.cancelEvent();
}
}
This will keep calling every tick until the camera is back in sync. So you have to keep that in mind when cancelling the event.
Player interaction events
Three small events fire when the player interacts with the world. They are
available and behave the same on every version, carry no data, and can all
be cancelled with event.cancel() — cancelling prevents the vanilla action:
OnAttackEvent: fired when the player attacks. Located incom.originmint.plugin.events.OnAttackEvent.OnUseItemEvent: fired when the player uses an item (right click). Located incom.originmint.plugin.events.OnUseItemEvent.OnBlockBreakEvent: fired when the player breaks a block. Located incom.originmint.plugin.events.OnBlockBreakEvent. It has a publicleftClickfield that tells you whether the break came from a left click.
@Subscribe
public void onAttack(OnAttackEvent event) {
// Cancel the attack
event.cancel();
}
@Subscribe
public void onUseItem(OnUseItemEvent event) {
// Right click / item use
}
@Subscribe
public void onBlockBreak(OnBlockBreakEvent event) {
if (event.leftClick) {
// Block break started from a left click
}
}