From 9be69863aefa049d487758b340f15b9e927ff912 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 29 Jul 2023 09:04:02 -0500 Subject: [PATCH] Improve networked multiverse support Also change "WorldChanged" -> "LevelChanged" --- .../distanthorizons/coreapi/ModInfo.java | 6 ++ .../core/api/internal/ClientApi.java | 84 ++++++++++++++----- 2 files changed, 69 insertions(+), 21 deletions(-) diff --git a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java index b32cb2b9d..74a336a10 100644 --- a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java +++ b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java @@ -28,6 +28,8 @@ public final class ModInfo public static final String ID = "distanthorizons"; /** The internal protocol version used for networking */ public static final int PROTOCOL_VERSION = 1; + /** The protocol version used for multiverse networking */ + public static final int MULTIVERSE_PLUGIN_PROTOCOL_VERSION = 1; /** The internal mod name */ public static final String NAME = "DistantHorizons"; /** Human-readable version of NAME */ @@ -43,4 +45,8 @@ public final class ModInfo /** This version should be updated whenever non-breaking fixes are added to the DH API */ public static final int API_PATH_VERSION = 0; + public static final String NETWORKING_RESOURCE_NAMESPACE = "distant_horizons"; + public static final String MULTIVERSE_PLUGIN_NAMESPACE = "world_control"; + + } 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 b97e8596e..9b4116ba5 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 @@ -166,7 +166,7 @@ public class ClientApi public void clientLevelUnloadEvent(IClientLevelWrapper level) { - LOGGER.info("Client level "+level+" unloading."); + LOGGER.info("Unloading client level ["+level+"]."); AbstractDhWorld world = SharedApi.getAbstractDhWorld(); if (world != null) @@ -174,18 +174,24 @@ public class ClientApi world.unloadLevel(level); ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level)); } + else + { + this.waitingClientLevels.remove(level); + } } - public void clientLevelLoadEvent(IClientLevelWrapper level) + public void clientLevelLoadEvent(IClientLevelWrapper level) { this.clientLevelLoadEvent(level, false); } + public void multiverseClientLevelLoadEvent(IClientLevelWrapper level) { this.clientLevelLoadEvent(level, true); } + private void clientLevelLoadEvent(IClientLevelWrapper level, boolean isServerCommunication) { - if (this.isServerCommunicationEnabled) + if (this.isServerCommunicationEnabled && !isServerCommunication) { LOGGER.info("Server supports communication, deferring loading."); return; } - LOGGER.info("Client level " + level + " loading."); + LOGGER.info("Loading "+(isServerCommunication ? "Multiverse" : "")+" client level [" + level + "]."); AbstractDhWorld world = SharedApi.getAbstractDhWorld(); if (world != null) @@ -326,22 +332,56 @@ public class ClientApi /** @param byteBuf is Netty's {@link ByteBuffer} wrapper. */ public void serverMessageReceived(ByteBuf byteBuf) { + // either value can be set to true to debug the received byte stream + boolean stopAndDisplayInputAsByteArray = false; + boolean stopAndDisplayInputAsString = false; + if (stopAndDisplayInputAsByteArray || stopAndDisplayInputAsString) + { + String messageString = ""; + if (stopAndDisplayInputAsByteArray) + { + int byteCount = byteBuf.readableBytes(); + byte[] arr = new byte[byteCount]; + StringBuilder stringBuilder = new StringBuilder("Server message received: ["); + for (int i = 0; i < byteCount; i++) + { + arr[i] = byteBuf.readByte(); + stringBuilder.append(arr[i]); + } + stringBuilder.append("]"); + + messageString = stringBuilder.toString(); + } + else if (stopAndDisplayInputAsString) + { + messageString = byteBuf.toString(StandardCharsets.UTF_8); + } + + // this is logged as an error so it is easier to see in an Intellij log + LOGGER.error(messageString); + return; + } + + + + // It is important to ensure malicious server input is ignored. if (this.serverNetworkingIsMalformed) { return; } + // check that the incoming message is within the expected size short commandLength = byteBuf.readShort(); - if (commandLength > 32) // TODO 32 should be put into a constant somewhere, what does it represent? + if (commandLength < 1 || commandLength > 32) { - LOGGER.error("Server sent command > 32"); + LOGGER.error("Server command length ["+commandLength+"] outside the expected range of 1 to 32 (inclusive)."); ClientApi.INSTANCE.serverNetworkingIsMalformed = true; return; } - - String eventType = null; + // parse the command + String eventType; try { eventType = byteBuf.readCharSequence(commandLength, StandardCharsets.UTF_8).toString(); @@ -355,27 +395,29 @@ public class ClientApi switch (eventType) { case "ServerCommsEnabled": - LOGGER.info("Server supports DH protocol."); + LOGGER.info("Server supports DH multiverse protocol."); ClientApi.INSTANCE.isServerCommunicationEnabled = true; 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. + MC.executeOnRenderThread(() -> + { + // Unload the current world, since it may be wrong. + // A followup WorldChanged event should be received from the server soon after this. + LOGGER.info("Unloading current client level so the server can define the correct multiverse level."); this.clientLevelUnloadEvent((IClientLevelWrapper) MC.getWrappedClientWorld()); }); break; - case "WorldChanged": - short worldKeyLength = byteBuf.readShort(); - if (worldKeyLength > 128) // TODO 128 should be put into a constant somewhere + case "LevelChanged": + short levelKeyLength = byteBuf.readShort(); + if (levelKeyLength < 1 || levelKeyLength > 128) // TODO 128 should be put into a constant somewhere { - LOGGER.error("Server sent worldKey > 128"); + LOGGER.error("Server [LevelChanged] command length ["+commandLength+"] outside the expected range of 1 to 128 (inclusive)."); this.serverNetworkingIsMalformed = true; return; } - String worldKey = byteBuf.readCharSequence(worldKeyLength, StandardCharsets.UTF_8).toString(); - if (!worldKey.matches("[a-zA-Z0-9_]+")) + String levelKey = byteBuf.readCharSequence(levelKeyLength, StandardCharsets.UTF_8).toString(); + if (!levelKey.matches("[a-zA-Z0-9_]+")) { LOGGER.error("Server sent invalid world key name, and is being ignored."); this.isServerCommunicationEnabled = false; @@ -383,15 +425,15 @@ public class ClientApi return; } - LOGGER.info("Server sent world change event: " + worldKey); + LOGGER.info("Server level change event received, changing the level to ["+levelKey+"]."); MC.executeOnRenderThread(() -> { if (MC.getWrappedClientWorld() != null) { this.clientLevelUnloadEvent((IClientLevelWrapper) MC.getWrappedClientWorld()); } - IServerKeyedClientLevel clientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(MC.getWrappedClientWorld(), worldKey); + IServerKeyedClientLevel clientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(MC.getWrappedClientWorld(), levelKey); KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel); - this.clientLevelLoadEvent(clientLevel); + this.multiverseClientLevelLoadEvent(clientLevel); }); break; }