Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core

This commit is contained in:
James Seibel
2025-10-19 16:06:24 -05:00
8 changed files with 67 additions and 14 deletions
@@ -31,7 +31,7 @@ public final class ModInfo
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial";
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */
public static final int PROTOCOL_VERSION = 11;
public static final int PROTOCOL_VERSION = 12;
public static final String WRAPPER_PACKET_PATH = "message";
/** The internal mod name */
@@ -77,7 +77,12 @@ public class ClientPluginChannelApi
private void onLevelInitMessage(LevelInitMessage msg)
{
if (!msg.levelKey.matches(LevelInitMessage.VALIDATION_REGEX))
if (!msg.serverKey.isEmpty() && !msg.serverKey.matches(LevelInitMessage.SERVER_KEY_REGEX))
{
throw new IllegalArgumentException("Server sent invalid server key.");
}
if (!msg.levelKey.matches(LevelInitMessage.LEVEL_KEY_REGEX))
{
throw new IllegalArgumentException("Server sent invalid level key.");
}
@@ -107,10 +112,12 @@ public class ClientPluginChannelApi
this.levelUnloadHandler.accept(clientLevel);
}
if (existingKeyedClientLevel == null || !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
if (existingKeyedClientLevel == null
|| !existingKeyedClientLevel.getServerKey().equals(msg.serverKey)
|| !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
{
LOGGER.info("Loading level with key: [" + msg.levelKey + "].");
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.levelKey);
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.serverKey, msg.levelKey);
this.levelLoadHandler.accept(keyedLevel);
}
});
@@ -1666,6 +1666,28 @@ public class Config
+ "")
.build();
public static ConfigEntry<Integer> serverId = new ConfigEntry.Builder<Integer>()
.set(new Random().nextInt())
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.comment(""
+ "DO NOT CHANGE UNLESS YOU KNOW WHAT YOU'RE DOING.\n"
+ "Autogenerated ID used to prevent multiple independent servers from accidentally\n"
+ "writing over each other's LODs when the same serverKey is set on both.\n"
+ "")
.build();
public static ConfigEntry<String> serverKey = new ConfigEntry.Builder<String>()
.setChatCommandName("levelKeys.serverKey")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.set("")
.comment(""
+ "Custom server key used which can be used to always reuse the same LOD data folder,\n"
+ "for cases when the server doesn't have a static IP for some reason.\n"
+ "If this value is empty, the client itself decides which folder name to use.\n"
+ "Requires rejoining the server to apply after changing.\n"
+ "")
.build();
public static ConfigEntry<String> levelKeyPrefix = new ConfigEntry.Builder<String>()
.setChatCommandName("levelKeys.prefix")
.set("")
@@ -81,13 +81,20 @@ public class ClientOnlySaveStructure implements ISaveStructure
{
IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper;
LOGGER.info("Loading level [" + newLevelWrapper.getDhIdentifier() + "] with key: [" + keyedClientLevel.getServerLevelKey() + "].");
// This world was identified by the server directly, so we can know for sure which folder to use.
saveFolder = getSaveFolderByLevelId(keyedClientLevel.getServerLevelKey());
String serverKey = keyedClientLevel.getServerKey();
if (serverKey.isEmpty())
{
serverKey = getServerFolderName();
}
// This world was identified by the server directly, so we can know for sure which folder to use.
saveFolder = getSaveFolderByLevelId(serverKey, keyedClientLevel.getServerLevelKey());
}
else
{
// get the default folder
saveFolder = getSaveFolderByLevelId(levelWrapper.getDhIdentifier());
saveFolder = getSaveFolderByLevelId(getServerFolderName(), levelWrapper.getDhIdentifier());
}
// Allow API users to override the save folder
@@ -116,7 +123,7 @@ public class ClientOnlySaveStructure implements ISaveStructure
return this.getSaveFolder(levelWrapper);
}
return getSaveFolderByLevelId(levelWrapper.getDimensionType().getName());
return getSaveFolderByLevelId(getServerFolderName(), levelWrapper.getDimensionType().getName());
}
@@ -173,11 +180,11 @@ public class ClientOnlySaveStructure implements ISaveStructure
}
private static File getSaveFolderByLevelId(String dimensionName)
private static File getSaveFolderByLevelId(String folderName, String dimensionName)
{
String path = MC_SHARED.getInstallationDirectory().getPath() + File.separatorChar
+ SERVER_DATA_FOLDER_NAME + File.separatorChar
+ getServerFolderName() + File.separatorChar
+ folderName + File.separatorChar
+ dimensionName.replaceAll(":", "@@");
return new File(path);
@@ -30,7 +30,7 @@ public interface IKeyedClientLevelManager extends IBindable
{
IServerKeyedClientLevel getServerKeyedLevel();
/** Called when a client level is wrapped by a ServerEnhancedClientLevel, for integration into mod internals. */
IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String levelKey);
IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String serverKey, String levelKey);
void clearKeyedLevel();
boolean isEnabled();
@@ -24,6 +24,9 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
/** Enhances a {@link IClientLevelWrapper} with server provided level information. */
public interface IServerKeyedClientLevel extends IClientLevelWrapper
{
/** Returns the folder name the server wants the player to use. */
String getServerKey();
/** Returns the level key, which is used to select the correct folder on the client. */
String getServerLevelKey();
@@ -28,6 +28,10 @@ public class ServerPlayerState implements Closeable
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage);
private final String serverKeyWithoutId = Config.Server.serverKey.get();
private final String serverKey = (this.serverKeyWithoutId.isEmpty() ? "" : Config.Server.serverId.get() + "_" + this.serverKeyWithoutId.trim())
.replaceAll("[^" + LevelInitMessage.ALLOWED_CHARS_REGEX + " ]", "")
.replaceAll(" ", "_");
private String lastLevelKey = "";
@@ -90,7 +94,7 @@ public class ServerPlayerState implements Closeable
if (!levelKey.equals(this.lastLevelKey))
{
this.lastLevelKey = levelKey;
this.networkSession.sendMessage(new LevelInitMessage(levelKey));
this.networkSession.sendMessage(new LevelInitMessage(this.serverKey, levelKey));
}
}
}
@@ -10,12 +10,18 @@ public class LevelInitMessage extends AbstractNetworkMessage
public static final String ALLOWED_CHARS_REGEX = "a-zA-Z0-9-_";
// A plain string of characters
// 1-150 characters in total
public static final String SERVER_KEY_REGEX = String.format("^(?=.{1,%s}$)[%s]+$",
MAX_LENGTH, ALLOWED_CHARS_REGEX);
// prefix@namespace:path
// 1-150 characters in total, all parts except namespace can be omitted
public static final String VALIDATION_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$",
public static final String LEVEL_KEY_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$",
MAX_LENGTH, ALLOWED_CHARS_REGEX, ALLOWED_CHARS_REGEX, ALLOWED_CHARS_REGEX);
public String serverKey;
public String levelKey;
public long serverTime;
@@ -26,8 +32,9 @@ public class LevelInitMessage extends AbstractNetworkMessage
//==============//
public LevelInitMessage() { }
public LevelInitMessage(String levelKey)
public LevelInitMessage(String serverKey, String levelKey)
{
this.serverKey = serverKey;
this.levelKey = levelKey;
this.serverTime = System.currentTimeMillis();
}
@@ -41,6 +48,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
@Override
public void encode(ByteBuf out)
{
this.writeString(this.serverKey, out);
this.writeString(this.levelKey, out);
out.writeLong(this.serverTime);
}
@@ -48,6 +56,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
@Override
public void decode(ByteBuf in)
{
this.serverKey = this.readString(in);
this.levelKey = this.readString(in);
this.serverTime = in.readLong();
}
@@ -62,6 +71,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("serverKey", this.serverKey)
.add("levelKey", this.levelKey)
.add("serverTime", this.serverTime);
}