Fix Forge client-side rendering

This commit is contained in:
James Seibel
2023-07-27 21:37:00 -05:00
parent a2ac2e00da
commit 1ee02211b9
6 changed files with 173 additions and 140 deletions
@@ -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)
{
@@ -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;
}
}
}
@@ -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);
@@ -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));
}
}
}
@@ -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. <br><br>
*
* This is also the mixin for rendering the clouds
*