diff --git a/coreSubProjects b/coreSubProjects index af5bb351e..895a0db54 160000 --- a/coreSubProjects +++ b/coreSubProjects @@ -1 +1 @@ -Subproject commit af5bb351e8a99344321ec2c0ebb0fd9dcffb35d6 +Subproject commit 895a0db54291403b50c1aade87a08af986895abf diff --git a/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/client/MixinClientPacketListener.java b/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/client/MixinClientPacketListener.java index b4feaa656..7b368aaa6 100644 --- a/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/client/MixinClientPacketListener.java +++ b/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/client/MixinClientPacketListener.java @@ -24,12 +24,6 @@ public class MixinClientPacketListener - /** THIS EXPLANATION IS WRITTEN BY FABRIC. - * An explanation why we unload entities during onGameJoin: (On in our remapping name case, handleLogin(TODO: CHECK)) - * 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 = "handleLogin", at = @At("HEAD")) void onHandleLoginStart(CallbackInfo ci) { diff --git a/forge/src/main/java/com/seibel/distanthorizons/forge/ForgeClientProxy.java b/forge/src/main/java/com/seibel/distanthorizons/forge/ForgeClientProxy.java index be557912a..d9e2d8f3a 100644 --- a/forge/src/main/java/com/seibel/distanthorizons/forge/ForgeClientProxy.java +++ b/forge/src/main/java/com/seibel/distanthorizons/forge/ForgeClientProxy.java @@ -21,14 +21,21 @@ package com.seibel.distanthorizons.forge; import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper; import com.seibel.distanthorizons.core.api.internal.ClientApi; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; +import com.seibel.distanthorizons.coreapi.ModInfo; +import io.netty.buffer.ByteBuf; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.LevelAccessor; import net.minecraft.client.multiplayer.ClientLevel; #if PRE_MC_1_19_2 +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.event.world.WorldEvent; #else @@ -36,6 +43,9 @@ import net.minecraftforge.event.level.ChunkEvent; import net.minecraftforge.event.level.LevelEvent; #endif +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; import org.apache.logging.log4j.Logger; import org.lwjgl.glfw.GLFW; @@ -51,18 +61,28 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; * and is the starting point for most of the mod. * * @author James_Seibel - * @version 11-12-2021 + * @version 2023-7-27 */ public class ForgeClientProxy { + private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + + private static SimpleChannel SIMPLE_CHANNEL; + + #if PRE_MC_1_19_2 private static LevelAccessor GetLevel(WorldEvent e) { return e.getWorld(); } #else private static LevelAccessor GetLevel(LevelEvent e) { return e.getLevel(); } #endif - - private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - + + + + //=============// + // tick events // + //=============// + @SubscribeEvent public void clientTickEvent(TickEvent.ClientTickEvent event) { @@ -71,26 +91,79 @@ public class ForgeClientProxy ClientApi.INSTANCE.clientTickEvent(); } } - - // ClientLevelLoadEvent - Done in MixinClientPacketListener -// @SubscribeEvent -// public void clientLevelLoadEvent(WorldEvent.Load event) -// { -// if (event.getWorld() instanceof ClientLevel) -// { -// ClientApi.INSTANCE.clientLevelLoadEvent(ClientLevelWrapper.getWrapper((ClientLevel) event.getWorld())); -// } -// } - // ClientLevelUnloadEvent - Done in MixinClientPacketListener -// @SubscribeEvent -// public void clientLevelUnloadEvent(WorldEvent.Unload event) -// { -// if (event.getWorld() instanceof ClientLevel) -// { -// ClientApi.INSTANCE.clientLevelUnloadEvent(ClientLevelWrapper.getWrapper((ClientLevel) event.getWorld())); -// } -// } - + + + + //==============// + // world events // + //==============// + + @SubscribeEvent + public void clientLevelLoadEvent(WorldEvent.Load event) + { + LOGGER.info("level load"); + + if (event != null && event.getWorld() != null && event.getWorld() instanceof ClientLevel) + { + ClientLevel clientLevel = (ClientLevel) event.getWorld(); + IClientLevelWrapper clientLevelWrapper = ClientLevelWrapper.getWrapper(clientLevel); + // TODO this causes a crash due to level being set to null somewhere + ClientApi.INSTANCE.clientLevelLoadEvent(clientLevelWrapper); + } + } + @SubscribeEvent + public void clientLevelUnloadEvent(WorldEvent.Unload event) + { + LOGGER.info("level unload"); + + if (event != null && event.getWorld() != null && event.getWorld() instanceof ClientLevel) + { + ClientLevel clientLevel = (ClientLevel) event.getWorld(); + IClientLevelWrapper clientLevelWrapper = ClientLevelWrapper.getWrapper(clientLevel); + ClientApi.INSTANCE.clientLevelUnloadEvent(clientLevelWrapper); + } + } + + + + //==============// + // chunk events // + //==============// + + @SubscribeEvent + public void rightClickBlockEvent(PlayerInteractEvent.RightClickBlock event) + { + LOGGER.trace("interact or block place event at blockPos: " + event.getPos()); + + LevelAccessor level = event.getWorld(); + ChunkAccess chunk = level.getChunk(event.getPos()); + this.onClientBlockChangeEvent(level, chunk); + } + @SubscribeEvent + public void leftClickBlockEvent(PlayerInteractEvent.LeftClickBlock event) + { + LOGGER.trace("break or block attack at blockPos: " + event.getPos()); + + LevelAccessor level = event.getWorld(); + ChunkAccess chunk = level.getChunk(event.getPos()); + this.onClientBlockChangeEvent(level, chunk); + } + private void onClientBlockChangeEvent(LevelAccessor level, ChunkAccess chunk) + { + // TODO rate limit this event per blockPos to prevent spam + + // if we have access to the server, use the chunk save event instead + if (MC.clientConnectedToDedicatedServer()) + { + if (chunk != null) + { + IClientLevelWrapper wrappedLevel = ClientLevelWrapper.getWrapper((ClientLevel) level); + ClientApi.INSTANCE.clientChunkLoadEvent(new ChunkWrapper(chunk, level, wrappedLevel), wrappedLevel); + } + } + } + + @SubscribeEvent public void clientChunkLoadEvent(ChunkEvent.Load event) { @@ -102,7 +175,7 @@ public class ForgeClientProxy } } @SubscribeEvent - public void clientChunkSaveEvent(ChunkEvent.Unload event) + public void clientChunkUnloadEvent(ChunkEvent.Unload event) { if (GetLevel(event) instanceof ClientLevel) { @@ -111,82 +184,78 @@ public class ForgeClientProxy ClientApi.INSTANCE.clientChunkSaveEvent(chunk, ClientLevelWrapper.getWrapper((ClientLevel) GetLevel(event))); } } - - // RendererStartupEvent - Done in MixinGameRenderer - // RendererShutdownEvent - Done in MixinGameRenderer - // ClientRenderLevelTerrainEvent - Done in MixinGameRenderer - - // Register KeyBindings + + + + //==============// + // key bindings // + //==============// + @SubscribeEvent public void registerKeyBindings(#if PRE_MC_1_19_2 InputEvent.KeyInputEvent #else InputEvent.Key #endif event) { - if (Minecraft.getInstance().player == null) return; - if (event.getAction() != GLFW.GLFW_PRESS) return; + if (Minecraft.getInstance().player == null) + { + return; + } + if (event.getAction() != GLFW.GLFW_PRESS) + { + return; + } + ClientApi.INSTANCE.keyPressedEvent(event.getKey()); } - -// @SubscribeEvent -// public void serverTickEvent(TickEvent.ServerTickEvent event) -// { -// if (event.phase != TickEvent.Phase.START) return; -// eventApi.serverTickEvent(); -// } -//// -// -// @SubscribeEvent -// public void chunkLoadEvent(ChunkEvent.Load event) -// { -// clientApi.clientChunkLoadEvent(new ChunkWrapper(event.getChunk(), event.getWorld()), LevelWrapper.getWorldWrapper(event.getWorld())); -// } -// -// @SubscribeEvent -// public void worldSaveEvent(WorldEvent.Save event) -// { -// eventApi.worldSaveEvent(); -// } -// -// /** This is also called when a new dimension loads */ -// @SubscribeEvent -// public void worldLoadEvent(WorldEvent.Load event) -// { -// if (Minecraft.getInstance().screen instanceof TitleScreen) return; -// if (event.getWorld() != null) { -// eventApi.worldLoadEvent(LevelWrapper.getWorldWrapper(event.getWorld())); -// } -// } -// -// @SubscribeEvent -// public void worldUnloadEvent(WorldEvent.Unload event) -// { -// eventApi.worldUnloadEvent(LevelWrapper.getWorldWrapper(event.getWorld())); -// } -// -// @SubscribeEvent -// public void blockChangeEvent(BlockEvent event) -// { -// // we only care about certain block events -// if (event.getClass() == BlockEvent.BreakEvent.class || -// event.getClass() == BlockEvent.EntityPlaceEvent.class || -// event.getClass() == BlockEvent.EntityMultiPlaceEvent.class || -// event.getClass() == BlockEvent.FluidPlaceBlockEvent.class || -// event.getClass() == BlockEvent.PortalSpawnEvent.class) -// { -// IChunkWrapper chunk = new ChunkWrapper(event.getWorld().getChunk(event.getPos()), event.getWorld()); -// DimensionTypeWrapper dimType = DimensionTypeWrapper.getDimensionTypeWrapper(event.getWorld().dimensionType()); -// -// // recreate the LOD where the blocks were changed -// eventApi.blockChangeEvent(chunk, dimType); -// } -// } -// -// @SubscribeEvent -// public void onKeyInput(InputEvent.KeyInputEvent event) -// { -// if (Minecraft.getInstance().player == null) return; -// if (event.getAction() != GLFW.GLFW_PRESS) return; -// clientApi.keyPressedEvent(event.getKey()); -// } -// + + + + //============// + // networking // + //============// + + /** @param event this is just to ensure the event is called at the right time, if it is called outside the {@link FMLClientSetupEvent} event, the binding may fail */ + public static void setupNetworkingListeners(FMLClientSetupEvent event) + { + + SIMPLE_CHANNEL = NetworkRegistry.newSimpleChannel( + new ResourceLocation("distant_horizons", "world_control"), // TODO move to common location + () -> ModInfo.PROTOCOL_VERSION+"", + // client accepted versions + ForgeClientProxy::isReceivedProtocolVersionAcceptable, + // server accepted versions + ForgeClientProxy::isReceivedProtocolVersionAcceptable + ); + + SIMPLE_CHANNEL.registerMessage(0, ByteBuf.class, + // encoder + (pack, buf) -> { }, + // decoder + (buf) -> buf.asByteBuf(), + // message consumer + (nettyByteBuf, contextSupplier) -> + { + ClientApi.INSTANCE.serverMessageReceived(nettyByteBuf); + } + ); + } + + public static boolean isReceivedProtocolVersionAcceptable(String versionString) + { + try + { + // may be necessary in order to connect to vanilla servers? + if (versionString.toLowerCase().contains("allowvanilla")) + { + return true; + } + + int version = Integer.parseInt(versionString); + return ModInfo.PROTOCOL_VERSION == version; + } + catch (NumberFormatException ignored) + { + return false; + } + } } diff --git a/forge/src/main/java/com/seibel/distanthorizons/forge/ForgeMain.java b/forge/src/main/java/com/seibel/distanthorizons/forge/ForgeMain.java index 0cf2e3c04..801d26ed0 100644 --- a/forge/src/main/java/com/seibel/distanthorizons/forge/ForgeMain.java +++ b/forge/src/main/java/com/seibel/distanthorizons/forge/ForgeMain.java @@ -131,8 +131,9 @@ public class ForgeMain implements LodForgeMethodCaller ModLoadingContext.get().registerExtensionPoint(ConfigScreenHandler.ConfigScreenFactory.class, () -> new ConfigScreenHandler.ConfigScreenFactory((client, parent) -> GetConfigScreen.getScreen(parent))); #endif - - + + ForgeClientProxy.setupNetworkingListeners(event); + LOGGER.info(ModInfo.READABLE_NAME + " Initialized"); ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterDhInitEvent.class, null); diff --git a/forge/src/main/java/com/seibel/distanthorizons/forge/mixins/client/MixinClientPacketListener.java b/forge/src/main/java/com/seibel/distanthorizons/forge/mixins/client/MixinClientPacketListener.java index c3e956bf3..67c3791fd 100644 --- a/forge/src/main/java/com/seibel/distanthorizons/forge/mixins/client/MixinClientPacketListener.java +++ b/forge/src/main/java/com/seibel/distanthorizons/forge/mixins/client/MixinClientPacketListener.java @@ -1,11 +1,8 @@ package com.seibel.distanthorizons.forge.mixins.client; -import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper; import com.seibel.distanthorizons.core.api.internal.ClientApi; -import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientPacketListener; 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; @@ -13,17 +10,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(ClientPacketListener.class) public class MixinClientPacketListener { - @Shadow - private ClientLevel level; - - - - /** THIS EXPLANATION IS WRITTEN BY FABRIC. - * An explanation why we unload entities during onGameJoin: (On in our remapping name case, handleLogin(TODO: CHECK)) - * 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. - */ + // TODO update fabric version as well + @Inject(method = "handleLogin", at = @At("HEAD")) void onHandleLoginStart(CallbackInfo ci) { @@ -32,24 +20,5 @@ public class MixinClientPacketListener } @Inject(method = "handleLogin", at = @At("RETURN")) void onHandleLoginEnd(CallbackInfo ci) { ClientApi.INSTANCE.onClientOnlyConnected(); } - - @Inject(method = "handleRespawn", at = @At("HEAD")) - void onHandleRespawnStart(CallbackInfo ci) { ClientApi.INSTANCE.clientLevelUnloadEvent(ClientLevelWrapper.getWrapper(level)); } - @Inject(method = "handleRespawn", at = @At("RETURN")) - void onHandleRespawnEnd(CallbackInfo ci) { ClientApi.INSTANCE.clientLevelLoadEvent(ClientLevelWrapper.getWrapper(level)); } - - #if PRE_MC_1_19_4 - @Inject(method = "cleanup", at = @At("HEAD")) - #else - @Inject(method = "close", at = @At("HEAD")) - #endif - void onCleanupStart(CallbackInfo ci) - { - // TODO which unload method should be used? do we need both? - if (level != null) - { - ClientApi.INSTANCE.clientLevelUnloadEvent(ClientLevelWrapper.getWrapper(level)); - } - } - + } diff --git a/forge/src/main/java/com/seibel/distanthorizons/forge/mixins/client/MixinLevelRenderer.java b/forge/src/main/java/com/seibel/distanthorizons/forge/mixins/client/MixinLevelRenderer.java index 53d158446..7761a05aa 100644 --- a/forge/src/main/java/com/seibel/distanthorizons/forge/mixins/client/MixinLevelRenderer.java +++ b/forge/src/main/java/com/seibel/distanthorizons/forge/mixins/client/MixinLevelRenderer.java @@ -51,7 +51,7 @@ import java.nio.FloatBuffer; * before Minecraft starts rendering blocks. * If this wasn't done, and we used Forge's * render last event, the LODs would render on top - * of the normal terrain. + * of the normal terrain.

* * This is also the mixin for rendering the clouds *