diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml
index 5a1e8e262..d71a20f42 100644
--- a/bootstrap/bungeecord/pom.xml
+++ b/bootstrap/bungeecord/pom.xml
@@ -24,6 +24,12 @@
a7c6ede
provided
+
+ net.kyori
+ adventure-text-serializer-bungeecord
+ ${adventure-platform.version}
+ compile
+
${outputName}-BungeeCord
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java
index 0883c5ff0..e8d44b02f 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java
@@ -149,6 +149,8 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(geyser));
+
+ this.getProxy().getPluginManager().registerListener(this, new GeyserBungeeUpdateListener());
}
@Override
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java
new file mode 100644
index 000000000..bbde8771e
--- /dev/null
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019-2022 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/Geyser
+ */
+
+package org.geysermc.geyser.platform.bungeecord;
+
+import net.md_5.bungee.api.connection.ProxiedPlayer;
+import net.md_5.bungee.api.event.PostLoginEvent;
+import net.md_5.bungee.api.plugin.Listener;
+import net.md_5.bungee.event.EventHandler;
+import org.geysermc.geyser.Constants;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSender;
+import org.geysermc.geyser.util.VersionCheckUtils;
+
+public final class GeyserBungeeUpdateListener implements Listener {
+
+ @EventHandler
+ public void onPlayerJoin(final PostLoginEvent event) {
+ if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
+ final ProxiedPlayer player = event.getPlayer();
+ if (player.hasPermission(Constants.UPDATE_PERMISSION)) {
+ VersionCheckUtils.checkForGeyserUpdate(() -> new BungeeCommandSender(player));
+ }
+ }
+ }
+}
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSender.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSender.java
index 05df8ba97..dcf5bd689 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSender.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSender.java
@@ -25,11 +25,15 @@
package org.geysermc.geyser.platform.bungeecord.command;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.geysermc.geyser.command.CommandSender;
import org.geysermc.geyser.text.GeyserLocale;
+import java.util.Locale;
+
public class BungeeCommandSender implements CommandSender {
private final net.md_5.bungee.api.CommandSender handle;
@@ -50,6 +54,18 @@ public class BungeeCommandSender implements CommandSender {
handle.sendMessage(TextComponent.fromLegacyText(message));
}
+ private static final int PROTOCOL_HEX_COLOR = 713; // Added 20w17a
+
+ @Override
+ public void sendMessage(Component message) {
+ if (handle instanceof ProxiedPlayer player && player.getPendingConnection().getVersion() >= PROTOCOL_HEX_COLOR) {
+ // Include hex colors
+ handle.sendMessage(BungeeComponentSerializer.get().serialize(message));
+ return;
+ }
+ handle.sendMessage(BungeeComponentSerializer.legacy().serialize(message));
+ }
+
@Override
public boolean isConsole() {
return !(handle instanceof ProxiedPlayer);
@@ -58,8 +74,11 @@ public class BungeeCommandSender implements CommandSender {
@Override
public String getLocale() {
if (handle instanceof ProxiedPlayer player) {
- String locale = player.getLocale().getLanguage() + "_" + player.getLocale().getCountry();
- return GeyserLocale.formatLocale(locale);
+ Locale locale = player.getLocale();
+ if (locale != null) {
+ // Locale can be null early on in the conneciton
+ return GeyserLocale.formatLocale(locale.getLanguage() + "_" + locale.getCountry());
+ }
}
return GeyserLocale.getDefaultLocale();
}
diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml
index 371ed9bca..35ec15abe 100644
--- a/bootstrap/pom.xml
+++ b/bootstrap/pom.xml
@@ -11,6 +11,10 @@
bootstrap-parent
pom
+
+ 4.1.2
+
+
spigot-public
diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml
index 5142d2bc3..ad4b58fe2 100644
--- a/bootstrap/spigot/pom.xml
+++ b/bootstrap/spigot/pom.xml
@@ -59,7 +59,13 @@
me.lucko
commodore
- 1.13
+ 2.2
+ compile
+
+
+ net.kyori
+ adventure-text-serializer-bungeecord
+ ${adventure-platform.version}
compile
@@ -107,6 +113,9 @@
net.kyori
org.geysermc.geyser.platform.spigot.shaded.kyori
+
+ net.kyori.adventure.text.logger.slf4j.ComponentLogger
+
org.objectweb.asm
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperLogger.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperLogger.java
new file mode 100644
index 000000000..930f84cec
--- /dev/null
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperLogger.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019-2022 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/Geyser
+ */
+
+package org.geysermc.geyser.platform.spigot;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
+import org.bukkit.plugin.Plugin;
+
+import java.util.logging.Logger;
+
+public final class GeyserPaperLogger extends GeyserSpigotLogger {
+ private final ComponentLogger componentLogger;
+
+ public GeyserPaperLogger(Plugin plugin, Logger logger, boolean debug) {
+ super(logger, debug);
+ componentLogger = plugin.getComponentLogger();
+ }
+
+ /**
+ * Since 1.18.2 this is required so legacy format symbols don't show up in the console for colors
+ */
+ @Override
+ public void sendMessage(Component message) {
+ // Done like this so the native component object field isn't relocated
+ componentLogger.info("{}", PaperAdventure.toNativeComponent(message));
+ }
+
+ static boolean supported() {
+ try {
+ Plugin.class.getMethod("getComponentLogger");
+ return true;
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java
index 21c54308d..a1d9245e8 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java
@@ -123,6 +123,22 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
return;
}
+ try {
+ Class.forName("net.md_5.bungee.chat.ComponentSerializer");
+ } catch (ClassNotFoundException e) {
+ if (!PaperAdventure.canSendMessageUsingComponent()) { // Prepare for Paper eventually removing Bungee chat
+ getLogger().severe("*********************************************");
+ getLogger().severe("");
+ getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.header", getServer().getName()));
+ getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper"));
+ getLogger().severe("");
+ getLogger().severe("*********************************************");
+
+ Bukkit.getPluginManager().disablePlugin(this);
+ return;
+ }
+ }
+
// By default this should be localhost but may need to be changed in some circumstances
if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) {
geyserConfig.setAutoconfiguredRemote(true);
@@ -137,7 +153,8 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
geyserConfig.getBedrock().setPort(Bukkit.getPort());
}
- this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
+ this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode())
+ : new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
// Remove this in like a year
@@ -266,12 +283,16 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
GeyserLocale.getLocaleStringLog(command.getDescription()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
}
+ Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION,
+ "Whether update notifications can be seen", PermissionDefault.OP));
// Events cannot be unregistered - re-registering results in duplicate firings
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
+
+ Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this);
}
boolean brigadierSupported = CommodoreProvider.isSupported();
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java
new file mode 100644
index 000000000..02f5367b3
--- /dev/null
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotUpdateListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019-2022 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/Geyser
+ */
+
+package org.geysermc.geyser.platform.spigot;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.geysermc.geyser.Constants;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.platform.spigot.command.SpigotCommandSender;
+import org.geysermc.geyser.util.VersionCheckUtils;
+
+public final class GeyserSpigotUpdateListener implements Listener {
+
+ @EventHandler
+ public void onPlayerJoin(final PlayerJoinEvent event) {
+ if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
+ final Player player = event.getPlayer();
+ if (player.hasPermission(Constants.UPDATE_PERMISSION)) {
+ VersionCheckUtils.checkForGeyserUpdate(() -> new SpigotCommandSender(player));
+ }
+ }
+ }
+}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java
new file mode 100644
index 000000000..5dd16da33
--- /dev/null
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2019-2022 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/Geyser
+ */
+
+package org.geysermc.geyser.platform.spigot;
+
+import com.github.steveice10.mc.protocol.data.DefaultComponentSerializer;
+import net.kyori.adventure.text.Component;
+import org.bukkit.command.CommandSender;
+import org.geysermc.geyser.GeyserImpl;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Utility class for converting our shaded Adventure into the Adventure bundled in Paper.
+ *
+ * Code mostly taken from https://github.com/KyoriPowered/adventure-platform/blob/94d5821f2e755170f42bd8a5fe1d5bf6f66d04ad/platform-bukkit/src/main/java/net/kyori/adventure/platform/bukkit/PaperFacet.java#L46
+ * and the MinecraftReflection class.
+ */
+public final class PaperAdventure {
+ private static final MethodHandle NATIVE_GSON_COMPONENT_SERIALIZER_DESERIALIZE_METHOD_BOUND;
+ private static final Method SEND_MESSAGE_COMPONENT;
+
+ static {
+ final MethodHandles.Lookup lookup = MethodHandles.lookup();
+
+ MethodHandle nativeGsonComponentSerializerDeserializeMethodBound = null;
+
+ // String.join because otherwise the class name will be relocated
+ final Class> nativeGsonComponentSerializerClass = findClass(String.join(".",
+ "net", "kyori", "adventure", "text", "serializer", "gson", "GsonComponentSerializer"));
+ final Class> nativeGsonComponentSerializerImplClass = findClass(String.join(".",
+ "net", "kyori", "adventure", "text", "serializer", "gson", "GsonComponentSerializerImpl"));
+ if (nativeGsonComponentSerializerClass != null && nativeGsonComponentSerializerImplClass != null) {
+ MethodHandle nativeGsonComponentSerializerGsonGetter = null;
+ try {
+ nativeGsonComponentSerializerGsonGetter = lookup.findStatic(nativeGsonComponentSerializerClass,
+ "gson", MethodType.methodType(nativeGsonComponentSerializerClass));
+ } catch (final NoSuchMethodException | IllegalAccessException ignored) {
+ }
+
+ MethodHandle nativeGsonComponentSerializerDeserializeMethod = null;
+ try {
+ final Method method = nativeGsonComponentSerializerImplClass.getDeclaredMethod("deserialize", String.class);
+ method.setAccessible(true);
+ nativeGsonComponentSerializerDeserializeMethod = lookup.unreflect(method);
+ } catch (final NoSuchMethodException | IllegalAccessException ignored) {
+ }
+
+ if (nativeGsonComponentSerializerGsonGetter != null) {
+ if (nativeGsonComponentSerializerDeserializeMethod != null) {
+ try {
+ nativeGsonComponentSerializerDeserializeMethodBound = nativeGsonComponentSerializerDeserializeMethod
+ .bindTo(nativeGsonComponentSerializerGsonGetter.invoke());
+ } catch (final Throwable throwable) {
+ GeyserImpl.getInstance().getLogger().error("Failed to access native GsonComponentSerializer", throwable);
+ }
+ }
+ }
+ }
+
+ NATIVE_GSON_COMPONENT_SERIALIZER_DESERIALIZE_METHOD_BOUND = nativeGsonComponentSerializerDeserializeMethodBound;
+
+ Method playerComponentSendMessage = null;
+ final Class> nativeComponentClass = findClass(String.join(".",
+ "net", "kyori", "adventure", "text", "Component"));
+ if (nativeComponentClass != null) {
+ try {
+ playerComponentSendMessage = CommandSender.class.getMethod("sendMessage", nativeComponentClass);
+ } catch (final NoSuchMethodException e) {
+ if (GeyserImpl.getInstance().getLogger().isDebug()) {
+ e.printStackTrace();
+ }
+ }
+ }
+ SEND_MESSAGE_COMPONENT = playerComponentSendMessage;
+ }
+
+ public static Object toNativeComponent(final Component component) {
+ if (NATIVE_GSON_COMPONENT_SERIALIZER_DESERIALIZE_METHOD_BOUND == null) {
+ GeyserImpl.getInstance().getLogger().error("Illegal state where Component serialization was called when it wasn't available!");
+ return null;
+ }
+
+ try {
+ return NATIVE_GSON_COMPONENT_SERIALIZER_DESERIALIZE_METHOD_BOUND.invoke(DefaultComponentSerializer.get().serialize(component));
+ } catch (final Throwable throwable) {
+ GeyserImpl.getInstance().getLogger().error("Failed to create native Component message", throwable);
+ return null;
+ }
+ }
+
+ public static void sendMessage(final CommandSender sender, final Component component) {
+ if (SEND_MESSAGE_COMPONENT == null) {
+ GeyserImpl.getInstance().getLogger().error("Illegal state where Component sendMessage was called when it wasn't available!");
+ return;
+ }
+
+ final Object nativeComponent = toNativeComponent(component);
+ if (nativeComponent != null) {
+ try {
+ SEND_MESSAGE_COMPONENT.invoke(sender, nativeComponent);
+ } catch (final InvocationTargetException | IllegalAccessException e) {
+ GeyserImpl.getInstance().getLogger().error("Failed to send native Component message", e);
+ }
+ }
+ }
+
+ public static boolean canSendMessageUsingComponent() {
+ return SEND_MESSAGE_COMPONENT != null;
+ }
+
+ /**
+ * Gets a class by the first name available.
+ *
+ * @return a class or {@code null} if not found
+ */
+ private static @Nullable Class> findClass(final String className) {
+ try {
+ return Class.forName(className);
+ } catch (final ClassNotFoundException ignored) {
+ }
+ return null;
+ }
+
+ private PaperAdventure() {
+ }
+}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSender.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSender.java
index a05a6ebe0..c6314ced5 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSender.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSender.java
@@ -25,10 +25,13 @@
package org.geysermc.geyser.platform.spigot.command;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.CommandSender;
+import org.geysermc.geyser.platform.spigot.PaperAdventure;
import org.geysermc.geyser.text.GeyserLocale;
import java.lang.reflect.InvocationTargetException;
@@ -63,6 +66,16 @@ public class SpigotCommandSender implements CommandSender {
handle.sendMessage(message);
}
+ @Override
+ public void sendMessage(Component message) {
+ if (PaperAdventure.canSendMessageUsingComponent()) {
+ PaperAdventure.sendMessage(handle, message);
+ return;
+ }
+
+ handle.sendMessage(BungeeComponentSerializer.get().serialize(message));
+ }
+
@Override
public boolean isConsole() {
return handle instanceof ConsoleCommandSender;
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java
index a03549444..0a6117b43 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java
@@ -38,14 +38,14 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.plugin.Plugin;
-import org.geysermc.geyser.network.MinecraftProtocol;
-import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
+import org.geysermc.geyser.level.GameRule;
import org.geysermc.geyser.level.GeyserWorldManager;
import org.geysermc.geyser.level.block.BlockStateValues;
+import org.geysermc.geyser.network.MinecraftProtocol;
import org.geysermc.geyser.registry.BlockRegistries;
+import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
import org.geysermc.geyser.util.BlockEntityUtils;
-import org.geysermc.geyser.level.GameRule;
import java.util.ArrayList;
import java.util.List;
diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java
index 3bd2a3960..78e603d7c 100644
--- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java
+++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java
@@ -31,11 +31,10 @@ import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
-import org.geysermc.geyser.command.CommandSender;
import org.geysermc.geyser.text.ChatColor;
@Log4j2
-public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger, CommandSender {
+public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger {
@Override
protected boolean isRunning() {
@@ -95,24 +94,4 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
public boolean isDebug() {
return log.isDebugEnabled();
}
-
- @Override
- public String name() {
- return "CONSOLE";
- }
-
- @Override
- public void sendMessage(String message) {
- info(message);
- }
-
- @Override
- public boolean isConsole() {
- return true;
- }
-
- @Override
- public boolean hasPermission(String permission) {
- return true;
- }
}
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java
index 4a8a50da8..13a07121e 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java
@@ -161,6 +161,8 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
} else {
this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer);
}
+
+ proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener());
}
@Override
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java
new file mode 100644
index 000000000..506dfff71
--- /dev/null
+++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityUpdateListener.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2019-2022 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/Geyser
+ */
+
+package org.geysermc.geyser.platform.velocity;
+
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.event.connection.PostLoginEvent;
+import com.velocitypowered.api.proxy.Player;
+import org.geysermc.geyser.Constants;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.platform.velocity.command.VelocityCommandSender;
+import org.geysermc.geyser.util.VersionCheckUtils;
+
+public final class GeyserVelocityUpdateListener {
+
+ @Subscribe
+ public void onPlayerJoin(PostLoginEvent event) {
+ if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
+ final Player player = event.getPlayer();
+ if (player.hasPermission(Constants.UPDATE_PERMISSION)) {
+ VersionCheckUtils.checkForGeyserUpdate(() -> new VelocityCommandSender(player));
+ }
+ }
+ }
+}
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSender.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSender.java
index d5e4804ee..a5474c3e0 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSender.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSender.java
@@ -28,6 +28,7 @@ package org.geysermc.geyser.platform.velocity.command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.ConsoleCommandSource;
import com.velocitypowered.api.proxy.Player;
+import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.geysermc.geyser.command.CommandSender;
import org.geysermc.geyser.text.GeyserLocale;
@@ -59,6 +60,12 @@ public class VelocityCommandSender implements CommandSender {
handle.sendMessage(LegacyComponentSerializer.legacy('ยง').deserialize(message));
}
+ @Override
+ public void sendMessage(Component message) {
+ // Be careful that we don't shade in Adventure!!
+ handle.sendMessage(message);
+ }
+
@Override
public boolean isConsole() {
return handle instanceof ConsoleCommandSource;
diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java
index 23fb76d16..6a53c37de 100644
--- a/core/src/main/java/org/geysermc/geyser/Constants.java
+++ b/core/src/main/java/org/geysermc/geyser/Constants.java
@@ -37,6 +37,9 @@ public final class Constants {
public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/";
+ public static final String GEYSER_DOWNLOAD_LOCATION = "https://ci.geysermc.org";
+ public static final String UPDATE_PERMISSION = "geyser.update";
+
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
static {
diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java
index 4322dde59..d9f4d8a15 100644
--- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java
+++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java
@@ -41,6 +41,8 @@ import io.netty.util.internal.SystemPropertyUtil;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.Geyser;
@@ -66,7 +68,6 @@ import org.geysermc.geyser.session.SessionManager;
import org.geysermc.geyser.session.auth.AuthType;
import org.geysermc.geyser.skin.FloodgateSkinUploader;
import org.geysermc.geyser.skin.SkinProvider;
-import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.text.MinecraftLocale;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
@@ -303,8 +304,8 @@ public class GeyserImpl implements GeyserApi {
int port = config.getBedrock().getPort();
logger.severe(GeyserLocale.getLocaleStringLog("geyser.core.fail", address, String.valueOf(port)));
if (!"0.0.0.0".equals(address)) {
- logger.info(ChatColor.GREEN + "Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0");
- logger.info(ChatColor.GREEN + "Then, restart this server.");
+ logger.info(Component.text("Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0", NamedTextColor.GREEN));
+ logger.info(Component.text("Then, restart this server.", NamedTextColor.GREEN));
}
}
}).join();
@@ -454,6 +455,9 @@ public class GeyserImpl implements GeyserApi {
}
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
+ if (config.isNotifyOnNewBedrockUpdate()) {
+ VersionCheckUtils.checkForGeyserUpdate(this::getLogger);
+ }
}
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java
index b47801cb5..197a031dd 100644
--- a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java
+++ b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java
@@ -25,9 +25,12 @@
package org.geysermc.geyser;
+import net.kyori.adventure.text.Component;
+import org.geysermc.geyser.command.CommandSender;
+
import javax.annotation.Nullable;
-public interface GeyserLogger {
+public interface GeyserLogger extends CommandSender {
/**
* Logs a severe message to console
@@ -73,6 +76,15 @@ public interface GeyserLogger {
*/
void info(String message);
+ /**
+ * Logs an info component to console
+ *
+ * @param message the message to log
+ */
+ default void info(Component message) {
+ sendMessage(message);
+ }
+
/**
* Logs a debug message to console
*
@@ -100,4 +112,24 @@ public interface GeyserLogger {
* If debug is enabled for this logger
*/
boolean isDebug();
+
+ @Override
+ default String name() {
+ return "CONSOLE";
+ }
+
+ @Override
+ default void sendMessage(String message) {
+ info(message);
+ }
+
+ @Override
+ default boolean isConsole() {
+ return true;
+ }
+
+ @Override
+ default boolean hasPermission(String permission) {
+ return true;
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandSender.java b/core/src/main/java/org/geysermc/geyser/command/CommandSender.java
index d9d1bcfbc..61adad717 100644
--- a/core/src/main/java/org/geysermc/geyser/command/CommandSender.java
+++ b/core/src/main/java/org/geysermc/geyser/command/CommandSender.java
@@ -25,6 +25,8 @@
package org.geysermc.geyser.command;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.geysermc.geyser.text.GeyserLocale;
/**
@@ -43,6 +45,10 @@ public interface CommandSender {
void sendMessage(String message);
+ default void sendMessage(Component message) {
+ sendMessage(LegacyComponentSerializer.legacySection().serialize(message));
+ }
+
/**
* @return true if the specified sender is from the console.
*/
diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java
index 1f188cf40..f605ad103 100644
--- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java
+++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java
@@ -105,6 +105,8 @@ public interface GeyserConfiguration {
int getCustomSkullRenderDistance();
+ boolean isNotifyOnNewBedrockUpdate();
+
IMetricsInfo getMetrics();
int getPendingAuthenticationTimeout();
diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java
index 30a947e53..80fa22ede 100644
--- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java
+++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java
@@ -148,6 +148,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("xbox-achievements-enabled")
private boolean xboxAchievementsEnabled = false;
+ @JsonProperty("notify-on-new-bedrock-update")
+ private boolean notifyOnNewBedrockUpdate = true;
+
private MetricsInfo metrics = new MetricsInfo();
@JsonProperty("pending-authentication-timeout")
diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java
index 239f5c865..7996d1188 100644
--- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java
+++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java
@@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.packet.SetTitlePacket;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
import lombok.Setter;
+import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.scoreboard.Scoreboard;
import org.geysermc.geyser.scoreboard.ScoreboardUpdater.ScoreboardSession;
import org.geysermc.geyser.session.GeyserSession;
@@ -172,6 +173,7 @@ public final class WorldCache {
if (serverVerifiedState.sequence <= sequence) {
// This block may be out of sync with the server
// In 1.19.0 Java, you can verify this by trying to mine in spawn protection
+ System.out.println("Resetting " + entry.getKey() + " to " + BlockRegistries.JAVA_BLOCKS.get(serverVerifiedState.blockState).getJavaIdentifier());
ChunkUtils.updateBlockClientSide(session, serverVerifiedState.blockState, entry.getKey());
it.remove();
}
diff --git a/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java b/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java
index 934680ce1..b1f97989f 100644
--- a/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java
+++ b/core/src/main/java/org/geysermc/geyser/util/VersionCheckUtils.java
@@ -25,10 +25,22 @@
package org.geysermc.geyser.util;
+import com.fasterxml.jackson.databind.JsonNode;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.TextReplacementConfig;
+import net.kyori.adventure.text.event.ClickEvent;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextDecoration;
import org.geysermc.geyser.Constants;
+import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
+import org.geysermc.geyser.command.CommandSender;
+import org.geysermc.geyser.network.MinecraftProtocol;
import org.geysermc.geyser.text.GeyserLocale;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+
public final class VersionCheckUtils {
public static void checkForOutdatedFloodgate(GeyserLogger logger) {
@@ -42,6 +54,41 @@ public final class VersionCheckUtils {
}
}
+ public static void checkForGeyserUpdate(Supplier recipient) {
+ CompletableFuture.runAsync(() -> {
+ try {
+ JsonNode json = WebUtils.getJson("https://api.geysermc.org/v2/versions/geyser");
+ JsonNode bedrock = json.get("bedrock").get("protocol");
+ int protocolVersion = bedrock.get("id").asInt();
+ if (MinecraftProtocol.getBedrockCodec(protocolVersion) != null) {
+ // We support the latest version! No need to print a message.
+ return;
+ }
+
+ final String newBedrockVersion = bedrock.get("name").asText();
+
+ // Delayed for two reasons: save unnecessary processing, and wait to load locale if this is on join.
+ CommandSender sender = recipient.get();
+
+ // Overarching component is green - geyser.version.new component cannot be green or else the link blue is overshadowed
+ Component message = Component.text().color(NamedTextColor.GREEN)
+ .append(Component.text(GeyserLocale.getPlayerLocaleString("geyser.version.new", sender.getLocale(), newBedrockVersion))
+ .replaceText(TextReplacementConfig.builder()
+ .match("\\{1\\}") // Replace "Download here: {1}" so we can use fancy text component yesyes
+ .replacement(Component.text()
+ .content(Constants.GEYSER_DOWNLOAD_LOCATION)
+ .color(NamedTextColor.BLUE)
+ .decoration(TextDecoration.UNDERLINED, TextDecoration.State.TRUE)
+ .clickEvent(ClickEvent.openUrl(Constants.GEYSER_DOWNLOAD_LOCATION)))
+ .build()))
+ .build();
+ sender.sendMessage(message);
+ } catch (Exception e) {
+ GeyserImpl.getInstance().getLogger().error("Error whilst checking for Geyser update!", e);
+ }
+ });
+ }
+
private VersionCheckUtils() {
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java
index f9574f08b..c0889f1c5 100644
--- a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java
+++ b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java
@@ -73,6 +73,8 @@ public class WebUtils {
public static JsonNode getJson(String reqURL) throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(reqURL).openConnection();
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION);
+ con.setConnectTimeout(10000);
+ con.setReadTimeout(10000);
return GeyserImpl.JSON_MAPPER.readTree(con.getInputStream());
}
diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml
index c331a7e62..5a32a6599 100644
--- a/core/src/main/resources/config.yml
+++ b/core/src/main/resources/config.yml
@@ -175,6 +175,11 @@ force-resource-packs: true
# THIS DISABLES ALL COMMANDS FROM SUCCESSFULLY RUNNING FOR BEDROCK IN-GAME, as otherwise Bedrock thinks you are cheating.
xbox-achievements-enabled: false
+# Whether to alert the console and operators that a new Geyser version is available that supports a Bedrock version
+# that this Geyser version does not support. It's recommended to keep this option enabled, as many Bedrock platforms
+# auto-update.
+notify-on-new-bedrock-update: true
+
# bStats is a stat tracker that is entirely anonymous and tracks only basic information
# about Geyser, such as how many people are online, how many servers are using Geyser,
# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/.