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