diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/LifecycleEvents.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/LifecycleEvent.java
similarity index 95%
rename from lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/LifecycleEvents.java
rename to lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/LifecycleEvent.java
index fa930d69..11cd195d 100644
--- a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/LifecycleEvents.java
+++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/LifecycleEvent.java
@@ -4,7 +4,7 @@
import net.minecraft.registry.DynamicRegistryManager;
-public final class LifecycleEvents {
+public class LifecycleEvent {
/**
* Called when tags are loaded or updated.
diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerBlockEntityEvent.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerBlockEntityEvent.java
new file mode 100644
index 00000000..508fbfe7
--- /dev/null
+++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerBlockEntityEvent.java
@@ -0,0 +1,42 @@
+package band.kessoku.lib.events.lifecycle.api;
+
+import band.kessoku.lib.event.api.Event;
+
+import net.minecraft.block.entity.BlockEntity;
+import net.minecraft.server.world.ServerWorld;
+
+public class ServerBlockEntityEvent {
+
+ /**
+ * Called when an BlockEntity is loaded into a ServerWorld.
+ *
+ *
When this is event is called, the block entity is already in the world.
+ * However, its data might not be loaded yet, so don't rely on it.
+ */
+ public static final Event LOADED = Event.of(loadeds -> (blockEntity, world) -> {
+ for (Loaded loaded : loadeds) {
+ loaded.onLoaded(blockEntity, world);
+ }
+ });
+
+ /**
+ * Called when an BlockEntity is about to be unloaded from a ServerWorld.
+ *
+ * When this event is called, the block entity is still present on the world.
+ */
+ public static final Event UNLOADED = Event.of(unloadeds -> (blockEntity, world) -> {
+ for (Unloaded unloaded : unloadeds) {
+ unloaded.onUnloaded(blockEntity, world);
+ }
+ });
+
+ @FunctionalInterface
+ public interface Loaded {
+ void onLoaded(BlockEntity blockEntity, ServerWorld world);
+ }
+
+ @FunctionalInterface
+ public interface Unloaded {
+ void onUnloaded(BlockEntity blockEntity, ServerWorld world);
+ }
+}
diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerChunkEvent.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerChunkEvent.java
new file mode 100644
index 00000000..1ce25fe8
--- /dev/null
+++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerChunkEvent.java
@@ -0,0 +1,41 @@
+package band.kessoku.lib.events.lifecycle.api;
+
+import band.kessoku.lib.event.api.Event;
+
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.world.chunk.WorldChunk;
+
+public class ServerChunkEvent {
+
+ /**
+ * Called when a chunk is loaded into a ServerWorld.
+ *
+ * When this event is called, the chunk is already in the world.
+ */
+ public static final Event LOADED = Event.of(loadeds -> (serverWorld, chunk) -> {
+ for (Loaded loaded : loadeds) {
+ loaded.onChunkLoaded(serverWorld, chunk);
+ }
+ });
+
+ /**
+ * Called when a chunk is unloaded from a ServerWorld.
+ *
+ * When this event is called, the chunk is still present in the world.
+ */
+ public static final Event UNLOADED = Event.of(unloadeds -> (serverWorld, chunk) -> {
+ for (Unloaded unloaded : unloadeds) {
+ unloaded.onChunkUnloaded(serverWorld, chunk);
+ }
+ });
+
+ @FunctionalInterface
+ public interface Loaded {
+ void onChunkLoaded(ServerWorld world, WorldChunk chunk);
+ }
+
+ @FunctionalInterface
+ public interface Unloaded {
+ void onChunkUnloaded(ServerWorld world, WorldChunk chunk);
+ }
+}
diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerEntityEvent.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerEntityEvent.java
new file mode 100644
index 00000000..a3c56338
--- /dev/null
+++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerEntityEvent.java
@@ -0,0 +1,60 @@
+package band.kessoku.lib.events.lifecycle.api;
+
+import band.kessoku.lib.event.api.Event;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.server.world.ServerWorld;
+
+public class ServerEntityEvent {
+ /**
+ * Called when an Entity is loaded into a ServerWorld.
+ *
+ * When this event is called, the entity is already in the world.
+ */
+ public static final Event LOADED = Event.of(loadeds -> (entity, world) -> {
+ for (Loaded loaded : loadeds) {
+ loaded.onLoaded(entity, world);
+ }
+ });
+
+ /**
+ * Called when an Entity is unloaded from a ServerWorld.
+ *
+ * This event is called before the entity is removed from the world.
+ */
+ public static final Event UNLOADED = Event.of(unloadeds -> (entity, world) -> {
+ for (Unloaded unloaded : unloadeds) {
+ unloaded.onUnloaded(entity, world);
+ }
+ });
+
+ /**
+ * Called during {@link LivingEntity#tick()} if the Entity's equipment has been changed or mutated.
+ *
+ * This event is also called when the entity joins the world.
+ * A change in equipment is determined by {@link ItemStack#areEqual(ItemStack, ItemStack)}.
+ */
+ public static final Event EQUIPMENT_CHANGED = Event.of(equipmentChangeds -> (livingEntity, equipmentSlot, previous, next) -> {
+ for (EquipmentChanged equipmentChanged : equipmentChangeds) {
+ equipmentChanged.onChanged(livingEntity, equipmentSlot, previous, next);
+ }
+ });
+
+ @FunctionalInterface
+ public interface Loaded {
+ void onLoaded(Entity entity, ServerWorld world);
+ }
+
+ @FunctionalInterface
+ public interface Unloaded {
+ void onUnloaded(Entity entity, ServerWorld world);
+ }
+
+ @FunctionalInterface
+ public interface EquipmentChanged {
+ void onChanged(LivingEntity livingEntity, EquipmentSlot equipmentSlot, ItemStack previousStack, ItemStack currentStack);
+ }
+}
diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/server/ServerLifecycleEvents.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerLifecycleEvent.java
similarity index 98%
rename from lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/server/ServerLifecycleEvents.java
rename to lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerLifecycleEvent.java
index 086d878b..8efc8311 100644
--- a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/server/ServerLifecycleEvents.java
+++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerLifecycleEvent.java
@@ -1,4 +1,4 @@
-package band.kessoku.lib.events.lifecycle.api.server;
+package band.kessoku.lib.events.lifecycle.api;
import band.kessoku.lib.event.api.Event;
@@ -6,7 +6,7 @@
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
-public class ServerLifecycleEvents {
+public class ServerLifecycleEvent {
/**
* Called when a Minecraft server is starting.
diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/server/ServerTickEvents.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerTickEvent.java
similarity index 95%
rename from lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/server/ServerTickEvents.java
rename to lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerTickEvent.java
index d73760a6..99998118 100644
--- a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/server/ServerTickEvents.java
+++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerTickEvent.java
@@ -1,11 +1,11 @@
-package band.kessoku.lib.events.lifecycle.api.server;
+package band.kessoku.lib.events.lifecycle.api;
import band.kessoku.lib.event.api.Event;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld;
-public final class ServerTickEvents {
+public class ServerTickEvent {
/**
* Called at the start of the server tick.
diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/server/ServerWorldEvents.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerWorldEvent.java
similarity index 90%
rename from lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/server/ServerWorldEvents.java
rename to lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerWorldEvent.java
index 4dd75be2..1eff65ea 100644
--- a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/server/ServerWorldEvents.java
+++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/ServerWorldEvent.java
@@ -1,11 +1,11 @@
-package band.kessoku.lib.events.lifecycle.api.server;
+package band.kessoku.lib.events.lifecycle.api;
import band.kessoku.lib.event.api.Event;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld;
-public class ServerWorldEvents {
+public class ServerWorldEvent {
/**
* Called just after a world is loaded by a Minecraft server.
@@ -21,7 +21,7 @@ public class ServerWorldEvents {
/**
* Called before a world is unloaded by a Minecraft server.
*
- * This typically occurs after a server has {@link ServerLifecycleEvents#STOPPING started shutting down}.
+ *
This typically occurs after a server has {@link ServerLifecycleEvent#STOPPING started shutting down}.
* Mods which allow dynamic world (un)registration should call this event so mods can let go of world handles when a world is removed.
*/
public static final Event UNLOADED = Event.of(unloadeds -> (server, world) -> {
diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientLifecycleEvent.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientLifecycleEvent.java
new file mode 100644
index 00000000..be801cb4
--- /dev/null
+++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientLifecycleEvent.java
@@ -0,0 +1,43 @@
+package band.kessoku.lib.events.lifecycle.api.client;
+
+import band.kessoku.lib.event.api.Event;
+
+import net.minecraft.client.MinecraftClient;
+
+public class ClientLifecycleEvent {
+
+ /**
+ * Called when Minecraft has started and it's client about to tick for the first time.
+ *
+ * This occurs while the splash screen is displayed.
+ */
+ public static final Event STARTED = Event.of(starteds -> client -> {
+ for (Client.Started started : starteds) {
+ started.onClientStarted(client);
+ }
+ });
+
+ /**
+ * Called when Minecraft's client begins to stop.
+ * This is caused by quitting while in game, or closing the game window.
+ *
+ * This will be called before the integrated server is stopped if it is running.
+ */
+ public static final Event STOPPING = Event.of(stoppings -> client -> {
+ for (Client.Stopping stopping : stoppings) {
+ stopping.onClientStopping(client);
+ }
+ });
+
+ public interface Client {
+ @FunctionalInterface
+ interface Started {
+ void onClientStarted(MinecraftClient client);
+ }
+
+ @FunctionalInterface
+ interface Stopping {
+ void onClientStopping(MinecraftClient client);
+ }
+ }
+}
diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientTickEvent.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientTickEvent.java
new file mode 100644
index 00000000..1045c0bd
--- /dev/null
+++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientTickEvent.java
@@ -0,0 +1,73 @@
+package band.kessoku.lib.events.lifecycle.api.client;
+
+import band.kessoku.lib.event.api.Event;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.world.ClientWorld;
+
+public class ClientTickEvent {
+
+ /**
+ * Called at the start of the client tick.
+ */
+ public static final Event START_CLIENT_TICK = Event.of(starts -> client -> {
+ for (ClientTick.Start start : starts) {
+ start.onStartTick(client);
+ }
+ });
+
+ /**
+ * Called at the end of the client tick.
+ */
+ public static final Event END_CLIENT_TICK = Event.of(ends -> client -> {
+ for (ClientTick.End end : ends) {
+ end.onEndTick(client);
+ }
+ });
+
+ /**
+ * Called at the start of a ClientWorld's tick.
+ */
+ public static final Event START_WORLD_TICK = Event.of(starts -> world -> {
+ for (WorldTick.Start start : starts) {
+ start.onStartTick(world);
+ }
+ });
+
+ /**
+ * Called at the end of a ClientWorld's tick.
+ *
+ * End of world tick may be used to start async computations for the next tick.
+ */
+ public static final Event END_WORLD_TICK = Event.of(ends -> world -> {
+ for (WorldTick.End end : ends) {
+ end.onEndTick(world);
+ }
+ });
+
+ public interface ClientTick {
+ @FunctionalInterface
+ interface Start {
+ void onStartTick(MinecraftClient client);
+ }
+
+ @FunctionalInterface
+ interface End {
+ void onEndTick(MinecraftClient client);
+ }
+ }
+
+ public interface WorldTick {
+ @FunctionalInterface
+ interface Start {
+ void onStartTick(ClientWorld world);
+ }
+
+ @FunctionalInterface
+ interface End {
+ void onEndTick(ClientWorld world);
+ }
+ }
+
+
+}
diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImpl.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImpl.java
new file mode 100644
index 00000000..5061cd2f
--- /dev/null
+++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImpl.java
@@ -0,0 +1,48 @@
+package band.kessoku.lib.events.lifecycle.impl;
+
+import band.kessoku.lib.events.lifecycle.api.ServerBlockEntityEvent;
+import band.kessoku.lib.events.lifecycle.api.ServerChunkEvent;
+import band.kessoku.lib.events.lifecycle.api.ServerEntityEvent;
+import band.kessoku.lib.events.lifecycle.api.ServerWorldEvent;
+
+import net.minecraft.block.entity.BlockEntity;
+import net.minecraft.entity.Entity;
+import net.minecraft.world.chunk.WorldChunk;
+
+public class KessokuLifecycleEventsImpl {
+ public static void clientInit() {
+
+ }
+
+ public static void init() {
+ // Part of impl for block entity events
+ ServerChunkEvent.LOADED.register((world, chunk) -> {
+ ((LoadedChunksCache) world).kessoku$markLoaded(chunk);
+ });
+
+ ServerChunkEvent.UNLOADED.register((world, chunk) -> {
+ ((LoadedChunksCache) world).kessoku$markUnloaded(chunk);
+ });
+
+ // Fire block entity unload events.
+ // This handles the edge case where going through a portal will cause block entities to unload without warning.
+ ServerChunkEvent.UNLOADED.register((world, chunk) -> {
+ for (BlockEntity blockEntity : chunk.getBlockEntities().values()) {
+ ServerBlockEntityEvent.UNLOADED.invoker().onUnloaded(blockEntity, world);
+ }
+ });
+
+ // We use the world unload event so worlds that are dynamically hot(un)loaded get (block) entity unload events fired when shut down.
+ ServerWorldEvent.UNLOADED.register((server, world) -> {
+ for (WorldChunk chunk : ((LoadedChunksCache) world).kessoku$getLoadedChunks()) {
+ for (BlockEntity blockEntity : chunk.getBlockEntities().values()) {
+ ServerBlockEntityEvent.UNLOADED.invoker().onUnloaded(blockEntity, world);
+ }
+ }
+
+ for (Entity entity : world.iterateEntities()) {
+ ServerEntityEvent.UNLOADED.invoker().onUnloaded(entity, world);
+ }
+ });
+ }
+}
diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/impl/LoadedChunksCache.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/impl/LoadedChunksCache.java
new file mode 100644
index 00000000..85bc749f
--- /dev/null
+++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/impl/LoadedChunksCache.java
@@ -0,0 +1,22 @@
+package band.kessoku.lib.events.lifecycle.impl;
+
+import net.minecraft.world.chunk.WorldChunk;
+
+import java.util.Set;
+
+/**
+ * A simple marker interface which holds references to chunks which block entities may be loaded or unloaded from.
+ */
+public interface LoadedChunksCache {
+ Set kessoku$getLoadedChunks();
+
+ /**
+ * Marks a chunk as loaded in a world.
+ */
+ void kessoku$markLoaded(WorldChunk chunk);
+
+ /**
+ * Marks a chunk as unloaded in a world.
+ */
+ void kessoku$markUnloaded(WorldChunk chunk);
+}
diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/mixin/WorldMixin.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/mixin/WorldMixin.java
new file mode 100644
index 00000000..6d8f27c4
--- /dev/null
+++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/mixin/WorldMixin.java
@@ -0,0 +1,32 @@
+package band.kessoku.lib.events.lifecycle.mixin;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import net.minecraft.world.World;
+import net.minecraft.world.chunk.WorldChunk;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+
+import band.kessoku.lib.events.lifecycle.impl.LoadedChunksCache;
+
+@Mixin(World.class)
+public abstract class WorldMixin implements LoadedChunksCache {
+ @Unique
+ private final Set kessoku$loadedChunks = new HashSet<>();
+
+ @Override
+ public Set kessoku$getLoadedChunks() {
+ return this.kessoku$loadedChunks;
+ }
+
+ @Override
+ public void kessoku$markLoaded(WorldChunk chunk) {
+ this.kessoku$loadedChunks.add(chunk);
+ }
+
+ @Override
+ public void kessoku$markUnloaded(WorldChunk chunk) {
+ this.kessoku$loadedChunks.remove(chunk);
+ }
+}
diff --git a/lifecycle-events-common/src/main/resources/kessoku-lifecycle-events.mixins.json b/lifecycle-events-common/src/main/resources/kessoku-lifecycle-events.mixins.json
new file mode 100644
index 00000000..7c1fc2a6
--- /dev/null
+++ b/lifecycle-events-common/src/main/resources/kessoku-lifecycle-events.mixins.json
@@ -0,0 +1,12 @@
+{
+ "required": true,
+ "package": "band.kessoku.lib.events.lifecycle.mixin",
+ "compatibilityLevel": "JAVA_17",
+ "mixins": [
+ "WorldMixin"
+ ],
+ "injectors": {
+ "defaultRequire": 1,
+ "maxShiftBy": 3
+ }
+}
\ No newline at end of file
diff --git a/lifecycle-events-fabric/src/main/java/band/kessoku/lib/events/lifecycle/KessokuLifecycleEventsEntrypoint.java b/lifecycle-events-fabric/src/main/java/band/kessoku/lib/events/lifecycle/KessokuLifecycleEventsEntrypoint.java
index 09f5f229..3f0c3b40 100644
--- a/lifecycle-events-fabric/src/main/java/band/kessoku/lib/events/lifecycle/KessokuLifecycleEventsEntrypoint.java
+++ b/lifecycle-events-fabric/src/main/java/band/kessoku/lib/events/lifecycle/KessokuLifecycleEventsEntrypoint.java
@@ -1,12 +1,18 @@
package band.kessoku.lib.events.lifecycle;
-import band.kessoku.lib.events.lifecycle.impl.KessokuLifecycleEventsImpl;
+import band.kessoku.lib.events.lifecycle.impl.KessokuLifecycleEventsImplFabric;
+import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.api.ModInitializer;
-public class KessokuLifecycleEventsEntrypoint implements ModInitializer {
+public class KessokuLifecycleEventsEntrypoint implements ModInitializer, ClientModInitializer {
@Override
public void onInitialize() {
- KessokuLifecycleEventsImpl.registerCommonEvents();
+ KessokuLifecycleEventsImplFabric.registerCommonEvents();
+ }
+
+ @Override
+ public void onInitializeClient() {
+ KessokuLifecycleEventsImplFabric.registerClientEvents();
}
}
diff --git a/lifecycle-events-fabric/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImpl.java b/lifecycle-events-fabric/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImpl.java
deleted file mode 100644
index 3411541f..00000000
--- a/lifecycle-events-fabric/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImpl.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package band.kessoku.lib.events.lifecycle.impl;
-
-import band.kessoku.lib.events.lifecycle.api.LifecycleEvents;
-
-import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents;
-import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
-import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
-import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents;
-
-public class KessokuLifecycleEventsImpl {
- public static void registerCommonEvents() {
- CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> LifecycleEvents.TAG_LOADED.invoker().onTagsLoaded(registries, client));
-
- ServerLifecycleEvents.SERVER_STARTING.register(server -> band.kessoku.lib.events.lifecycle.api.server.ServerLifecycleEvents.STARTING.invoker().onServerStarting(server));
- ServerLifecycleEvents.SERVER_STARTED.register(server -> band.kessoku.lib.events.lifecycle.api.server.ServerLifecycleEvents.STARTED.invoker().onServerStarted(server));
- ServerLifecycleEvents.SERVER_STOPPING.register(server -> band.kessoku.lib.events.lifecycle.api.server.ServerLifecycleEvents.STOPPING.invoker().onServerStopping(server));
- ServerLifecycleEvents.SERVER_STOPPED.register(server -> band.kessoku.lib.events.lifecycle.api.server.ServerLifecycleEvents.STOPPED.invoker().onServerStopped(server));
-
- ServerTickEvents.START_SERVER_TICK.register(server -> band.kessoku.lib.events.lifecycle.api.server.ServerTickEvents.START_SERVER_TICK.invoker().onStartTick(server));
- ServerTickEvents.END_SERVER_TICK.register(server -> band.kessoku.lib.events.lifecycle.api.server.ServerTickEvents.END_SERVER_TICK.invoker().onEndTick(server));
- ServerTickEvents.START_WORLD_TICK.register(world -> band.kessoku.lib.events.lifecycle.api.server.ServerTickEvents.START_WORLD_TICK.invoker().onStartTick(world));
- ServerTickEvents.END_WORLD_TICK.register(world -> band.kessoku.lib.events.lifecycle.api.server.ServerTickEvents.END_WORLD_TICK.invoker().onEndTick(world));
-
- ServerWorldEvents.LOAD.register((server, world) -> band.kessoku.lib.events.lifecycle.api.server.ServerWorldEvents.LOADED.invoker().onWorldLoaded(server, world));
- ServerWorldEvents.UNLOAD.register((server, world) -> band.kessoku.lib.events.lifecycle.api.server.ServerWorldEvents.UNLOADED.invoker().onWorldUnloaded(server, world));
-
- ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS.register((player, joined) -> band.kessoku.lib.events.lifecycle.api.server.ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS.invoker().onSyncDataPackContents(player, joined));
- }
-}
diff --git a/lifecycle-events-fabric/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImplFabric.java b/lifecycle-events-fabric/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImplFabric.java
new file mode 100644
index 00000000..1adb4a96
--- /dev/null
+++ b/lifecycle-events-fabric/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImplFabric.java
@@ -0,0 +1,59 @@
+package band.kessoku.lib.events.lifecycle.impl;
+
+import band.kessoku.lib.events.lifecycle.api.*;
+import band.kessoku.lib.events.lifecycle.api.client.ClientLifecycleEvent;
+import band.kessoku.lib.events.lifecycle.api.client.ClientTickEvent;
+
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.event.lifecycle.v1.*;
+
+public class KessokuLifecycleEventsImplFabric {
+ public static void registerClientEvents() {
+ KessokuLifecycleEventsImpl.clientInit();
+
+ ClientLifecycleEvents.CLIENT_STARTED.register(client -> ClientLifecycleEvent.STARTED.invoker().onClientStarted(client));
+ ClientLifecycleEvents.CLIENT_STOPPING.register(client -> ClientLifecycleEvent.STOPPING.invoker().onClientStopping(client));
+
+ ClientTickEvents.START_CLIENT_TICK.register(client -> ClientTickEvent.START_CLIENT_TICK.invoker().onStartTick(client));
+ ClientTickEvents.END_CLIENT_TICK.register(client -> ClientTickEvent.END_CLIENT_TICK.invoker().onEndTick(client));
+ ClientTickEvents.START_WORLD_TICK.register(world -> ClientTickEvent.START_WORLD_TICK.invoker().onStartTick(world));
+ ClientTickEvents.END_WORLD_TICK.register(world -> ClientTickEvent.END_WORLD_TICK.invoker().onEndTick(world));
+ }
+
+ public static void registerCommonEvents() {
+ KessokuLifecycleEventsImpl.init();
+
+ CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> LifecycleEvent.TAG_LOADED.invoker().onTagsLoaded(registries, client));
+
+ ServerLifecycleEvents.SERVER_STARTING.register(server -> ServerLifecycleEvent.STARTING.invoker().onServerStarting(server));
+ ServerLifecycleEvents.SERVER_STARTED.register(server -> ServerLifecycleEvent.STARTED.invoker().onServerStarted(server));
+ ServerLifecycleEvents.SERVER_STOPPING.register(server -> ServerLifecycleEvent.STOPPING.invoker().onServerStopping(server));
+ ServerLifecycleEvents.SERVER_STOPPED.register(server -> ServerLifecycleEvent.STOPPED.invoker().onServerStopped(server));
+
+ ServerTickEvents.START_SERVER_TICK.register(server -> ServerTickEvent.START_SERVER_TICK.invoker().onStartTick(server));
+ ServerTickEvents.END_SERVER_TICK.register(server -> ServerTickEvent.END_SERVER_TICK.invoker().onEndTick(server));
+ ServerTickEvents.START_WORLD_TICK.register(world -> ServerTickEvent.START_WORLD_TICK.invoker().onStartTick(world));
+ ServerTickEvents.END_WORLD_TICK.register(world -> ServerTickEvent.END_WORLD_TICK.invoker().onEndTick(world));
+
+ ServerWorldEvents.LOAD.register((server, world) -> ServerWorldEvent.LOADED.invoker().onWorldLoaded(server, world));
+ ServerWorldEvents.UNLOAD.register((server, world) -> ServerWorldEvent.UNLOADED.invoker().onWorldUnloaded(server, world));
+
+ ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS.register((player, joined) -> ServerLifecycleEvent.SYNC_DATA_PACK_CONTENTS.invoker().onSyncDataPackContents(player, joined));
+ ServerLifecycleEvents.START_DATA_PACK_RELOAD.register((server, resourceManager) -> ServerLifecycleEvent.START_DATA_PACK_RELOAD.invoker().startDataPackReload(server, resourceManager));
+ ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, success) -> ServerLifecycleEvent.END_DATA_PACK_RELOAD.invoker().endDataPackReload(server, resourceManager, success));
+
+ ServerLifecycleEvents.BEFORE_SAVE.register((server, flush, force) -> ServerLifecycleEvent.BEFORE_SAVE.invoker().onBeforeSaveData(server, flush, force));
+ ServerLifecycleEvents.AFTER_SAVE.register((server, flush, force) -> ServerLifecycleEvent.AFTER_SAVE.invoker().onAfterSaveData(server, flush, force));
+
+ ServerEntityEvents.ENTITY_LOAD.register((entity, world) -> ServerEntityEvent.LOADED.invoker().onLoaded(entity, world));
+ ServerEntityEvents.ENTITY_UNLOAD.register((entity, world) -> ServerEntityEvent.UNLOADED.invoker().onUnloaded(entity, world));
+ ServerEntityEvents.EQUIPMENT_CHANGE.register((livingEntity, equipmentSlot, previousStack, currentStack) -> ServerEntityEvent.EQUIPMENT_CHANGED.invoker().onChanged(livingEntity, equipmentSlot, previousStack, currentStack));
+
+ ServerChunkEvents.CHUNK_LOAD.register((world, chunk) -> ServerChunkEvent.LOADED.invoker().onChunkLoaded(world, chunk));
+ ServerChunkEvents.CHUNK_UNLOAD.register((world, chunk) -> ServerChunkEvent.UNLOADED.invoker().onChunkUnloaded(world, chunk));
+
+ ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.register((blockEntity, world) -> ServerBlockEntityEvent.LOADED.invoker().onLoaded(blockEntity, world));
+ ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register((blockEntity, world) -> ServerBlockEntityEvent.UNLOADED.invoker().onUnloaded(blockEntity, world));
+ }
+}
diff --git a/lifecycle-events-fabric/src/main/resources/fabric.mod.json b/lifecycle-events-fabric/src/main/resources/fabric.mod.json
index 3f4d4280..babb0eef 100644
--- a/lifecycle-events-fabric/src/main/resources/fabric.mod.json
+++ b/lifecycle-events-fabric/src/main/resources/fabric.mod.json
@@ -2,8 +2,8 @@
"schemaVersion": 1,
"id": "kessoku_lifecycle_events",
"version": "${version}",
- "name": "Kessoku Command API",
- "description": "About command registries.",
+ "name": "Kessoku Lifecycle Events",
+ "description": "Events for the game's lifecycle.",
"authors": [
"Kessoku Tea Time"
],
@@ -15,12 +15,23 @@
"license": "LGPL-3.0-only",
"icon": "icon.png",
"environment": "*",
+ "entrypoints": {
+ "main": [
+ "band.kessoku.lib.events.lifecycle.KessokuLifecycleEventsEntrypoint::onInitialize"
+ ],
+ "client": [
+ "band.kessoku.lib.events.lifecycle.KessokuLifecycleEventsEntrypoint::onInitializeClient"
+ ]
+ },
"depends": {
"fabricloader": ">=0.16.0",
"minecraft": "1.21",
"java": ">=21",
"fabric-api": "*"
},
+ "mixins": [
+ "kessoku-lifecycle-events.mixins.json"
+ ],
"custom": {
"modmenu": {
"badges": [
diff --git a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/KessokuLifecycleEventsEntrypoint.java b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/KessokuLifecycleEventsEntrypoint.java
index 7fee7619..d430d856 100644
--- a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/KessokuLifecycleEventsEntrypoint.java
+++ b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/KessokuLifecycleEventsEntrypoint.java
@@ -1,10 +1,11 @@
package band.kessoku.lib.events.lifecycle;
-import band.kessoku.lib.events.lifecycle.impl.KessokuLifecycleEventsImpl;
+import band.kessoku.lib.events.lifecycle.impl.KessokuLifecycleEventsImplNeo;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;
+import net.neoforged.fml.loading.FMLLoader;
import net.neoforged.neoforge.common.NeoForge;
@Mod(KessokuLifecycleEvents.MOD_ID)
@@ -12,6 +13,10 @@ public class KessokuLifecycleEventsEntrypoint {
public KessokuLifecycleEventsEntrypoint(IEventBus modEventBus, ModContainer modContainer) {
var forgeEventBus = NeoForge.EVENT_BUS;
- KessokuLifecycleEventsImpl.registerCommonEvents(modEventBus, forgeEventBus);
+ KessokuLifecycleEventsImplNeo.registerCommonEvents(modEventBus, forgeEventBus);
+
+ if (FMLLoader.getDist().isClient()) {
+ KessokuLifecycleEventsImplNeo.registerClientEvents(modEventBus, forgeEventBus);
+ }
}
}
diff --git a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImpl.java b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImpl.java
deleted file mode 100644
index 69b4010c..00000000
--- a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImpl.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package band.kessoku.lib.events.lifecycle.impl;
-
-import band.kessoku.lib.event.util.NeoEventUtils;
-import band.kessoku.lib.events.lifecycle.api.LifecycleEvents;
-import band.kessoku.lib.events.lifecycle.api.server.ServerLifecycleEvents;
-import band.kessoku.lib.events.lifecycle.api.server.ServerTickEvents;
-import band.kessoku.lib.events.lifecycle.api.server.ServerWorldEvents;
-
-import net.minecraft.server.network.ServerPlayerEntity;
-import net.minecraft.server.world.ServerWorld;
-
-import net.neoforged.bus.api.IEventBus;
-import net.neoforged.neoforge.event.OnDatapackSyncEvent;
-import net.neoforged.neoforge.event.TagsUpdatedEvent;
-import net.neoforged.neoforge.event.level.LevelEvent;
-import net.neoforged.neoforge.event.server.ServerStartedEvent;
-import net.neoforged.neoforge.event.server.ServerStartingEvent;
-import net.neoforged.neoforge.event.server.ServerStoppedEvent;
-import net.neoforged.neoforge.event.server.ServerStoppingEvent;
-import net.neoforged.neoforge.event.tick.LevelTickEvent;
-import net.neoforged.neoforge.event.tick.ServerTickEvent;
-
-public class KessokuLifecycleEventsImpl {
- public static void registerClientEvents(IEventBus modEventBus, IEventBus forgeEventBus) {
-
- }
-
- public static void registerCommonEvents(IEventBus modEventBus, IEventBus forgeEventBus) {
- NeoEventUtils.registerEvent(forgeEventBus, TagsUpdatedEvent.class, event -> {
- LifecycleEvents.TAG_LOADED.invoker().onTagsLoaded(event.getRegistryAccess(), event.getUpdateCause() == TagsUpdatedEvent.UpdateCause.CLIENT_PACKET_RECEIVED);
- });
-
- NeoEventUtils.registerEvent(forgeEventBus, ServerStartingEvent.class, event -> {
- ServerLifecycleEvents.STARTING.invoker().onServerStarting(event.getServer());
- });
- NeoEventUtils.registerEvent(forgeEventBus, ServerStartedEvent.class, event -> {
- ServerLifecycleEvents.STARTED.invoker().onServerStarted(event.getServer());
- });
- NeoEventUtils.registerEvent(forgeEventBus, ServerStoppingEvent.class, event -> {
- ServerLifecycleEvents.STOPPING.invoker().onServerStopping(event.getServer());
- });
- NeoEventUtils.registerEvent(forgeEventBus, ServerStoppedEvent.class, event -> {
- ServerLifecycleEvents.STOPPED.invoker().onServerStopped(event.getServer());
- });
-
- NeoEventUtils.registerEvent(forgeEventBus, ServerTickEvent.Pre.class, event -> {
- ServerTickEvents.START_SERVER_TICK.invoker().onStartTick(event.getServer());
- });
- NeoEventUtils.registerEvent(forgeEventBus, ServerTickEvent.Post.class, event -> {
- ServerTickEvents.END_SERVER_TICK.invoker().onEndTick(event.getServer());
- });
- NeoEventUtils.registerEvent(forgeEventBus, LevelTickEvent.Pre.class, event -> {
- if (event.getLevel() instanceof ServerWorld world) {
- ServerTickEvents.START_WORLD_TICK.invoker().onStartTick(world);
- }
- });
- NeoEventUtils.registerEvent(forgeEventBus, LevelTickEvent.Post.class, event -> {
- if (event.getLevel() instanceof ServerWorld world) {
- ServerTickEvents.END_WORLD_TICK.invoker().onEndTick(world);
- }
- });
-
- NeoEventUtils.registerEvent(forgeEventBus, LevelEvent.Load.class, event -> {
- if (event.getLevel() instanceof ServerWorld world) {
- ServerWorldEvents.LOADED.invoker().onWorldLoaded(world.getServer(), world);
- }
- });
- NeoEventUtils.registerEvent(forgeEventBus, LevelEvent.Unload.class, event -> {
- if (event.getLevel() instanceof ServerWorld world) {
- ServerWorldEvents.UNLOADED.invoker().onWorldUnloaded(world.getServer(), world);
- }
- });
-
- NeoEventUtils.registerEvent(forgeEventBus, OnDatapackSyncEvent.class, event -> {
- if (event.getPlayer() != null) {
- ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS.invoker().onSyncDataPackContents(event.getPlayer(), true);
- } else {
- for (ServerPlayerEntity player : event.getPlayerList().getPlayerList()) {
- ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS.invoker().onSyncDataPackContents(player, false);
- }
- }
- });
- }
-}
diff --git a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImplNeo.java b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImplNeo.java
new file mode 100644
index 00000000..7ce9558a
--- /dev/null
+++ b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/impl/KessokuLifecycleEventsImplNeo.java
@@ -0,0 +1,108 @@
+package band.kessoku.lib.events.lifecycle.impl;
+
+import band.kessoku.lib.event.util.NeoEventUtils;
+import band.kessoku.lib.events.lifecycle.api.*;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.server.world.ServerWorld;
+
+import net.neoforged.bus.api.IEventBus;
+import net.neoforged.neoforge.client.event.ClientTickEvent;
+import net.neoforged.neoforge.event.OnDatapackSyncEvent;
+import net.neoforged.neoforge.event.TagsUpdatedEvent;
+import net.neoforged.neoforge.event.entity.living.LivingEquipmentChangeEvent;
+import net.neoforged.neoforge.event.level.LevelEvent;
+import net.neoforged.neoforge.event.server.ServerStartedEvent;
+import net.neoforged.neoforge.event.server.ServerStartingEvent;
+import net.neoforged.neoforge.event.server.ServerStoppedEvent;
+import net.neoforged.neoforge.event.server.ServerStoppingEvent;
+import net.neoforged.neoforge.event.tick.LevelTickEvent;
+
+public class KessokuLifecycleEventsImplNeo {
+ public static void registerClientEvents(IEventBus modEventBus, IEventBus forgeEventBus) {
+ KessokuLifecycleEventsImpl.clientInit();
+
+ NeoEventUtils.registerEvent(forgeEventBus, ClientTickEvent.Pre.class, event -> {
+ band.kessoku.lib.events.lifecycle.api.client.ClientTickEvent.START_CLIENT_TICK.invoker().onStartTick(MinecraftClient.getInstance());
+ });
+ NeoEventUtils.registerEvent(forgeEventBus, ClientTickEvent.Post.class, event -> {
+ band.kessoku.lib.events.lifecycle.api.client.ClientTickEvent.END_CLIENT_TICK.invoker().onEndTick(MinecraftClient.getInstance());
+ });
+
+ NeoEventUtils.registerEvent(forgeEventBus, LevelTickEvent.Pre.class, event -> {
+ if (event.getLevel() instanceof ClientWorld world) {
+ band.kessoku.lib.events.lifecycle.api.client.ClientTickEvent.START_WORLD_TICK.invoker().onStartTick(world);
+ }
+ });
+ NeoEventUtils.registerEvent(forgeEventBus, LevelTickEvent.Post.class, event -> {
+ if (event.getLevel() instanceof ClientWorld world) {
+ band.kessoku.lib.events.lifecycle.api.client.ClientTickEvent.END_WORLD_TICK.invoker().onEndTick(world);
+ }
+ });
+ }
+
+ public static void registerCommonEvents(IEventBus modEventBus, IEventBus forgeEventBus) {
+ KessokuLifecycleEventsImpl.init();
+
+ NeoEventUtils.registerEvent(forgeEventBus, TagsUpdatedEvent.class, event -> {
+ LifecycleEvent.TAG_LOADED.invoker().onTagsLoaded(event.getRegistryAccess(), event.getUpdateCause() == TagsUpdatedEvent.UpdateCause.CLIENT_PACKET_RECEIVED);
+ });
+
+ NeoEventUtils.registerEvent(forgeEventBus, ServerStartingEvent.class, event -> {
+ ServerLifecycleEvent.STARTING.invoker().onServerStarting(event.getServer());
+ });
+ NeoEventUtils.registerEvent(forgeEventBus, ServerStartedEvent.class, event -> {
+ ServerLifecycleEvent.STARTED.invoker().onServerStarted(event.getServer());
+ });
+ NeoEventUtils.registerEvent(forgeEventBus, ServerStoppingEvent.class, event -> {
+ ServerLifecycleEvent.STOPPING.invoker().onServerStopping(event.getServer());
+ });
+ NeoEventUtils.registerEvent(forgeEventBus, ServerStoppedEvent.class, event -> {
+ ServerLifecycleEvent.STOPPED.invoker().onServerStopped(event.getServer());
+ });
+
+ NeoEventUtils.registerEvent(forgeEventBus, net.neoforged.neoforge.event.tick.ServerTickEvent.Pre.class, event -> {
+ ServerTickEvent.START_SERVER_TICK.invoker().onStartTick(event.getServer());
+ });
+ NeoEventUtils.registerEvent(forgeEventBus, net.neoforged.neoforge.event.tick.ServerTickEvent.Post.class, event -> {
+ ServerTickEvent.END_SERVER_TICK.invoker().onEndTick(event.getServer());
+ });
+// NeoEventUtils.registerEvent(forgeEventBus, LevelTickEvent.Pre.class, event -> {
+// if (event.getLevel() instanceof ServerWorld world) {
+// ServerTickEvent.START_WORLD_TICK.invoker().onStartTick(world);
+// }
+// });
+// NeoEventUtils.registerEvent(forgeEventBus, LevelTickEvent.Post.class, event -> {
+// if (event.getLevel() instanceof ServerWorld world) {
+// ServerTickEvent.END_WORLD_TICK.invoker().onEndTick(world);
+// }
+// });
+
+ NeoEventUtils.registerEvent(forgeEventBus, LevelEvent.Load.class, event -> {
+ if (event.getLevel() instanceof ServerWorld world) {
+ ServerWorldEvent.LOADED.invoker().onWorldLoaded(world.getServer(), world);
+ }
+ });
+ NeoEventUtils.registerEvent(forgeEventBus, LevelEvent.Unload.class, event -> {
+ if (event.getLevel() instanceof ServerWorld world) {
+ ServerWorldEvent.UNLOADED.invoker().onWorldUnloaded(world.getServer(), world);
+ }
+ });
+
+ NeoEventUtils.registerEvent(forgeEventBus, LivingEquipmentChangeEvent.class, event -> {
+ ServerEntityEvent.EQUIPMENT_CHANGED.invoker().onChanged(event.getEntity(), event.getSlot(), event.getFrom(), event.getTo());
+ });
+
+ NeoEventUtils.registerEvent(forgeEventBus, OnDatapackSyncEvent.class, event -> {
+ if (event.getPlayer() != null) {
+ ServerLifecycleEvent.SYNC_DATA_PACK_CONTENTS.invoker().onSyncDataPackContents(event.getPlayer(), true);
+ } else {
+ for (ServerPlayerEntity player : event.getPlayerList().getPlayerList()) {
+ ServerLifecycleEvent.SYNC_DATA_PACK_CONTENTS.invoker().onSyncDataPackContents(player, false);
+ }
+ }
+ });
+ }
+}
diff --git a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/MinecraftServerMixin.java b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/MinecraftServerMixin.java
new file mode 100644
index 00000000..39973de6
--- /dev/null
+++ b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/MinecraftServerMixin.java
@@ -0,0 +1,44 @@
+package band.kessoku.lib.events.lifecycle.mixin.neo;
+
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import net.minecraft.server.MinecraftServer;
+
+import band.kessoku.lib.events.lifecycle.api.ServerLifecycleEvent;
+
+@Mixin(MinecraftServer.class)
+public abstract class MinecraftServerMixin {
+ @Shadow
+ private MinecraftServer.ResourceManagerHolder resourceManagerHolder;
+
+ @Inject(method = "reloadResources", at = @At("HEAD"))
+ private void startResourceReload(Collection collection, CallbackInfoReturnable> cir) {
+ ServerLifecycleEvent.START_DATA_PACK_RELOAD.invoker().startDataPackReload((MinecraftServer) (Object) this, this.resourceManagerHolder.resourceManager());
+ }
+
+ @Inject(method = "reloadResources", at = @At("TAIL"))
+ private void endResourceReload(Collection collection, CallbackInfoReturnable> cir) {
+ cir.getReturnValue().handleAsync((value, throwable) -> {
+ // Hook into fail
+ ServerLifecycleEvent.END_DATA_PACK_RELOAD.invoker().endDataPackReload((MinecraftServer) (Object) this, this.resourceManagerHolder.resourceManager(), throwable == null);
+ return value;
+ }, (MinecraftServer) (Object) this);
+ }
+
+ @Inject(method = "save", at = @At("HEAD"))
+ private void startSave(boolean suppressLogs, boolean flush, boolean force, CallbackInfoReturnable cir) {
+ ServerLifecycleEvent.BEFORE_SAVE.invoker().onBeforeSaveData((MinecraftServer) (Object) this, flush, force);
+ }
+
+ @Inject(method = "save", at = @At("TAIL"))
+ private void endSave(boolean suppressLogs, boolean flush, boolean force, CallbackInfoReturnable cir) {
+ ServerLifecycleEvent.AFTER_SAVE.invoker().onAfterSaveData((MinecraftServer) (Object) this, flush, force);
+ }
+}
diff --git a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/ServerEntityHandlerMixin.java b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/ServerEntityHandlerMixin.java
new file mode 100644
index 00000000..be419af2
--- /dev/null
+++ b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/ServerEntityHandlerMixin.java
@@ -0,0 +1,32 @@
+package band.kessoku.lib.events.lifecycle.mixin.neo;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.server.world.ServerWorld;
+
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import band.kessoku.lib.events.lifecycle.api.ServerEntityEvent;
+
+@Mixin(targets = "net/minecraft/server/world/ServerWorld$ServerEntityHandler")
+public class ServerEntityHandlerMixin {
+ // final synthetic Lnet/minecraft/server/world/ServerWorld; field_26936
+ @SuppressWarnings("ShadowTarget")
+ @Shadow
+ @Final
+ private ServerWorld field_26936;
+
+ @Inject(method = "startTracking(Lnet/minecraft/entity/Entity;)V", at = @At("TAIL"))
+ private void invokeEntityLoadEvent(Entity entity, CallbackInfo ci) {
+ ServerEntityEvent.LOADED.invoker().onLoaded(entity, this.field_26936);
+ }
+
+ @Inject(method = "stopTracking(Lnet/minecraft/entity/Entity;)V", at = @At("HEAD"))
+ private void invokeEntityUnloadEvent(Entity entity, CallbackInfo info) {
+ ServerEntityEvent.UNLOADED.invoker().onUnloaded(entity, this.field_26936);
+ }
+}
diff --git a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/ServerWorldMixin.java b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/ServerWorldMixin.java
new file mode 100644
index 00000000..20b8acf9
--- /dev/null
+++ b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/ServerWorldMixin.java
@@ -0,0 +1,27 @@
+package band.kessoku.lib.events.lifecycle.mixin.neo;
+
+import java.util.function.BooleanSupplier;
+
+import org.objectweb.asm.Opcodes;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import net.minecraft.server.world.ServerWorld;
+
+import band.kessoku.lib.events.lifecycle.api.ServerTickEvent;
+
+@Mixin(ServerWorld.class)
+public abstract class ServerWorldMixin {
+ // Make sure "insideBlockTick" is true before we call the start tick, so inject after it is set
+ @Inject(method = "tick", at = @At(value = "FIELD", target = "Lnet/minecraft/server/world/ServerWorld;inBlockTick:Z", opcode = Opcodes.PUTFIELD, ordinal = 0, shift = At.Shift.AFTER))
+ private void startWorldTick(BooleanSupplier shouldKeepTicking, CallbackInfo ci) {
+ ServerTickEvent.START_WORLD_TICK.invoker().onStartTick((ServerWorld) (Object) this);
+ }
+
+ @Inject(method = "tick", at = @At(value = "TAIL"))
+ private void endWorldTick(BooleanSupplier shouldKeepTicking, CallbackInfo ci) {
+ ServerTickEvent.END_WORLD_TICK.invoker().onEndTick((ServerWorld) (Object) this);
+ }
+}
diff --git a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/server/WorldChunkMixin.java b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/server/WorldChunkMixin.java
new file mode 100644
index 00000000..92aa5ea3
--- /dev/null
+++ b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/server/WorldChunkMixin.java
@@ -0,0 +1,82 @@
+package band.kessoku.lib.events.lifecycle.mixin.neo.server;
+
+import java.util.Map;
+
+import com.llamalad7.mixinextras.sugar.Local;
+import org.jetbrains.annotations.Nullable;
+import org.objectweb.asm.Opcodes;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.Redirect;
+import org.spongepowered.asm.mixin.injection.Slice;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import net.minecraft.block.entity.BlockEntity;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import net.minecraft.world.chunk.WorldChunk;
+
+import band.kessoku.lib.events.lifecycle.api.ServerBlockEntityEvent;
+
+/**
+ * This is a server only mixin for good reason:
+ * Since all block entity tracking is now on the world chunk, we inject into WorldChunk.
+ * In order to prevent client logic from being loaded due to the mixin, we have a mixin for the client and this one for the server.
+ */
+@Mixin(WorldChunk.class)
+abstract class WorldChunkMixin {
+ @Shadow
+ public abstract World getWorld();
+
+ @Inject(method = "setBlockEntity", at = @At(value = "INVOKE", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", shift = At.Shift.BY, by = 3))
+ private void onLoadBlockEntity(BlockEntity blockEntity, CallbackInfo ci, @Local(ordinal = 1) BlockEntity removedBlockEntity) {
+ // Only fire the load event if the block entity has actually changed
+ if (blockEntity != null && blockEntity != removedBlockEntity) {
+ if (this.getWorld() instanceof ServerWorld) {
+ ServerBlockEntityEvent.LOADED.invoker().onLoaded(blockEntity, (ServerWorld) this.getWorld());
+ }
+ }
+ }
+
+ @Inject(method = "setBlockEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/entity/BlockEntity;markRemoved()V", shift = At.Shift.AFTER))
+ private void onRemoveBlockEntity(BlockEntity blockEntity, CallbackInfo info, @Local(ordinal = 1) BlockEntity removedBlockEntity) {
+ if (this.getWorld() instanceof ServerWorld) {
+ ServerBlockEntityEvent.UNLOADED.invoker().onUnloaded(removedBlockEntity, (ServerWorld) this.getWorld());
+ }
+ }
+
+ // Use the slice to not redirect codepath where block entity is loaded
+ @Redirect(
+ method = "getBlockEntity(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/chunk/WorldChunk$CreationType;)Lnet/minecraft/block/entity/BlockEntity;",
+ at = @At(
+ value = "INVOKE",
+ target = "Ljava/util/Map;remove(Ljava/lang/Object;)Ljava/lang/Object;"),
+ slice = @Slice(
+ to = @At(
+ value = "FIELD",
+ target = "Lnet/minecraft/world/chunk/WorldChunk;blockEntityNbts:Ljava/util/Map;",
+ opcode = Opcodes.GETFIELD
+ )
+ )
+ )
+ private Object onRemoveBlockEntity(Map map, K key) {
+ @Nullable final V removed = map.remove(key);
+
+ if (removed != null && this.getWorld() instanceof ServerWorld) {
+ ServerBlockEntityEvent.UNLOADED.invoker().onUnloaded((BlockEntity) removed, (ServerWorld) this.getWorld());
+ }
+
+ return removed;
+ }
+
+ @Inject(method = "removeBlockEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/entity/BlockEntity;markRemoved()V"), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+ private void onRemoveBlockEntity(BlockPos pos, CallbackInfo ci, @Nullable BlockEntity removed) {
+ if (removed != null && this.getWorld() instanceof ServerWorld) {
+ ServerBlockEntityEvent.UNLOADED.invoker().onUnloaded(removed, (ServerWorld) this.getWorld());
+ }
+ }
+}
diff --git a/lifecycle-events-neo/src/main/resources/META-INF/neoforge.mods.toml b/lifecycle-events-neo/src/main/resources/META-INF/neoforge.mods.toml
new file mode 100644
index 00000000..4620df78
--- /dev/null
+++ b/lifecycle-events-neo/src/main/resources/META-INF/neoforge.mods.toml
@@ -0,0 +1,35 @@
+modLoader = "javafml"
+loaderVersion = "[4,)"
+license = "LGPL-3.0-only"
+issueTrackerURL = "https://github.com/KessokuTeaTime/KessokuLib/issues"
+
+[[mods]]
+modId = "kessoku_lifecycle_events"
+version = "${version}"
+displayName = "Kessoku Lifecycle Events"
+description = '''
+Events for the game's lifecycle.
+'''
+logoFile = "icon.png"
+authors = "Kessoku Tea Time"
+displayURL = "https://modrinth.com/mod/kessoku-lib"
+
+[[mixins]]
+config = "kessoku-lifecycle-events.mixins.json"
+
+[[mixins]]
+config = "kessoku-lifecycle-events.neo.mixins.json"
+
+[[dependencies.kessoku-event-base]]
+modId = "neoforge"
+type = "required"
+versionRange = "[21.0,)"
+ordering = "NONE"
+side = "BOTH"
+
+[[dependencies.kessoku-event-base]]
+modId = "minecraft"
+type = "required"
+versionRange = "[1.21,)"
+ordering = "NONE"
+side = "BOTH"
\ No newline at end of file
diff --git a/lifecycle-events-neo/src/main/resources/kessoku-lifecycle-events.neo.mixins.json b/lifecycle-events-neo/src/main/resources/kessoku-lifecycle-events.neo.mixins.json
new file mode 100644
index 00000000..05930534
--- /dev/null
+++ b/lifecycle-events-neo/src/main/resources/kessoku-lifecycle-events.neo.mixins.json
@@ -0,0 +1,17 @@
+{
+ "required": true,
+ "package": "band.kessoku.lib.events.lifecycle.mixin.neo",
+ "compatibilityLevel": "JAVA_17",
+ "mixins": [
+ "MinecraftServerMixin",
+ "ServerEntityHandlerMixin",
+ "ServerWorldMixin"
+ ],
+ "server": [
+ "server.WorldChunkMixin"
+ ],
+ "injectors": {
+ "defaultRequire": 1,
+ "maxShiftBy": 3
+ }
+}
\ No newline at end of file