From 28287824f962579bba440b825a22e1d607442745 Mon Sep 17 00:00:00 2001 From: Brennan Ward <3682588+Shadows-of-Fire@users.noreply.github.com> Date: Tue, 16 Apr 2024 22:24:14 -0700 Subject: [PATCH 1/5] Refactor tick events into distinct subclasses --- .../net/minecraft/client/Minecraft.java.patch | 24 ++-- .../server/MinecraftServer.java.patch | 11 +- .../world/entity/player/Player.java.patch | 4 +- .../capabilities/CapabilityHooks.java | 16 +-- .../neoforge/client/ClientHooks.java | 38 +++++++ .../client/event/ClientTickEvent.java | 36 ++++++ .../client/event/RenderFrameEvent.java | 54 +++++++++ .../neoforge/common/CommonHooks.java | 20 +++- .../neoforge/common/NeoForgeEventHandler.java | 11 +- .../neoforged/neoforge/event/EventHooks.java | 79 ++++++++----- .../neoforged/neoforge/event/TickEvent.java | 107 ------------------ .../event/entity/living/LivingEvent.java | 18 --- .../neoforge/event/tick/LevelTickEvent.java | 70 ++++++++++++ .../neoforge/event/tick/LivingTickEvent.java | 58 ++++++++++ .../neoforge/event/tick/PlayerTickEvent.java | 50 ++++++++ .../neoforge/event/tick/ServerTickEvent.java | 67 +++++++++++ .../neoforge/event/tick/package-info.java | 13 +++ .../neoforge/debug/client/ClientTests.java | 5 +- .../neoforge/debug/level/LevelTests.java | 4 +- .../rendering/CustomParticleTypeTest.java | 6 +- 20 files changed, 493 insertions(+), 198 deletions(-) create mode 100644 src/main/java/net/neoforged/neoforge/client/event/ClientTickEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/client/event/RenderFrameEvent.java delete mode 100644 src/main/java/net/neoforged/neoforge/event/TickEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/event/tick/LevelTickEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/event/tick/LivingTickEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/event/tick/PlayerTickEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/event/tick/ServerTickEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/event/tick/package-info.java diff --git a/patches/net/minecraft/client/Minecraft.java.patch b/patches/net/minecraft/client/Minecraft.java.patch index 744ebb1662..7a8dcecbfe 100644 --- a/patches/net/minecraft/client/Minecraft.java.patch +++ b/patches/net/minecraft/client/Minecraft.java.patch @@ -209,11 +209,11 @@ this.profiler.pop(); if (!this.noRender) { + this.realPartialTick = this.pause ? this.pausePartialTick : this.timer.partialTick; // Cache this since pause is volatile -+ net.neoforged.neoforge.event.EventHooks.onRenderTickStart(this.realPartialTick); ++ net.neoforged.neoforge.client.ClientHooks.fireRenderFramePre(this.realPartialTick); this.profiler.popPush("gameRenderer"); this.gameRenderer.render(this.pause ? this.pausePartialTick : this.timer.partialTick, i, p_91384_); this.profiler.pop(); -+ net.neoforged.neoforge.event.EventHooks.onRenderTickEnd(this.realPartialTick); ++ net.neoforged.neoforge.client.ClientHooks.fireRenderFramePost(this.realPartialTick); } if (this.fpsPieResults != null) { @@ -326,20 +326,20 @@ if (!itemstack.isEmpty()) { InteractionResult interactionresult2 = this.gameMode.useItem(this.player, interactionhand); if (interactionresult2.consumesAction()) { -@@ -1808,6 +_,8 @@ - this.rightClickDelay--; - } +@@ -1800,6 +_,8 @@ -+ net.neoforged.neoforge.event.EventHooks.onPreClientTick(); + public void tick() { + this.clientTickCount++; ++ net.neoforged.neoforge.client.ClientHooks.fireClientTickPre(); + - this.profiler.push("gui"); - this.chatListener.tick(); - this.gui.tick(this.pause); + if (this.level != null && !this.pause) { + this.level.tickRateManager().tick(); + } @@ -1892,6 +_,7 @@ this.tutorial.tick(); -+ net.neoforged.neoforge.event.EventHooks.onPreLevelTick(this.level, () -> true); ++ net.neoforged.neoforge.event.EventHooks.fireLevelTickPre(this.level, () -> true); try { this.level.tick(() -> true); } catch (Throwable throwable) { @@ -347,7 +347,7 @@ throw new ReportedException(crashreport); } -+ net.neoforged.neoforge.event.EventHooks.onPostLevelTick(this.level, () -> true); ++ net.neoforged.neoforge.event.EventHooks.fireLevelTickPost(this.level, () -> true); } this.profiler.popPush("animateTick"); @@ -356,7 +356,7 @@ this.keyboardHandler.tick(); this.profiler.pop(); + -+ net.neoforged.neoforge.event.EventHooks.onPostClientTick(); ++ net.neoforged.neoforge.client.ClientHooks.fireClientTickPost(); } private boolean isMultiplayerServer() { diff --git a/patches/net/minecraft/server/MinecraftServer.java.patch b/patches/net/minecraft/server/MinecraftServer.java.patch index 1fa90521d0..caa911ba00 100644 --- a/patches/net/minecraft/server/MinecraftServer.java.patch +++ b/patches/net/minecraft/server/MinecraftServer.java.patch @@ -86,12 +86,11 @@ this.onServerExit(); } } -@@ -876,12 +_,14 @@ - +@@ -877,11 +_,13 @@ public void tickServer(BooleanSupplier p_129871_) { long i = Util.getNanos(); -+ net.neoforged.neoforge.event.EventHooks.onPreServerTick(p_129871_, this); this.tickCount++; ++ net.neoforged.neoforge.event.EventHooks.fireServerTickPre(p_129871_, this); this.tickRateManager.tick(); this.tickChildren(p_129871_); if (i - this.lastServerStatus >= STATUS_EXPIRE_TIME_NANOS) { @@ -105,7 +104,7 @@ this.smoothedTickTimeMillis = this.smoothedTickTimeMillis * 0.8F + (float)j / (float)TimeUtil.NANOSECONDS_PER_MILLISECOND * 0.19999999F; this.logTickMethodTime(i); this.profiler.pop(); -+ net.neoforged.neoforge.event.EventHooks.onPostServerTick(p_129871_, this); ++ net.neoforged.neoforge.event.EventHooks.fireServerTickPost(p_129871_, this); } private void logTickMethodTime(long p_321837_) { @@ -150,7 +149,7 @@ } this.profiler.push("tick"); -+ net.neoforged.neoforge.event.EventHooks.onPreLevelTick(serverlevel, p_129954_); ++ net.neoforged.neoforge.event.EventHooks.fireLevelTickPre(serverlevel, p_129954_); try { serverlevel.tick(p_129954_); @@ -158,7 +157,7 @@ serverlevel.fillReportDetails(crashreport); throw new ReportedException(crashreport); } -+ net.neoforged.neoforge.event.EventHooks.onPostLevelTick(serverlevel, p_129954_); ++ net.neoforged.neoforge.event.EventHooks.fireLevelTickPost(serverlevel, p_129954_); this.profiler.pop(); this.profiler.pop(); diff --git a/patches/net/minecraft/world/entity/player/Player.java.patch b/patches/net/minecraft/world/entity/player/Player.java.patch index a710798b92..530f8ebdf7 100644 --- a/patches/net/minecraft/world/entity/player/Player.java.patch +++ b/patches/net/minecraft/world/entity/player/Player.java.patch @@ -35,7 +35,7 @@ @Override public void tick() { -+ net.neoforged.neoforge.event.EventHooks.onPlayerPreTick(this); ++ net.neoforged.neoforge.event.EventHooks.firePlayerTickPre(this); this.noPhysics = this.isSpectator(); if (this.isSpectator()) { this.setOnGround(false); @@ -52,7 +52,7 @@ this.turtleHelmetTick(); this.cooldowns.tick(); this.updatePlayerPose(); -+ net.neoforged.neoforge.event.EventHooks.onPlayerPostTick(this); ++ net.neoforged.neoforge.event.EventHooks.firePlayerTickPost(this); } @Override diff --git a/src/main/java/net/neoforged/neoforge/capabilities/CapabilityHooks.java b/src/main/java/net/neoforged/neoforge/capabilities/CapabilityHooks.java index 7f9869a35e..7d53bf075c 100644 --- a/src/main/java/net/neoforged/neoforge/capabilities/CapabilityHooks.java +++ b/src/main/java/net/neoforged/neoforge/capabilities/CapabilityHooks.java @@ -20,8 +20,8 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.neoforged.fml.ModLoader; import net.neoforged.neoforge.common.NeoForgeMod; -import net.neoforged.neoforge.event.TickEvent; import net.neoforged.neoforge.event.level.ChunkEvent; +import net.neoforged.neoforge.event.tick.LevelTickEvent; import net.neoforged.neoforge.fluids.capability.wrappers.FluidBucketWrapper; import net.neoforged.neoforge.items.VanillaHopperItemHandler; import net.neoforged.neoforge.items.wrapper.CombinedInvWrapper; @@ -152,20 +152,20 @@ else if (entity instanceof LivingEntity livingEntity) } public static void invalidateCapsOnChunkLoad(ChunkEvent.Load event) { - if (!event.getLevel().isClientSide()) { - ((ServerLevel) event.getLevel()).invalidateCapabilities(event.getChunk().getPos()); + if (event.getLevel() instanceof ServerLevel sl) { + sl.invalidateCapabilities(event.getChunk().getPos()); } } public static void invalidateCapsOnChunkUnload(ChunkEvent.Unload event) { - if (!event.getLevel().isClientSide()) { - ((ServerLevel) event.getLevel()).invalidateCapabilities(event.getChunk().getPos()); + if (event.getLevel() instanceof ServerLevel sl) { + sl.invalidateCapabilities(event.getChunk().getPos()); } } - public static void cleanCapabilityListenerReferencesOnTick(TickEvent.LevelTickEvent event) { - if (event.phase == TickEvent.Phase.END && event.side.isServer()) { - ((ServerLevel) event.level).cleanCapabilityListenerReferences(); + public static void cleanCapabilityListenerReferencesOnTick(LevelTickEvent.Post event) { + if (event.getLevel() instanceof ServerLevel sl) { + sl.cleanCapabilityListenerReferences(); } } } diff --git a/src/main/java/net/neoforged/neoforge/client/ClientHooks.java b/src/main/java/net/neoforged/neoforge/client/ClientHooks.java index 8d50223480..23cfa1d4de 100644 --- a/src/main/java/net/neoforged/neoforge/client/ClientHooks.java +++ b/src/main/java/net/neoforged/neoforge/client/ClientHooks.java @@ -134,6 +134,7 @@ import net.neoforged.neoforge.client.event.ClientPauseUpdatedEvent; import net.neoforged.neoforge.client.event.ClientPlayerChangeGameTypeEvent; import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; +import net.neoforged.neoforge.client.event.ClientTickEvent; import net.neoforged.neoforge.client.event.ComputeFovModifierEvent; import net.neoforged.neoforge.client.event.CustomizeGuiOverlayEvent; import net.neoforged.neoforge.client.event.EntityRenderersEvent; @@ -149,6 +150,7 @@ import net.neoforged.neoforge.client.event.RegisterSpriteSourceTypesEvent; import net.neoforged.neoforge.client.event.RenderArmEvent; import net.neoforged.neoforge.client.event.RenderBlockScreenEffectEvent; +import net.neoforged.neoforge.client.event.RenderFrameEvent; import net.neoforged.neoforge.client.event.RenderHandEvent; import net.neoforged.neoforge.client.event.RenderHighlightEvent; import net.neoforged.neoforge.client.event.RenderLevelStageEvent; @@ -1003,4 +1005,40 @@ public static void initClientHooks(Minecraft mc, ReloadableResourceManager resou ItemDecoratorHandler.init(); PresetEditorManager.init(); } + + /** + * Fires {@link RenderFrameEvent.Pre}. Called just before {@link GameRenderer#render(float, long, boolean)} in {@link Minecraft#runTick(boolean)}. + *
+ * Fired before the profiler section for "gameRenderer" is started. + * + * @param partialTick The current partial tick + */ + public static void fireRenderFramePre(float partialTick) { + NeoForge.EVENT_BUS.post(new RenderFrameEvent.Pre(partialTick)); + } + + /** + * Fires {@link RenderFrameEvent.Post}. Called just after {@link GameRenderer#render(float, long, boolean)} in {@link Minecraft#runTick(boolean)}. + *
+ * Fired after the profiler section for "gameRenderer" is ended. + * + * @param partialTick The current partial tick + */ + public static void fireRenderFramePost(float partialRick) { + NeoForge.EVENT_BUS.post(new RenderFrameEvent.Post(partialRick)); + } + + /** + * Fires {@link ClientTickEvent.Pre}. Called from the head of {@link Minecraft#tick()}. + */ + public static void fireClientTickPre() { + NeoForge.EVENT_BUS.post(new ClientTickEvent.Pre()); + } + + /** + * Fires {@link ClientTickEvent.Post}. Called from the tail of {@link Minecraft#tick()}. + */ + public static void fireClientTickPost() { + NeoForge.EVENT_BUS.post(new ClientTickEvent.Post()); + } } diff --git a/src/main/java/net/neoforged/neoforge/client/event/ClientTickEvent.java b/src/main/java/net/neoforged/neoforge/client/event/ClientTickEvent.java new file mode 100644 index 0000000000..80341cb7b0 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/event/ClientTickEvent.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.event; + +import net.neoforged.bus.api.Event; + +/** + * Base class of the two client tick events. + * + * @see Pre + * @see Post + */ +public abstract class ClientTickEvent extends Event { + public ClientTickEvent() {} + + /** + * {@link ClientTickEvent.Pre} is fired once per client tick, before the client performs work for the current tick. + *
+ * This event only fires on the physical client. + */ + public static class Pre extends ClientTickEvent { + public Pre() {} + } + + /** + * {@link ClientTickEvent.Post} is fired once per client tick, after the client performs work for the current tick. + *
+ * This event only fires on the physical client. + */ + public static class Post extends ClientTickEvent { + public Post() {} + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/event/RenderFrameEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RenderFrameEvent.java new file mode 100644 index 0000000000..59cbd54a6a --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/event/RenderFrameEvent.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.event; + +import net.minecraft.client.renderer.GameRenderer; +import net.neoforged.bus.api.Event; + +/** + * Base class of the two render frame events. + *
+ * These events can be used to setup and teardown global render state that must persist for the current frame. + * + * @see Pre + * @see Post + */ +public abstract class RenderFrameEvent extends Event { + protected final float partialTick; + + protected RenderFrameEvent(float partialTick) { + this.partialTick = partialTick; + } + + /** + * {@return the current partial tick, which is either the true partial tick or the pause partial tick, depending on if the game is paused} + */ + public float getPartialTick() { + return this.partialTick; + } + + /** + * {@link RenderFrameEvent.Pre} is fired once per frame, before the current frame is rendered via {@link GameRenderer#render(float, long, boolean)}. + *
+ * This event only fires on the physical client. + */ + public static class Pre extends RenderFrameEvent { + public Pre(float partialTick) { + super(partialTick); + } + } + + /** + * {@link RenderFrameEvent.Post} is fired once per frame, after the current frame is rendered via {@link GameRenderer#render(float, long, boolean)}. + *
+ * This event only fires on the physical client.
+ */
+ public static class Post extends RenderFrameEvent {
+ public Post(float partialTick) {
+ super(partialTick);
+ }
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/common/CommonHooks.java b/src/main/java/net/neoforged/neoforge/common/CommonHooks.java
index ce5d91c41a..935fb33eb8 100644
--- a/src/main/java/net/neoforged/neoforge/common/CommonHooks.java
+++ b/src/main/java/net/neoforged/neoforge/common/CommonHooks.java
@@ -185,6 +185,7 @@
import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
import net.neoforged.neoforge.event.level.BlockEvent;
import net.neoforged.neoforge.event.level.NoteBlockEvent;
+import net.neoforged.neoforge.event.tick.LivingTickEvent;
import net.neoforged.neoforge.fluids.FluidType;
import net.neoforged.neoforge.registries.NeoForgeRegistries;
import net.neoforged.neoforge.resource.ResourcePackLoader;
@@ -234,8 +235,23 @@ public static LivingChangeTargetEvent onLivingChangeTarget(LivingEntity entity,
return event;
}
- public static boolean onLivingTick(LivingEntity entity) {
- return NeoForge.EVENT_BUS.post(new LivingEvent.LivingTickEvent(entity)).isCanceled();
+ /**
+ * Fires {@link LivingTickEvent.Pre}. Called from the head of {@link LivingEntity#tick()}.
+ *
+ * @param entity The entity being ticked
+ * @return The event
+ */
+ public static LivingTickEvent.Pre fireLivingTickPre(LivingEntity entity) {
+ return NeoForge.EVENT_BUS.post(new LivingTickEvent.Pre(entity));
+ }
+
+ /**
+ * Fires {@link LivingTickEvent.Post}. Called from the tail of {@link LivingEntity#tick()}.
+ *
+ * @param entity The entity being ticked
+ */
+ public static void fireLivingTickPost(LivingEntity entity) {
+ NeoForge.EVENT_BUS.post(new LivingTickEvent.Post(entity));
}
public static boolean onLivingAttack(LivingEntity entity, DamageSource src, float amount) {
diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java
index 5c2b42b1cf..4397530fb4 100644
--- a/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java
+++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java
@@ -30,11 +30,11 @@
import net.neoforged.neoforge.event.OnDatapackSyncEvent;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
import net.neoforged.neoforge.event.TagsUpdatedEvent;
-import net.neoforged.neoforge.event.TickEvent;
import net.neoforged.neoforge.event.entity.EntityJoinLevelEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import net.neoforged.neoforge.event.level.ChunkEvent;
import net.neoforged.neoforge.event.level.LevelEvent;
+import net.neoforged.neoforge.event.tick.ServerTickEvent;
import net.neoforged.neoforge.network.PacketDistributor;
import net.neoforged.neoforge.network.payload.RegistryDataMapSyncPayload;
import net.neoforged.neoforge.registries.DataMapLoader;
@@ -70,14 +70,13 @@ public void onDimensionUnload(LevelEvent.Unload event) {
}
@SubscribeEvent
- public void onServerTick(TickEvent.ServerTickEvent event) {
- WorldWorkerManager.tick(event.phase == TickEvent.Phase.START);
+ public void preServerTick(ServerTickEvent.Pre event) {
+ WorldWorkerManager.tick(true);
}
@SubscribeEvent
- public void checkSettings(TickEvent.ClientTickEvent event) {
- //if (event.phase == Phase.END)
- // CloudRenderer.updateCloudSettings();
+ public void postServerTick(ServerTickEvent.Post event) {
+ WorldWorkerManager.tick(false);
}
@SubscribeEvent
diff --git a/src/main/java/net/neoforged/neoforge/event/EventHooks.java b/src/main/java/net/neoforged/neoforge/event/EventHooks.java
index a5972ce6ab..7dbc1227a7 100644
--- a/src/main/java/net/neoforged/neoforge/event/EventHooks.java
+++ b/src/main/java/net/neoforged/neoforge/event/EventHooks.java
@@ -15,6 +15,7 @@
import java.util.function.Consumer;
import net.minecraft.advancements.AdvancementHolder;
import net.minecraft.advancements.AdvancementProgress;
+import net.minecraft.client.Minecraft;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
@@ -93,7 +94,6 @@
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import net.neoforged.bus.api.Event.Result;
-import net.neoforged.fml.LogicalSide;
import net.neoforged.fml.ModLoader;
import net.neoforged.neoforge.common.EffectCure;
import net.neoforged.neoforge.common.NeoForge;
@@ -160,6 +160,9 @@
import net.neoforged.neoforge.event.level.PistonEvent;
import net.neoforged.neoforge.event.level.SaplingGrowTreeEvent;
import net.neoforged.neoforge.event.level.SleepFinishedTimeEvent;
+import net.neoforged.neoforge.event.tick.LevelTickEvent;
+import net.neoforged.neoforge.event.tick.PlayerTickEvent;
+import net.neoforged.neoforge.event.tick.ServerTickEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
@@ -809,44 +812,62 @@ public static void firePlayerSmeltedEvent(Player player, ItemStack smelted) {
NeoForge.EVENT_BUS.post(new PlayerEvent.ItemSmeltedEvent(player, smelted));
}
- public static void onRenderTickStart(float timer) {
- NeoForge.EVENT_BUS.post(new TickEvent.RenderTickEvent(TickEvent.Phase.START, timer));
- }
-
- public static void onRenderTickEnd(float timer) {
- NeoForge.EVENT_BUS.post(new TickEvent.RenderTickEvent(TickEvent.Phase.END, timer));
- }
-
- public static void onPlayerPreTick(Player player) {
- NeoForge.EVENT_BUS.post(new TickEvent.PlayerTickEvent(TickEvent.Phase.START, player));
- }
-
- public static void onPlayerPostTick(Player player) {
- NeoForge.EVENT_BUS.post(new TickEvent.PlayerTickEvent(TickEvent.Phase.END, player));
- }
-
- public static void onPreLevelTick(Level level, BooleanSupplier haveTime) {
- NeoForge.EVENT_BUS.post(new TickEvent.LevelTickEvent(level.isClientSide ? LogicalSide.CLIENT : LogicalSide.SERVER, TickEvent.Phase.START, level, haveTime));
+ /**
+ * Fires {@link PlayerTickEvent.Pre}. Called from the head of {@link Player#tick()}.
+ *
+ * @param player The player being ticked
+ */
+ public static void firePlayerTickPre(Player player) {
+ NeoForge.EVENT_BUS.post(new PlayerTickEvent.Pre(player));
}
- public static void onPostLevelTick(Level level, BooleanSupplier haveTime) {
- NeoForge.EVENT_BUS.post(new TickEvent.LevelTickEvent(level.isClientSide ? LogicalSide.CLIENT : LogicalSide.SERVER, TickEvent.Phase.END, level, haveTime));
+ /**
+ * Fires {@link PlayerTickEvent.Post}. Called from the tail of {@link Player#tick()}.
+ *
+ * @param player The player being ticked
+ */
+ public static void firePlayerTickPost(Player player) {
+ NeoForge.EVENT_BUS.post(new PlayerTickEvent.Post(player));
}
- public static void onPreClientTick() {
- NeoForge.EVENT_BUS.post(new TickEvent.ClientTickEvent(TickEvent.Phase.START));
+ /**
+ * Fires {@link LevelTickEvent.Pre}. Called from {@link Minecraft#tick()} and {@link MinecraftServer#tickChildren(BooleanSupplier)} just before the try block for level tick is entered.
+ *
+ * @param level The level being ticked
+ * @param haveTime The time supplier, indicating if there is remaining time to do work in the current tick.
+ */
+ public static void fireLevelTickPre(Level level, BooleanSupplier haveTime) {
+ NeoForge.EVENT_BUS.post(new LevelTickEvent.Pre(haveTime, level));
}
- public static void onPostClientTick() {
- NeoForge.EVENT_BUS.post(new TickEvent.ClientTickEvent(TickEvent.Phase.END));
+ /**
+ * Fires {@link LevelTickEvent.Post}. Called from {@link Minecraft#tick()} and {@link MinecraftServer#tickChildren(BooleanSupplier)} just after the try block for level tick is exited.
+ *
+ * @param level The level being ticked
+ * @param haveTime The time supplier, indicating if there is remaining time to do work in the current tick.
+ */
+ public static void fireLevelTickPost(Level level, BooleanSupplier haveTime) {
+ NeoForge.EVENT_BUS.post(new LevelTickEvent.Post(haveTime, level));
}
- public static void onPreServerTick(BooleanSupplier haveTime, MinecraftServer server) {
- NeoForge.EVENT_BUS.post(new TickEvent.ServerTickEvent(TickEvent.Phase.START, haveTime, server));
+ /**
+ * Fires {@link ServerTickEvent.Pre}. Called from the head of {@link MinecraftServer#tickServer(BooleanSupplier)}.
+ *
+ * @param haveTime The time supplier, indicating if there is remaining time to do work in the current tick.
+ * @param server The current server
+ */
+ public static void fireServerTickPre(BooleanSupplier haveTime, MinecraftServer server) {
+ NeoForge.EVENT_BUS.post(new ServerTickEvent.Pre(haveTime, server));
}
- public static void onPostServerTick(BooleanSupplier haveTime, MinecraftServer server) {
- NeoForge.EVENT_BUS.post(new TickEvent.ServerTickEvent(TickEvent.Phase.END, haveTime, server));
+ /**
+ * Fires {@link ServerTickEvent.Post}. Called from the tail of {@link MinecraftServer#tickServer(BooleanSupplier)}.
+ *
+ * @param haveTime The time supplier, indicating if there is remaining time to do work in the current tick.
+ * @param server The current server
+ */
+ public static void fireServerTickPost(BooleanSupplier haveTime, MinecraftServer server) {
+ NeoForge.EVENT_BUS.post(new ServerTickEvent.Post(haveTime, server));
}
private static final WeightedRandomList
+ * This event fires on both the logical client and logical server, for {@link ClientLevel} and {@link ServerLevel} respectively.
+ */
+ public static class Pre extends LevelTickEvent {
+ @ApiStatus.Internal
+ public Pre(BooleanSupplier haveTime, Level level) {
+ super(haveTime, level);
+ }
+ }
+
+ /**
+ * {@link LevelTickEvent.Post} is fired once per game tick, per level, after the level performs work for the current tick.
+ *
+ * This event fires on both the logical client and logical server, for {@link ClientLevel} and {@link ServerLevel} respectively.
+ */
+ public static class Post extends LevelTickEvent {
+ @ApiStatus.Internal
+ public Post(BooleanSupplier haveTime, Level level) {
+ super(haveTime, level);
+ }
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/event/tick/LivingTickEvent.java b/src/main/java/net/neoforged/neoforge/event/tick/LivingTickEvent.java
new file mode 100644
index 0000000000..6fdc7cad1b
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/event/tick/LivingTickEvent.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.event.tick;
+
+import net.minecraft.world.entity.LivingEntity;
+import net.neoforged.bus.api.ICancellableEvent;
+import net.neoforged.neoforge.event.entity.living.LivingEvent;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Base class of the two player tick events.
+ *
+ * @see Pre
+ * @see Post
+ */
+public abstract class LivingTickEvent extends LivingEvent {
+ @ApiStatus.Internal
+ public LivingTickEvent(LivingEntity entity) {
+ super(entity);
+ }
+
+ /**
+ * {@link LivingTickEvent.Pre} is fired once per game tick, per entity, before the entity performs work for the current tick.
+ *
+ * This event fires on both the logical server and logical client.
+ */
+ public static class Pre extends LivingTickEvent implements ICancellableEvent {
+ public Pre(LivingEntity entity) {
+ super(entity);
+ }
+
+ /**
+ * Cancels this event, preventing the current tick from being executed for the entity.
+ *
+ * Additionally, if this event is canceled, then {@link LivingTickEvent.Post} will not be fired for the current tick.
+ */
+ @Override
+ public void setCanceled(boolean canceled) {
+ ICancellableEvent.super.setCanceled(canceled);
+ }
+ }
+
+ /**
+ * {@link LivingTickEvent.Post} is fired once per game tick, per entity, after the entity performs work for the current tick.
+ *
+ * If {@link LivingTickEvent.Pre} was canceled for the current tick, this event will not fire.
+ *
+ * This event fires on both the logical server and logical client.
+ */
+ public static class Post extends LivingTickEvent {
+ public Post(LivingEntity entity) {
+ super(entity);
+ }
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/event/tick/PlayerTickEvent.java b/src/main/java/net/neoforged/neoforge/event/tick/PlayerTickEvent.java
new file mode 100644
index 0000000000..f05e5eb0a0
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/event/tick/PlayerTickEvent.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.event.tick;
+
+import net.minecraft.world.entity.player.Player;
+import net.neoforged.neoforge.event.entity.player.PlayerEvent;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Base class of the two player tick events.
+ *
+ * @see Pre
+ * @see Post
+ */
+public abstract class PlayerTickEvent extends PlayerEvent {
+ protected PlayerTickEvent(Player player) {
+ super(player);
+ }
+
+ /**
+ * {@link PlayerTickEvent.Pre} is fired once per game tick, per player, before the player performs work for the current tick.
+ *
+ * This event will fire on both the logical server and logical client, for all subclasses of {@link Player} on their respective sides.
+ *
+ * As such, be sure to check {@link Level#isClientSide()} before performing any operations.
+ */
+ public static class Pre extends PlayerTickEvent {
+ @ApiStatus.Internal
+ public Pre(Player player) {
+ super(player);
+ }
+ }
+
+ /**
+ * {@link PlayerTickEvent.Post} is fired once per game tick, per player, after the player performs work for the current tick.
+ *
+ * This event will fire on both the logical server and logical client, for all subclasses of {@link Player} on their respective sides.
+ *
+ * As such, be sure to check {@link Level#isClientSide()} before performing any operations.
+ */
+ public static class Post extends PlayerTickEvent {
+ @ApiStatus.Internal
+ public Post(Player player) {
+ super(player);
+ }
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/event/tick/ServerTickEvent.java b/src/main/java/net/neoforged/neoforge/event/tick/ServerTickEvent.java
new file mode 100644
index 0000000000..ecaed07ffc
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/event/tick/ServerTickEvent.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.event.tick;
+
+import java.util.function.BooleanSupplier;
+import net.minecraft.server.MinecraftServer;
+import net.neoforged.bus.api.Event;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Base class of the two server tick events.
+ *
+ * @see Pre
+ * @see Post
+ */
+public abstract class ServerTickEvent extends Event {
+ private final BooleanSupplier hasTime;
+ private final MinecraftServer server;
+
+ protected ServerTickEvent(BooleanSupplier hasTime, MinecraftServer server) {
+ this.hasTime = hasTime;
+ this.server = server;
+ }
+
+ /**
+ * {@return true if the server has enough time to perform any
+ * additional tasks (usually IO related) during the current tick,
+ * otherwise false}
+ */
+ public boolean hasTime() {
+ return this.hasTime.getAsBoolean();
+ }
+
+ /**
+ * {@return the server instance}
+ */
+ public MinecraftServer getServer() {
+ return server;
+ }
+
+ /**
+ * {@link ServerTickEvent.Pre} is fired once per server tick, before the server performs work for the current tick.
+ *
+ * This event only fires on the logical server.
+ */
+ public static class Pre extends ServerTickEvent {
+ @ApiStatus.Internal
+ public Pre(BooleanSupplier haveTime, MinecraftServer server) {
+ super(haveTime, server);
+ }
+ }
+
+ /**
+ * {@link ServerTickEvent.Post} is fired once per server tick, after the server performs work for the current tick.
+ *
+ * This event only fires on the logical server.
+ */
+ public static class Post extends ServerTickEvent {
+ @ApiStatus.Internal
+ public Post(BooleanSupplier haveTime, MinecraftServer server) {
+ super(haveTime, server);
+ }
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/event/tick/package-info.java b/src/main/java/net/neoforged/neoforge/event/tick/package-info.java
new file mode 100644
index 0000000000..3d0ece4888
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/event/tick/package-info.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+@FieldsAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+@ParametersAreNonnullByDefault
+package net.neoforged.neoforge.event.tick;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import net.minecraft.FieldsAreNonnullByDefault;
+import net.minecraft.MethodsReturnNonnullByDefault;
diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/client/ClientTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/client/ClientTests.java
index e117d3ec74..290dadd9d6 100644
--- a/tests/src/main/java/net/neoforged/neoforge/debug/client/ClientTests.java
+++ b/tests/src/main/java/net/neoforged/neoforge/debug/client/ClientTests.java
@@ -24,9 +24,9 @@
import net.minecraft.world.phys.Vec3;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.neoforge.client.event.ClientChatEvent;
+import net.neoforged.neoforge.client.event.ClientTickEvent;
import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent;
import net.neoforged.neoforge.common.data.LanguageProvider;
-import net.neoforged.neoforge.event.TickEvent;
import net.neoforged.testframework.DynamicTest;
import net.neoforged.testframework.annotation.ForEachTest;
import net.neoforged.testframework.annotation.TestHolder;
@@ -63,8 +63,7 @@ static void keyMappingTest(final DynamicTest test) {
event.register(stickKey);
});
- test.eventListeners().forge().addListener((final TickEvent.ClientTickEvent event) -> {
- if (event.phase != TickEvent.Phase.START) return;
+ test.eventListeners().forge().addListener((ClientTickEvent.Pre event) -> {
if (stickKey.consumeClick()) {
Player player = Minecraft.getInstance().player;
if (player != null && player.getMainHandItem().is(Items.STICK)) {
diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/level/LevelTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/level/LevelTests.java
index cd1a005c47..0bfd2a8609 100644
--- a/tests/src/main/java/net/neoforged/neoforge/debug/level/LevelTests.java
+++ b/tests/src/main/java/net/neoforged/neoforge/debug/level/LevelTests.java
@@ -9,7 +9,7 @@
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.GameType;
-import net.neoforged.neoforge.event.entity.living.LivingEvent;
+import net.neoforged.neoforge.event.tick.LivingTickEvent;
import net.neoforged.testframework.DynamicTest;
import net.neoforged.testframework.annotation.ForEachTest;
import net.neoforged.testframework.annotation.TestHolder;
@@ -49,7 +49,7 @@ static void customGameRule(final DynamicTest test) {
final GameRules.Key
* This event fires on both the logical server and logical client.
*/
- public static class Pre extends LivingTickEvent implements ICancellableEvent {
- public Pre(LivingEntity entity) {
+ public static class Pre extends EntityTickEvent implements ICancellableEvent {
+ public Pre(Entity entity) {
super(entity);
}
/**
* Cancels this event, preventing the current tick from being executed for the entity.
*
- * Additionally, if this event is canceled, then {@link LivingTickEvent.Post} will not be fired for the current tick.
+ * Additionally, if this event is canceled, then {@link EntityTickEvent.Post} will not be fired for the current tick.
*/
@Override
public void setCanceled(boolean canceled) {
@@ -44,14 +45,14 @@ public void setCanceled(boolean canceled) {
}
/**
- * {@link LivingTickEvent.Post} is fired once per game tick, per entity, after the entity performs work for the current tick.
+ * {@link EntityTickEvent.Post} is fired once per game tick, per entity, after the entity performs work for the current tick.
*
- * If {@link LivingTickEvent.Pre} was canceled for the current tick, this event will not fire.
+ * If {@link EntityTickEvent.Pre} was canceled for the current tick, this event will not fire.
*
* This event fires on both the logical server and logical client.
*/
- public static class Post extends LivingTickEvent {
- public Post(LivingEntity entity) {
+ public static class Post extends EntityTickEvent {
+ public Post(Entity entity) {
super(entity);
}
}
diff --git a/src/main/java/net/neoforged/neoforge/event/tick/PlayerTickEvent.java b/src/main/java/net/neoforged/neoforge/event/tick/PlayerTickEvent.java
index f05e5eb0a0..a84cf29b5c 100644
--- a/src/main/java/net/neoforged/neoforge/event/tick/PlayerTickEvent.java
+++ b/src/main/java/net/neoforged/neoforge/event/tick/PlayerTickEvent.java
@@ -5,12 +5,23 @@
package net.neoforged.neoforge.event.tick;
+import org.jetbrains.annotations.ApiStatus;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.network.ServerGamePacketListenerImpl;
+import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.level.Level;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
-import org.jetbrains.annotations.ApiStatus;
/**
* Base class of the two player tick events.
+ *
+ * These events are separate from {@link LivingTickEvent} due to the semantics of player ticks.
+ * On the client, players tick from the usual {@link Entity#tick()} method, but on the server, they rely
+ * on {@link ServerPlayer#doTick()} which is called from {@link ServerGamePacketListenerImpl#tick()}.
+ *
+ * Use of these events should only be necessary if you rely on this specific timing.
*
* @see Pre
* @see Post
@@ -23,7 +34,7 @@ protected PlayerTickEvent(Player player) {
/**
* {@link PlayerTickEvent.Pre} is fired once per game tick, per player, before the player performs work for the current tick.
*
- * This event will fire on both the logical server and logical client, for all subclasses of {@link Player} on their respective sides.
+ * This event will fire on both the logical server and logical client, for subclasses of {@link Player} on their respective sides.
*
* As such, be sure to check {@link Level#isClientSide()} before performing any operations.
*/
@@ -37,7 +48,7 @@ public Pre(Player player) {
/**
* {@link PlayerTickEvent.Post} is fired once per game tick, per player, after the player performs work for the current tick.
*
- * This event will fire on both the logical server and logical client, for all subclasses of {@link Player} on their respective sides.
+ * This event will fire on both the logical server and logical client, for subclasses of {@link Player} on their respective sides.
*
* As such, be sure to check {@link Level#isClientSide()} before performing any operations.
*/
diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/level/LevelTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/level/LevelTests.java
index 0bfd2a8609..0310ba7cdb 100644
--- a/tests/src/main/java/net/neoforged/neoforge/debug/level/LevelTests.java
+++ b/tests/src/main/java/net/neoforged/neoforge/debug/level/LevelTests.java
@@ -9,7 +9,7 @@
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.GameType;
-import net.neoforged.neoforge.event.tick.LivingTickEvent;
+import net.neoforged.neoforge.event.tick.EntityTickEvent;
import net.neoforged.testframework.DynamicTest;
import net.neoforged.testframework.annotation.ForEachTest;
import net.neoforged.testframework.annotation.TestHolder;
@@ -49,7 +49,7 @@ static void customGameRule(final DynamicTest test) {
final GameRules.Key
diff --git a/src/main/java/net/neoforged/neoforge/client/event/RenderFrameEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RenderFrameEvent.java
index 59cbd54a6a..7f7ea7d287 100644
--- a/src/main/java/net/neoforged/neoforge/client/event/RenderFrameEvent.java
+++ b/src/main/java/net/neoforged/neoforge/client/event/RenderFrameEvent.java
@@ -13,8 +13,8 @@
*
* These events can be used to setup and teardown global render state that must persist for the current frame.
*
- * @see Pre
- * @see Post
+ * @see RenderFrameEvent.Pre
+ * @see RenderFrameEvent.Post
*/
public abstract class RenderFrameEvent extends Event {
protected final float partialTick;
diff --git a/src/main/java/net/neoforged/neoforge/event/tick/EntityTickEvent.java b/src/main/java/net/neoforged/neoforge/event/tick/EntityTickEvent.java
index d2af6aebf5..b70e452c1a 100644
--- a/src/main/java/net/neoforged/neoforge/event/tick/EntityTickEvent.java
+++ b/src/main/java/net/neoforged/neoforge/event/tick/EntityTickEvent.java
@@ -8,17 +8,15 @@
import net.minecraft.world.entity.Entity;
import net.neoforged.bus.api.ICancellableEvent;
import net.neoforged.neoforge.event.entity.EntityEvent;
-import org.jetbrains.annotations.ApiStatus;
/**
* Base class of the two entity tick events.
*
- * @see Pre
- * @see Post
+ * @see EntityTickEvent.Pre
+ * @see EntityTickEvent.Post
*/
public abstract class EntityTickEvent extends EntityEvent {
- @ApiStatus.Internal
- public EntityTickEvent(Entity entity) {
+ protected EntityTickEvent(Entity entity) {
super(entity);
}
diff --git a/src/main/java/net/neoforged/neoforge/event/tick/LevelTickEvent.java b/src/main/java/net/neoforged/neoforge/event/tick/LevelTickEvent.java
index d3f2af47a5..879ad503e0 100644
--- a/src/main/java/net/neoforged/neoforge/event/tick/LevelTickEvent.java
+++ b/src/main/java/net/neoforged/neoforge/event/tick/LevelTickEvent.java
@@ -10,13 +10,12 @@
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.neoforged.bus.api.Event;
-import org.jetbrains.annotations.ApiStatus;
/**
* Base class of the two level tick events.
*
- * @see Pre
- * @see Post
+ * @see LevelTickEvent.Pre
+ * @see LevelTickEvent.Post
*/
public abstract class LevelTickEvent extends Event {
private final BooleanSupplier hasTime;
@@ -50,7 +49,6 @@ public Level getLevel() {
* This event fires on both the logical client and logical server, for {@link ClientLevel} and {@link ServerLevel} respectively.
*/
public static class Pre extends LevelTickEvent {
- @ApiStatus.Internal
public Pre(BooleanSupplier haveTime, Level level) {
super(haveTime, level);
}
@@ -62,7 +60,6 @@ public Pre(BooleanSupplier haveTime, Level level) {
* This event fires on both the logical client and logical server, for {@link ClientLevel} and {@link ServerLevel} respectively.
*/
public static class Post extends LevelTickEvent {
- @ApiStatus.Internal
public Post(BooleanSupplier haveTime, Level level) {
super(haveTime, level);
}
diff --git a/src/main/java/net/neoforged/neoforge/event/tick/PlayerTickEvent.java b/src/main/java/net/neoforged/neoforge/event/tick/PlayerTickEvent.java
index 6f0a4072e3..148de6e6e1 100644
--- a/src/main/java/net/neoforged/neoforge/event/tick/PlayerTickEvent.java
+++ b/src/main/java/net/neoforged/neoforge/event/tick/PlayerTickEvent.java
@@ -11,7 +11,6 @@
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
-import org.jetbrains.annotations.ApiStatus;
/**
* Base class of the two player tick events.
@@ -22,8 +21,8 @@
*
* Use of these events should only be necessary if you rely on this specific timing.
*
- * @see Pre
- * @see Post
+ * @see PlayerTickEvent.Pre
+ * @see PlayerTickEvent.Post
*/
public abstract class PlayerTickEvent extends PlayerEvent {
protected PlayerTickEvent(Player player) {
@@ -38,7 +37,6 @@ protected PlayerTickEvent(Player player) {
* As such, be sure to check {@link Level#isClientSide()} before performing any operations.
*/
public static class Pre extends PlayerTickEvent {
- @ApiStatus.Internal
public Pre(Player player) {
super(player);
}
@@ -52,7 +50,6 @@ public Pre(Player player) {
* As such, be sure to check {@link Level#isClientSide()} before performing any operations.
*/
public static class Post extends PlayerTickEvent {
- @ApiStatus.Internal
public Post(Player player) {
super(player);
}
diff --git a/src/main/java/net/neoforged/neoforge/event/tick/ServerTickEvent.java b/src/main/java/net/neoforged/neoforge/event/tick/ServerTickEvent.java
index ecaed07ffc..9c48e8103c 100644
--- a/src/main/java/net/neoforged/neoforge/event/tick/ServerTickEvent.java
+++ b/src/main/java/net/neoforged/neoforge/event/tick/ServerTickEvent.java
@@ -8,13 +8,12 @@
import java.util.function.BooleanSupplier;
import net.minecraft.server.MinecraftServer;
import net.neoforged.bus.api.Event;
-import org.jetbrains.annotations.ApiStatus;
/**
* Base class of the two server tick events.
*
- * @see Pre
- * @see Post
+ * @see ServerTickEvent.Pre
+ * @see ServerTickEvent.Post
*/
public abstract class ServerTickEvent extends Event {
private final BooleanSupplier hasTime;
@@ -47,7 +46,6 @@ public MinecraftServer getServer() {
* This event only fires on the logical server.
*/
public static class Pre extends ServerTickEvent {
- @ApiStatus.Internal
public Pre(BooleanSupplier haveTime, MinecraftServer server) {
super(haveTime, server);
}
@@ -59,7 +57,6 @@ public Pre(BooleanSupplier haveTime, MinecraftServer server) {
* This event only fires on the logical server.
*/
public static class Post extends ServerTickEvent {
- @ApiStatus.Internal
public Post(BooleanSupplier haveTime, MinecraftServer server) {
super(haveTime, server);
}
From 7dc32e7b3961b3c4ae1224486bd1f49f02646863 Mon Sep 17 00:00:00 2001
From: Brennan Ward <3682588+Shadows-of-Fire@users.noreply.github.com>
Date: Sat, 27 Apr 2024 14:48:11 -0700
Subject: [PATCH 5/5] link renderframe/clienttick via doc
---
.../net/neoforged/neoforge/client/event/ClientTickEvent.java | 2 ++
.../net/neoforged/neoforge/client/event/RenderFrameEvent.java | 2 ++
2 files changed, 4 insertions(+)
diff --git a/src/main/java/net/neoforged/neoforge/client/event/ClientTickEvent.java b/src/main/java/net/neoforged/neoforge/client/event/ClientTickEvent.java
index e448dbf353..78c33948c0 100644
--- a/src/main/java/net/neoforged/neoforge/client/event/ClientTickEvent.java
+++ b/src/main/java/net/neoforged/neoforge/client/event/ClientTickEvent.java
@@ -9,6 +9,8 @@
/**
* Base class of the two client tick events.
+ *
+ * For the event that fires once per frame (instead of per tick), see {@link RenderFrameEvent}.
*
* @see ClientTickEvent.Pre
* @see ClientTickEvent.Post
diff --git a/src/main/java/net/neoforged/neoforge/client/event/RenderFrameEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RenderFrameEvent.java
index 7f7ea7d287..f5451cb8db 100644
--- a/src/main/java/net/neoforged/neoforge/client/event/RenderFrameEvent.java
+++ b/src/main/java/net/neoforged/neoforge/client/event/RenderFrameEvent.java
@@ -12,6 +12,8 @@
* Base class of the two render frame events.
*
* These events can be used to setup and teardown global render state that must persist for the current frame.
+ *
+ * For the event that fires once per client tick (instead of per frame), see {@link ClientTickEvent}.
*
* @see RenderFrameEvent.Pre
* @see RenderFrameEvent.Post
- *
- * This event is fired via the {@link CommonHooks#onLivingTick(LivingEntity)}.
- *
- * This event is {@link ICancellableEvent}.
- * If this event is canceled, the Entity does not update.
- *
- * This event does not have a result. {@link HasResult}
- *
- * This event is fired on the {@link NeoForge#EVENT_BUS}.
- **/
- public static class LivingTickEvent extends LivingEvent implements ICancellableEvent {
- public LivingTickEvent(LivingEntity e) {
- super(e);
- }
- }
-
/**
* LivingJumpEvent is fired when an Entity jumps.
* This event is fired whenever an Entity jumps in
diff --git a/src/main/java/net/neoforged/neoforge/event/tick/LevelTickEvent.java b/src/main/java/net/neoforged/neoforge/event/tick/LevelTickEvent.java
new file mode 100644
index 0000000000..d3f2af47a5
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/event/tick/LevelTickEvent.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.event.tick;
+
+import java.util.function.BooleanSupplier;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.level.Level;
+import net.neoforged.bus.api.Event;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Base class of the two level tick events.
+ *
+ * @see Pre
+ * @see Post
+ */
+public abstract class LevelTickEvent extends Event {
+ private final BooleanSupplier hasTime;
+ private final Level level;
+
+ protected LevelTickEvent(BooleanSupplier hasTime, Level level) {
+ this.hasTime = hasTime;
+ this.level = level;
+ }
+
+ /**
+ * On the server, returns true if the server has enough time to perform any
+ * additional tasks (usually IO related) during the current tick.
+ *
+ * On the client, always returns true.
+ */
+ public boolean hasTime() {
+ return this.hasTime.getAsBoolean();
+ }
+
+ /**
+ * {@return the level being ticked}
+ */
+ public Level getLevel() {
+ return level;
+ }
+
+ /**
+ * {@link LevelTickEvent.Pre} is fired once per game tick, per level, before the level performs work for the current tick.
+ *