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
*