diff --git a/bungee/base/build.gradle.kts b/bungee/base/build.gradle.kts index dcd0123c..341ff4af 100644 --- a/bungee/base/build.gradle.kts +++ b/bungee/base/build.gradle.kts @@ -5,6 +5,7 @@ dependencies { compileOnlyApi(projects.isolation) implementation(libs.cloud.bungee) + implementation(libs.adventure.platform.bungee) } relocate("net.kyori") diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeDataAddon.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeDataAddon.java index 8f13f9fb..9dec9148 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeDataAddon.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeDataAddon.java @@ -32,11 +32,11 @@ import jakarta.inject.Named; import jakarta.inject.Singleton; import org.geysermc.api.connection.Connection; import org.geysermc.floodgate.api.inject.InjectorAddon; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.addon.data.PacketBlocker; import org.geysermc.floodgate.core.config.ProxyFloodgateConfig; import org.geysermc.floodgate.core.connection.DataSeeker; import org.geysermc.floodgate.core.connection.FloodgateDataHandler; +import org.geysermc.floodgate.core.logger.FloodgateLogger; @Singleton public class BungeeDataAddon implements InjectorAddon { diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeProxyDataHandler.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeProxyDataHandler.java index 860b98a4..5f2cdc47 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeProxyDataHandler.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeProxyDataHandler.java @@ -38,12 +38,12 @@ import net.md_5.bungee.protocol.DefinedPacket; import net.md_5.bungee.protocol.PacketWrapper; import net.md_5.bungee.protocol.packet.Handshake; import org.geysermc.api.connection.Connection; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.addon.data.CommonNettyDataHandler; import org.geysermc.floodgate.core.addon.data.PacketBlocker; import org.geysermc.floodgate.core.config.ProxyFloodgateConfig; import org.geysermc.floodgate.core.connection.DataSeeker; import org.geysermc.floodgate.core.connection.FloodgateDataHandler; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.util.ReflectionUtils; @SuppressWarnings("ConstantConditions") diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/inject/BungeeInjector.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/inject/BungeeInjector.java index b9376221..50d2e5d0 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/inject/BungeeInjector.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/inject/BungeeInjector.java @@ -39,9 +39,9 @@ import net.md_5.bungee.netty.PipelineUtils; import net.md_5.bungee.protocol.MinecraftEncoder; import net.md_5.bungee.protocol.Varint21LengthFieldExtraBufPrepender; import net.md_5.bungee.protocol.Varint21LengthFieldPrepender; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.bungee.util.BungeeReflectionUtils; import org.geysermc.floodgate.core.inject.CommonPlatformInjector; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.util.ReflectionUtils; @Singleton diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/listener/BungeeListener.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/listener/BungeeListener.java index c98036ee..0b168063 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/listener/BungeeListener.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/listener/BungeeListener.java @@ -42,11 +42,11 @@ import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventPriority; import org.geysermc.api.connection.Connection; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.bungee.player.BungeeConnectionManager; import org.geysermc.floodgate.core.api.SimpleFloodgateApi; import org.geysermc.floodgate.core.config.ProxyFloodgateConfig; import org.geysermc.floodgate.core.listener.McListener; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.skin.SkinApplier; import org.geysermc.floodgate.core.skin.SkinDataImpl; import org.geysermc.floodgate.core.util.LanguageManager; diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/pluginmessage/BungeePluginMessageUtils.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/pluginmessage/BungeePluginMessageUtils.java index ec50e8a6..cec4d03e 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/pluginmessage/BungeePluginMessageUtils.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/pluginmessage/BungeePluginMessageUtils.java @@ -38,8 +38,8 @@ import net.md_5.bungee.api.event.PluginMessageEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventPriority; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.listener.McListener; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.platform.pluginmessage.PluginMessageUtils; import org.geysermc.floodgate.core.pluginmessage.PluginMessageChannel; import org.geysermc.floodgate.core.pluginmessage.PluginMessageChannel.Identity; diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/pluginmessage/BungeeSkinApplier.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/pluginmessage/BungeeSkinApplier.java index 9bba3ff9..714547e7 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/pluginmessage/BungeeSkinApplier.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/pluginmessage/BungeeSkinApplier.java @@ -42,9 +42,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.api.connection.Connection; import org.geysermc.floodgate.api.event.skin.SkinApplyEvent; import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.event.EventBus; import org.geysermc.floodgate.core.event.skin.SkinApplyEventImpl; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.skin.SkinApplier; import org.geysermc.floodgate.core.skin.SkinDataImpl; import org.geysermc.floodgate.core.util.ReflectionUtils; diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/util/BungeeCommandUtil.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/util/BungeeCommandUtil.java index db5e3916..253c01c1 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/util/BungeeCommandUtil.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/util/BungeeCommandUtil.java @@ -29,6 +29,8 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.Collection; import java.util.UUID; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.connection.ProxiedPlayer; @@ -38,6 +40,7 @@ import org.geysermc.floodgate.core.connection.audience.UserAudience; import org.geysermc.floodgate.core.connection.audience.UserAudience.ConsoleAudience; import org.geysermc.floodgate.core.connection.audience.UserAudience.PlayerAudience; import org.geysermc.floodgate.core.platform.command.CommandUtil; +import org.geysermc.floodgate.core.util.Constants; import org.geysermc.floodgate.core.util.LanguageManager; import org.geysermc.floodgate.core.util.Utils; @@ -106,15 +109,25 @@ public final class BungeeCommandUtil extends CommandUtil { } @Override - public void sendMessage(Object target, String message) { - ((CommandSender) target).sendMessage(message); + public void sendMessage(Object target, Component message) { + if (target instanceof ProxiedPlayer player && player.getPendingConnection().getVersion() >= Constants.PROTOCOL_HEX_COLOR) { + player.sendMessage(BungeeComponentSerializer.get().serialize(message)); + return; + } + ((CommandSender) target).sendMessage(BungeeComponentSerializer.legacy().serialize(message)); } @Override - public void kickPlayer(Object player, String message) { + public void kickPlayer(Object target, Component message) { // can also be a console - if (player instanceof ProxiedPlayer) { - ((ProxiedPlayer) player).disconnect(message); + if (!(target instanceof ProxiedPlayer player)) { + return; } + + if (player.getPendingConnection().getVersion() >= Constants.PROTOCOL_HEX_COLOR) { + player.disconnect(BungeeComponentSerializer.get().serialize(message)); + return; + } + player.disconnect(BungeeComponentSerializer.legacy().serialize(message)); } } diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/util/BungeeReflectionUtils.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/util/BungeeReflectionUtils.java index 0fe6068c..497a888d 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/util/BungeeReflectionUtils.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/util/BungeeReflectionUtils.java @@ -25,8 +25,6 @@ package org.geysermc.floodgate.bungee.util; -import static org.geysermc.floodgate.core.util.MessageFormatter.format; - import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -40,12 +38,13 @@ public class BungeeReflectionUtils { unsafeField.setAccessible(true); UNSAFE = (sun.misc.Unsafe) unsafeField.get(null); } catch (Exception exception) { - throw new RuntimeException(format( - "Cannot initialize required reflection setup :/\nJava version: {}\nVendor: {} ({})", - System.getProperty("java.version"), - System.getProperty("java.vendor"), - System.getProperty("java.vendor.url") - ), exception); + throw new RuntimeException( + String.format( + "Cannot initialize required reflection setup :/\nJava version: %s\nVendor: %s (%s)", + System.getProperty("java.version"), + System.getProperty("java.vendor"), + System.getProperty("java.vendor.url")), + exception); } } @@ -62,12 +61,14 @@ public class BungeeReflectionUtils { } else { UNSAFE.putObject(object, offset, result); } - } catch (Exception e) { - throw new RuntimeException(format( - "Java version: {}\nVendor: {} ({})", - System.getProperty("java.version"), - System.getProperty("java.vendor"), - System.getProperty("java.vendor.url"), e)); + } catch (Exception exception) { + throw new RuntimeException( + String.format( + "Java version: %s\nVendor: %s (%s)", + System.getProperty("java.version"), + System.getProperty("java.vendor"), + System.getProperty("java.vendor.url")), + exception); } } } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 11320071..ba2ee8d6 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { api(libs.cloud.core) api(libs.snakeyaml) api(libs.bstats) + api(libs.adventure.text.minimessage) api(libs.micronaut.inject) annotationProcessor(libs.micronaut.inject.java) diff --git a/core/src/main/java/org/geysermc/floodgate/core/FloodgatePlatform.java b/core/src/main/java/org/geysermc/floodgate/core/FloodgatePlatform.java index a460d559..a9671642 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/FloodgatePlatform.java +++ b/core/src/main/java/org/geysermc/floodgate/core/FloodgatePlatform.java @@ -39,7 +39,6 @@ import org.geysermc.floodgate.api.InstanceHolder; import org.geysermc.floodgate.api.event.FloodgateEventBus; import org.geysermc.floodgate.api.handshake.HandshakeHandlers; import org.geysermc.floodgate.api.inject.PlatformInjector; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.packet.PacketHandlers; import org.geysermc.floodgate.core.config.ConfigLoader; import org.geysermc.floodgate.core.config.FloodgateConfig; @@ -47,6 +46,9 @@ import org.geysermc.floodgate.core.database.loader.DatabaseLoader; import org.geysermc.floodgate.core.event.EventBus; import org.geysermc.floodgate.core.event.lifecycle.PostEnableEvent; import org.geysermc.floodgate.core.event.lifecycle.ShutdownEvent; +import org.geysermc.floodgate.core.logger.FloodgateLogger; +import org.geysermc.floodgate.core.platform.CommonPlatformMessages; +import org.geysermc.floodgate.core.platform.command.Placeholder; import org.geysermc.floodgate.isolation.IsolatedPlatform; import org.geysermc.floodgate.isolation.library.LibraryManager; @@ -98,8 +100,9 @@ public abstract class FloodgatePlatform implements IsolatedPlatform { Geyser.set(api); long endTime = System.currentTimeMillis(); - context.getBean(FloodgateLogger.class) - .translatedInfo("floodgate.core.finish", endTime - startTime); + context.getBean(FloodgateLogger.class).translatedInfo( + CommonPlatformMessages.CORE_FINISH, + Placeholder.literal("time_in_ms", endTime - startTime)); } @Override diff --git a/core/src/main/java/org/geysermc/floodgate/core/addon/DebugAddon.java b/core/src/main/java/org/geysermc/floodgate/core/addon/DebugAddon.java index e571d36c..044b888f 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/addon/DebugAddon.java +++ b/core/src/main/java/org/geysermc/floodgate/core/addon/DebugAddon.java @@ -31,11 +31,11 @@ import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.inject.Singleton; import org.geysermc.floodgate.api.inject.InjectorAddon; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.addon.debug.ChannelInDebugHandler; import org.geysermc.floodgate.core.addon.debug.ChannelOutDebugHandler; import org.geysermc.floodgate.core.addon.debug.StateChangeDetector; import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.util.Utils; @Singleton diff --git a/core/src/main/java/org/geysermc/floodgate/core/addon/data/CommonNettyDataHandler.java b/core/src/main/java/org/geysermc/floodgate/core/addon/data/CommonNettyDataHandler.java index 6d521b63..a2ae2f1f 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/addon/data/CommonNettyDataHandler.java +++ b/core/src/main/java/org/geysermc/floodgate/core/addon/data/CommonNettyDataHandler.java @@ -35,13 +35,13 @@ import java.util.concurrent.ConcurrentLinkedQueue; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.geysermc.api.connection.Connection; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.connection.DataSeeker; import org.geysermc.floodgate.core.connection.DataSeeker.DataSeekerResult; import org.geysermc.floodgate.core.connection.FloodgateDataHandler; import org.geysermc.floodgate.core.connection.FloodgateDataHandler.HandleResult; import org.geysermc.floodgate.core.crypto.exception.UnsupportedVersionException; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.util.InvalidFormatException; @RequiredArgsConstructor diff --git a/core/src/main/java/org/geysermc/floodgate/core/addon/debug/ChannelInDebugHandler.java b/core/src/main/java/org/geysermc/floodgate/core/addon/debug/ChannelInDebugHandler.java index eef25e4d..1ff68975 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/addon/debug/ChannelInDebugHandler.java +++ b/core/src/main/java/org/geysermc/floodgate/core/addon/debug/ChannelInDebugHandler.java @@ -30,7 +30,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; -import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.logger.FloodgateLogger; @Sharable public final class ChannelInDebugHandler extends SimpleChannelInboundHandler { diff --git a/core/src/main/java/org/geysermc/floodgate/core/addon/debug/ChannelOutDebugHandler.java b/core/src/main/java/org/geysermc/floodgate/core/addon/debug/ChannelOutDebugHandler.java index 21c6eab9..e79687c6 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/addon/debug/ChannelOutDebugHandler.java +++ b/core/src/main/java/org/geysermc/floodgate/core/addon/debug/ChannelOutDebugHandler.java @@ -30,7 +30,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; -import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.logger.FloodgateLogger; @Sharable public final class ChannelOutDebugHandler extends MessageToByteEncoder { diff --git a/core/src/main/java/org/geysermc/floodgate/core/addon/debug/StateChangeDetector.java b/core/src/main/java/org/geysermc/floodgate/core/addon/debug/StateChangeDetector.java index 86e86d7c..50f0ce57 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/addon/debug/StateChangeDetector.java +++ b/core/src/main/java/org/geysermc/floodgate/core/addon/debug/StateChangeDetector.java @@ -30,7 +30,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelPipeline; import java.nio.charset.StandardCharsets; -import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.util.Constants; import org.geysermc.floodgate.core.util.Utils; diff --git a/core/src/main/java/org/geysermc/floodgate/core/api/SimpleFloodgateApi.java b/core/src/main/java/org/geysermc/floodgate/core/api/SimpleFloodgateApi.java index f28a70aa..81b3fbf2 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/api/SimpleFloodgateApi.java +++ b/core/src/main/java/org/geysermc/floodgate/core/api/SimpleFloodgateApi.java @@ -39,10 +39,10 @@ import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.util.FormBuilder; import org.geysermc.floodgate.api.InstanceHolder; import org.geysermc.floodgate.api.link.PlayerLink; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.connection.ConnectionManager; import org.geysermc.floodgate.core.http.xbox.XboxClient; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.pluginmessage.PluginMessageManager; import org.geysermc.floodgate.core.pluginmessage.channel.FormChannel; import org.geysermc.floodgate.core.pluginmessage.channel.TransferChannel; diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/CommonCommandMessage.java b/core/src/main/java/org/geysermc/floodgate/core/command/CommonCommandMessage.java index d4e9e23f..c158e63f 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/CommonCommandMessage.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/CommonCommandMessage.java @@ -25,32 +25,19 @@ package org.geysermc.floodgate.core.command; -import lombok.Getter; +import org.geysermc.floodgate.core.platform.command.MessageType; import org.geysermc.floodgate.core.platform.command.TranslatableMessage; /** * Messages (or part of messages) that are used in two or more commands and thus are 'commonly * used' */ -@Getter -public enum CommonCommandMessage implements TranslatableMessage { - LINKING_DISABLED("floodgate.commands.linking_disabled"), - NOT_A_PLAYER("floodgate.commands.not_a_player"), - CHECK_CONSOLE("floodgate.commands.check_console"), - IS_LINKED_ERROR("floodgate.commands.is_linked_error"), - LOCAL_LINKING_NOTICE("floodgate.commands.local_linking_notice"), - GLOBAL_LINKING_NOTICE("floodgate.commands.global_linking_notice"); - - private final String rawMessage; - private final String[] translateParts; - - CommonCommandMessage(String rawMessage) { - this.rawMessage = rawMessage; - this.translateParts = rawMessage.split(" "); - } - - @Override - public String toString() { - return getRawMessage(); - } +public class CommonCommandMessage { + public static final TranslatableMessage LINKING_DISABLED = new TranslatableMessage("floodgate.commands.linking_disabled", MessageType.ERROR); + public static final TranslatableMessage NOT_A_PLAYER = new TranslatableMessage("floodgate.commands.not_a_player", MessageType.ERROR); + public static final TranslatableMessage CHECK_CONSOLE = new TranslatableMessage("floodgate.commands.check_console"); + public static final TranslatableMessage UNEXPECTED_ERROR = new TranslatableMessage("floodgate.commands.unexpected_error " + CommonCommandMessage.CHECK_CONSOLE, MessageType.ERROR); + public static final TranslatableMessage IS_LINKED_ERROR = new TranslatableMessage("floodgate.commands.is_linked_error", MessageType.ERROR); + public static final TranslatableMessage LOCAL_LINKING_NOTICE = new TranslatableMessage("floodgate.commands.local_linking_notice", MessageType.INFO); + public static final TranslatableMessage GLOBAL_LINKING_NOTICE = new TranslatableMessage("floodgate.commands.global_linking_notice", MessageType.INFO); } diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/LinkAccountCommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/LinkAccountCommand.java index 7b0c3d56..b9f2f7c5 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/LinkAccountCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/LinkAccountCommand.java @@ -25,13 +25,12 @@ package org.geysermc.floodgate.core.command; +import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; import static org.incendo.cloud.parser.standard.StringParser.stringParser; import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.concurrent.CompletableFuture; -import lombok.Getter; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.api.SimpleFloodgateApi; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.config.FloodgateConfig; @@ -40,7 +39,9 @@ import org.geysermc.floodgate.core.connection.audience.UserAudience; import org.geysermc.floodgate.core.connection.audience.UserAudience.PlayerAudience; import org.geysermc.floodgate.core.link.CommonPlayerLink; import org.geysermc.floodgate.core.link.LinkVerificationException; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.platform.command.FloodgateCommand; +import org.geysermc.floodgate.core.platform.command.MessageType; import org.geysermc.floodgate.core.platform.command.TranslatableMessage; import org.geysermc.floodgate.core.util.Constants; import org.geysermc.floodgate.core.util.Utils; @@ -75,11 +76,15 @@ public final class LinkAccountCommand implements FloodgateCommand { if (!linkState.globalLinkingEnabled()) { sender.sendMessage(CommonCommandMessage.LINKING_DISABLED); } else { - sender.sendMessage(CommonCommandMessage.GLOBAL_LINKING_NOTICE, Constants.LINK_INFO_URL); + sender.sendMessage( + CommonCommandMessage.GLOBAL_LINKING_NOTICE, + literal("url", Constants.LINK_INFO_URL)); } return; } else if (linkState.globalLinkingEnabled()) { - sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, Constants.LINK_INFO_URL); + sender.sendMessage( + CommonCommandMessage.LOCAL_LINKING_NOTICE, + literal("url", Constants.LINK_INFO_URL)); } ProfileAudience targetUser = context.get("player"); @@ -89,7 +94,7 @@ public final class LinkAccountCommand implements FloodgateCommand { // when the player is a Bedrock player if (api.isBedrockPlayer(sender.uuid())) { if (!context.contains("code")) { - sender.sendMessage(Message.BEDROCK_USAGE); + sender.sendMessage(Message.BEDROCK_USAGE, literal("command", "/linkaccount ")); return; } @@ -121,20 +126,23 @@ public final class LinkAccountCommand implements FloodgateCommand { ) .whenComplete(($, throwable) -> { if (throwable instanceof LinkVerificationException exception) { - sender.sendMessage(exception.message()); + sender.sendMessage(exception.message(), exception.placeholders()); return; } if (throwable != null) { sender.sendMessage(Message.LINK_REQUEST_ERROR); return; } - sender.disconnect(Message.LINK_REQUEST_COMPLETED, targetName); + sender.disconnect( + Message.LINK_REQUEST_COMPLETED, + literal("target", targetName), + literal("command", "/unlinkaccount")); }); return; } if (context.contains("code")) { - sender.sendMessage(Message.JAVA_USAGE); + sender.sendMessage(Message.JAVA_USAGE, literal("command", "/linkaccount ")); return; } @@ -147,7 +155,11 @@ public final class LinkAccountCommand implements FloodgateCommand { sender.sendMessage(Message.LINK_REQUEST_ERROR); return; } - sender.sendMessage(Message.LINK_REQUEST_CREATED, targetName, username, code); + sender.sendMessage( + Message.LINK_REQUEST_CREATED, + literal("target", targetName), + literal("command", "/linkaccount " + username + " " + code), + literal("unlink_command", "/unlinkaccount")); }); } @@ -158,24 +170,15 @@ public final class LinkAccountCommand implements FloodgateCommand { (linkConfig.enableOwnLinking() || linkConfig.enableGlobalLinking()); } - @Getter - public enum Message implements TranslatableMessage { - ALREADY_LINKED("floodgate.command.link_account.already_linked"), - JAVA_USAGE("floodgate.command.link_account.java_usage"), - LINK_REQUEST_CREATED("floodgate.command.link_account.link_request_created"), - BEDROCK_USAGE("floodgate.command.link_account.bedrock_usage"), - LINK_REQUEST_EXPIRED("floodgate.command.link_account.link_request_expired"), - LINK_REQUEST_COMPLETED("floodgate.command.link_account.link_request_completed"), - LINK_REQUEST_ERROR("floodgate.command.link_request.error " + CommonCommandMessage.CHECK_CONSOLE), - INVALID_CODE("floodgate.command.link_account.invalid_code"), - NO_LINK_REQUESTED("floodgate.command.link_account.no_link_requested"); - - private final String rawMessage; - private final String[] translateParts; - - Message(String rawMessage) { - this.rawMessage = rawMessage; - this.translateParts = rawMessage.split(" "); - } + public static final class Message { + public static final TranslatableMessage ALREADY_LINKED = new TranslatableMessage("floodgate.command.link_account.already_linked", MessageType.ERROR); + public static final TranslatableMessage JAVA_USAGE = new TranslatableMessage("floodgate.command.link_account.java_usage", MessageType.ERROR); + public static final TranslatableMessage LINK_REQUEST_CREATED = new TranslatableMessage("floodgate.command.link_account.link_request_created", MessageType.SUCCESS); + public static final TranslatableMessage BEDROCK_USAGE = new TranslatableMessage("floodgate.command.link_account.bedrock_usage", MessageType.ERROR); + public static final TranslatableMessage LINK_REQUEST_EXPIRED = new TranslatableMessage("floodgate.command.link_account.link_request_expired", MessageType.ERROR); + public static final TranslatableMessage LINK_REQUEST_COMPLETED = new TranslatableMessage("floodgate.command.link_account.link_request_completed", MessageType.SUCCESS); + public static final TranslatableMessage LINK_REQUEST_ERROR = new TranslatableMessage("floodgate.command.link_account.link_request_error " + CommonCommandMessage.CHECK_CONSOLE, MessageType.ERROR); + public static final TranslatableMessage INVALID_CODE = new TranslatableMessage("floodgate.command.link_account.invalid_code", MessageType.ERROR); + public static final TranslatableMessage NO_LINK_REQUESTED = new TranslatableMessage("floodgate.command.link_account.no_link_requested", MessageType.ERROR); } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/UnlinkAccountCommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/UnlinkAccountCommand.java index 5e4fcd50..896a4947 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/UnlinkAccountCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/UnlinkAccountCommand.java @@ -25,15 +25,17 @@ package org.geysermc.floodgate.core.command; +import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; + import jakarta.inject.Inject; import jakarta.inject.Singleton; -import lombok.Getter; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.connection.audience.UserAudience; import org.geysermc.floodgate.core.connection.audience.UserAudience.PlayerAudience; import org.geysermc.floodgate.core.link.CommonPlayerLink; import org.geysermc.floodgate.core.platform.command.FloodgateCommand; +import org.geysermc.floodgate.core.platform.command.MessageType; import org.geysermc.floodgate.core.platform.command.TranslatableMessage; import org.geysermc.floodgate.core.util.Constants; import org.incendo.cloud.Command; @@ -63,11 +65,13 @@ public final class UnlinkAccountCommand implements FloodgateCommand { if (!linkState.globalLinkingEnabled()) { sender.sendMessage(CommonCommandMessage.LINKING_DISABLED); } else { - sender.sendMessage(CommonCommandMessage.GLOBAL_LINKING_NOTICE, Constants.LINK_INFO_URL); + sender.sendMessage( + CommonCommandMessage.GLOBAL_LINKING_NOTICE, + literal("url", Constants.LINK_INFO_URL)); } return; } else if (linkState.globalLinkingEnabled()) { - sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, Constants.LINK_INFO_URL); + sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, literal("url", Constants.LINK_INFO_URL)); } link.isLinked(sender.uuid()) @@ -101,18 +105,9 @@ public final class UnlinkAccountCommand implements FloodgateCommand { (linkConfig.enableOwnLinking() || linkConfig.enableGlobalLinking()); } - @Getter - public enum Message implements TranslatableMessage { - NOT_LINKED("floodgate.command.unlink_account.not_linked"), - UNLINK_SUCCESS("floodgate.command.unlink_account.unlink_success"), - UNLINK_ERROR("floodgate.command.unlink_account.error " + CommonCommandMessage.CHECK_CONSOLE); - - private final String rawMessage; - private final String[] translateParts; - - Message(String rawMessage) { - this.rawMessage = rawMessage; - this.translateParts = rawMessage.split(" "); - } + public static final class Message { + public static final TranslatableMessage NOT_LINKED = new TranslatableMessage("floodgate.command.unlink_account.not_linked", MessageType.ERROR); + public static final TranslatableMessage UNLINK_SUCCESS = new TranslatableMessage("floodgate.command.unlink_account.unlink_success", MessageType.SUCCESS); + public static final TranslatableMessage UNLINK_ERROR = new TranslatableMessage("floodgate.command.unlink_account.error " + CommonCommandMessage.CHECK_CONSOLE, MessageType.ERROR); } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/WhitelistCommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/WhitelistCommand.java index 282029ae..86a0f93a 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/WhitelistCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/WhitelistCommand.java @@ -25,20 +25,22 @@ package org.geysermc.floodgate.core.command; +import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; + import io.micronaut.http.client.exceptions.HttpClientResponseException; import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.UUID; -import lombok.Getter; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.config.ProxyFloodgateConfig; import org.geysermc.floodgate.core.connection.audience.ProfileAudience; import org.geysermc.floodgate.core.connection.audience.UserAudience; import org.geysermc.floodgate.core.http.xbox.XboxClient; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.platform.command.CommandUtil; import org.geysermc.floodgate.core.platform.command.FloodgateCommand; +import org.geysermc.floodgate.core.platform.command.MessageType; import org.geysermc.floodgate.core.platform.command.TranslatableMessage; import org.incendo.cloud.Command; import org.incendo.cloud.CommandManager; @@ -76,7 +78,7 @@ public class WhitelistCommand implements FloodgateCommand { String name = profile.username(); if (name == null && uuid == null) { - sender.sendMessage(Message.UNEXPECTED_ERROR); + sender.sendMessage(CommonCommandMessage.UNEXPECTED_ERROR); return; } @@ -90,16 +92,15 @@ public class WhitelistCommand implements FloodgateCommand { if (add) { if (commandUtil.whitelistPlayer(uuid, "unknown")) { - sender.sendMessage(Message.PLAYER_ADDED, uuid.toString()); + sender.sendMessage(Message.PLAYER_ADDED, literal("target", uuid.toString())); } else { - sender.sendMessage(Message.PLAYER_ALREADY_WHITELISTED, - uuid.toString()); + sender.sendMessage(Message.PLAYER_ALREADY_WHITELISTED, literal("target", uuid.toString())); } } else { if (commandUtil.removePlayerFromWhitelist(uuid, "unknown")) { - sender.sendMessage(Message.PLAYER_REMOVED, uuid.toString()); + sender.sendMessage(Message.PLAYER_REMOVED, literal("target", uuid.toString())); } else { - sender.sendMessage(Message.PLAYER_NOT_WHITELISTED, uuid.toString()); + sender.sendMessage(Message.PLAYER_NOT_WHITELISTED, literal("target", uuid.toString())); } } return; @@ -132,7 +133,7 @@ public class WhitelistCommand implements FloodgateCommand { error.printStackTrace(); return; } - sender.sendMessage(Message.UNEXPECTED_ERROR); + sender.sendMessage(CommonCommandMessage.UNEXPECTED_ERROR); //todo proper non-200 status handler // var response = exception.getResponse().getBody(UnsuccessfulResponse.class); @@ -149,7 +150,7 @@ public class WhitelistCommand implements FloodgateCommand { Long xuid = result.xuid(); if (xuid == null) { - sender.sendMessage(Message.USER_NOT_FOUND); + sender.sendMessage(Message.ACCOUNT_NOT_FOUND); return; } @@ -158,16 +159,15 @@ public class WhitelistCommand implements FloodgateCommand { try { if (add) { if (commandUtil.whitelistPlayer(xuid, correctName)) { - sender.sendMessage(Message.PLAYER_ADDED, strippedName); + sender.sendMessage(Message.PLAYER_ADDED, literal("target", strippedName)); } else { - sender.sendMessage(Message.PLAYER_ALREADY_WHITELISTED, - strippedName); + sender.sendMessage(Message.PLAYER_ALREADY_WHITELISTED, literal("target", strippedName)); } } else { if (commandUtil.removePlayerFromWhitelist(xuid, correctName)) { - sender.sendMessage(Message.PLAYER_REMOVED, strippedName); + sender.sendMessage(Message.PLAYER_REMOVED, literal("target", strippedName)); } else { - sender.sendMessage(Message.PLAYER_NOT_WHITELISTED, strippedName); + sender.sendMessage(Message.PLAYER_NOT_WHITELISTED, literal("target", strippedName)); } } } catch (Exception exception) { @@ -185,23 +185,13 @@ public class WhitelistCommand implements FloodgateCommand { return !(config instanceof ProxyFloodgateConfig); } - @Getter - public enum Message implements TranslatableMessage { - INVALID_USERNAME("floodgate.command.fwhitelist.invalid_username"), - API_UNAVAILABLE("floodgate.command.fwhitelist.api_unavailable " + CommonCommandMessage.CHECK_CONSOLE), - USER_NOT_FOUND("floodgate.command.fwhitelist.user_not_found"), - PLAYER_ADDED("floodgate.command.fwhitelist.player_added"), - PLAYER_REMOVED("floodgate.command.fwhitelist.player_removed"), - PLAYER_ALREADY_WHITELISTED("floodgate.command.fwhitelist.player_already_whitelisted"), - PLAYER_NOT_WHITELISTED("floodgate.command.fwhitelist.player_not_whitelisted"), - UNEXPECTED_ERROR("floodgate.command.fwhitelist.unexpected_error " + CommonCommandMessage.CHECK_CONSOLE); - - private final String rawMessage; - private final String[] translateParts; - - Message(String rawMessage) { - this.rawMessage = rawMessage; - this.translateParts = rawMessage.split(" "); - } + public static final class Message { + public static final TranslatableMessage INVALID_USERNAME = new TranslatableMessage("floodgate.command.fwhitelist.invalid_username", MessageType.ERROR); + public static final TranslatableMessage API_UNAVAILABLE = new TranslatableMessage("floodgate.command.fwhitelist.api_unavailable " + CommonCommandMessage.CHECK_CONSOLE, MessageType.ERROR); + public static final TranslatableMessage ACCOUNT_NOT_FOUND = new TranslatableMessage("floodgate.command.fwhitelist.account_not_found", MessageType.ERROR); + public static final TranslatableMessage PLAYER_ADDED = new TranslatableMessage("floodgate.command.fwhitelist.player_added", MessageType.SUCCESS); + public static final TranslatableMessage PLAYER_REMOVED = new TranslatableMessage("floodgate.command.fwhitelist.player_removed", MessageType.SUCCESS); + public static final TranslatableMessage PLAYER_ALREADY_WHITELISTED = new TranslatableMessage("floodgate.command.fwhitelist.player_already_whitelisted", MessageType.NORMAL); + public static final TranslatableMessage PLAYER_NOT_WHITELISTED = new TranslatableMessage("floodgate.command.fwhitelist.player_not_whitelisted", MessageType.NORMAL); } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/AddLinkedAccountCommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/AddLinkedAccountCommand.java index 18868012..6c7b6e56 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/AddLinkedAccountCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/AddLinkedAccountCommand.java @@ -1,6 +1,7 @@ package org.geysermc.floodgate.core.command.linkedaccounts; -import static org.geysermc.floodgate.core.command.linkedaccounts.LinkedAccountsCommand.linkInfoMessage; +import static org.geysermc.floodgate.core.platform.command.Placeholder.dynamic; +import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; import jakarta.inject.Inject; import jakarta.inject.Singleton; @@ -8,15 +9,18 @@ import java.util.ArrayList; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.command.CommonCommandMessage; import org.geysermc.floodgate.core.command.LinkAccountCommand; +import org.geysermc.floodgate.core.command.linkedaccounts.LinkedAccountsCommand.LinkedAccountsCommonMessage; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.connection.audience.ProfileAudience; import org.geysermc.floodgate.core.connection.audience.UserAudience; import org.geysermc.floodgate.core.http.ProfileFetcher; import org.geysermc.floodgate.core.link.LocalPlayerLinking; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand; +import org.geysermc.floodgate.core.platform.command.MessageType; +import org.geysermc.floodgate.core.platform.command.TranslatableMessage; import org.geysermc.floodgate.core.util.Constants; import org.incendo.cloud.Command; import org.incendo.cloud.context.CommandContext; @@ -49,7 +53,7 @@ final class AddLinkedAccountCommand extends FloodgateSubCommand { var linking = optionalLinking.get(); if (linking.state().globalLinkingEnabled()) { - sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, Constants.LINK_INFO_URL); + sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, literal("url", Constants.LINK_INFO_URL)); } ProfileAudience bedrockInput = context.get("bedrock"); @@ -61,7 +65,10 @@ final class AddLinkedAccountCommand extends FloodgateSubCommand { if (bedrockRef.get().uuid() == null) { futures.add(fetcher.fetchXuidFor(bedrockRef.get().username()).thenAccept(bedrockRef::set)); + } else { + futures.add(fetcher.fetchGamertagFor(bedrockRef.get().uuid()).thenAccept(bedrockRef::set)); } + if (javaRef.get().uuid() == null) { futures.add(fetcher.fetchUniqueIdFor(javaRef.get().username()).thenAccept(javaRef::set)); } @@ -72,10 +79,16 @@ final class AddLinkedAccountCommand extends FloodgateSubCommand { var java = javaRef.get(); if (bedrock == null) { - sender.sendMessage("Could not find Bedrock account with username " + bedrockInput.username()); + sender.sendMessage( + LinkedAccountsCommonMessage.NOT_FOUND, + literal("platform", "Bedrock"), + literal("target", bedrockInput.username())); } if (java == null) { - sender.sendMessage("Could not find Java account with username " + javaInput.username()); + sender.sendMessage( + LinkedAccountsCommonMessage.NOT_FOUND, + literal("platform", "Java"), + literal("target", javaInput.username())); } linking.addLink(java.uuid(), java.username(), bedrock.uuid()).whenComplete((player, throwable) -> { @@ -84,8 +97,18 @@ final class AddLinkedAccountCommand extends FloodgateSubCommand { logger.error("Exception while manually linking accounts", throwable); return; } - sender.sendMessage("You've successfully linked:\n" + linkInfoMessage(player)); + sender.sendMessage( + Message.ADD_SUCCESS, + dynamic("link_info", LinkedAccountsCommonMessage.LINK_INFO, sender), + literal("bedrock_id", bedrock.uuid()), + literal("bedrock_name", bedrock.username()), + literal("java_name", java.username()), + literal("java_uuid", java.uuid())); }); }); } + + public static final class Message { + public static final TranslatableMessage ADD_SUCCESS = new TranslatableMessage("floodgate.command.linkedaccounts.add.success", MessageType.SUCCESS); + } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/InfoLinkedAccountCommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/InfoLinkedAccountCommand.java index 55247596..283f6503 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/InfoLinkedAccountCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/InfoLinkedAccountCommand.java @@ -1,17 +1,23 @@ package org.geysermc.floodgate.core.command.linkedaccounts; +import static org.geysermc.floodgate.core.platform.command.Placeholder.dynamic; +import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; + import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.command.CommonCommandMessage; +import org.geysermc.floodgate.core.command.linkedaccounts.LinkedAccountsCommand.LinkedAccountsCommonMessage; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.connection.audience.ProfileAudience; import org.geysermc.floodgate.core.connection.audience.UserAudience; import org.geysermc.floodgate.core.http.ProfileFetcher; import org.geysermc.floodgate.core.link.LocalPlayerLinking; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand; +import org.geysermc.floodgate.core.platform.command.MessageType; +import org.geysermc.floodgate.core.platform.command.TranslatableMessage; import org.geysermc.floodgate.core.util.Constants; import org.incendo.cloud.Command; import org.incendo.cloud.context.CommandContext; @@ -43,22 +49,26 @@ final class InfoLinkedAccountCommand extends FloodgateSubCommand { var linking = optionalLinking.get(); if (linking.state().globalLinkingEnabled()) { - sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, Constants.LINK_INFO_URL); + sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, literal("url", Constants.LINK_INFO_URL)); } ProfileAudience playerInput = context.get("player"); + String gamertagInput; final boolean bedrock; var future = CompletableFuture.completedFuture(playerInput); if (playerInput.uuid() == null) { if (playerInput.username().startsWith(config.usernamePrefix())) { - future = fetcher.fetchXuidFor(playerInput.username().substring(config.usernamePrefix().length())); + gamertagInput = playerInput.username().substring(config.usernamePrefix().length()); + future = fetcher.fetchXuidFor(gamertagInput); bedrock = true; } else { + gamertagInput = null; bedrock = false; future = fetcher.fetchUniqueIdFor(playerInput.username()); } } else { + gamertagInput = null; bedrock = playerInput.uuid().getMostSignificantBits() == 0; } @@ -71,13 +81,16 @@ final class InfoLinkedAccountCommand extends FloodgateSubCommand { } if (result == null) { - sender.sendMessage("Could not find %s user with username %s".formatted(platform, playerInput.username())); + sender.sendMessage( + LinkedAccountsCommonMessage.NOT_FOUND, + literal("platform", platform), + literal("target", playerInput.username())); return; } linking.fetchLink(result.uuid()).whenComplete((link, error) -> { if (error != null) { - sender.sendMessage("Error while looking up player link, see console"); + sender.sendMessage(Message.INFO_ERROR); logger.error("Exception while fetching link status", error); return; } @@ -85,13 +98,43 @@ final class InfoLinkedAccountCommand extends FloodgateSubCommand { var usernameOrUniqueId = playerInput.username() != null ? playerInput.username() : playerInput.uuid(); if (link == null) { - sender.sendMessage("%s user %s is not linked!".formatted(platform, usernameOrUniqueId)); + sender.sendMessage( + Message.INFO_NOT_LINKED, + literal("platform", platform), + literal("target", usernameOrUniqueId)); return; } - sender.sendMessage("Link info for %s user %s:\n%s" - .formatted(platform, usernameOrUniqueId, LinkedAccountsCommand.linkInfoMessage(link))); + CompletableFuture gamertagFetch = + CompletableFuture.completedFuture(new ProfileAudience(null, gamertagInput)); + if (gamertagInput == null) { + gamertagFetch = fetcher.fetchGamertagFor(link.bedrockId()); + } + + gamertagFetch.whenComplete((gamertagResult, $) -> { + String gamertag = "unknown"; + if (gamertagResult != null) { + gamertag = gamertagResult.username(); + } + + sender.sendMessage( + Message.INFO_LINKED, + literal("platform", platform), + literal("target", usernameOrUniqueId), + dynamic("link_info", LinkedAccountsCommonMessage.LINK_INFO, sender), + literal("bedrock_id", link.bedrockId()), + literal("bedrock_name", gamertag), + literal("java_name", link.javaUsername()), + literal("java_uuid", link.javaUniqueId())); + }); + }); }); } + + public static final class Message { + public static final TranslatableMessage INFO_ERROR = new TranslatableMessage("floodgate.command.linkedaccounts.info.error", MessageType.ERROR); + public static final TranslatableMessage INFO_NOT_LINKED = new TranslatableMessage("floodgate.command.linkedaccounts.info.not_linked", MessageType.NORMAL); + public static final TranslatableMessage INFO_LINKED = new TranslatableMessage("floodgate.command.linkedaccounts.info.linked", MessageType.NORMAL); + } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/LinkedAccountsCommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/LinkedAccountsCommand.java index 7cb251b4..a3e90c6e 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/LinkedAccountsCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/LinkedAccountsCommand.java @@ -2,8 +2,9 @@ package org.geysermc.floodgate.core.command.linkedaccounts; import jakarta.inject.Singleton; import org.geysermc.floodgate.core.command.util.Permission; -import org.geysermc.floodgate.core.database.entity.LinkedPlayer; +import org.geysermc.floodgate.core.platform.command.MessageType; import org.geysermc.floodgate.core.platform.command.SubCommands; +import org.geysermc.floodgate.core.platform.command.TranslatableMessage; @Singleton final class LinkedAccountsCommand extends SubCommands { @@ -11,9 +12,8 @@ final class LinkedAccountsCommand extends SubCommands { super("linkedaccounts", "Manage locally linked accounts", Permission.COMMAND_LINKED); } - @Singleton - static String linkInfoMessage(LinkedPlayer player) { - return "Java UUID: %s\nJava username: %s\nBedrock UUID: %s" - .formatted(player.javaUniqueId(), player.javaUsername(), player.bedrockId()); + public static final class LinkedAccountsCommonMessage { + public static final TranslatableMessage NOT_FOUND = new TranslatableMessage("floodgate.command.linkedaccounts.common.not_found", MessageType.ERROR); + public static final TranslatableMessage LINK_INFO = new TranslatableMessage("floodgate.command.linkedaccounts.common.link_info"); } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/RemoveLinkedAccountCommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/RemoveLinkedAccountCommand.java index 9c757c49..f3023eb5 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/RemoveLinkedAccountCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/linkedaccounts/RemoveLinkedAccountCommand.java @@ -1,19 +1,24 @@ package org.geysermc.floodgate.core.command.linkedaccounts; +import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; + import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.command.CommonCommandMessage; import org.geysermc.floodgate.core.command.LinkAccountCommand; +import org.geysermc.floodgate.core.command.linkedaccounts.LinkedAccountsCommand.LinkedAccountsCommonMessage; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.connection.audience.ProfileAudience; import org.geysermc.floodgate.core.connection.audience.UserAudience; import org.geysermc.floodgate.core.http.ProfileFetcher; import org.geysermc.floodgate.core.link.LocalPlayerLinking; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand; +import org.geysermc.floodgate.core.platform.command.MessageType; +import org.geysermc.floodgate.core.platform.command.TranslatableMessage; import org.geysermc.floodgate.core.util.Constants; import org.incendo.cloud.Command; import org.incendo.cloud.context.CommandContext; @@ -45,7 +50,7 @@ final class RemoveLinkedAccountCommand extends FloodgateSubCommand { var linking = optionalLinking.get(); if (linking.state().globalLinkingEnabled()) { - sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, Constants.LINK_INFO_URL); + sender.sendMessage(CommonCommandMessage.LOCAL_LINKING_NOTICE, literal("url", Constants.LINK_INFO_URL)); } ProfileAudience playerInput = context.get("player"); @@ -73,8 +78,10 @@ final class RemoveLinkedAccountCommand extends FloodgateSubCommand { } if (result == null) { - sender.sendMessage("Could not find %s user with username %s" - .formatted(platform, playerInput.username())); + sender.sendMessage( + LinkedAccountsCommonMessage.NOT_FOUND, + literal("platform", platform), + literal("target", playerInput.username())); return; } @@ -84,9 +91,15 @@ final class RemoveLinkedAccountCommand extends FloodgateSubCommand { logger.error("Exception while manually linking accounts", error); return; } - sender.sendMessage("You've successfully unlinked %s user %s" - .formatted(platform, playerInput.username())); + sender.sendMessage( + Message.REMOVE_SUCCESS, + literal("platform", platform), + literal("target", playerInput.username())); }); }); } + + public static final class Message { + public static final TranslatableMessage REMOVE_SUCCESS = new TranslatableMessage("floodgate.command.linkedaccounts.remove.success", MessageType.SUCCESS); + } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/main/FirewallCheckSubcommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/main/FirewallCheckSubcommand.java index 87f82d63..f6963c96 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/main/FirewallCheckSubcommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/main/FirewallCheckSubcommand.java @@ -25,7 +25,7 @@ package org.geysermc.floodgate.core.command.main; -import static org.geysermc.floodgate.core.util.Constants.COLOR_CHAR; +import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; import com.google.gson.JsonElement; import it.unimi.dsi.fastutil.Pair; @@ -37,6 +37,8 @@ import java.util.function.BooleanSupplier; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.connection.audience.UserAudience; import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand; +import org.geysermc.floodgate.core.platform.command.MessageType; +import org.geysermc.floodgate.core.platform.command.TranslatableMessage; import org.geysermc.floodgate.core.util.Constants; import org.geysermc.floodgate.core.util.HttpClient; import org.geysermc.floodgate.core.util.HttpClient.HttpResponse; @@ -62,10 +64,10 @@ final class FirewallCheckSubcommand extends FloodgateSubCommand { executeChecks( globalApiCheck(sender) ).whenComplete((response, $) -> - sender.sendMessage(String.format( - COLOR_CHAR + "eThe checks have finished. %s/%s were successful", - response.left(), response.left() + response.right() - )) + sender.sendMessage( + Message.FIREWALL_RESULT, + literal("successful", response.left()), + literal("total", response.left() + response.right())) ); } @@ -88,14 +90,14 @@ final class FirewallCheckSubcommand extends FloodgateSubCommand { private BooleanSupplier executeFirewallText( UserAudience sender, String name, Runnable runnable) { return () -> { - sender.sendMessage(COLOR_CHAR + "eTesting " + name + "..."); + sender.sendMessage(Message.CHECK_START, literal("target", name)); try { runnable.run(); - sender.sendMessage(COLOR_CHAR + "aWas able to connect to " + name + "!"); + sender.sendMessage(Message.CHECK_SUCCESS, literal("target", name)); return true; - } catch (Exception e) { - sender.sendMessage(COLOR_CHAR + "cFailed to connect:"); - sender.sendMessage(Utils.getStackTrace(e)); + } catch (Exception exception) { + sender.sendMessage(Message.CHECK_FAILED, literal("target", name)); + sender.sendRaw(Utils.getStackTrace(exception), MessageType.ERROR); return false; } }; @@ -117,4 +119,11 @@ final class FirewallCheckSubcommand extends FloodgateSubCommand { return Pair.of(okCount.get(), failCount.get()); }); } + + public static final class Message { + public static final TranslatableMessage FIREWALL_RESULT = new TranslatableMessage("floodgate.command.main.firewall.result", MessageType.INFO); + public static final TranslatableMessage CHECK_START = new TranslatableMessage("floodgate.command.main.firewall.check.start", MessageType.INFO); + public static final TranslatableMessage CHECK_SUCCESS = new TranslatableMessage("floodgate.command.main.firewall.check.success", MessageType.SUCCESS); + public static final TranslatableMessage CHECK_FAILED = new TranslatableMessage("floodgate.command.main.firewall.check.failed", MessageType.ERROR); + } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/command/main/VersionSubcommand.java b/core/src/main/java/org/geysermc/floodgate/core/command/main/VersionSubcommand.java index cde47006..9b45b77d 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/command/main/VersionSubcommand.java +++ b/core/src/main/java/org/geysermc/floodgate/core/command/main/VersionSubcommand.java @@ -25,16 +25,18 @@ package org.geysermc.floodgate.core.command.main; -import static org.geysermc.floodgate.core.util.Constants.COLOR_CHAR; +import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; import com.google.gson.JsonElement; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.core.command.WhitelistCommand.Message; +import org.geysermc.floodgate.core.command.CommonCommandMessage; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.connection.audience.UserAudience; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand; +import org.geysermc.floodgate.core.platform.command.MessageType; +import org.geysermc.floodgate.core.platform.command.TranslatableMessage; import org.geysermc.floodgate.core.util.Constants; import org.geysermc.floodgate.core.util.HttpClient; import org.incendo.cloud.context.CommandContext; @@ -56,12 +58,11 @@ public class VersionSubcommand extends FloodgateSubCommand { @Override public void execute(CommandContext context) { UserAudience sender = context.sender(); - sender.sendMessage(String.format( - COLOR_CHAR + "7You're currently on " + COLOR_CHAR + "b%s" + - COLOR_CHAR + "7 (branch: " + COLOR_CHAR + "b%s" + COLOR_CHAR + "7)\n" + - COLOR_CHAR + "eFetching latest build info...", - Constants.FULL_VERSION, Constants.GIT_BRANCH - )); + sender.sendMessage( + Message.VERSION_INFO, + literal("version", Constants.FULL_VERSION), + literal("branch", Constants.GIT_BRANCH)); + sender.sendMessage(Message.VERSION_FETCH_INFO); String baseUrl = String.format( "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/%s/lastSuccessfulBuild/", @@ -73,7 +74,7 @@ public class VersionSubcommand extends FloodgateSubCommand { JsonElement.class ).whenComplete((result, error) -> { if (error != null) { - sender.sendMessage(COLOR_CHAR + "cCould not retrieve latest version info!"); + sender.sendMessage(Message.VERSION_FETCH_ERROR); error.printStackTrace(); return; } @@ -84,10 +85,7 @@ public class VersionSubcommand extends FloodgateSubCommand { logger.info("{}", result.getHttpCode()); if (result.getHttpCode() == 404) { - sender.sendMessage( - COLOR_CHAR + "cGot a 404 (not found) while requesting the latest version." + - " Are you using a custom Floodgate version?" - ); + sender.sendMessage(Message.VERSION_FETCH_NOT_FOUND); return; } @@ -97,26 +95,34 @@ public class VersionSubcommand extends FloodgateSubCommand { "Got an error from requesting the latest Floodgate version: {}", response.toString() ); - sender.sendMessage(Message.UNEXPECTED_ERROR); + sender.sendMessage(CommonCommandMessage.UNEXPECTED_ERROR); return; } int buildNumber = response.getAsInt(); if (buildNumber > Constants.BUILD_NUMBER) { - sender.sendMessage(String.format( - COLOR_CHAR + "7There is a newer version of Floodgate available!\n" + - COLOR_CHAR + "7You are " + COLOR_CHAR + "e%s " + COLOR_CHAR + "7builds behind.\n" + - COLOR_CHAR + "7Download the latest Floodgate version here: " + COLOR_CHAR + "b%s", - buildNumber - Constants.BUILD_NUMBER, baseUrl - )); + sender.sendMessage( + Message.VERSION_OUTDATED, + literal("count", buildNumber - Constants.BUILD_NUMBER), + literal("url", baseUrl)); return; } if (buildNumber == Constants.BUILD_NUMBER) { - sender.sendMessage(COLOR_CHAR + "aYou're running the latest version of Floodgate!"); + sender.sendMessage(Message.VERSION_LATEST); return; } - sender.sendMessage(COLOR_CHAR + "cCannot check version for custom Floodgate versions!"); + sender.sendMessage(Message.VERSION_CUSTOM); }); } + + public static final class Message { + public static final TranslatableMessage VERSION_INFO = new TranslatableMessage("floodgate.command.main.version.info", MessageType.NORMAL); + public static final TranslatableMessage VERSION_FETCH_INFO = new TranslatableMessage("floodgate.command.main.version.fetch.info", MessageType.INFO); + public static final TranslatableMessage VERSION_FETCH_ERROR = new TranslatableMessage("floodgate.command.main.version.fetch.error", MessageType.ERROR); + public static final TranslatableMessage VERSION_FETCH_NOT_FOUND = new TranslatableMessage("floodgate.command.main.version.fetch.not_found", MessageType.ERROR); + public static final TranslatableMessage VERSION_OUTDATED = new TranslatableMessage("floodgate.command.main.version.fetch.result.outdated", MessageType.NORMAL); + public static final TranslatableMessage VERSION_LATEST = new TranslatableMessage("floodgate.command.main.version.fetch.result.latest", MessageType.SUCCESS); + public static final TranslatableMessage VERSION_CUSTOM = new TranslatableMessage("floodgate.command.main.version.fetch.result.custom", MessageType.ERROR); + } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/connection/ConnectionManager.java b/core/src/main/java/org/geysermc/floodgate/core/connection/ConnectionManager.java index 763213d8..3995c0fb 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/connection/ConnectionManager.java +++ b/core/src/main/java/org/geysermc/floodgate/core/connection/ConnectionManager.java @@ -25,6 +25,8 @@ package org.geysermc.floodgate.core.connection; +import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; + import jakarta.inject.Inject; import java.util.Collection; import java.util.Collections; @@ -38,7 +40,8 @@ import java.util.WeakHashMap; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.connection.Connection; -import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.logger.FloodgateLogger; +import org.geysermc.floodgate.core.platform.CommonPlatformMessages; public abstract class ConnectionManager { private final Set connections = Collections.synchronizedSet(new HashSet<>()); @@ -93,9 +96,9 @@ public abstract class ConnectionManager { pendingConnections.add(connection); logger.translatedInfo( - "floodgate.ingame.login_name", - connection.javaUsername(), connection.javaUuid() - ); + CommonPlatformMessages.CONNECTION_LOGIN, + literal("target", connection.javaUsername()), + literal("target_uuid", connection.javaUuid())); } public boolean addAcceptedConnection(Connection connection) { @@ -134,7 +137,9 @@ public abstract class ConnectionManager { pendingConnections.remove(connection); uuidToConnection.remove(connection.javaUuid(), connection); xuidToConnection.remove(connection.xuid(), connection); - logger.translatedInfo("floodgate.ingame.disconnect_name", connection.javaUsername()); + logger.translatedInfo( + CommonPlatformMessages.CONNECTION_DISCONNECT, + literal("target", connection.javaUsername())); } public Collection acceptedConnections() { diff --git a/core/src/main/java/org/geysermc/floodgate/core/connection/FloodgateDataHandler.java b/core/src/main/java/org/geysermc/floodgate/core/connection/FloodgateDataHandler.java index b4da7364..57062d7d 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/connection/FloodgateDataHandler.java +++ b/core/src/main/java/org/geysermc/floodgate/core/connection/FloodgateDataHandler.java @@ -25,21 +25,25 @@ package org.geysermc.floodgate.core.connection; +import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; + import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.concurrent.CompletableFuture; import lombok.NonNull; import lombok.SneakyThrows; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.geysermc.floodgate.api.event.FloodgateEventBus; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.addon.data.CommonNettyDataHandler; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.crypto.FloodgateDataCodec; import org.geysermc.floodgate.core.crypto.exception.UnsupportedVersionException; import org.geysermc.floodgate.core.event.ConnectionJoinEvent; import org.geysermc.floodgate.core.link.CommonPlayerLink; +import org.geysermc.floodgate.core.logger.FloodgateLogger; +import org.geysermc.floodgate.core.platform.CommonPlatformMessages; import org.geysermc.floodgate.core.util.Constants; import org.geysermc.floodgate.core.util.InvalidFormatException; import org.geysermc.floodgate.core.util.LanguageManager; @@ -93,11 +97,11 @@ public final class FloodgateDataHandler { private JoinResult canJoin(FloodgateConnection connection) { String disconnectReason = null; if (config.playerLink().requireLink() && !connection.isLinked()) { - disconnectReason = languageManager.getString( - "floodgate.core.not_linked", - connection.languageCode(), - Constants.LINK_INFO_URL - ); + disconnectReason = MiniMessage.miniMessage().serialize( + CommonPlatformMessages.NOT_LINKED.translateMessage( + languageManager, + connection.languageCode(), + literal("url", Constants.LINK_INFO_URL))); } var event = new ConnectionJoinEvent(connection, disconnectReason); diff --git a/core/src/main/java/org/geysermc/floodgate/core/connection/audience/UserAudience.java b/core/src/main/java/org/geysermc/floodgate/core/connection/audience/UserAudience.java index 3b1b497f..d24889f2 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/connection/audience/UserAudience.java +++ b/core/src/main/java/org/geysermc/floodgate/core/connection/audience/UserAudience.java @@ -29,9 +29,13 @@ import java.util.Objects; import java.util.UUID; import lombok.Getter; import lombok.experimental.Accessors; +import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.core.platform.command.CommandUtil; +import org.geysermc.floodgate.core.platform.command.MessageType; +import org.geysermc.floodgate.core.platform.command.Placeholder; import org.geysermc.floodgate.core.platform.command.TranslatableMessage; +import org.geysermc.floodgate.core.util.MiniMessageUtils; @Getter @Accessors(fluent = true) public class UserAudience { @@ -58,23 +62,30 @@ public class UserAudience { return commandUtil.hasPermission(source(), permission); } - public void sendMessage(String message) { + /** + * This is only meant for development use, before the translations have been added + */ + public void sendRaw(String message, MessageType type, Placeholder... placeholders) { + sendMessage(MiniMessageUtils.formatMessage(message, type, placeholders)); + } + + public void sendMessage(Component message) { commandUtil.sendMessage(source(), message); } - public void sendMessage(TranslatableMessage message, Object... args) { - sendMessage(translateMessage(message, args)); + public void sendMessage(TranslatableMessage message, Placeholder... placeholders) { + sendMessage(translateMessage(message, placeholders)); } - public void disconnect(@NonNull String reason) { + public void disconnect(Component reason) { commandUtil.kickPlayer(source(), reason); } - public void disconnect(TranslatableMessage message, Object... args) { + public void disconnect(TranslatableMessage message, Placeholder... args) { disconnect(translateMessage(message, args)); } - public String translateMessage(TranslatableMessage message, Object... args) { + public Component translateMessage(TranslatableMessage message, Placeholder... args) { return commandUtil.translateMessage(locale(), message, args); } diff --git a/core/src/main/java/org/geysermc/floodgate/core/link/CommonPlayerLink.java b/core/src/main/java/org/geysermc/floodgate/core/link/CommonPlayerLink.java index dc309af0..0ee12a9f 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/link/CommonPlayerLink.java +++ b/core/src/main/java/org/geysermc/floodgate/core/link/CommonPlayerLink.java @@ -32,11 +32,11 @@ import lombok.AccessLevel; import lombok.Getter; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.api.GeyserApiBase; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.database.entity.LinkRequest; import org.geysermc.floodgate.core.database.entity.LinkedPlayer; +import org.geysermc.floodgate.core.logger.FloodgateLogger; public abstract class CommonPlayerLink { @Getter private boolean enabled; diff --git a/core/src/main/java/org/geysermc/floodgate/core/link/LinkVerificationException.java b/core/src/main/java/org/geysermc/floodgate/core/link/LinkVerificationException.java index d1ed6e5e..8ddf14b3 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/link/LinkVerificationException.java +++ b/core/src/main/java/org/geysermc/floodgate/core/link/LinkVerificationException.java @@ -25,24 +25,34 @@ package org.geysermc.floodgate.core.link; +import static org.geysermc.floodgate.core.platform.command.Placeholder.literal; + import org.geysermc.floodgate.core.command.LinkAccountCommand.Message; +import org.geysermc.floodgate.core.platform.command.Placeholder; +import org.geysermc.floodgate.core.platform.command.TranslatableMessage; public class LinkVerificationException extends RuntimeException { public static final LinkVerificationException NO_LINK_REQUESTED = - new LinkVerificationException(Message.NO_LINK_REQUESTED); + new LinkVerificationException(Message.NO_LINK_REQUESTED, literal("command", "/linkaccount")); public static final LinkVerificationException INVALID_CODE = - new LinkVerificationException(Message.INVALID_CODE); + new LinkVerificationException(Message.INVALID_CODE, literal("command", "/linkaccount")); public static final LinkVerificationException LINK_REQUEST_EXPIRED = - new LinkVerificationException(Message.LINK_REQUEST_EXPIRED); + new LinkVerificationException(Message.LINK_REQUEST_EXPIRED, literal("command", "/linkaccount")); - private final Message message; + private final TranslatableMessage message; + private final Placeholder[] placeholders; - private LinkVerificationException(Message message) { + private LinkVerificationException(TranslatableMessage message, Placeholder... placeholders) { super(null, null, true, false); this.message = message; + this.placeholders = placeholders; } - public Message message() { + public TranslatableMessage message() { return message; } + + public Placeholder[] placeholders() { + return placeholders; + } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/listener/McListenerRegister.java b/core/src/main/java/org/geysermc/floodgate/core/listener/McListenerRegister.java index 9d577710..7160050c 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/listener/McListenerRegister.java +++ b/core/src/main/java/org/geysermc/floodgate/core/listener/McListenerRegister.java @@ -29,8 +29,8 @@ import io.micronaut.runtime.event.annotation.EventListener; import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.Set; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.event.lifecycle.PostEnableEvent; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.platform.listener.ListenerRegistration; @Singleton diff --git a/api/src/main/java/org/geysermc/floodgate/api/logger/FloodgateLogger.java b/core/src/main/java/org/geysermc/floodgate/core/logger/FloodgateLogger.java similarity index 90% rename from api/src/main/java/org/geysermc/floodgate/api/logger/FloodgateLogger.java rename to core/src/main/java/org/geysermc/floodgate/core/logger/FloodgateLogger.java index e3afee90..ab25eb6f 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/logger/FloodgateLogger.java +++ b/core/src/main/java/org/geysermc/floodgate/core/logger/FloodgateLogger.java @@ -23,7 +23,11 @@ * @link https://github.com/GeyserMC/Floodgate */ -package org.geysermc.floodgate.api.logger; +package org.geysermc.floodgate.core.logger; + +import net.kyori.adventure.text.Component; +import org.geysermc.floodgate.core.platform.command.Placeholder; +import org.geysermc.floodgate.core.platform.command.TranslatableMessage; public interface FloodgateLogger { String LOGGER_NAME = "Floodgate"; @@ -61,7 +65,9 @@ public interface FloodgateLogger { */ void info(String message, Object... args); - void translatedInfo(String message, Object... args); + void info(Component message); + + void translatedInfo(TranslatableMessage message, Placeholder... args); /** * Logs a debug message to the console, with 0 or more arguments. diff --git a/core/src/main/java/org/geysermc/floodgate/core/logger/Slf4jFloodgateLogger.java b/core/src/main/java/org/geysermc/floodgate/core/logger/Slf4jFloodgateLogger.java index 9f905354..73e58269 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/logger/Slf4jFloodgateLogger.java +++ b/core/src/main/java/org/geysermc/floodgate/core/logger/Slf4jFloodgateLogger.java @@ -25,15 +25,17 @@ package org.geysermc.floodgate.core.logger; -import static org.geysermc.floodgate.core.util.MessageFormatter.format; - import io.micronaut.context.BeanProvider; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import org.geysermc.floodgate.api.logger.FloodgateLogger; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.platform.command.Placeholder; +import org.geysermc.floodgate.core.platform.command.TranslatableMessage; import org.geysermc.floodgate.core.util.LanguageManager; import org.slf4j.Logger; +import org.slf4j.helpers.MessageFormatter; @Singleton public final class Slf4jFloodgateLogger implements FloodgateLogger { @@ -54,7 +56,7 @@ public final class Slf4jFloodgateLogger implements FloodgateLogger { @Override public void error(String message, Throwable throwable, Object... args) { - logger.error(format(message, args), throwable); + logger.error(MessageFormatter.basicArrayFormat(message, args), throwable); } @Override @@ -68,8 +70,16 @@ public final class Slf4jFloodgateLogger implements FloodgateLogger { } @Override - public void translatedInfo(String message, Object... args) { - logger.info(languageManager.get().getLogString(message, args)); + public void info(Component message) { + //todo check on other platforms if just serializing the component works. + // it does on Velocity + logger.info(MiniMessage.miniMessage().serialize(message)); + } + + @Override + public void translatedInfo(TranslatableMessage message, Placeholder... args) { + var manager = languageManager.get(); + info(message.translateMessage(manager, manager.getDefaultLocale(), args)); } @Override diff --git a/core/src/main/java/org/geysermc/floodgate/core/platform/CommonPlatformMessages.java b/core/src/main/java/org/geysermc/floodgate/core/platform/CommonPlatformMessages.java new file mode 100644 index 00000000..f143268e --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/platform/CommonPlatformMessages.java @@ -0,0 +1,12 @@ +package org.geysermc.floodgate.core.platform; + +import org.geysermc.floodgate.core.platform.command.MessageType; +import org.geysermc.floodgate.core.platform.command.TranslatableMessage; + +public class CommonPlatformMessages { + public static final TranslatableMessage CORE_FINISH = new TranslatableMessage("floodgate.core.finish"); + public static final TranslatableMessage CONNECTION_LOGIN = new TranslatableMessage("floodgate.ingame.login_name"); + public static final TranslatableMessage CONNECTION_DISCONNECT = new TranslatableMessage("floodgate.ingame.disconnect_name"); + + public static final TranslatableMessage NOT_LINKED = new TranslatableMessage("floodgate.core.not_linked", MessageType.ERROR); +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/platform/command/CommandUtil.java b/core/src/main/java/org/geysermc/floodgate/core/platform/command/CommandUtil.java index 4dd43045..87bf12db 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/platform/command/CommandUtil.java +++ b/core/src/main/java/org/geysermc/floodgate/core/platform/command/CommandUtil.java @@ -36,6 +36,7 @@ import java.util.Objects; import java.util.UUID; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.GeyserApiBase; @@ -161,20 +162,24 @@ public abstract class CommandUtil { * @param target the player that should receive the message * @param message the message */ - public abstract void sendMessage(Object target, String message); + public abstract void sendMessage(Object target, Component message); /** * Kicks the given player using the given message as the kick reason. * * @param player the player that should be kicked - * @param message the command message + * @param message the message */ - public abstract void kickPlayer(Object player, String message); + public abstract void kickPlayer(Object player, Component message); - public String translateMessage(String locale, TranslatableMessage message, Object... args) { + public Component translateMessage(String locale, TranslatableMessage message, Placeholder... args) { return message.translateMessage(manager, locale, args); } + public LanguageManager languageManager() { + return manager; + } + /** * Whitelist the given Bedrock player. * diff --git a/core/src/main/java/org/geysermc/floodgate/core/platform/command/MessageType.java b/core/src/main/java/org/geysermc/floodgate/core/platform/command/MessageType.java new file mode 100644 index 00000000..da95a0c4 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/platform/command/MessageType.java @@ -0,0 +1,29 @@ +package org.geysermc.floodgate.core.platform.command; + +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.checkerframework.checker.nullness.qual.Nullable; + +public enum MessageType { + NONE(null, null), + NORMAL(null, NamedTextColor.AQUA), + INFO(NamedTextColor.YELLOW, NamedTextColor.AQUA), + SUCCESS(NamedTextColor.GREEN, NamedTextColor.GOLD), + ERROR(NamedTextColor.RED, NamedTextColor.GOLD); + + private final TextColor primaryColor; + private final TextColor secondaryColor; + + MessageType(@Nullable TextColor primaryColor, @Nullable TextColor secondaryColor) { + this.primaryColor = primaryColor; + this.secondaryColor = secondaryColor; + } + + public @Nullable TextColor primaryColor() { + return primaryColor; + } + + public @Nullable TextColor secondaryColor() { + return secondaryColor; + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/platform/command/Placeholder.java b/core/src/main/java/org/geysermc/floodgate/core/platform/command/Placeholder.java new file mode 100644 index 00000000..d1515a5b --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/platform/command/Placeholder.java @@ -0,0 +1,56 @@ +package org.geysermc.floodgate.core.platform.command; + +import java.util.Objects; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.minimessage.tag.Inserting; +import net.kyori.adventure.text.minimessage.tag.PreProcess; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.TagPattern; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import org.geysermc.floodgate.core.connection.audience.UserAudience; + +public record Placeholder(@TagPattern String key, Tag tag) { + public static Placeholder dynamic(@TagPattern String key, String value) { + return new Placeholder(key, Tag.preProcessParsed(value)); + } + + public static Placeholder dynamic(@TagPattern String key, TranslatableMessage message, UserAudience audience) { + return dynamic(key, audience.commandUtil().languageManager().rawTranslation(message.toString(), audience.locale())); + } + + public static Placeholder literal(@TagPattern String key, String value) { + return literal(key, Component.text(value)); + } + + public static Placeholder literal(@TagPattern String key, ComponentLike component) { + return new Placeholder(key, Tag.selfClosingInserting(component)); + } + + public static Placeholder literal(@TagPattern String key, Object value) { + return literal(key, Objects.toString(value)); + } + + public TagResolver resolver(MessageType type) { + return TagResolver.resolver(key, ($, context) -> { + var root = Component.empty(); + + // We've only looked at PreProcess and Inserting. + // We've excluded PreProcess because we expect the placeholders of the PreProcess to have the secondary + // color, not the PreProcess itself. + if (type.secondaryColor() != null && tag instanceof Inserting) { + root = root.color(type.secondaryColor()); + } + + if (tag instanceof PreProcess preProcess) { + root = root.append(context.deserialize(preProcess.value())); + } else if (tag instanceof Inserting inserting) { + root = root.append(inserting.value()); + } else { + throw new UnsupportedOperationException("Only PreProcess and Inserting have been implemented!"); + } + + return Tag.selfClosingInserting(root); + }); + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/platform/command/SubCommands.java b/core/src/main/java/org/geysermc/floodgate/core/platform/command/SubCommands.java index c7aadf95..33a1f2ed 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/platform/command/SubCommands.java +++ b/core/src/main/java/org/geysermc/floodgate/core/platform/command/SubCommands.java @@ -25,13 +25,13 @@ package org.geysermc.floodgate.core.platform.command; -import static org.geysermc.floodgate.core.util.Constants.COLOR_CHAR; import static org.incendo.cloud.description.Description.description; import jakarta.annotation.PostConstruct; import jakarta.inject.Inject; -import java.util.Locale; import java.util.Set; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.geysermc.floodgate.core.command.util.Permission; import org.geysermc.floodgate.core.connection.audience.UserAudience; import org.incendo.cloud.Command; @@ -68,19 +68,21 @@ public abstract class SubCommands implements FloodgateCommand { } public void execute(CommandContext context) { - StringBuilder helpMessage = new StringBuilder("Available subcommands are:\n"); + var helpMessage = Component.text("Available subcommands are:").appendNewline(); //todo add translation + //todo this should probably follow MessageType as well for (FloodgateSubCommand subCommand : subCommands) { var permission = subCommand.permission(); if (permission == null || context.sender().hasPermission(permission.get())) { - helpMessage.append('\n').append(COLOR_CHAR).append('b') - .append(subCommand.name().toLowerCase(Locale.ROOT)) - .append(COLOR_CHAR).append("f - ").append(COLOR_CHAR).append('7') - .append(subCommand.description()); + var component = Component.newline() + .append(Component.text(subCommand.name(), NamedTextColor.AQUA)) + .append(Component.text(" - ")) + .append(Component.text(subCommand.description(), NamedTextColor.GRAY)); + helpMessage = helpMessage.append(component); } } - context.sender().sendMessage(helpMessage.toString()); + context.sender().sendMessage(helpMessage); } @PostConstruct diff --git a/core/src/main/java/org/geysermc/floodgate/core/platform/command/TranslatableMessage.java b/core/src/main/java/org/geysermc/floodgate/core/platform/command/TranslatableMessage.java index cebeba6d..f39b72c7 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/platform/command/TranslatableMessage.java +++ b/core/src/main/java/org/geysermc/floodgate/core/platform/command/TranslatableMessage.java @@ -25,36 +25,49 @@ package org.geysermc.floodgate.core.platform.command; +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.floodgate.core.util.LanguageManager; /** - * TranslatableMessage is the interface for a message that can be translated. Messages are generally - * implemented using enums. + * TranslatableMessage is the common class for a message that can be translated */ -public interface TranslatableMessage { - /** - * Returns the message attached to the enum identifier - */ - String getRawMessage(); +public class TranslatableMessage { + private final String rawMessage; + private final String[] translateParts; + private final MessageType type; - /** - * Returns the parts of this message (getRawMessage() split on " ") - */ - String[] getTranslateParts(); + public TranslatableMessage(String rawMessage, MessageType type) { + this.rawMessage = rawMessage; + this.translateParts = rawMessage.split(" "); + this.type = type; + } + + public TranslatableMessage(String rawMessage) { + this(rawMessage, MessageType.NONE); + } + + public Component translateMessage(LanguageManager manager, @Nullable String locale, Placeholder... placeholders) { + if (locale == null) { + locale = manager.getDefaultLocale(); + } - default String translateMessage(LanguageManager manager, String locale, Object... args) { - String[] translateParts = getTranslateParts(); if (translateParts.length == 1) { - return manager.getString(getRawMessage(), locale, args); + return manager.getString(rawMessage, locale, type, placeholders); } - // todo only works when one section has arguments - StringBuilder builder = new StringBuilder(); + + Component complete = Component.empty(); for (int i = 0; i < translateParts.length; i++) { - builder.append(manager.getString(translateParts[i], locale, args)); - if (translateParts.length != i + 1) { - builder.append(' '); + if (i != 0) { + complete = complete.append(Component.text(' ')); } + complete = complete.append(manager.getString(translateParts[i], locale, type, placeholders)); } - return builder.toString(); + return complete; + } + + @Override + public String toString() { + return rawMessage; } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/pluginmessage/channel/FormChannel.java b/core/src/main/java/org/geysermc/floodgate/core/pluginmessage/channel/FormChannel.java index 1c25f486..7a931175 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/pluginmessage/channel/FormChannel.java +++ b/core/src/main/java/org/geysermc/floodgate/core/pluginmessage/channel/FormChannel.java @@ -35,8 +35,8 @@ import java.util.concurrent.atomic.AtomicInteger; import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.impl.FormDefinition; import org.geysermc.cumulus.form.impl.FormDefinitions; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.platform.pluginmessage.PluginMessageUtils; import org.geysermc.floodgate.core.pluginmessage.PluginMessageChannel; diff --git a/core/src/main/java/org/geysermc/floodgate/core/util/LanguageManager.java b/core/src/main/java/org/geysermc/floodgate/core/util/LanguageManager.java index d2802d75..9a90e8f1 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/util/LanguageManager.java +++ b/core/src/main/java/org/geysermc/floodgate/core/util/LanguageManager.java @@ -28,7 +28,6 @@ package org.geysermc.floodgate.core.util; import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.net.URL; -import java.text.MessageFormat; import java.util.Arrays; import java.util.HashMap; import java.util.Locale; @@ -36,8 +35,15 @@ import java.util.Map; import java.util.Properties; import java.util.stream.Collectors; import lombok.Getter; -import org.geysermc.floodgate.api.logger.FloodgateLogger; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.Inserting; +import net.kyori.adventure.text.minimessage.tag.PreProcess; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.logger.FloodgateLogger; +import org.geysermc.floodgate.core.platform.command.MessageType; +import org.geysermc.floodgate.core.platform.command.Placeholder; /** * Manages translations for strings in Floodgate @@ -124,46 +130,38 @@ public final class LanguageManager { return false; } - /** - * Get a formatted language string with the default locale for Floodgate - * - * @param key language string to translate - * @param values values to put into the string - * @return translated string or "key arg1, arg2 (etc.)" if it was not found in the given locale - */ - public String getLogString(String key, Object... values) { - return getString(key, defaultLocale, values); - } - /** * Get a formatted language string with the given locale for Floodgate * * @param key language string to translate * @param locale locale to translate to - * @param values values to put into the string + * @param type determines the scale of the message (and its arguments) + * @param placeholders values to put into the string * @return translated string or "key arg1, arg2 (etc.)" if it was not found in the given locale */ - public String getString(String key, String locale, Object... values) { + public Component getString(String key, @Nullable String locale, MessageType type, Placeholder... placeholders) { + var translated = rawTranslation(key, locale); + if (translated == null) { + translated = formatNotFound(key, placeholders); + } + return MiniMessageUtils.formatMessage(translated, type, placeholders); + } + + public String rawTranslation(String key, @Nullable String locale) { Properties properties = localeMappings.get(locale); - String formatString = null; + String translated = null; if (properties != null) { - formatString = properties.getProperty(key); + translated = properties.getProperty(key); } // try and get the key from the default locale - if (formatString == null) { + if (translated == null) { properties = localeMappings.get(defaultLocale); - formatString = properties.getProperty(key); + translated = properties.getProperty(key); } - // key wasn't found - if (formatString == null) { - return formatNotFound(key, values); - } - - //todo don't use color codes in the strings - return MessageFormat.format(formatString.replace("'", "''").replace("&", "\u00a7"), values); + return translated; } /** @@ -187,8 +185,16 @@ public final class LanguageManager { return true; } - private String formatNotFound(String key, Object... rawArgs) { - var args = Arrays.stream(rawArgs).map(Object::toString).collect(Collectors.joining(", ")); - return key + " " + args; + private String formatNotFound(String key, Placeholder... placeholders) { + return key + ' ' + Arrays.stream(placeholders) + .map((resolver) -> { + if (resolver.tag() instanceof PreProcess preProcess) { + return resolver.key() + ": " + preProcess.value(); + } else if (resolver.tag() instanceof Inserting inserting) { + return resolver.key() + ": " + MiniMessage.miniMessage().serialize(inserting.value()); + } + return resolver.key() + " (unknown tag)"; + }) + .collect(Collectors.joining(", ")); } } diff --git a/core/src/main/java/org/geysermc/floodgate/core/util/MessageFormatter.java b/core/src/main/java/org/geysermc/floodgate/core/util/MessageFormatter.java deleted file mode 100644 index dc37db95..00000000 --- a/core/src/main/java/org/geysermc/floodgate/core/util/MessageFormatter.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Floodgate - */ - -package org.geysermc.floodgate.core.util; - -public final class MessageFormatter { - private static final String DELIM_STR = "{}"; - private static final int DELIM_LENGTH = DELIM_STR.length(); - - public static String format(String message, Object... arguments) { - // simple variant of slf4j's parameters. - if (arguments == null || arguments.length == 0) { - return message; - } - - String[] args = new String[arguments.length]; - for (int i = 0; i < arguments.length; i++) { - Object arg = arguments[i]; - args[i] = arg != null ? arg.toString() : "null"; - } - - int previousIndex = -1; - int currentIndex; - StringBuilder stringBuilder = - new StringBuilder(message.length() + getArgsContentLength(args)); - - for (String argument : args) { - currentIndex = message.indexOf(DELIM_STR, previousIndex); - if (currentIndex == -1) { - // no parameter places left in message, - // we'll ignore the remaining parameters and return the message - if (previousIndex == -1) { - return message; - } else { - stringBuilder.append(message.substring(previousIndex)); - return stringBuilder.toString(); - } - } - - if (previousIndex == -1) { - stringBuilder.append(message, 0, currentIndex); - } else { - stringBuilder.append(message, previousIndex, currentIndex); - } - stringBuilder.append(argument); - - // we finished this argument, so we're past the current delimiter - previousIndex = currentIndex + DELIM_LENGTH; - } - - if (previousIndex != message.length()) { - stringBuilder.append(message, previousIndex, message.length()); - } - return stringBuilder.toString(); - } - - public static int getArgsContentLength(String... args) { - int length = 0; - for (String arg : args) { - length += arg.length(); - } - return length; - } -} diff --git a/core/src/main/java/org/geysermc/floodgate/core/util/Metrics.java b/core/src/main/java/org/geysermc/floodgate/core/util/Metrics.java index 1d78cabe..622c2ccf 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/util/Metrics.java +++ b/core/src/main/java/org/geysermc/floodgate/core/util/Metrics.java @@ -41,10 +41,10 @@ import org.bstats.charts.SimplePie; import org.bstats.charts.SingleLineChart; import org.bstats.json.JsonObjectBuilder; import org.geysermc.api.GeyserApiBase; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.config.FloodgateConfig.MetricsConfig; import org.geysermc.floodgate.core.event.lifecycle.ShutdownEvent; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.platform.util.PlatformUtils; @Singleton diff --git a/core/src/main/java/org/geysermc/floodgate/core/util/MiniMessageUtils.java b/core/src/main/java/org/geysermc/floodgate/core/util/MiniMessageUtils.java new file mode 100644 index 00000000..7acbf044 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/util/MiniMessageUtils.java @@ -0,0 +1,25 @@ +package org.geysermc.floodgate.core.util; + +import java.util.Arrays; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import org.geysermc.floodgate.core.platform.command.MessageType; +import org.geysermc.floodgate.core.platform.command.Placeholder; + +public final class MiniMessageUtils { + private MiniMessageUtils() {} + + public static Component formatMessage(String message, MessageType type, Placeholder... placeholders) { + var styledResolvers = Arrays.stream(placeholders) + .map(placeholder -> placeholder.resolver(type)) + .toArray(TagResolver[]::new); + + var component = MiniMessage.miniMessage().deserialize(message, styledResolvers); + + if (type.primaryColor() != null) { + component = Component.empty().color(type.primaryColor()).append(component); + } + return component; + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/util/PostEnableMessages.java b/core/src/main/java/org/geysermc/floodgate/core/util/PostEnableMessages.java index 0ef1f34c..288f0ec4 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/util/PostEnableMessages.java +++ b/core/src/main/java/org/geysermc/floodgate/core/util/PostEnableMessages.java @@ -34,9 +34,9 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.event.lifecycle.PostEnableEvent; +import org.geysermc.floodgate.core.logger.FloodgateLogger; @Singleton public final class PostEnableMessages { @@ -57,12 +57,13 @@ public final class PostEnableMessages { } builder.append("**********************************"); - messages.add(MessageFormatter.format(builder.toString(), args)); + messages.add(String.format(builder.toString(), args)); } @PostConstruct void registerPrefixMessages() { String prefix = config.rawUsernamePrefix(); + //todo these messages should also be translated if (prefix.isEmpty()) { add(new String[]{ @@ -72,21 +73,21 @@ public final class PostEnableMessages { }); } else if (!Utils.isUniquePrefix(prefix)) { add(new String[]{ - "The prefix you entered in your Floodgate config ({}) could lead to username conflicts!", - "Should a Java player join with the username {}Notch, and a Bedrock player join as Notch (who will be given the name {}Notch), unwanted results will happen!", + "The prefix you entered in your Floodgate config (%s) could lead to username conflicts!", + "Should a Java player join with the username %sNotch, and a Bedrock player join as Notch (who will be given the name %sNotch), unwanted results will happen!", "We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *" - }, prefix, prefix, prefix, prefix); + }, prefix, prefix, prefix); } if (prefix.length() >= 16) { add(new String[]{ - "The prefix you entered in your Floodgate config ({}) is longer than a Java username can be!", + "The prefix you entered in your Floodgate config (%s) is longer than a Java username can be!", "Because of this, we reset the prefix to the default Floodgate prefix (.)" }, prefix); } else if (prefix.length() > 2) { // we only have to warn them if we haven't replaced the prefix add(new String[]{ - "The prefix you entered in your Floodgate config ({}) is long! ({} characters)", + "The prefix you entered in your Floodgate config (%s) is long! (%s characters)", "A prefix is there to prevent username conflicts. However, a long prefix makes the chance of username conflicts higher.", "We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *" }, prefix, prefix.length()); diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 204f4fe4..35823c28 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 204f4fe4920defac3a472e762d95233d0756f35f +Subproject commit 35823c28bbd6779b301f757175e01bf6f7dfeb15 diff --git a/core/src/main/templates/org/geysermc/floodgate/core/util/Constants.java b/core/src/main/templates/org/geysermc/floodgate/core/util/Constants.java index 83f21521..ca25c57d 100644 --- a/core/src/main/templates/org/geysermc/floodgate/core/util/Constants.java +++ b/core/src/main/templates/org/geysermc/floodgate/core/util/Constants.java @@ -34,8 +34,7 @@ public final class Constants { public static final int CONFIG_VERSION = 3; - public static final char COLOR_CHAR = '\u00A7'; - + public static final int PROTOCOL_HEX_COLOR = 713; // added in 20w17a (1.16 snapshot) public static final boolean DEBUG_MODE = false; public static final boolean PRINT_ALL_PACKETS = false; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e0234bb2..fc4b7128 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,8 @@ fastutil = "8.5.3" cloud = "2.0.0-beta.2" snakeyaml = "2.0" bstats = "3.0.2" +adventure = "4.16.0" +adventure-platform = "4.3.2" # bungee bungee = "master-SNAPSHOT" @@ -59,6 +61,10 @@ cloud-core = { module = "org.incendo:cloud-core", version.ref = "cloud" } snakeyaml = { module = "org.yaml:snakeyaml", version.ref = "snakeyaml" } bstats = { module = "org.bstats:bstats-base", version.ref = "bstats" } +adventure-text-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" } +adventure-platform-bukkit = { module = "net.kyori:adventure-platform-bukkit", version.ref = "adventure-platform"} +adventure-platform-bungee = { module = "net.kyori:adventure-platform-bungeecord", version.ref = "adventure-platform"} + micronaut-inject = { module = "io.micronaut:micronaut-inject" } micronaut-inject-java = { module = "io.micronaut:micronaut-inject-java" } micronaut-context = { module = "io.micronaut:micronaut-context" } diff --git a/spigot/base/build.gradle.kts b/spigot/base/build.gradle.kts index e197a707..2ff18f10 100644 --- a/spigot/base/build.gradle.kts +++ b/spigot/base/build.gradle.kts @@ -5,6 +5,7 @@ dependencies { compileOnlyApi(projects.isolation) implementation(libs.cloud.paper) + implementation(libs.adventure.platform.bukkit) compileOnlyApi(libs.paper.api) } diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/addon/data/SpigotDataAddon.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/addon/data/SpigotDataAddon.java index 5d6549c1..a414034d 100644 --- a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/addon/data/SpigotDataAddon.java +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/addon/data/SpigotDataAddon.java @@ -32,10 +32,10 @@ import jakarta.inject.Named; import jakarta.inject.Singleton; import org.geysermc.api.connection.Connection; import org.geysermc.floodgate.api.inject.InjectorAddon; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.connection.DataSeeker; import org.geysermc.floodgate.core.connection.FloodgateDataHandler; +import org.geysermc.floodgate.core.logger.FloodgateLogger; @Singleton public final class SpigotDataAddon implements InjectorAddon { diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/addon/data/SpigotDataHandler.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/addon/data/SpigotDataHandler.java index 07d15674..8ac6ca9c 100644 --- a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/addon/data/SpigotDataHandler.java +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/addon/data/SpigotDataHandler.java @@ -34,13 +34,13 @@ import io.netty.util.AttributeKey; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; import org.geysermc.api.connection.Connection; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.addon.data.CommonNettyDataHandler; import org.geysermc.floodgate.core.addon.data.PacketBlocker; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.connection.DataSeeker; import org.geysermc.floodgate.core.connection.FloodgateDataHandler; import org.geysermc.floodgate.core.connection.FloodgateDataHandler.HandleResult; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.spigot.util.ClassNames; import org.geysermc.floodgate.spigot.util.ProxyUtils; diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/inject/SpigotInjector.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/inject/SpigotInjector.java index de6bb166..12e70fcc 100644 --- a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/inject/SpigotInjector.java +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/inject/SpigotInjector.java @@ -38,8 +38,8 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; import lombok.Getter; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.inject.CommonPlatformInjector; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.util.ReflectionUtils; import org.geysermc.floodgate.spigot.util.ClassNames; diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/listener/SpigotListener.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/listener/SpigotListener.java index 24b97d00..47d7ccf3 100644 --- a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/listener/SpigotListener.java +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/listener/SpigotListener.java @@ -32,10 +32,10 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerQuitEvent; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.api.SimpleFloodgateApi; import org.geysermc.floodgate.core.connection.ConnectionManager; import org.geysermc.floodgate.core.listener.McListener; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.util.LanguageManager; @Singleton diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotCommandUtil.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotCommandUtil.java index abf0df5a..3a32b860 100644 --- a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotCommandUtil.java +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotCommandUtil.java @@ -29,7 +29,8 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.Collection; import java.util.UUID; -import org.bukkit.OfflinePlayer; +import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer; +import net.kyori.adventure.text.Component; import org.bukkit.Server; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -114,15 +115,18 @@ public final class SpigotCommandUtil extends CommandUtil { } @Override - public void sendMessage(Object target, String message) { + public void sendMessage(Object target, Component message) { ((CommandSender) target).sendMessage(message); } @Override - public void kickPlayer(Object player, String message) { + public void kickPlayer(Object target, Component message) { // can also be console - if (player instanceof Player) { - versionSpecificMethods.schedule(() -> ((Player) player).kickPlayer(message), 0); + if (target instanceof Player player) { + versionSpecificMethods.schedule(() -> { + //todo don't include Adventure & use Component variants if/when there will be a Paper platform + player.kickPlayer(BukkitComponentSerializer.legacy().serialize( message)); + }, 0); } } diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotHandshakeHandler.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotHandshakeHandler.java index 71f61e72..9bd7a9fd 100644 --- a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotHandshakeHandler.java +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotHandshakeHandler.java @@ -31,7 +31,7 @@ import java.util.UUID; import org.geysermc.floodgate.api.handshake.HandshakeData; import org.geysermc.floodgate.api.handshake.HandshakeHandler; import org.geysermc.floodgate.api.handshake.HandshakeHandlers; -import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.util.BedrockData; @Singleton diff --git a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityDataAddon.java b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityDataAddon.java index e3c54ffe..1132e735 100644 --- a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityDataAddon.java +++ b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityDataAddon.java @@ -32,11 +32,11 @@ import jakarta.inject.Named; import jakarta.inject.Singleton; import org.geysermc.api.connection.Connection; import org.geysermc.floodgate.api.inject.InjectorAddon; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.addon.data.PacketBlocker; import org.geysermc.floodgate.core.config.ProxyFloodgateConfig; import org.geysermc.floodgate.core.connection.DataSeeker; import org.geysermc.floodgate.core.connection.FloodgateDataHandler; +import org.geysermc.floodgate.core.logger.FloodgateLogger; @Singleton public final class VelocityDataAddon implements InjectorAddon { diff --git a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityProxyDataHandler.java b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityProxyDataHandler.java index 2cbaf65d..7aee850a 100644 --- a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityProxyDataHandler.java +++ b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityProxyDataHandler.java @@ -40,13 +40,13 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.InetSocketAddress; import org.geysermc.api.connection.Connection; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.addon.data.CommonNettyDataHandler; import org.geysermc.floodgate.core.addon.data.PacketBlocker; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.connection.DataSeeker; import org.geysermc.floodgate.core.connection.FloodgateDataHandler; import org.geysermc.floodgate.core.connection.FloodgateDataHandler.HandleResult; +import org.geysermc.floodgate.core.logger.FloodgateLogger; public final class VelocityProxyDataHandler extends CommonNettyDataHandler { private static final Field HANDSHAKE; diff --git a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/listener/VelocityListener.java b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/listener/VelocityListener.java index 7180a82b..aa3bef57 100644 --- a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/listener/VelocityListener.java +++ b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/listener/VelocityListener.java @@ -41,10 +41,10 @@ import jakarta.inject.Singleton; import java.util.Collections; import net.kyori.adventure.text.Component; import org.geysermc.api.connection.Connection; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.api.SimpleFloodgateApi; import org.geysermc.floodgate.core.config.ProxyFloodgateConfig; import org.geysermc.floodgate.core.listener.McListener; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.util.LanguageManager; import org.geysermc.floodgate.velocity.player.VelocityConnectionManager; diff --git a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/pluginmessage/VelocityPluginMessageUtils.java b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/pluginmessage/VelocityPluginMessageUtils.java index f1c060ce..327ea58a 100644 --- a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/pluginmessage/VelocityPluginMessageUtils.java +++ b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/pluginmessage/VelocityPluginMessageUtils.java @@ -39,8 +39,8 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.UUID; import net.kyori.adventure.text.Component; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.listener.McListener; +import org.geysermc.floodgate.core.logger.FloodgateLogger; import org.geysermc.floodgate.core.platform.pluginmessage.PluginMessageUtils; import org.geysermc.floodgate.core.pluginmessage.PluginMessageChannel; import org.geysermc.floodgate.core.pluginmessage.PluginMessageChannel.Identity; diff --git a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/util/VelocityCommandUtil.java b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/util/VelocityCommandUtil.java index 54bb6482..f22da398 100644 --- a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/util/VelocityCommandUtil.java +++ b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/util/VelocityCommandUtil.java @@ -109,14 +109,14 @@ public final class VelocityCommandUtil extends CommandUtil { } @Override - public void sendMessage(Object target, String message) { - ((CommandSource) target).sendMessage(Component.text(message)); + public void sendMessage(Object target, Component message) { + ((CommandSource) target).sendMessage(message); } @Override - public void kickPlayer(Object player, String message) { + public void kickPlayer(Object player, Component message) { if (player instanceof Player) { - ((Player) player).disconnect(Component.text(message)); + ((Player) player).disconnect(message); } } }