From eccb4aefe10b91f694a42e4c21b9e27eaa8efc40 Mon Sep 17 00:00:00 2001 From: TexTrue <3140846162@qq.com> Date: Mon, 5 Aug 2024 10:46:51 +0800 Subject: [PATCH] `lifecycle-events` done --- .../api/client/ClientBlockEntityEvent.java | 42 +++++++++ .../api/client/ClientChunkEvent.java | 41 ++++++++ .../api/client/ClientEntityEvent.java | 41 ++++++++ .../lifecycle/api/client/ClientTickEvent.java | 2 - .../KessokuLifecycleEventsImplFabric.java | 15 ++- lifecycle-events-neo/build.gradle | 6 ++ .../KessokuLifecycleEventsEntrypoint.java | 5 +- .../impl/KessokuLifecycleEventsImplNeo.java | 29 +++++- .../neo/client/ClientChunkManagerMixin.java | 50 ++++++++++ .../neo/client/ClientEntityHandlerMixin.java | 34 +++++++ .../client/ClientPlayNetworkHandlerMixin.java | 80 ++++++++++++++++ .../neo/client/MinecraftClientMixin.java | 24 +++++ .../mixin/neo/client/WorldChunkMixin.java | 94 +++++++++++++++++++ .../resources/META-INF/accesstransformer.cfg | 3 + .../resources/META-INF/neoforge.mods.toml | 3 + .../kessoku-lifecycle-events.neo.mixins.json | 7 ++ 16 files changed, 465 insertions(+), 11 deletions(-) create mode 100644 lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientBlockEntityEvent.java create mode 100644 lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientChunkEvent.java create mode 100644 lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientEntityEvent.java create mode 100644 lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/ClientChunkManagerMixin.java create mode 100644 lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/ClientEntityHandlerMixin.java create mode 100644 lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/ClientPlayNetworkHandlerMixin.java create mode 100644 lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/MinecraftClientMixin.java create mode 100644 lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/WorldChunkMixin.java create mode 100644 lifecycle-events-neo/src/main/resources/META-INF/accesstransformer.cfg diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientBlockEntityEvent.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientBlockEntityEvent.java new file mode 100644 index 00000000..cc5e2abc --- /dev/null +++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientBlockEntityEvent.java @@ -0,0 +1,42 @@ +package band.kessoku.lib.events.lifecycle.api.client; + +import band.kessoku.lib.event.api.Event; + +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.client.world.ClientWorld; + +public class ClientBlockEntityEvent { + + /** + * Called when a BlockEntity is loaded into a ClientWorld. + * + *

When this 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 a BlockEntity is about to be unloaded from a ClientWorld. + * + *

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, ClientWorld world); + } + + @FunctionalInterface + public interface Unloaded { + void onUnloaded(BlockEntity blockEntity, ClientWorld world); + } +} diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientChunkEvent.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientChunkEvent.java new file mode 100644 index 00000000..20d369ca --- /dev/null +++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientChunkEvent.java @@ -0,0 +1,41 @@ +package band.kessoku.lib.events.lifecycle.api.client; + +import band.kessoku.lib.event.api.Event; + +import net.minecraft.client.world.ClientWorld; +import net.minecraft.world.chunk.WorldChunk; + +public class ClientChunkEvent { + + /** + * Called when a chunk is loaded into a ClientWorld. + * + *

When this event is called, the chunk is already in the world. + */ + public static final Event LOADED = Event.of(loadeds -> (clientWorld, chunk) -> { + for (Loaded callback : loadeds) { + callback.onChunkLoaded(clientWorld, chunk); + } + }); + + /** + * Called when a chunk is about to be unloaded from a ClientWorld. + * + *

When this event is called, the chunk is still present in the world. + */ + public static final Event UNLOADED = Event.of(unloadeds -> (clientWorld, chunk) -> { + for (Unloaded unloaded : unloadeds) { + unloaded.onChunkUnloaded(clientWorld, chunk); + } + }); + + @FunctionalInterface + public interface Loaded { + void onChunkLoaded(ClientWorld world, WorldChunk chunk); + } + + @FunctionalInterface + public interface Unloaded { + void onChunkUnloaded(ClientWorld world, WorldChunk chunk); + } +} diff --git a/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientEntityEvent.java b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientEntityEvent.java new file mode 100644 index 00000000..513344cd --- /dev/null +++ b/lifecycle-events-common/src/main/java/band/kessoku/lib/events/lifecycle/api/client/ClientEntityEvent.java @@ -0,0 +1,41 @@ +package band.kessoku.lib.events.lifecycle.api.client; + +import band.kessoku.lib.event.api.Event; + +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; + +public class ClientEntityEvent { + + /** + * Called when an Entity is loaded into a ClientWorld. + * + *

When this event is called, the chunk 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 about to be unloaded from a ClientWorld. + * + *

This event is called before the entity is unloaded from the world. + */ + public static final Event UNLOADED = Event.of(unloadeds -> (entity, world) -> { + for (Unloaded unloaded : unloadeds) { + unloaded.onUnloaded(entity, world); + } + }); + + @FunctionalInterface + public interface Loaded { + void onLoaded(Entity entity, ClientWorld world); + } + + @FunctionalInterface + public interface Unloaded { + void onUnloaded(Entity entity, ClientWorld world); + } +} 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 index 1045c0bd..1a60b556 100644 --- 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 @@ -68,6 +68,4 @@ interface End { void onEndTick(ClientWorld world); } } - - } 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 index 1adb4a96..15fd85b4 100644 --- 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 @@ -1,11 +1,9 @@ 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 band.kessoku.lib.events.lifecycle.api.client.*; -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.client.event.lifecycle.v1.*; import net.fabricmc.fabric.api.event.lifecycle.v1.*; public class KessokuLifecycleEventsImplFabric { @@ -19,6 +17,15 @@ public static void registerClientEvents() { 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)); + + ClientEntityEvents.ENTITY_LOAD.register((entity, world) -> ClientEntityEvent.LOADED.invoker().onLoaded(entity, world)); + ClientEntityEvents.ENTITY_UNLOAD.register((entity, world) -> ClientEntityEvent.UNLOADED.invoker().onUnloaded(entity, world)); + + ClientChunkEvents.CHUNK_LOAD.register((world, chunk) -> ClientChunkEvent.LOADED.invoker().onChunkLoaded(world, chunk)); + ClientChunkEvents.CHUNK_UNLOAD.register((world, chunk) -> ClientChunkEvent.UNLOADED.invoker().onChunkUnloaded(world, chunk)); + + ClientBlockEntityEvents.BLOCK_ENTITY_LOAD.register((blockEntity, world) -> ClientBlockEntityEvent.LOADED.invoker().onLoaded(blockEntity, world)); + ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register((blockEntity, world) -> ClientBlockEntityEvent.UNLOADED.invoker().onUnloaded(blockEntity, world)); } public static void registerCommonEvents() { diff --git a/lifecycle-events-neo/build.gradle b/lifecycle-events-neo/build.gradle index a47865b0..8226ca98 100644 --- a/lifecycle-events-neo/build.gradle +++ b/lifecycle-events-neo/build.gradle @@ -14,6 +14,12 @@ architectury { neoForge() } +loom { + neoForge { + setAccessTransformers(files("src/main/resources/META-INF/accesstransformer.cfg")) + } +} + repositories { maven { url "https://maven.neoforged.net/releases/" } } 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 d430d856..287388a9 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 @@ -13,10 +13,9 @@ public class KessokuLifecycleEventsEntrypoint { public KessokuLifecycleEventsEntrypoint(IEventBus modEventBus, ModContainer modContainer) { var forgeEventBus = NeoForge.EVENT_BUS; - KessokuLifecycleEventsImplNeo.registerCommonEvents(modEventBus, forgeEventBus); - + KessokuLifecycleEventsImplNeo.registerCommonEvents(forgeEventBus); if (FMLLoader.getDist().isClient()) { - KessokuLifecycleEventsImplNeo.registerClientEvents(modEventBus, forgeEventBus); + KessokuLifecycleEventsImplNeo.registerClientEvents(forgeEventBus); } } } 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 index 7ce9558a..5e0be43a 100644 --- 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 @@ -3,16 +3,19 @@ import band.kessoku.lib.event.util.NeoEventUtils; import band.kessoku.lib.events.lifecycle.api.*; +import band.kessoku.lib.events.lifecycle.api.client.ClientChunkEvent; 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.minecraft.world.chunk.WorldChunk; 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.ChunkEvent; import net.neoforged.neoforge.event.level.LevelEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStartingEvent; @@ -21,9 +24,20 @@ import net.neoforged.neoforge.event.tick.LevelTickEvent; public class KessokuLifecycleEventsImplNeo { - public static void registerClientEvents(IEventBus modEventBus, IEventBus forgeEventBus) { + public static void registerClientEvents(IEventBus forgeEventBus) { KessokuLifecycleEventsImpl.clientInit(); + NeoEventUtils.registerEvent(forgeEventBus, ChunkEvent.Load.class, event -> { + if (event.getLevel() instanceof ClientWorld world && event.getChunk() instanceof WorldChunk chunk) { + ClientChunkEvent.LOADED.invoker().onChunkLoaded(world, chunk); + } + }); + NeoEventUtils.registerEvent(forgeEventBus, ChunkEvent.Unload.class, event -> { + if (event.getLevel() instanceof ClientWorld world && event.getChunk() instanceof WorldChunk chunk) { + ClientChunkEvent.UNLOADED.invoker().onChunkUnloaded(world, chunk); + } + }); + NeoEventUtils.registerEvent(forgeEventBus, ClientTickEvent.Pre.class, event -> { band.kessoku.lib.events.lifecycle.api.client.ClientTickEvent.START_CLIENT_TICK.invoker().onStartTick(MinecraftClient.getInstance()); }); @@ -43,13 +57,24 @@ public static void registerClientEvents(IEventBus modEventBus, IEventBus forgeEv }); } - public static void registerCommonEvents(IEventBus modEventBus, IEventBus forgeEventBus) { + public static void registerCommonEvents(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, ChunkEvent.Load.class, event -> { + if (event.getLevel() instanceof ServerWorld world && event.getChunk() instanceof WorldChunk chunk) { + ServerChunkEvent.LOADED.invoker().onChunkLoaded(world, chunk); + } + }); + NeoEventUtils.registerEvent(forgeEventBus, ChunkEvent.Unload.class, event -> { + if (event.getLevel() instanceof ServerWorld world && event.getChunk() instanceof WorldChunk chunk) { + ServerChunkEvent.UNLOADED.invoker().onChunkUnloaded(world, chunk); + } + }); + NeoEventUtils.registerEvent(forgeEventBus, ServerStartingEvent.class, event -> { ServerLifecycleEvent.STARTING.invoker().onServerStarting(event.getServer()); }); diff --git a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/ClientChunkManagerMixin.java b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/ClientChunkManagerMixin.java new file mode 100644 index 00000000..cc4a375b --- /dev/null +++ b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/ClientChunkManagerMixin.java @@ -0,0 +1,50 @@ +package band.kessoku.lib.events.lifecycle.mixin.neo.client; + +import java.util.function.Consumer; + +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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import net.minecraft.client.world.ClientChunkManager; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.s2c.play.ChunkData; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.chunk.WorldChunk; + +import band.kessoku.lib.events.lifecycle.api.client.ClientChunkEvent; + +@Mixin(ClientChunkManager.class) +public abstract class ClientChunkManagerMixin { + @Final + @Shadow + private ClientWorld world; + + @Inject(method = "loadChunkFromPacket", at = @At(value = "NEW", target = "net/minecraft/world/chunk/WorldChunk", shift = At.Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILHARD) + private void onChunkUnload(int x, int z, PacketByteBuf buf, NbtCompound tag, Consumer consumer, CallbackInfoReturnable info, int index, WorldChunk worldChunk, ChunkPos chunkPos) { + if (worldChunk != null) { + ClientChunkEvent.UNLOADED.invoker().onChunkUnloaded(this.world, worldChunk); + } + } + + @Inject( + method = "updateLoadDistance", + at = @At( + value = "INVOKE", + target = "net/minecraft/client/world/ClientChunkManager$ClientChunkMap.isInRadius(II)Z" + ), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void onUpdateLoadDistance(int loadDistance, CallbackInfo ci, int oldRadius, int newRadius, ClientChunkManager.ClientChunkMap clientChunkMap, int k, WorldChunk oldChunk, ChunkPos chunkPos) { + if (!clientChunkMap.isInRadius(chunkPos.x, chunkPos.z)) { + ClientChunkEvent.UNLOADED.invoker().onChunkUnloaded(this.world, oldChunk); + } + } +} diff --git a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/ClientEntityHandlerMixin.java b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/ClientEntityHandlerMixin.java new file mode 100644 index 00000000..f749e470 --- /dev/null +++ b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/ClientEntityHandlerMixin.java @@ -0,0 +1,34 @@ +package band.kessoku.lib.events.lifecycle.mixin.neo.client; + +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 net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; + +import band.kessoku.lib.events.lifecycle.api.client.ClientEntityEvent; + +@Mixin(targets = "net/minecraft/client/world/ClientWorld$ClientEntityHandler") +public class ClientEntityHandlerMixin { + // final synthetic Lnet/minecraft/client/world/ClientWorld; field_27735 + @SuppressWarnings("ShadowTarget") + @Shadow + @Final + private ClientWorld field_27735; + + // Call our load event after vanilla has loaded the entity + @Inject(method = "startTracking(Lnet/minecraft/entity/Entity;)V", at = @At("TAIL")) + private void invokeLoadEntity(Entity entity, CallbackInfo ci) { + ClientEntityEvent.LOADED.invoker().onLoaded(entity, this.field_27735); + } + + // Call our unload event before vanilla does. + @Inject(method = "stopTracking(Lnet/minecraft/entity/Entity;)V", at = @At("HEAD")) + private void invokeUnloadEntity(Entity entity, CallbackInfo ci) { + ClientEntityEvent.UNLOADED.invoker().onUnloaded(entity, this.field_27735); + } +} diff --git a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/ClientPlayNetworkHandlerMixin.java b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/ClientPlayNetworkHandlerMixin.java new file mode 100644 index 00000000..3865e5dd --- /dev/null +++ b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/ClientPlayNetworkHandlerMixin.java @@ -0,0 +1,80 @@ +package band.kessoku.lib.events.lifecycle.mixin.neo.client; + +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 net.minecraft.block.entity.BlockEntity; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket; +import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket; +import net.minecraft.world.chunk.WorldChunk; + +import band.kessoku.lib.events.lifecycle.api.client.ClientBlockEntityEvent; +import band.kessoku.lib.events.lifecycle.api.client.ClientEntityEvent; +import band.kessoku.lib.events.lifecycle.impl.LoadedChunksCache; + +@Mixin(ClientPlayNetworkHandler.class) +abstract class ClientPlayNetworkHandlerMixin { + @Shadow + private ClientWorld world; + + @Inject(method = "onPlayerRespawn", at = @At(value = "NEW", target = "net/minecraft/client/world/ClientWorld")) + private void onPlayerRespawn(PlayerRespawnS2CPacket packet, CallbackInfo ci) { + // If a world already exists, we need to unload all (block)entities in the world. + if (this.world != null) { + for (Entity entity : this.world.getEntities()) { + ClientEntityEvent.UNLOADED.invoker().onUnloaded(entity, this.world); + } + + for (WorldChunk chunk : ((LoadedChunksCache) this.world).kessoku$getLoadedChunks()) { + for (BlockEntity blockEntity : chunk.getBlockEntities().values()) { + ClientBlockEntityEvent.UNLOADED.invoker().onUnloaded(blockEntity, this.world); + } + } + } + } + + /** + * An explanation why we unload entities during onGameJoin: + * Proxies such as Waterfall may send another Game Join packet if entity meta rewrite is disabled, so we will cover ourselves. + * Velocity by default will send a Game Join packet when the player changes servers, which will create a new client world. + * Also anyone can send another GameJoinPacket at any time, so we need to watch out. + */ + @Inject(method = "onGameJoin", at = @At(value = "NEW", target = "net/minecraft/client/world/ClientWorld")) + private void onGameJoin(GameJoinS2CPacket packet, CallbackInfo ci) { + // If a world already exists, we need to unload all (block)entities in the world. + if (this.world != null) { + for (Entity entity : world.getEntities()) { + ClientEntityEvent.UNLOADED.invoker().onUnloaded(entity, this.world); + } + + for (WorldChunk chunk : ((LoadedChunksCache) this.world).kessoku$getLoadedChunks()) { + for (BlockEntity blockEntity : chunk.getBlockEntities().values()) { + ClientBlockEntityEvent.UNLOADED.invoker().onUnloaded(blockEntity, this.world); + } + } + } + } + + // Called when the client disconnects from a server or enters reconfiguration. + @Inject(method = "clearWorld", at = @At("HEAD")) + private void onClearWorld(CallbackInfo ci) { + // If a world already exists, we need to unload all (block)entities in the world. + if (this.world != null) { + for (Entity entity : this.world.getEntities()) { + ClientEntityEvent.UNLOADED.invoker().onUnloaded(entity, this.world); + } + + for (WorldChunk chunk : ((LoadedChunksCache) this.world).kessoku$getLoadedChunks()) { + for (BlockEntity blockEntity : chunk.getBlockEntities().values()) { + ClientBlockEntityEvent.UNLOADED.invoker().onUnloaded(blockEntity, this.world); + } + } + } + } +} diff --git a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/MinecraftClientMixin.java b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/MinecraftClientMixin.java new file mode 100644 index 00000000..da692f9f --- /dev/null +++ b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/MinecraftClientMixin.java @@ -0,0 +1,24 @@ +package band.kessoku.lib.events.lifecycle.mixin.neo.client; + +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.client.MinecraftClient; + +import band.kessoku.lib.events.lifecycle.api.client.ClientLifecycleEvent; + +@Mixin(MinecraftClient.class) +public abstract class MinecraftClientMixin { + @Inject(at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;)V", shift = At.Shift.AFTER, remap = false), method = "stop") + private void onStopping(CallbackInfo ci) { + ClientLifecycleEvent.STOPPING.invoker().onClientStopping((MinecraftClient) (Object) this); + } + + // We inject after the thread field is set so `ThreadExecutor#getThread` will work + @Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/client/MinecraftClient;thread:Ljava/lang/Thread;", shift = At.Shift.AFTER, ordinal = 0), method = "run") + private void onStart(CallbackInfo ci) { + ClientLifecycleEvent.STARTED.invoker().onClientStarted((MinecraftClient) (Object) this); + } +} diff --git a/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/WorldChunkMixin.java b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/WorldChunkMixin.java new file mode 100644 index 00000000..1a509b7f --- /dev/null +++ b/lifecycle-events-neo/src/main/java/band/kessoku/lib/events/lifecycle/mixin/neo/client/WorldChunkMixin.java @@ -0,0 +1,94 @@ +package band.kessoku.lib.events.lifecycle.mixin.neo.client; + +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.client.world.ClientWorld; +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; +import band.kessoku.lib.events.lifecycle.api.client.ClientBlockEntityEvent; + +@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()); + } else if (this.getWorld() instanceof ClientWorld) { + ClientBlockEntityEvent.LOADED.invoker().onLoaded(blockEntity, (ClientWorld) 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 (removedBlockEntity != null) { + if (this.getWorld() instanceof ServerWorld) { + ServerBlockEntityEvent.UNLOADED.invoker().onUnloaded(removedBlockEntity, (ServerWorld) this.getWorld()); + } else if (this.getWorld() instanceof ClientWorld) { + ClientBlockEntityEvent.UNLOADED.invoker().onUnloaded(removedBlockEntity, (ClientWorld) 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) { + if (this.getWorld() instanceof ServerWorld) { + ServerBlockEntityEvent.UNLOADED.invoker().onUnloaded((BlockEntity) removed, (ServerWorld) this.getWorld()); + } else if (this.getWorld() instanceof ClientWorld) { + ClientBlockEntityEvent.UNLOADED.invoker().onUnloaded((BlockEntity) removed, (ClientWorld) 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) { + if (this.getWorld() instanceof ServerWorld) { + ServerBlockEntityEvent.UNLOADED.invoker().onUnloaded(removed, (ServerWorld) this.getWorld()); + } else if (this.getWorld() instanceof ClientWorld) { + ClientBlockEntityEvent.UNLOADED.invoker().onUnloaded(removed, (ClientWorld) this.getWorld()); + } + } + } +} diff --git a/lifecycle-events-neo/src/main/resources/META-INF/accesstransformer.cfg b/lifecycle-events-neo/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 00000000..11e2bad8 --- /dev/null +++ b/lifecycle-events-neo/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,3 @@ +public net.minecraft.server.MinecraftServer$ReloadableResources +public net.minecraft.client.multiplayer.ClientChunkCache$Storage +public net.minecraft.client.multiplayer.ClientChunkCache$Storage inRange(II)Z # inRange \ No newline at end of file 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 index 4620df78..dacde038 100644 --- a/lifecycle-events-neo/src/main/resources/META-INF/neoforge.mods.toml +++ b/lifecycle-events-neo/src/main/resources/META-INF/neoforge.mods.toml @@ -20,6 +20,9 @@ config = "kessoku-lifecycle-events.mixins.json" [[mixins]] config = "kessoku-lifecycle-events.neo.mixins.json" +[[accessTransformers]] +file = "META-INF/accesstransformer.cfg" + [[dependencies.kessoku-event-base]] modId = "neoforge" type = "required" 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 index 05930534..f5267d43 100644 --- 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 @@ -10,6 +10,13 @@ "server": [ "server.WorldChunkMixin" ], + "client": [ + "client.ClientChunkManagerMixin", + "client.ClientPlayNetworkHandlerMixin", + "client.ClientEntityHandlerMixin", + "client.MinecraftClientMixin", + "client.WorldChunkMixin" + ], "injectors": { "defaultRequire": 1, "maxShiftBy": 3