diff --git a/api/src/main/java/com/seibel/distanthorizons/api/methods/events/interfaces/IDhServerMessageRecieved.java b/api/src/main/java/com/seibel/distanthorizons/api/methods/events/interfaces/IDhServerMessageReceived.java similarity index 69% rename from api/src/main/java/com/seibel/distanthorizons/api/methods/events/interfaces/IDhServerMessageRecieved.java rename to api/src/main/java/com/seibel/distanthorizons/api/methods/events/interfaces/IDhServerMessageReceived.java index dd31f79b7..378f8fe3d 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/methods/events/interfaces/IDhServerMessageRecieved.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/methods/events/interfaces/IDhServerMessageReceived.java @@ -1,15 +1,15 @@ package com.seibel.distanthorizons.api.methods.events.interfaces; /** - * * @author Cailin */ -public interface IDhServerMessageRecieved extends IDhApiEvent +public interface IDhServerMessageReceived extends IDhApiEvent { /** * Triggered when a plugin message is received from the server. * @param channel The name of the channel this was received on. * @param message The message sent from the server. */ - void serverMessageRecieved(String channel, byte[] message); + void serverMessageReceived(String channel, byte[] message); + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java index aeabb36c8..d2fd8fb9f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java @@ -21,10 +21,9 @@ package com.seibel.distanthorizons.core.api.internal; import com.seibel.distanthorizons.api.methods.events.abstractEvents.*; import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; -import com.seibel.distanthorizons.core.level.IServerEnhancedClientLevel; -import com.seibel.distanthorizons.core.level.IEnhancedServerManager; +import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel; +import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager; import com.seibel.distanthorizons.core.world.*; -import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IFriendlyByteBuf; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.config.Config; @@ -45,20 +44,19 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; +import io.netty.buffer.ByteBuf; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.lwjgl.glfw.GLFW; -import java.nio.charset.Charset; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; /** * This holds the methods that should be called * by the host mod loader (Fabric, Forge, etc.). * Specifically for the client. - * - * @author James Seibel - * @version 2022-9-16 */ public class ClientApi { @@ -70,8 +68,7 @@ public class ClientApi public static TestRenderer testRenderer = new TestRenderer(); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); - private static final IEnhancedServerManager ENHANCED_SERVER_MANAGER - = SingletonInjector.INSTANCE.get(IEnhancedServerManager.class); + private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class); public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS); @@ -131,8 +128,8 @@ public class ClientApi } this.isServerCommunicationEnabled = false; this.serverIsMalformed = false; - ENHANCED_SERVER_MANAGER.setUseOverrideWrapper(false); - ENHANCED_SERVER_MANAGER.registerServerEnhancedLevel(null); + KEYED_CLIENT_LEVEL_MANAGER.setUseOverrideWrapper(false); + KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(null); } } @@ -177,15 +174,21 @@ public class ClientApi public void clientLevelLoadEvent(IClientLevelWrapper level) { - if (ENABLE_EVENT_LOGGING) { - if (this.isServerCommunicationEnabled) { + if (this.isServerCommunicationEnabled) + { + if (ENABLE_EVENT_LOGGING) + { LOGGER.info("Server supports communication, deferring loading."); - return; } - + return; + } + + + if (ENABLE_EVENT_LOGGING) + { LOGGER.info("Client level " + level + " loading."); } - + AbstractDhWorld world = SharedApi.getAbstractDhWorld(); if (world != null) { @@ -194,11 +197,11 @@ public class ClientApi } } - public void serverLevelLoadEvent(IServerEnhancedClientLevel level) + public void serverLevelLoadEvent(IServerKeyedClientLevel level) { if (ENABLE_EVENT_LOGGING) { - LOGGER.info("Server level " + level + " (" + level.getServerWorldKey() + ") loading."); + LOGGER.info("Server level " + level + " (" + level.getServerLevelKey() + ") loading."); } AbstractDhWorld world = SharedApi.getAbstractDhWorld(); @@ -258,54 +261,65 @@ public class ClientApi } profiler.pop(); } - - public void serverMessageReceived(IFriendlyByteBuf buf) + + /** @param byteBuf is Netty's {@link ByteBuffer} wrapper. */ + public void serverMessageReceived(ByteBuf byteBuf) { // It is important to ensure malicious server input is ignored. - if(this.serverIsMalformed) { + if (this.serverIsMalformed) + { return; } - short commandLength = buf.readShort(); - if(commandLength > 32) { + + short commandLength = byteBuf.readShort(); + if (commandLength > 32) + { LOGGER.error("Server sent command > 32"); ClientApi.INSTANCE.serverIsMalformed = true; return; } - String eventType = buf.readCharSequence(commandLength, Charset.forName("UTF-8")).toString(); - switch(eventType) { + + String eventType = byteBuf.readCharSequence(commandLength, StandardCharsets.UTF_8).toString(); + switch (eventType) + { case "ServerCommsEnabled": LOGGER.info("Server supports DH protocol."); ClientApi.INSTANCE.isServerCommunicationEnabled = true; - ENHANCED_SERVER_MANAGER.setUseOverrideWrapper(true); - MC.execute(() -> { + KEYED_CLIENT_LEVEL_MANAGER.setUseOverrideWrapper(true); + MC.executeOnRenderThread(() -> { // Go ahead and unload the current world, because it may be wrong. We expect // a followup WorldChanged event from the server soon anyways. - clientLevelUnloadEvent((IClientLevelWrapper) MC.getWrappedClientWorld()); + this.clientLevelUnloadEvent((IClientLevelWrapper) MC.getWrappedClientWorld()); }); break; + case "WorldChanged": - short worldKeyLength = buf.readShort(); - if(worldKeyLength > 128) { + short worldKeyLength = byteBuf.readShort(); + if (worldKeyLength > 128) + { LOGGER.error("Server sent worldKey > 128"); this.serverIsMalformed = true; return; } - String worldKey = buf.readCharSequence(worldKeyLength, Charset.forName("UTF-8")).toString(); - if(!worldKey.matches("[a-zA-Z0-9_]+")) { + + String worldKey = byteBuf.readCharSequence(worldKeyLength, StandardCharsets.UTF_8).toString(); + if (!worldKey.matches("[a-zA-Z0-9_]+")) + { LOGGER.error("Server sent invalid world key name, and is being ignored."); this.isServerCommunicationEnabled = false; this.serverIsMalformed = true; return; } + LOGGER.info("Server sent world change event: " + worldKey); - MC.execute(() -> { - if(MC.getWrappedClientWorld() != null) { - clientLevelUnloadEvent((IClientLevelWrapper) MC.getWrappedClientWorld()); + MC.executeOnRenderThread(() -> { + if (MC.getWrappedClientWorld() != null) + { + this.clientLevelUnloadEvent((IClientLevelWrapper) MC.getWrappedClientWorld()); } - IServerEnhancedClientLevel clientLevel - = ENHANCED_SERVER_MANAGER.getServerEnhancedLevel(MC.getWrappedClientWorld(), worldKey); - ENHANCED_SERVER_MANAGER.registerServerEnhancedLevel(clientLevel); - serverLevelLoadEvent(clientLevel); + IServerKeyedClientLevel clientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(MC.getWrappedClientWorld(), worldKey); + KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel); + this.serverLevelLoadEvent(clientLevel); }); break; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java index 285009d32..a7ec08d8d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java @@ -5,10 +5,11 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.file.subDimMatching.SubDimensionLevelMatcher; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.api.enums.config.EServerFolderNameMode; -import com.seibel.distanthorizons.core.level.IServerEnhancedClientLevel; +import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel; import com.seibel.distanthorizons.core.util.objects.ParsedIp; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import javax.annotation.Nullable; @@ -26,17 +27,17 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure { final File folder; private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class); public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]"; - SubDimensionLevelMatcher fileMatcher = null; - final HashMap levelToFileMap = new HashMap<>(); + SubDimensionLevelMatcher subDimMatcher = null; + final HashMap levelWrapperToFileMap = new HashMap<>(); public ClientOnlySaveStructure() { - this.folder = new File(MC_CLIENT.getGameDirectory().getPath() + - File.separatorChar + "Distant_Horizons_server_data" + File.separatorChar + getServerFolderName()); + this.folder = new File(getSaveStructureFolderPath()); if (!this.folder.exists()) { @@ -55,42 +56,51 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure //================// @Override - public File getLevelFolder(ILevelWrapper level) + public File getLevelFolder(ILevelWrapper levelWrapper) { - return this.levelToFileMap.computeIfAbsent(level, (newLevel) -> + return this.levelWrapperToFileMap.computeIfAbsent(levelWrapper, (newLevelWrapper) -> { - if (newLevel instanceof IServerEnhancedClientLevel) { - IServerEnhancedClientLevel secl = (IServerEnhancedClientLevel) newLevel; + // Use the server provided key if one was provided + if (newLevelWrapper instanceof IServerKeyedClientLevel) + { + IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper; + LOGGER.info("Loading level "+newLevelWrapper.getDimensionType().getDimensionName()+" with key: "+keyedClientLevel.getServerLevelKey()); // This world was identified by the server directly, so we can know for sure which folder to use. - File seclFolder = new File(this.folder.getParent(), MC_CLIENT.getCurrentServerIp().toString()); - seclFolder = new File(seclFolder, secl.getServerWorldKey()); - return seclFolder; + return new File(getSaveStructureFolderPath() + File.separatorChar + keyedClientLevel.getServerLevelKey()); } - - if (Config.Client.Advanced.Multiplayer.multiverseSimilarityRequiredPercent.get() == 0) + + + // use multiverse matching if enabled + if (Config.Client.Advanced.Multiplayer.multiverseSimilarityRequiredPercent.get() != 0) { - if (this.fileMatcher != null) + // create the matcher if one doesn't exist + if (this.subDimMatcher == null || !this.subDimMatcher.isFindingLevel(newLevelWrapper)) { - this.fileMatcher.close(); - this.fileMatcher = null; + LOGGER.info("Loading level " + newLevelWrapper.getDimensionType().getDimensionName()); + this.subDimMatcher = new SubDimensionLevelMatcher(newLevelWrapper, this.folder, + this.getMatchingLevelFolders(newLevelWrapper).toArray(new File[0] /* surprisingly we don't need to create an array of any specific size for this to work */)); } - return this.getLevelFolderWithoutSimilarityMatching(newLevel); + + File levelFile = this.subDimMatcher.tryGetLevel(); + if (levelFile != null) + { + this.subDimMatcher.close(); + this.subDimMatcher = null; + } + return levelFile; } - - if (this.fileMatcher == null || !this.fileMatcher.isFindingLevel(newLevel)) + + // we aren't using multiverse matching, shut down the matcher + // TODO this additional call may not be needed + if (this.subDimMatcher != null) { - LOGGER.info("Loading level for world " + newLevel.getDimensionType().getDimensionName()); - this.fileMatcher = new SubDimensionLevelMatcher(newLevel, this.folder, - this.getMatchingLevelFolders(newLevel).toArray(new File[0] /* surprisingly we don't need to create an array of any specific size for this to work */)); + this.subDimMatcher.close(); + this.subDimMatcher = null; } - - File levelFile = this.fileMatcher.tryGetLevel(); - if (levelFile != null) - { - this.fileMatcher.close(); - this.fileMatcher = null; - } - return levelFile; + + + // get the default folder + return this.getLevelFolderWithoutSimilarityMatching(newLevelWrapper); }); } @@ -138,7 +148,7 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure @Override public File getRenderCacheFolder(ILevelWrapper level) { - File levelFolder = this.levelToFileMap.get(level); + File levelFolder = this.levelWrapperToFileMap.get(level); if (levelFolder == null) { return null; @@ -150,7 +160,7 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure @Override public File getFullDataFolder(ILevelWrapper level) { - File levelFolder = this.levelToFileMap.get(level); + File levelFolder = this.levelWrapperToFileMap.get(level); if (levelFolder == null) { return null; @@ -182,7 +192,16 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure // a valid level folder needs to have DH specific folders in it return files != null && files.length != 0; } - + + + private static String getSaveStructureFolderPath() + { + String path = MC_SHARED.getInstallationDirectory().getPath() + File.separatorChar + + "Distant_Horizons_server_data" + File.separatorChar + + getServerFolderName(); + return path; + } + /** Generated from the server the client is currently connected to. */ private static String getServerFolderName() { @@ -222,15 +241,15 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure // This fixes some issues when the server is named something in other languages return new PercentEscaper("", true).escape(folderName); } - - - + + + //==================// // override methods // //==================// @Override - public void close() { this.fileMatcher.close(); } + public void close() { this.subDimMatcher.close(); } @Override public String toString() { return "[" + this.getClass().getSimpleName() + "@" + this.folder.getName() + "]"; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/IEnhancedServerManager.java b/core/src/main/java/com/seibel/distanthorizons/core/level/IEnhancedServerManager.java deleted file mode 100644 index b24cdaf5b..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/IEnhancedServerManager.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.seibel.distanthorizons.core.level; - -import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; -import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; - -public interface IEnhancedServerManager extends IBindable { - /** - * Called when a client level is wrapped by a ServerEnhancedClientLevel, for integration into mod internals. - * @param clientLevel - */ - void registerServerEnhancedLevel(IServerEnhancedClientLevel clientLevel); - - /** - * Returns a new instance of a ServerEnhancedClientLevel. - * @param level - * @param worldKey - * @return - */ - IServerEnhancedClientLevel getServerEnhancedLevel(ILevelWrapper level, String worldKey); - - /** - * Sets the LOD engine to use the override wrapper, if the server has communication enabled. - * @param useOverrideWrapper - */ - void setUseOverrideWrapper(boolean useOverrideWrapper); -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/IKeyedClientLevelManager.java b/core/src/main/java/com/seibel/distanthorizons/core/level/IKeyedClientLevelManager.java new file mode 100644 index 000000000..b8e6a324e --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/IKeyedClientLevelManager.java @@ -0,0 +1,24 @@ +package com.seibel.distanthorizons.core.level; + +import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; + +/** + * Handles level overrides initiated by servers that + * support differentiating between different levels. + */ +public interface IKeyedClientLevelManager extends IBindable +{ + /** Called when a client level is wrapped by a ServerEnhancedClientLevel, for integration into mod internals. */ + void setServerKeyedLevel(IServerKeyedClientLevel clientLevel); + IServerKeyedClientLevel getOverrideWrapper(); + + /** Returns a new instance of a ServerEnhancedClientLevel. */ + IServerKeyedClientLevel getServerKeyedLevel(ILevelWrapper level, String serverLevelKey); + + /** Sets the LOD engine to use the override wrapper, if the server has communication enabled. */ + void setUseOverrideWrapper(boolean useOverrideWrapper); + boolean getUseOverrideWrapper(); + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/IServerEnhancedClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/IServerEnhancedClientLevel.java deleted file mode 100644 index 7f50aad1d..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/IServerEnhancedClientLevel.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.seibel.distanthorizons.core.level; - -import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; - -/** - * Enhances an IClientLevelWrapper with server provided world information. - */ -public interface IServerEnhancedClientLevel extends IClientLevelWrapper -{ - /** - * Returns the world key, which is used to select the correct folder on the client. - * @return - */ - String getServerWorldKey(); -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/IServerKeyedClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/IServerKeyedClientLevel.java new file mode 100644 index 000000000..24577e59e --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/IServerKeyedClientLevel.java @@ -0,0 +1,11 @@ +package com.seibel.distanthorizons.core.level; + +import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; + +/** Enhances a {@link IClientLevelWrapper} with server provided level information. */ +public interface IServerKeyedClientLevel extends IClientLevelWrapper +{ + /** Returns the level key, which is used to select the correct folder on the client. */ + String getServerLevelKey(); + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IFriendlyByteBuf.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IFriendlyByteBuf.java deleted file mode 100644 index fac5fac2b..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IFriendlyByteBuf.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft; - -import java.nio.charset.Charset; - -/** - * Interface that wraps the net.minecraft.network.FriendlyByteBuffer. - */ -public interface IFriendlyByteBuf { - - short readShort(); - - CharSequence readCharSequence(int length, Charset charset); -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftClientWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftClientWrapper.java index 57f8b573a..05f4e8979 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftClientWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftClientWrapper.java @@ -139,10 +139,7 @@ public interface IMinecraftClientWrapper extends IBindable Object getOptionsObject(); - /** - * Executes a task on the Minecraft render thread. - * @param runnable - */ - void execute(Runnable runnable); + /** Executes the given task on Minecraft's render thread. */ + void executeOnRenderThread(Runnable runnable); }