diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index c2741fab5..1d4d01d22 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -755,6 +755,8 @@ public class Config public static class Multiplayer { + public static ConfigCategory serverNetworking = new ConfigCategory.Builder().set(ServerNetworking.class).build(); + public static ConfigEntry serverFolderNameMode = new ConfigEntry.Builder() .set(EServerFolderNameMode.NAME_ONLY) .comment("" @@ -800,29 +802,46 @@ public class Config + "") .build(); - // not currently implemented - public static ConfigEntry enableServerNetworking = new ConfigEntry.Builder() - .set(false) - .comment("" - + "Attention: \n" - + " 1. This feature is not fully implemented. \n" - + " 2. If you really want to use it, enable it only on trusted server/with trusted players. \n" - + "\n" - + "If true Distant Horizons will attempt to communicate with the connected \n" - + "server in order to load LODs outside your vanilla render distance. \n" - + "\n" - + "Note: This requires DH to be installed on the server in order to function. \n" - + "") - .build(); - - public static ConfigEntry serverNetworkingRateLimit = new ConfigEntry.Builder() - .setMinDefaultMax(1, 20, 100) - .comment("" - + "Limits the amount of sent/processed LOD requests concurrently. \n" - + "\n" - + "Note: Server can set its own rate limit. \n" - + "") - .build(); + // TODO Write strings + public static class ServerNetworking + { + public static ConfigEntry enableServerNetworking = new ConfigEntry.Builder() + .set(true) + .comment("" + + "Attention: \n" + + " 1. This feature is not fully implemented. \n" + + " 2. If you really want to use it, enable it only on trusted server/with trusted players. \n" + + "\n" + + "If true Distant Horizons will attempt to communicate with the connected \n" + + "server in order to load LODs outside your vanilla render distance. \n" + + "\n" + + "Note: This requires DH to be installed on the server in order to function. \n" + + "") + .build(); + + public static ConfigEntry requestRateLimit = new ConfigEntry.Builder() + .setMinDefaultMax(1, 20, 100) + .comment("" + + "Limits the amount of sent/processed LOD requests concurrently. \n" + + "\n" + + "Note: Server can set its own rate limit. \n" + + "") + .build(); + + public static ConfigEntry enableRealTimeUpdates = new ConfigEntry.Builder() + .set(false) + .comment("" + + "Enables real time updates from server." + + "") + .build(); + + public static ConfigEntry enablePostRelogUpdate = new ConfigEntry.Builder() + .set(false) + .comment("" + + "Enables updating of LODs after relog." + + "") + .build(); + } } @@ -1081,6 +1100,7 @@ public class Config } + // TODO write strings public static class Debugging { public static ConfigCategory debugWireframeRendering = new ConfigCategory.Builder().set(DebugWireframeRendering.class).build(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/RemoteFullDataFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/RemoteFullDataFileHandler.java index 35a70c537..544ac0e7b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/RemoteFullDataFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/RemoteFullDataFileHandler.java @@ -19,6 +19,7 @@ package com.seibel.distanthorizons.core.file.fullDatafile; +import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; @@ -26,12 +27,12 @@ import com.seibel.distanthorizons.core.generation.IWorldGenerationQueue; import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; -import com.seibel.distanthorizons.core.multiplayer.ClientNetworkState; +import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState; import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException; import com.seibel.distanthorizons.core.network.exceptions.InvalidSectionPosException; +import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException; import com.seibel.distanthorizons.core.network.messages.fullData.updates.FullDataChangeSummaryRequestMessage; import com.seibel.distanthorizons.core.network.messages.fullData.updates.FullDataChangeSummaryResponseMessage; -import com.seibel.distanthorizons.core.pos.DhLodPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; @@ -144,11 +145,10 @@ public class RemoteFullDataFileHandler extends GeneratedFullDataFileHandler @Override public FullDataMetaFile getFileIfExist(DhSectionPos pos) { - // This feature is broken - same data may produce different hashes, apparently - if (true) + if (this.networkState == null || !this.isFileUnloaded(pos)) return super.getFileIfExist(pos); - if (this.networkState == null || !this.isFileUnloaded(pos)) + if (!this.networkState.config.postRelogUpdateEnabled) return super.getFileIfExist(pos); FullDataMetaFile metaFile = super.getFileIfExist(pos); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java index fdb7a3d12..b207eba97 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java @@ -8,7 +8,8 @@ import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.f3.F3Screen; -import com.seibel.distanthorizons.core.multiplayer.ClientNetworkState; +import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState; +import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException; import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException; import com.seibel.distanthorizons.core.network.messages.fullData.generation.FullDataSourceRequestMessage; import com.seibel.distanthorizons.core.network.messages.fullData.generation.FullDataSourceResponseMessage; @@ -178,13 +179,8 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug waitingTasks.remove(sectionPos); LOGGER.debug("FullDataSourceResponseMessage " + sectionPos); + CompleteFullDataSource fullDataSource = response.getFullDataSource(sectionPos, level); - - // FIXME Add dimension context to request instead - // Check is dimension has been switched - received data may no longer be relevant - if (fullDataSource == null) - throw new CancellationException(); - Consumer chunkDataConsumer = entry.tracker.getChunkDataConsumer(); // FIXME Why keeping a reference in first place @@ -193,6 +189,10 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug fullDataSource.splitIntoChunkSizedAccessors(chunkDataConsumer); } + catch (InvalidLevelException ignored) + { + // We're too late + } catch (ChannelException | RateLimitedException e) { if (e instanceof RateLimitedException) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java index 3b324752a..3ee003ff1 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java @@ -26,7 +26,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataFileHandl import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.generation.WorldRemoteGenerationQueue; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; -import com.seibel.distanthorizons.core.multiplayer.ClientNetworkState; +import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState; import com.seibel.distanthorizons.core.network.NetworkClient; import com.seibel.distanthorizons.core.network.ScopedNetworkEventSource; import com.seibel.distanthorizons.core.network.messages.fullData.updates.FullDataPartialUpdateMessage; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index bc75fdd5e..8ae9c8ac0 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -19,7 +19,6 @@ package com.seibel.distanthorizons.core.level; -import com.seibel.distanthorizons.core.config.AppliedConfigState; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource; @@ -28,13 +27,13 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.I import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile; import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; -import com.seibel.distanthorizons.core.multiplayer.ServerPlayerState; -import com.seibel.distanthorizons.core.multiplayer.RemotePlayerConnectionHandler; +import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState; +import com.seibel.distanthorizons.core.multiplayer.server.RemotePlayerConnectionHandler; import com.seibel.distanthorizons.core.network.ScopedNetworkEventSource; import com.seibel.distanthorizons.core.network.NetworkServer; -import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException; import com.seibel.distanthorizons.core.network.exceptions.InvalidSectionPosException; import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException; +import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException; import com.seibel.distanthorizons.core.network.messages.base.CancelMessage; import com.seibel.distanthorizons.core.network.messages.fullData.generation.FullDataSourceRequestMessage; import com.seibel.distanthorizons.core.network.messages.fullData.generation.FullDataSourceResponseMessage; @@ -72,7 +71,6 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel private final LinkedBlockingQueue worldGenLoopingQueue = new LinkedBlockingQueue<>(); private final ConcurrentMap incompleteDataSources = new ConcurrentHashMap<>(); private final ConcurrentMap fullDataRequests = new ConcurrentHashMap<>(); - private final AppliedConfigState rateLimitConfig = new AppliedConfigState<>(Config.Client.Advanced.Multiplayer.serverNetworkingRateLimit); public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, RemotePlayerConnectionHandler remotePlayerConnectionHandler) @@ -94,10 +92,10 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel { this.eventSource.registerHandler(FullDataSourceRequestMessage.class, remotePlayerConnectionHandler.currentLevelOnly(this, (msg, serverPlayerState) -> { - if (serverPlayerState.pendingFullDataRequests.incrementAndGet() > rateLimitConfig.get()) + if (serverPlayerState.pendingFullDataRequests.incrementAndGet() > serverPlayerState.config.getFullDataRequestRateLimit()) { serverPlayerState.pendingFullDataRequests.decrementAndGet(); - msg.sendResponse(new RateLimitedException("Max concurrent requests: " + rateLimitConfig.get())); + msg.sendResponse(new RateLimitedException("Max concurrent requests: " + serverPlayerState.config.getFullDataRequestRateLimit())); return; } @@ -110,7 +108,7 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel }); return newEntry; }); - // If this fails, current entry is being drained and need create another one + // If this fails, current entry is being drained and need to create another one if (entry.requestCollectionSemaphore.tryAcquire()) { fullDataRequests.put(msg.futureId, entry); @@ -130,6 +128,12 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel this.eventSource.registerHandler(FullDataChangeSummaryRequestMessage.class, remotePlayerConnectionHandler.currentLevelOnly(this, (msg, serverPlayerState) -> { + if (!Config.Client.Advanced.Multiplayer.ServerNetworking.enablePostRelogUpdate.get()) + { + msg.sendResponse(new RequestRejectedException("Operation is disabled from config.")); + return; + } + // Load files and check checksums HashSet changedPosList = new HashSet<>(); for (Map.Entry entry : msg.checksums.entrySet()) @@ -226,11 +230,16 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel if (future == null) return null; + if (!Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates.get()) + return future; + future.thenAccept(chunkSizedFullDataAccessor -> { for (ServerPlayerState serverPlayerState : remotePlayerConnectionHandler.getConnectedPlayers()) { - if (chunk.getChunkPos().distance(new DhChunkPos(serverPlayerState.serverPlayer.getPosition())) <= serverPlayerState.config.renderDistance) + if (serverPlayerState.config.isRealTimeUpdatesEnabled()) continue; + + if (chunk.getChunkPos().distance(new DhChunkPos(serverPlayerState.serverPlayer.getPosition())) <= serverPlayerState.config.getRenderDistance()) serverPlayerState.channelContext.writeAndFlush(new FullDataPartialUpdateMessage(chunkSizedFullDataAccessor, this)); } }); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/MultiplayerConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/MultiplayerConfig.java deleted file mode 100644 index f70cc6c0e..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/MultiplayerConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seibel.distanthorizons.core.multiplayer; - -import com.seibel.distanthorizons.core.network.protocol.INetworkObject; -import io.netty.buffer.ByteBuf; - -public class MultiplayerConfig implements INetworkObject -{ - public int renderDistance; - public int fullDataRequestRateLimit; - - - @Override - public void encode(ByteBuf out) - { - out.writeInt(this.renderDistance); - out.writeInt(this.fullDataRequestRateLimit); - } - - @Override - public void decode(ByteBuf in) - { - this.renderDistance = in.readInt(); - this.fullDataRequestRateLimit = in.readInt(); - } - - @Override public String toString() - { - return "MultiplayerConfig{" + - "renderDistance=" + renderDistance + - ", fullDataRequestRateLimit=" + fullDataRequestRateLimit + - '}'; - } - -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ClientNetworkState.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientNetworkState.java similarity index 69% rename from core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ClientNetworkState.java rename to core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientNetworkState.java index 59acd6b8b..4870863b5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ClientNetworkState.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientNetworkState.java @@ -1,7 +1,8 @@ -package com.seibel.distanthorizons.core.multiplayer; +package com.seibel.distanthorizons.core.multiplayer.client; -import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.multiplayer.config.MultiplayerConfig; +import com.seibel.distanthorizons.core.multiplayer.config.MultiplayerConfigChangeListener; import com.seibel.distanthorizons.core.network.ScopedNetworkEventSource; import com.seibel.distanthorizons.core.network.NetworkClient; import com.seibel.distanthorizons.core.network.messages.base.AckMessage; @@ -20,6 +21,7 @@ public class ClientNetworkState implements Closeable private final NetworkClient client; private final UUID playerUUID; public MultiplayerConfig config = new MultiplayerConfig(); + private final MultiplayerConfigChangeListener configChangeListener = new MultiplayerConfigChangeListener(this::onConfigChanged); /** * Returns the client used by this instance.

@@ -31,12 +33,13 @@ public class ClientNetworkState implements Closeable * Constructs a new instance. * * @param networkClient Client to use. It is assumed that this client will be at full control by this instance. - * @param playerUUID UUID of a player connected + * @param playerUUID UUID of a player connected. */ public ClientNetworkState(NetworkClient networkClient, UUID playerUUID) { this.client = networkClient; this.playerUUID = playerUUID; + this.registerNetworkHandlers(); this.client.startConnecting(); } @@ -48,23 +51,28 @@ public class ClientNetworkState implements Closeable LOGGER.info("Connected to server: "+helloMessage.getChannelContext().channel().remoteAddress()); this.getClient().sendRequest(new PlayerUUIDMessage(playerUUID), AckMessage.class) - .thenCompose(ack -> this.getClient().sendRequest(new RemotePlayerConfigMessage(new MultiplayerConfig() - {{ - renderDistance = Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get(); - fullDataRequestRateLimit = Config.Client.Advanced.Multiplayer.serverNetworkingRateLimit.get(); - }}), RemotePlayerConfigMessage.class)) - .thenAccept(msg -> { - this.config = msg.payload; - }) + .thenAccept(ack -> this.getClient().sendMessage(new RemotePlayerConfigMessage(new MultiplayerConfig()))) .exceptionally(throwable -> { LOGGER.error("Error while fetching server's config", throwable); return null; }); }); + + this.client.registerHandler(RemotePlayerConfigMessage.class, msg -> + { + LOGGER.info("Connection config was changed: " + msg.payload); + this.config = (MultiplayerConfig) msg.payload; + }); + } + + private void onConfigChanged() + { + this.getClient().sendMessage(new RemotePlayerConfigMessage(new MultiplayerConfig())); } public void close() { + this.configChangeListener.close(); this.client.close(); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/AbstractMultiplayerConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/AbstractMultiplayerConfig.java new file mode 100644 index 000000000..bfcd264b1 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/AbstractMultiplayerConfig.java @@ -0,0 +1,23 @@ +package com.seibel.distanthorizons.core.multiplayer.config; + +import com.seibel.distanthorizons.core.network.protocol.INetworkObject; +import io.netty.buffer.ByteBuf; + +public abstract class AbstractMultiplayerConfig implements INetworkObject +{ + public abstract int getRenderDistance(); + public abstract int getFullDataRequestRateLimit(); + public abstract boolean isRealTimeUpdatesEnabled(); + public abstract boolean isPostRelogUpdateEnabled(); + + @Override + public void encode(ByteBuf out) + { + out.writeInt(this.getRenderDistance()); + out.writeInt(this.getFullDataRequestRateLimit()); + out.writeBoolean(this.isRealTimeUpdatesEnabled()); + out.writeBoolean(this.isPostRelogUpdateEnabled()); + } + + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfig.java new file mode 100644 index 000000000..fae45c402 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfig.java @@ -0,0 +1,39 @@ +package com.seibel.distanthorizons.core.multiplayer.config; + +import com.seibel.distanthorizons.core.config.Config; +import io.netty.buffer.ByteBuf; + +public class MultiplayerConfig extends AbstractMultiplayerConfig +{ + public int renderDistance = Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get(); + @Override public int getRenderDistance() { return renderDistance; } + + public int fullDataRequestRateLimit = Config.Client.Advanced.Multiplayer.ServerNetworking.requestRateLimit.get(); + @Override public int getFullDataRequestRateLimit() { return fullDataRequestRateLimit; } + + public boolean realTimeUpdatesEnabled = Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates.get(); + @Override public boolean isRealTimeUpdatesEnabled() { return realTimeUpdatesEnabled; } + + public boolean postRelogUpdateEnabled = Config.Client.Advanced.Multiplayer.ServerNetworking.enablePostRelogUpdate.get(); + @Override public boolean isPostRelogUpdateEnabled() { return postRelogUpdateEnabled; } + + @Override + public void decode(ByteBuf in) + { + this.renderDistance = in.readInt(); + this.fullDataRequestRateLimit = in.readInt(); + this.realTimeUpdatesEnabled = in.readBoolean(); + this.postRelogUpdateEnabled = in.readBoolean(); + } + + @Override public String toString() + { + return "MultiplayerConfig{" + + "renderDistance=" + renderDistance + + ", fullDataRequestRateLimit=" + fullDataRequestRateLimit + + ", realTimeUpdatesEnabled=" + realTimeUpdatesEnabled + + ", postRelogUpdatesEnabled=" + postRelogUpdateEnabled + + '}'; + } + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfigChangeListener.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfigChangeListener.java new file mode 100644 index 000000000..286f7f976 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfigChangeListener.java @@ -0,0 +1,33 @@ +package com.seibel.distanthorizons.core.multiplayer.config; + +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; +import com.seibel.distanthorizons.core.config.types.ConfigEntry; + +import java.io.Closeable; + +public class MultiplayerConfigChangeListener implements Closeable +{ + private final ConfigChangeListener renderDistance; + private final ConfigChangeListener rateLimit; + private final ConfigChangeListener enableRealTimeUpdates; + private final ConfigChangeListener enablePostRelogUpdate; + + public MultiplayerConfigChangeListener(Runnable runnable) + { + renderDistance = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance, ignored -> runnable.run()); + rateLimit = new ConfigChangeListener<>(Config.Client.Advanced.Multiplayer.ServerNetworking.requestRateLimit, ignored -> runnable.run()); + enableRealTimeUpdates = new ConfigChangeListener<>(Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates, ignored -> runnable.run()); + enablePostRelogUpdate = new ConfigChangeListener<>(Config.Client.Advanced.Multiplayer.ServerNetworking.enablePostRelogUpdate, ignored -> runnable.run()); + } + + @Override + public void close() + { + renderDistance.close(); + rateLimit.close(); + enableRealTimeUpdates.close(); + enablePostRelogUpdate.close(); + } + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/RemotePlayerConnectionHandler.java similarity index 80% rename from core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java rename to core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/RemotePlayerConnectionHandler.java index 09a0b11db..e8828d75a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/RemotePlayerConnectionHandler.java @@ -1,14 +1,17 @@ -package com.seibel.distanthorizons.core.multiplayer; +package com.seibel.distanthorizons.core.multiplayer.server; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.seibel.distanthorizons.core.level.DhServerLevel; +import com.seibel.distanthorizons.core.multiplayer.config.MultiplayerConfig; +import com.seibel.distanthorizons.core.multiplayer.config.MultiplayerConfigChangeListener; import com.seibel.distanthorizons.core.network.ScopedNetworkEventSource; import com.seibel.distanthorizons.core.network.NetworkServer; import com.seibel.distanthorizons.core.network.messages.base.ILevelRelatedMessage; import com.seibel.distanthorizons.core.network.messages.base.AckMessage; import com.seibel.distanthorizons.core.network.messages.base.CloseEvent; import com.seibel.distanthorizons.core.network.messages.session.PlayerUUIDMessage; +import com.seibel.distanthorizons.core.network.messages.session.RemotePlayerConfigMessage; import com.seibel.distanthorizons.core.network.protocol.NetworkMessage; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import io.netty.channel.ChannelHandlerContext; @@ -26,6 +29,8 @@ public class RemotePlayerConnectionHandler implements Closeable private final HashMap playersByUUID = new HashMap<>(); private final BiMap playersByConnection = HashBiMap.create(); + private final MultiplayerConfigChangeListener configChangeListener = new MultiplayerConfigChangeListener(this::onConfigChanged); + public NetworkServer server() { return this.eventSource.parent; } public RemotePlayerConnectionHandler(NetworkServer networkServer) @@ -59,6 +64,12 @@ public class RemotePlayerConnectionHandler implements Closeable playerUUIDMessage.sendResponse(new AckMessage()); }); + this.eventSource.registerHandler(RemotePlayerConfigMessage.class, this.connectedPlayersOnly((remotePlayerConfigMessage, serverPlayerState) -> + { + serverPlayerState.config.clientConfig = (MultiplayerConfig) remotePlayerConfigMessage.payload; + serverPlayerState.channelContext.writeAndFlush(new RemotePlayerConfigMessage(serverPlayerState.config)); + })); + this.eventSource.registerHandler(CloseEvent.class, closeEvent -> { ServerPlayerState dhPlayer = this.playersByConnection.remove(closeEvent.getChannelContext()); @@ -69,6 +80,12 @@ public class RemotePlayerConnectionHandler implements Closeable }); } + private void onConfigChanged() + { + for (ServerPlayerState serverPlayerState : this.getConnectedPlayers()) + serverPlayerState.channelContext.writeAndFlush(new RemotePlayerConfigMessage(serverPlayerState.config)); + } + public Consumer connectedPlayersOnly(BiConsumer next) { return msg -> @@ -131,6 +148,7 @@ public class RemotePlayerConnectionHandler implements Closeable @Override public void close() { + this.configChangeListener.close(); this.eventSource.close(); this.server().close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ServerPlayerState.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java similarity index 78% rename from core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ServerPlayerState.java rename to core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java index bf7e83f7f..c2fde5ef5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ServerPlayerState.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java @@ -1,4 +1,4 @@ -package com.seibel.distanthorizons.core.multiplayer; +package com.seibel.distanthorizons.core.multiplayer.server; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import io.netty.channel.ChannelHandlerContext; @@ -8,8 +8,9 @@ import java.util.concurrent.atomic.AtomicInteger; public class ServerPlayerState { public IServerPlayerWrapper serverPlayer; - public MultiplayerConfig config; public ChannelHandlerContext channelContext; + + public ServersideMultiplayerConfig config = new ServersideMultiplayerConfig(); public final AtomicInteger pendingFullDataRequests = new AtomicInteger(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServersideMultiplayerConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServersideMultiplayerConfig.java new file mode 100644 index 000000000..0f6f36c13 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServersideMultiplayerConfig.java @@ -0,0 +1,44 @@ +package com.seibel.distanthorizons.core.multiplayer.server; + +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.multiplayer.config.AbstractMultiplayerConfig; +import com.seibel.distanthorizons.core.multiplayer.config.MultiplayerConfig; +import io.netty.buffer.ByteBuf; + +/** + * Used for constraining the client config to the server config. + */ +public class ServersideMultiplayerConfig extends AbstractMultiplayerConfig +{ + public MultiplayerConfig clientConfig = new MultiplayerConfig(); + + @Override + public int getRenderDistance() + { + return Math.min(clientConfig.renderDistance, Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get()); + } + + @Override + public int getFullDataRequestRateLimit() + { + return Math.min(clientConfig.fullDataRequestRateLimit, Config.Client.Advanced.Multiplayer.ServerNetworking.requestRateLimit.get()); + } + + @Override + public boolean isRealTimeUpdatesEnabled() + { + return clientConfig.realTimeUpdatesEnabled && Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates.get(); + } + + @Override + public boolean isPostRelogUpdateEnabled() { + return clientConfig.postRelogUpdateEnabled && Config.Client.Advanced.Multiplayer.ServerNetworking.enablePostRelogUpdate.get(); + } + + @Override + public void decode(ByteBuf in) + { + throw new UnsupportedOperationException("Decoding is not supported for server-only class."); + } + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java index ecbb55955..986da58eb 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java @@ -26,6 +26,7 @@ import com.seibel.distanthorizons.core.network.messages.base.HelloMessage; import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage; import com.seibel.distanthorizons.core.network.protocol.MessageHandler; import com.seibel.distanthorizons.core.network.protocol.NetworkChannelInitializer; +import com.seibel.distanthorizons.core.network.protocol.NetworkMessage; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; @@ -131,7 +132,7 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable return; } - channel.writeAndFlush(new HelloMessage()); + sendMessage(new HelloMessage()); ready = true; }); @@ -168,6 +169,11 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable }); } + public final void sendMessage(NetworkMessage msg) + { + this.channel.writeAndFlush(msg); + } + public final CompletableFuture sendRequest(FutureTrackableNetworkMessage msg, Class responseClass) { return this.sendRequest(this.channel.pipeline().context(MessageHandler.class), msg, responseClass); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/exceptions/RequestRejectedException.java b/core/src/main/java/com/seibel/distanthorizons/core/network/exceptions/RequestRejectedException.java new file mode 100644 index 000000000..00f1ca574 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/exceptions/RequestRejectedException.java @@ -0,0 +1,10 @@ +package com.seibel.distanthorizons.core.network.exceptions; + +public class RequestRejectedException extends Exception +{ + public RequestRejectedException(String message) + { + super(message); + } + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/base/ExceptionMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/base/ExceptionMessage.java index ffac30807..25f7b6ccd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/base/ExceptionMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/base/ExceptionMessage.java @@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.network.messages.base; import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException; import com.seibel.distanthorizons.core.network.exceptions.InvalidSectionPosException; import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException; +import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException; import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage; import io.netty.buffer.ByteBuf; @@ -36,6 +37,7 @@ public class ExceptionMessage extends FutureTrackableNetworkMessage add(RateLimitedException.class); add(InvalidLevelException.class); add(InvalidSectionPosException.class); + add(RequestRejectedException.class); }}; public Exception exception; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/fullData/generation/FullDataSourceResponseMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/fullData/generation/FullDataSourceResponseMessage.java index 930e1ea2d..742188dde 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/fullData/generation/FullDataSourceResponseMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/fullData/generation/FullDataSourceResponseMessage.java @@ -73,10 +73,8 @@ public class FullDataSourceResponseMessage extends FutureTrackableNetworkMessage this.dataBuffer = in.readBytes(in.readInt()); } - @Nullable public CompleteFullDataSource getFullDataSource(DhSectionPos pos, IDhLevel level) throws IOException, InterruptedException { - try (ByteBufInputStream inputStream = new ByteBufInputStream(dataBuffer)) { return fullDataSourceLoader.loadData(pos, new DhDataInputStream(inputStream), level); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/session/RemotePlayerConfigMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/session/RemotePlayerConfigMessage.java index dd70197bc..d4b2f7b55 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/session/RemotePlayerConfigMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/session/RemotePlayerConfigMessage.java @@ -19,23 +19,24 @@ package com.seibel.distanthorizons.core.network.messages.session; -import com.seibel.distanthorizons.core.multiplayer.MultiplayerConfig; -import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage; +import com.seibel.distanthorizons.core.multiplayer.config.AbstractMultiplayerConfig; +import com.seibel.distanthorizons.core.multiplayer.config.MultiplayerConfig; import com.seibel.distanthorizons.core.network.protocol.INetworkObject; +import com.seibel.distanthorizons.core.network.protocol.NetworkMessage; import io.netty.buffer.ByteBuf; -public class RemotePlayerConfigMessage extends FutureTrackableNetworkMessage +public class RemotePlayerConfigMessage extends NetworkMessage { - public MultiplayerConfig payload; + public AbstractMultiplayerConfig payload; public RemotePlayerConfigMessage() { } - public RemotePlayerConfigMessage(MultiplayerConfig payload) { this.payload = payload; } + public RemotePlayerConfigMessage(AbstractMultiplayerConfig payload) { this.payload = payload; } @Override - public void encode0(ByteBuf out) { this.payload.encode(out); } + public void encode(ByteBuf out) { this.payload.encode(out); } @Override - public void decode0(ByteBuf in) { this.payload = INetworkObject.decodeStatic(new MultiplayerConfig(), in); } + public void decode(ByteBuf in) { this.payload = INetworkObject.decodeStatic(new MultiplayerConfig(), in); } @Override public String toString() { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java index aed0f25e0..c21bb7114 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java @@ -24,7 +24,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure; import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.level.IDhLevel; -import com.seibel.distanthorizons.core.multiplayer.ClientNetworkState; +import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState; import com.seibel.distanthorizons.core.network.NetworkClient; import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.EventLoop; @@ -63,7 +63,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld this.saveStructure = new ClientOnlySaveStructure(); this.levels = new ConcurrentHashMap<>(); - if (Config.Client.Advanced.Multiplayer.enableServerNetworking.get()) + if (Config.Client.Advanced.Multiplayer.ServerNetworking.enableServerNetworking.get()) { // TODO server specific configs NetworkClient networkClient = new NetworkClient(MC_CLIENT.getCurrentServerIp(), 25049); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java index 2f8856f04..ae0ae691a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java @@ -19,15 +19,11 @@ package com.seibel.distanthorizons.core.world; -import com.seibel.distanthorizons.core.config.AppliedConfigState; -import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure; import com.seibel.distanthorizons.core.level.DhServerLevel; import com.seibel.distanthorizons.core.level.IDhLevel; -import com.seibel.distanthorizons.core.multiplayer.ServerPlayerState; -import com.seibel.distanthorizons.core.multiplayer.RemotePlayerConnectionHandler; +import com.seibel.distanthorizons.core.multiplayer.server.RemotePlayerConnectionHandler; import com.seibel.distanthorizons.core.network.NetworkServer; -import com.seibel.distanthorizons.core.network.messages.session.RemotePlayerConfigMessage; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; @@ -41,10 +37,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld { private final HashMap levels; public final LocalSaveStructure saveStructure; - private final RemotePlayerConnectionHandler remotePlayerConnectionHandler; - private final AppliedConfigState rateLimitConfig = new AppliedConfigState<>(Config.Client.Advanced.Multiplayer.serverNetworkingRateLimit); - //==============// @@ -61,21 +54,9 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld // TODO move to global payload once server specific configs are implemented NetworkServer networkServer = new NetworkServer(25049); this.remotePlayerConnectionHandler = new RemotePlayerConnectionHandler(networkServer); - this.registerNetworkHandlers(); LOGGER.info("Started "+DhServerWorld.class.getSimpleName()+" of type "+this.environment); } - - private void registerNetworkHandlers() - { - this.remotePlayerConnectionHandler.server().registerHandler(RemotePlayerConfigMessage.class, remotePlayerConfigMessage -> - { - this.remotePlayerConnectionHandler.getConnectedPlayer(remotePlayerConfigMessage).config = remotePlayerConfigMessage.payload; - - remotePlayerConfigMessage.payload.fullDataRequestRateLimit = Math.min(rateLimitConfig.get(), remotePlayerConfigMessage.payload.fullDataRequestRateLimit); - remotePlayerConfigMessage.sendResponse(remotePlayerConfigMessage); - }); - } @@ -95,8 +76,8 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld } public void changePlayerLevel(IServerPlayerWrapper player, IServerLevelWrapper origin, IServerLevelWrapper dest) { - this.getLevel(origin).removePlayer(player); this.getLevel(dest).addPlayer(player); + this.getLevel(origin).removePlayer(player); } @Override @@ -147,15 +128,6 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld public void serverTick() { this.levels.values().forEach(DhServerLevel::serverTick); - - if (rateLimitConfig.pollNewValue()) - { - for (ServerPlayerState serverPlayerState : this.remotePlayerConnectionHandler.getConnectedPlayers()) - { - serverPlayerState.config.fullDataRequestRateLimit = rateLimitConfig.get(); - serverPlayerState.channelContext.writeAndFlush(new RemotePlayerConfigMessage(serverPlayerState.config)); - } - } } public void doWorldGen() {