mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Move platforms for isolation
This commit is contained in:
parent
f1f30e5d19
commit
bc301fba60
56 changed files with 28 additions and 8 deletions
63
bootstrap/spigot/base/build.gradle.kts
Normal file
63
bootstrap/spigot/base/build.gradle.kts
Normal file
|
@ -0,0 +1,63 @@
|
|||
dependencies {
|
||||
api(projects.core)
|
||||
api(libs.erosion.bukkit.common) {
|
||||
isTransitive = false
|
||||
}
|
||||
|
||||
implementation(variantOf(libs.adapters.spigot) {
|
||||
classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations
|
||||
})
|
||||
|
||||
implementation(libs.commodore)
|
||||
|
||||
implementation(libs.adventure.text.serializer.bungeecord)
|
||||
|
||||
compileOnly(libs.folia.api)
|
||||
compileOnly(libs.paper.mojangapi)
|
||||
|
||||
implementation(libs.floodgate.spigot)
|
||||
}
|
||||
|
||||
platformRelocate("it.unimi.dsi.fastutil")
|
||||
platformRelocate("com.fasterxml.jackson")
|
||||
// Relocate net.kyori but exclude the component logger
|
||||
platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger")
|
||||
platformRelocate("org.objectweb.asm")
|
||||
platformRelocate("me.lucko.commodore")
|
||||
platformRelocate("org.yaml") // Broken as of 1.20
|
||||
|
||||
// These dependencies are already present on the platform
|
||||
provided(libs.viaversion)
|
||||
provided("com.mojang", "authlib", "1.5.21")
|
||||
|
||||
application {
|
||||
mainClass.set("org.geysermc.geyser.platform.spigot.GeyserSpigotMain")
|
||||
}
|
||||
|
||||
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
archiveBaseName.set("Geyser-Spigot")
|
||||
|
||||
dependencies {
|
||||
exclude(dependency("com.google.*:.*"))
|
||||
|
||||
// We cannot shade Netty, or else native libraries will not load
|
||||
// Needed because older Spigot builds do not provide the haproxy module
|
||||
exclude(dependency("io.netty:netty-transport-classes-epoll:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-classes-kqueue:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-native-kqueue:.*"))
|
||||
exclude(dependency("io.netty:netty-handler:.*"))
|
||||
exclude(dependency("io.netty:netty-common:.*"))
|
||||
exclude(dependency("io.netty:netty-buffer:.*"))
|
||||
exclude(dependency("io.netty:netty-resolver:.*"))
|
||||
exclude(dependency("io.netty:netty-transport:.*"))
|
||||
exclude(dependency("io.netty:netty-codec:.*"))
|
||||
exclude(dependency("io.netty:netty-codec-dns:.*"))
|
||||
exclude(dependency("io.netty:netty-resolver-dns:.*"))
|
||||
exclude(dependency("io.netty:netty-resolver-dns-native-macos:.*"))
|
||||
|
||||
// Commodore includes Brigadier
|
||||
exclude(dependency("com.mojang:.*"))
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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.destroystokyo.paper.event.server.PaperServerListPingEvent;
|
||||
import com.destroystokyo.paper.network.StatusClient;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.ping.GeyserPingInfo;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* This class is used if possible, so listeners listening for PaperServerListPingEvent exclusively have their changes
|
||||
* applied.
|
||||
*/
|
||||
public final class GeyserPaperPingPassthrough implements IGeyserPingPassthrough {
|
||||
private static final Constructor<PaperServerListPingEvent> OLD_CONSTRUCTOR = ReflectedNames.getOldPaperPingConstructor();
|
||||
|
||||
private final GeyserSpigotLogger logger;
|
||||
|
||||
public GeyserPaperPingPassthrough(GeyserSpigotLogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Nullable
|
||||
@Override
|
||||
public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) {
|
||||
try {
|
||||
// We'd rather *not* use deprecations here, but unfortunately any Adventure class would be relocated at
|
||||
// runtime because we still have to shade in our own Adventure class. For now.
|
||||
PaperServerListPingEvent event;
|
||||
if (OLD_CONSTRUCTOR != null) {
|
||||
// 1.19, removed in 1.19.4
|
||||
event = OLD_CONSTRUCTOR.newInstance(new GeyserStatusClient(inetSocketAddress),
|
||||
Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(),
|
||||
Bukkit.getMaxPlayers(), Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion(), null);
|
||||
} else {
|
||||
event = new PaperServerListPingEvent(new GeyserStatusClient(inetSocketAddress),
|
||||
Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(),
|
||||
Bukkit.getMaxPlayers(), Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion(), null);
|
||||
}
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
if (event.isCancelled()) {
|
||||
// We have to send a ping, so not really sure what else to do here.
|
||||
return null;
|
||||
}
|
||||
|
||||
GeyserPingInfo.Players players;
|
||||
if (event.shouldHidePlayers()) {
|
||||
players = new GeyserPingInfo.Players(1, 0);
|
||||
} else {
|
||||
players = new GeyserPingInfo.Players(event.getMaxPlayers(), event.getNumPlayers());
|
||||
}
|
||||
|
||||
return new GeyserPingInfo(event.getMotd(), players);
|
||||
} catch (Exception | LinkageError e) { // LinkageError in the event that method/constructor signatures change
|
||||
logger.debug("Error while getting Paper ping passthrough: " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private record GeyserStatusClient(InetSocketAddress address) implements StatusClient {
|
||||
@Override
|
||||
public @NonNull InetSocketAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion() {
|
||||
return GameProtocol.getJavaProtocolVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InetSocketAddress getVirtualHost() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2021 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 io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOutboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
|
||||
/**
|
||||
* Disables the compression packet (and the compression handlers from being added to the pipeline) for Geyser clients
|
||||
* that won't be receiving the data over the network.
|
||||
* <p>
|
||||
* As of 1.8 - 1.17.1, compression is enabled in the Netty pipeline by adding a listener after a packet is written.
|
||||
* If we simply "cancel" or don't forward the packet, then the listener is never called.
|
||||
*/
|
||||
public class GeyserSpigotCompressionDisabler extends ChannelOutboundHandlerAdapter {
|
||||
static final boolean ENABLED;
|
||||
|
||||
private static final Class<?> COMPRESSION_PACKET_CLASS;
|
||||
private static final Class<?> LOGIN_SUCCESS_PACKET_CLASS;
|
||||
private static final boolean PROTOCOL_SUPPORT_INSTALLED;
|
||||
|
||||
static {
|
||||
PROTOCOL_SUPPORT_INSTALLED = Bukkit.getPluginManager().getPlugin("ProtocolSupport") != null;
|
||||
|
||||
Class<?> compressionPacketClass = null;
|
||||
Class<?> loginSuccessPacketClass = null;
|
||||
boolean enabled = false;
|
||||
try {
|
||||
compressionPacketClass = findCompressionPacket();
|
||||
loginSuccessPacketClass = findLoginSuccessPacket();
|
||||
enabled = true;
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not initialize compression disabler!", e);
|
||||
}
|
||||
COMPRESSION_PACKET_CLASS = compressionPacketClass;
|
||||
LOGIN_SUCCESS_PACKET_CLASS = loginSuccessPacketClass;
|
||||
ENABLED = enabled;
|
||||
}
|
||||
|
||||
public GeyserSpigotCompressionDisabler() {
|
||||
if (!ENABLED) {
|
||||
throw new RuntimeException("Geyser compression disabler cannot be initialized in its current state!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
Class<?> msgClass = msg.getClass();
|
||||
// Don't let any compression packet get through
|
||||
if (!COMPRESSION_PACKET_CLASS.isAssignableFrom(msgClass)) {
|
||||
if (LOGIN_SUCCESS_PACKET_CLASS.isAssignableFrom(msgClass)) {
|
||||
if (PROTOCOL_SUPPORT_INSTALLED) {
|
||||
// ProtocolSupport must send the compression packet, so let's remove what it did before it does damage
|
||||
if (ctx.pipeline().get("compress") != null) {
|
||||
ctx.pipeline().remove("compress");
|
||||
}
|
||||
if (ctx.pipeline().get("decompress") != null) {
|
||||
ctx.pipeline().remove("decompress");
|
||||
}
|
||||
}
|
||||
// We're past the point that a compression packet can be sent, so we can safely yeet ourselves away
|
||||
ctx.channel().pipeline().remove(this);
|
||||
}
|
||||
super.write(ctx, msg, promise);
|
||||
} else if (PROTOCOL_SUPPORT_INSTALLED) {
|
||||
// We must indicate it "succeeded" or ProtocolSupport will time us out
|
||||
promise.setSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> findCompressionPacket() throws ClassNotFoundException {
|
||||
try {
|
||||
return Class.forName("net.minecraft.network.protocol.login.PacketLoginOutSetCompression");
|
||||
} catch (ClassNotFoundException e) {
|
||||
String prefix = Bukkit.getServer().getClass().getPackage().getName().replace("org.bukkit.craftbukkit", "net.minecraft.server");
|
||||
return Class.forName(prefix + ".PacketLoginOutSetCompression");
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> findLoginSuccessPacket() throws ClassNotFoundException {
|
||||
try {
|
||||
return Class.forName("net.minecraft.network.protocol.login.PacketLoginOutSuccess");
|
||||
} catch (ClassNotFoundException e) {
|
||||
String prefix = Bukkit.getServer().getClass().getPackage().getName().replace("org.bukkit.craftbukkit", "net.minecraft.server");
|
||||
return Class.forName(prefix + ".PacketLoginOutSuccess");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.geysermc.geyser.FloodgateKeyLoader;
|
||||
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration {
|
||||
@JsonIgnore
|
||||
private Path floodgateKeyPath;
|
||||
|
||||
// TODO REMOVE
|
||||
public void loadFloodgate(GeyserSpigotPlugin plugin) {
|
||||
Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate");
|
||||
Path geyserDataFolder = plugin.getDataFolder().toPath();
|
||||
Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
|
||||
|
||||
floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.text.AsteriskSerializer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class GeyserSpigotDumpInfo extends BootstrapDumpInfo {
|
||||
|
||||
private final String platformName;
|
||||
private final String platformVersion;
|
||||
private final String platformAPIVersion;
|
||||
private final boolean onlineMode;
|
||||
|
||||
@AsteriskSerializer.Asterisk(isIp = true)
|
||||
private final String serverIP;
|
||||
private final int serverPort;
|
||||
private final List<PluginInfo> plugins;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
GeyserSpigotDumpInfo() {
|
||||
super();
|
||||
this.platformName = Bukkit.getName();
|
||||
this.platformVersion = Bukkit.getVersion();
|
||||
this.platformAPIVersion = Bukkit.getBukkitVersion();
|
||||
this.onlineMode = Bukkit.getOnlineMode();
|
||||
this.serverIP = Bukkit.getIp();
|
||||
this.serverPort = Bukkit.getPort();
|
||||
this.plugins = new ArrayList<>();
|
||||
|
||||
for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
|
||||
this.plugins.add(new PluginInfo(plugin.isEnabled(), plugin.getName(), plugin.getDescription().getVersion(), plugin.getDescription().getMain(), plugin.getDescription().getAuthors()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* 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.MinecraftProtocol;
|
||||
import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.network.netty.GeyserInjector;
|
||||
import org.geysermc.geyser.network.netty.LocalServerChannelWrapper;
|
||||
import org.geysermc.geyser.network.netty.LocalSession;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.net.InetAddress;
|
||||
import java.util.List;
|
||||
|
||||
public class GeyserSpigotInjector extends GeyserInjector {
|
||||
/**
|
||||
* Used to determine if ViaVersion is setup to a state where Geyser players will fail at joining if injection is enabled
|
||||
*/
|
||||
private final boolean isViaVersion;
|
||||
/**
|
||||
* Used to uninject ourselves on shutdown.
|
||||
*/
|
||||
private List<ChannelFuture> allServerChannels;
|
||||
|
||||
public GeyserSpigotInjector(boolean isViaVersion) {
|
||||
this.isViaVersion = isViaVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void initializeLocalChannel0(GeyserBootstrap bootstrap) throws Exception {
|
||||
Class<?> serverClazz;
|
||||
try {
|
||||
serverClazz = Class.forName("net.minecraft.server.MinecraftServer");
|
||||
// We're using 1.17+
|
||||
} catch (ClassNotFoundException e) {
|
||||
// We're using pre-1.17
|
||||
String prefix = Bukkit.getServer().getClass().getPackage().getName().replace("org.bukkit.craftbukkit", "net.minecraft.server");
|
||||
serverClazz = Class.forName(prefix + ".MinecraftServer");
|
||||
}
|
||||
Method getServer = serverClazz.getDeclaredMethod("getServer");
|
||||
Object server = getServer.invoke(null);
|
||||
Object connection = null;
|
||||
// Find the class that manages network IO
|
||||
for (Method m : serverClazz.getDeclaredMethods()) {
|
||||
// First is Spigot-mapped name, second is Mojang-mapped name which is implemented as future-proofing
|
||||
if (m.getReturnType().getSimpleName().equals("ServerConnection") || m.getReturnType().getSimpleName().equals("ServerConnectionListener")) {
|
||||
if (m.getParameterTypes().length == 0) {
|
||||
connection = m.invoke(server);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (connection == null) {
|
||||
throw new RuntimeException("Unable to find ServerConnection class!");
|
||||
}
|
||||
|
||||
// Find the channel that Minecraft uses to listen to connections
|
||||
ChannelFuture listeningChannel = null;
|
||||
for (Field field : connection.getClass().getDeclaredFields()) {
|
||||
if (field.getType() != List.class) {
|
||||
continue;
|
||||
}
|
||||
field.setAccessible(true);
|
||||
boolean rightList = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0] == ChannelFuture.class;
|
||||
if (!rightList) continue;
|
||||
|
||||
allServerChannels = (List<ChannelFuture>) field.get(connection);
|
||||
for (ChannelFuture o : allServerChannels) {
|
||||
listeningChannel = o;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (listeningChannel == null) {
|
||||
throw new RuntimeException("Unable to find listening channel!");
|
||||
}
|
||||
|
||||
// Making this a function prevents childHandler from being treated as a non-final variable
|
||||
ChannelInitializer<Channel> childHandler = getChildHandler(bootstrap, listeningChannel);
|
||||
// This method is what initializes the connection in Java Edition, after Netty is all set.
|
||||
Method initChannel = childHandler.getClass().getDeclaredMethod("initChannel", Channel.class);
|
||||
initChannel.setAccessible(true);
|
||||
|
||||
ChannelFuture channelFuture = (new ServerBootstrap()
|
||||
.channel(LocalServerChannelWrapper.class)
|
||||
.childHandler(new ChannelInitializer<>() {
|
||||
@Override
|
||||
protected void initChannel(@NonNull Channel ch) throws Exception {
|
||||
initChannel.invoke(childHandler, ch);
|
||||
if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserSpigotCompressionDisabler.ENABLED) {
|
||||
ch.pipeline().addAfter("encoder", "geyser-compression-disabler", new GeyserSpigotCompressionDisabler());
|
||||
}
|
||||
|
||||
if (GeyserImpl.getInstance().getConfig().getRemote().authType() == AuthType.FLOODGATE) {
|
||||
// we have to add the packet blocker in the data handler, otherwise ProtocolSupport breaks
|
||||
ch.pipeline().addBefore(
|
||||
"packet_handler", "geyser_data_handler",
|
||||
new SpigotHybridChannelHandler()
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
// Set to MAX_PRIORITY as MultithreadEventLoopGroup#newDefaultThreadFactory which DefaultEventLoopGroup implements does by default
|
||||
.group(new DefaultEventLoopGroup(0, new DefaultThreadFactory("Geyser Spigot connection thread", Thread.MAX_PRIORITY)))
|
||||
.localAddress(LocalAddress.ANY))
|
||||
.bind()
|
||||
.syncUninterruptibly();
|
||||
// We don't need to add to the list, but plugins like ProtocolSupport and ProtocolLib that add to the main pipeline
|
||||
// will work when we add to the list.
|
||||
allServerChannels.add(channelFuture);
|
||||
this.localChannel = channelFuture;
|
||||
this.serverSocketAddress = channelFuture.channel().localAddress();
|
||||
|
||||
workAroundWeirdBug(bootstrap);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ChannelInitializer<Channel> getChildHandler(GeyserBootstrap bootstrap, ChannelFuture listeningChannel) {
|
||||
List<String> names = listeningChannel.channel().pipeline().names();
|
||||
ChannelInitializer<Channel> childHandler = null;
|
||||
for (String name : names) {
|
||||
ChannelHandler handler = listeningChannel.channel().pipeline().get(name);
|
||||
try {
|
||||
Field childHandlerField = handler.getClass().getDeclaredField("childHandler");
|
||||
childHandlerField.setAccessible(true);
|
||||
childHandler = (ChannelInitializer<Channel>) childHandlerField.get(handler);
|
||||
// ViaVersion non-Paper-injector workaround so we aren't double-injecting
|
||||
if (isViaVersion && childHandler instanceof BukkitChannelInitializer) {
|
||||
childHandler = ((BukkitChannelInitializer) childHandler).original();
|
||||
}
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
if (bootstrap.getGeyserConfig().isDebugMode()) {
|
||||
bootstrap.getGeyserLogger().debug("The handler " + name + " isn't a ChannelInitializer. THIS ERROR IS SAFE TO IGNORE!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (childHandler == null) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
return childHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work around an odd bug where the first connection might not initialize all channel handlers on the main pipeline -
|
||||
* send a dummy status request down that acts as the first connection, then.
|
||||
* For the future, if someone wants to properly fix this - as of December 28, 2021, it happens on 1.16.5/1.17.1/1.18.1 EXCEPT Spigot 1.16.5
|
||||
*/
|
||||
private void workAroundWeirdBug(GeyserBootstrap bootstrap) {
|
||||
MinecraftProtocol protocol = new MinecraftProtocol();
|
||||
LocalSession session = new LocalSession(null, bootstrap.getGeyserConfig().getRemote().address(),
|
||||
bootstrap.getGeyserConfig().getRemote().port(), this.serverSocketAddress,
|
||||
InetAddress.getLoopbackAddress().getHostAddress(), protocol, protocol.createHelper());
|
||||
session.connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (this.allServerChannels != null) {
|
||||
this.allServerChannels.remove(this.localChannel);
|
||||
this.allServerChannels = null;
|
||||
}
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class GeyserSpigotLogger implements GeyserLogger {
|
||||
private final Logger logger;
|
||||
@Getter @Setter
|
||||
private boolean debug;
|
||||
|
||||
@Override
|
||||
public void severe(String message) {
|
||||
logger.severe(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message, Throwable error) {
|
||||
logger.log(Level.SEVERE, message, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message) {
|
||||
logger.warning(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message, Throwable error) {
|
||||
logger.log(Level.WARNING, message, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String message) {
|
||||
error(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message) {
|
||||
logger.info(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
if (debug) {
|
||||
info(message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.geysermc.geyser.GeyserMain;
|
||||
|
||||
public class GeyserSpigotMain extends GeyserMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
new GeyserSpigotMain().displayMessage();
|
||||
}
|
||||
|
||||
public String getPluginType() {
|
||||
return "Spigot or Paper (recommended)";
|
||||
}
|
||||
|
||||
public String getPluginFolder() {
|
||||
return "plugins";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 lombok.AllArgsConstructor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.server.ServerListPingEvent;
|
||||
import org.bukkit.util.CachedServerIcon;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.ping.GeyserPingInfo;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class GeyserSpigotPingPassthrough implements IGeyserPingPassthrough {
|
||||
|
||||
private final GeyserSpigotLogger logger;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public @Nullable GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) {
|
||||
try {
|
||||
ServerListPingEvent event = new GeyserPingEvent(inetSocketAddress.getAddress(), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), Bukkit.getMaxPlayers());
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
return new GeyserPingInfo(event.getMotd(), event.getMaxPlayers(), event.getNumPlayers());
|
||||
} catch (Exception | LinkageError e) { // LinkageError in the event that method/constructor signatures change
|
||||
logger.debug("Error while getting Bukkit ping passthrough: " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// These methods are unimplemented on spigot api by default so we add stubs so plugins don't complain
|
||||
private static class GeyserPingEvent extends ServerListPingEvent {
|
||||
|
||||
public GeyserPingEvent(InetAddress address, String motd, int numPlayers, int maxPlayers) {
|
||||
super("", address, motd, numPlayers, maxPlayers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServerIcon(CachedServerIcon icon) throws IllegalArgumentException, UnsupportedOperationException {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Iterator<Player> iterator() throws UnsupportedOperationException {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,496 @@
|
|||
/*
|
||||
* 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.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.data.MappingData;
|
||||
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import me.lucko.commodore.CommodoreProvider;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.server.ServerLoadEvent;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.geysermc.floodgate.core.skin.SkinApplier;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.Constants;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.api.extension.Extension;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport;
|
||||
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor;
|
||||
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager;
|
||||
import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener;
|
||||
import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener;
|
||||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotLegacyNativeWorldManager;
|
||||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorldManager;
|
||||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
|
||||
private GeyserSpigotCommandManager geyserCommandManager;
|
||||
private GeyserSpigotConfiguration geyserConfig;
|
||||
private GeyserSpigotInjector geyserInjector;
|
||||
private GeyserSpigotLogger geyserLogger;
|
||||
private IGeyserPingPassthrough geyserSpigotPingPassthrough;
|
||||
private GeyserSpigotWorldManager geyserWorldManager;
|
||||
|
||||
private GeyserImpl geyser;
|
||||
|
||||
/**
|
||||
* The Minecraft server version, formatted as <code>1.#.#</code>
|
||||
*/
|
||||
private String minecraftVersion;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
onGeyserInitialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeyserInitialize() {
|
||||
GeyserLocale.init(this);
|
||||
|
||||
try {
|
||||
// AvailableCommandsSerializer_v291 complains otherwise - affects at least 1.8
|
||||
ByteBuf.class.getMethod("writeShortLE", int.class);
|
||||
// Only available in 1.13.x
|
||||
Class.forName("org.bukkit.event.server.ServerLoadEvent");
|
||||
// We depend on this as a fallback in certain scenarios
|
||||
BlockData.class.getMethod("getAsString");
|
||||
} catch (ClassNotFoundException | NoSuchMethodException e) {
|
||||
getLogger().severe("*********************************************");
|
||||
getLogger().severe("");
|
||||
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.header"));
|
||||
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2"));
|
||||
getLogger().severe("");
|
||||
getLogger().severe("*********************************************");
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Class.forName("io.netty.util.internal.ObjectPool$ObjectCreator");
|
||||
} catch (ClassNotFoundException e) {
|
||||
getLogger().severe("*********************************************");
|
||||
getLogger().severe("");
|
||||
getLogger().severe("This version of Spigot is using an outdated version of netty. Please use Paper instead!");
|
||||
getLogger().severe("");
|
||||
getLogger().severe("*********************************************");
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode())
|
||||
: new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
|
||||
// Turn "(MC: 1.16.4)" into 1.16.4.
|
||||
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
|
||||
|
||||
this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (Bukkit.getPluginManager().getPlugin("floodgate") != null) {
|
||||
geyserLogger.severe("WHY DO YOU HAVE FLOODGATE INSTALLED!!!!!!! REMOVE IT!!!!");
|
||||
}
|
||||
|
||||
this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
|
||||
this.geyserCommandManager.init();
|
||||
|
||||
// Because Bukkit locks its command map upon startup, we need to
|
||||
// add our plugin commands in onEnable, but populating the executor
|
||||
// can happen at any time (later in #onGeyserEnable())
|
||||
CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap();
|
||||
for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) {
|
||||
// Thanks again, Bukkit
|
||||
try {
|
||||
Constructor<PluginCommand> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
|
||||
constructor.setAccessible(true);
|
||||
|
||||
PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
|
||||
pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
|
||||
|
||||
commandMap.register(extension.description().id(), "geyserext", pluginCommand);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
|
||||
this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
|
||||
Bukkit.getPluginManager().registerEvents(new Listener() {
|
||||
|
||||
@EventHandler
|
||||
public void onServerLoaded(ServerLoadEvent event) {
|
||||
if (event.getType() == ServerLoadEvent.LoadType.RELOAD) {
|
||||
geyser.setShuttingDown(false);
|
||||
}
|
||||
onGeyserEnable();
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
public void onGeyserEnable() {
|
||||
// Configs are loaded once early - so we can create the logger, then load extensions and finally register
|
||||
// extension commands in #onEnable. To ensure reloading geyser also reloads the geyser config, this exists
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(this.geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
}
|
||||
|
||||
GeyserImpl.start();
|
||||
|
||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
} else {
|
||||
if (ReflectedNames.checkPaperPingEvent()) {
|
||||
this.geyserSpigotPingPassthrough = new GeyserPaperPingPassthrough(geyserLogger);
|
||||
} else if (ReflectedNames.newSpigotPingConstructorExists()) {
|
||||
this.geyserSpigotPingPassthrough = new GeyserSpigotPingPassthrough(geyserLogger);
|
||||
} else {
|
||||
// Can't enable one of the other options
|
||||
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
}
|
||||
}
|
||||
geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass()));
|
||||
|
||||
// Don't need to re-create the world manager/re-register commands/reinject when reloading
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null;
|
||||
|
||||
// Check to ensure the current setup can support the protocol version Geyser uses
|
||||
GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);
|
||||
|
||||
// We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib
|
||||
// To do their job injecting, then connect into *that*
|
||||
this.geyserInjector = new GeyserSpigotInjector(isViaVersion);
|
||||
this.geyserInjector.initializeLocalChannel(this);
|
||||
|
||||
if (Boolean.parseBoolean(System.getProperty("Geyser.UseDirectAdapters", "true"))) {
|
||||
try {
|
||||
String name = Bukkit.getServer().getClass().getPackage().getName();
|
||||
String nmsVersion = name.substring(name.lastIndexOf('.') + 1);
|
||||
SpigotAdapters.registerWorldAdapter(nmsVersion);
|
||||
if (isViaVersion && isViaVersionNeeded()) {
|
||||
this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this);
|
||||
} else {
|
||||
// No ViaVersion
|
||||
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this);
|
||||
}
|
||||
geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion);
|
||||
} catch (Exception e) {
|
||||
if (geyserConfig.isDebugMode()) {
|
||||
geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
geyserLogger.debug("Not using NMS adapter as it is disabled via system property.");
|
||||
}
|
||||
|
||||
if (this.geyserWorldManager == null) {
|
||||
// No NMS adapter
|
||||
this.geyserWorldManager = new GeyserSpigotWorldManager(this);
|
||||
geyserLogger.debug("Using default world manager.");
|
||||
}
|
||||
|
||||
PluginCommand geyserCommand = this.getCommand("geyser");
|
||||
Objects.requireNonNull(geyserCommand, "base command cannot be null");
|
||||
geyserCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands()));
|
||||
|
||||
for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
|
||||
Map<String, Command> commands = entry.getValue();
|
||||
if (commands.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PluginCommand command = this.getCommand(entry.getKey().description().id());
|
||||
if (command == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands));
|
||||
}
|
||||
|
||||
// Register permissions so they appear in, for example, LuckPerms' UI
|
||||
// Re-registering permissions throws an error
|
||||
for (Map.Entry<String, Command> entry : geyserCommandManager.commands().entrySet()) {
|
||||
Command command = entry.getValue();
|
||||
if (command.aliases().contains(entry.getKey())) {
|
||||
// Don't register aliases
|
||||
continue;
|
||||
}
|
||||
|
||||
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
|
||||
GeyserLocale.getLocaleStringLog(command.description()),
|
||||
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
|
||||
}
|
||||
|
||||
// Register permissions for extension commands
|
||||
for (Map.Entry<Extension, Map<String, Command>> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) {
|
||||
for (Map.Entry<String, Command> entry : commandEntry.getValue().entrySet()) {
|
||||
Command command = entry.getValue();
|
||||
if (command.aliases().contains(entry.getKey())) {
|
||||
// Don't register aliases
|
||||
continue;
|
||||
}
|
||||
|
||||
if (command.permission().isBlank()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid registering the same permission twice, e.g. for the extension help commands
|
||||
if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
|
||||
continue;
|
||||
}
|
||||
|
||||
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
|
||||
GeyserLocale.getLocaleStringLog(command.description()),
|
||||
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();
|
||||
geyserLogger.debug("Brigadier supported? " + brigadierSupported);
|
||||
if (brigadierSupported) {
|
||||
GeyserBrigadierSupport.loadBrigadier(this, geyserCommand);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeyserDisable() {
|
||||
if (geyser != null) {
|
||||
geyser.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeyserShutdown() {
|
||||
if (geyser != null) {
|
||||
geyser.shutdown();
|
||||
}
|
||||
if (geyserInjector != null) {
|
||||
geyserInjector.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
this.onGeyserShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserSpigotConfiguration getGeyserConfig() {
|
||||
return geyserConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserSpigotLogger getGeyserLogger() {
|
||||
return geyserLogger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserCommandManager getGeyserCommandManager() {
|
||||
return this.geyserCommandManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IGeyserPingPassthrough getGeyserPingPassthrough() {
|
||||
return geyserSpigotPingPassthrough;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldManager getWorldManager() {
|
||||
return this.geyserWorldManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getConfigFolder() {
|
||||
return getDataFolder().toPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BootstrapDumpInfo getDumpInfo() {
|
||||
return new GeyserSpigotDumpInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMinecraftServerVersion() {
|
||||
return this.minecraftVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getSocketAddress() {
|
||||
return this.geyserInjector.getServerSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SkinApplier createSkinApplier() {
|
||||
// return new SpigotSkinApplier(new SpigotVersionSpecificMethods(this), this);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the server version before ViaVersion finishes initializing
|
||||
*/
|
||||
public ProtocolVersion getServerProtocolVersion() {
|
||||
return ProtocolVersion.getClosest(this.minecraftVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function should not run unless ViaVersion is installed on the server.
|
||||
*
|
||||
* @return true if there is any block mappings difference between the server and client.
|
||||
*/
|
||||
private boolean isViaVersionNeeded() {
|
||||
ProtocolVersion serverVersion = getServerProtocolVersion();
|
||||
List<ProtocolPathEntry> protocolList = Via.getManager().getProtocolManager().getProtocolPath(GameProtocol.getJavaProtocolVersion(),
|
||||
serverVersion.getVersion());
|
||||
if (protocolList == null) {
|
||||
// No translation needed!
|
||||
return false;
|
||||
}
|
||||
for (int i = protocolList.size() - 1; i >= 0; i--) {
|
||||
MappingData mappingData = protocolList.get(i).getProtocol().getMappingData();
|
||||
if (mappingData != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// All mapping data is null, which means client and server block states are the same
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getServerBindAddress() {
|
||||
return Bukkit.getIp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getServerPort() {
|
||||
return Bukkit.getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testFloodgatePluginPresent() {
|
||||
if (Bukkit.getPluginManager().getPlugin("floodgate") != null) {
|
||||
geyserConfig.loadFloodgate(this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean loadConfig() {
|
||||
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
|
||||
try {
|
||||
if (!getDataFolder().exists()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml",
|
||||
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -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.SpigotCommandSource;
|
||||
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 SpigotCommandSource(player));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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.viaversion.viaversion.api.Via;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.UnsafeValues;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
public final class GeyserSpigotVersionChecker {
|
||||
private static final String VIAVERSION_DOWNLOAD_URL = "https://ci.viaversion.com/job/ViaVersion/";
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void checkForSupportedProtocol(GeyserLogger logger, boolean viaversion) {
|
||||
if (viaversion) {
|
||||
checkViaVersionSupportedVersions(logger);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// This method is only present on later versions of Paper
|
||||
UnsafeValues.class.getMethod("getProtocolVersion");
|
||||
if (Bukkit.getUnsafe().getProtocolVersion() != GameProtocol.getJavaProtocolVersion()) {
|
||||
sendOutdatedMessage(logger);
|
||||
}
|
||||
return;
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
|
||||
// Otherwise, we can just try to find the SharedConstants class
|
||||
// It isn't present in all server versions, but if we can't find it, then we're probably not in the latest version
|
||||
Class<?> sharedConstants;
|
||||
try {
|
||||
sharedConstants = Class.forName("net.minecraft.SharedConstants");
|
||||
} catch (ClassNotFoundException e) {
|
||||
// We're using pre-1.17
|
||||
String prefix = Bukkit.getServer().getClass().getPackage().getName().replace("org.bukkit.craftbukkit", "net.minecraft.server");
|
||||
try {
|
||||
sharedConstants = Class.forName(prefix + ".SharedConstants");
|
||||
} catch (ClassNotFoundException e2) {
|
||||
sendOutdatedMessage(logger);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (Method method : sharedConstants.getMethods()) {
|
||||
if (method.getReturnType() == int.class && Modifier.isStatic(method.getModifiers())) {
|
||||
int protocolVersion;
|
||||
try {
|
||||
protocolVersion = (int) method.invoke(null);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
logger.warning("Could not determine server version! This is safe to ignore, but please report to the developers: " + e.getMessage());
|
||||
if (logger.isDebug()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (protocolVersion != GameProtocol.getJavaProtocolVersion()) {
|
||||
sendOutdatedMessage(logger);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
sendOutdatedMessage(logger);
|
||||
}
|
||||
|
||||
private static void checkViaVersionSupportedVersions(GeyserLogger logger) {
|
||||
// Run after ViaVersion has obtained the server protocol version
|
||||
Via.getPlatform().runSync(() -> {
|
||||
if (Via.getAPI().getSupportedVersions().contains(GameProtocol.getJavaProtocolVersion())) {
|
||||
// Via supports this protocol version; we will be able to connect.
|
||||
return;
|
||||
}
|
||||
if (Via.getAPI().getFullSupportedVersions().contains(GameProtocol.getJavaProtocolVersion())) {
|
||||
// ViaVersion supports our protocol, but the user has blocked them from connecting.
|
||||
logger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.viaversion.blocked", GameProtocol.getAllSupportedJavaVersions()));
|
||||
return;
|
||||
}
|
||||
// Else, presumably, ViaVersion is not updated.
|
||||
sendOutdatedViaVersionMessage(logger);
|
||||
});
|
||||
}
|
||||
|
||||
public static void sendOutdatedViaVersionMessage(GeyserLogger logger) {
|
||||
logger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.viaversion.too_old",
|
||||
VIAVERSION_DOWNLOAD_URL));
|
||||
}
|
||||
|
||||
private static void sendOutdatedMessage(GeyserLogger logger) {
|
||||
logger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.no_supported_protocol", GameProtocol.getAllSupportedJavaVersions(), VIAVERSION_DOWNLOAD_URL));
|
||||
}
|
||||
|
||||
private GeyserSpigotVersionChecker() {
|
||||
}
|
||||
}
|
|
@ -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.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
|
||||
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.
|
||||
* <p>
|
||||
* Code mostly taken from <a href="https://github.com/KyoriPowered/adventure-platform/blob/94d5821f2e755170f42bd8a5fe1d5bf6f66d04ad/platform-bukkit/src/main/java/net/kyori/adventure/platform/bukkit/PaperFacet.java#L46">here</a>
|
||||
* 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 @Nullable 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() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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.destroystokyo.paper.event.server.PaperServerListPingEvent;
|
||||
import com.destroystokyo.paper.network.StatusClient;
|
||||
import org.bukkit.event.server.ServerListPingEvent;
|
||||
import org.bukkit.util.CachedServerIcon;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.InetAddress;
|
||||
|
||||
/**
|
||||
* A utility class for checking on the existence of classes, constructors, fields, methods
|
||||
*/
|
||||
public final class ReflectedNames {
|
||||
|
||||
static boolean checkPaperPingEvent() {
|
||||
try {
|
||||
Class.forName("com.destroystokyo.paper.event.server.PaperServerListPingEvent");
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static boolean newSpigotPingConstructorExists() {
|
||||
return getConstructor(ServerListPingEvent.class, InetAddress.class, String.class, boolean.class, int.class, int.class) != null;
|
||||
}
|
||||
|
||||
static @Nullable Constructor<PaperServerListPingEvent> getOldPaperPingConstructor() {
|
||||
if (getConstructor(PaperServerListPingEvent.class, StatusClient.class, String.class, int.class,
|
||||
int.class, String.class, int.class, CachedServerIcon.class) != null) {
|
||||
// @NotNull StatusClient client, @NotNull String motd, int numPlayers, int maxPlayers,
|
||||
// @NotNull String version, int protocolVersion, @Nullable CachedServerIcon favicon
|
||||
// New constructor is present
|
||||
return null;
|
||||
}
|
||||
// @NotNull StatusClient client, @NotNull String motd, boolean shouldSendChatPreviews, int numPlayers, int maxPlayers,
|
||||
// @NotNull String version, int protocolVersion, @Nullable CachedServerIcon favicon
|
||||
return getConstructor(PaperServerListPingEvent.class, StatusClient.class, String.class, boolean.class, int.class, int.class,
|
||||
String.class, int.class, CachedServerIcon.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if this class has a constructor with the specified arguments
|
||||
*/
|
||||
@Nullable
|
||||
private static <T> Constructor<T> getConstructor(Class<T> clazz, Class<?>... args) {
|
||||
try {
|
||||
return clazz.getConstructor(args);
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ReflectedNames() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import org.geysermc.api.connection.Connection;
|
||||
import org.geysermc.floodgate.spigot.util.ClassNames;
|
||||
import org.geysermc.geyser.floodgate.IntegratedFloodgateProvider;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static org.geysermc.floodgate.core.util.ReflectionUtils.setValue;
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
public final class SpigotHybridChannelHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void channelRead(@Nonnull ChannelHandlerContext ctx, @Nonnull Object packet) throws Exception {
|
||||
Connection session = ctx.channel().attr(IntegratedFloodgateProvider.SESSION_KEY).get();
|
||||
// TODO generify this code within Floodgate
|
||||
if (ClassNames.LOGIN_START_PACKET.isInstance(packet)) {
|
||||
Object networkManager = ctx.channel().pipeline().get("packet_handler");
|
||||
Object packetListener = ClassNames.PACKET_LISTENER.get(networkManager);
|
||||
|
||||
setValue(networkManager, "spoofedUUID", session.javaUuid());
|
||||
|
||||
// check if the server is actually in the Login state
|
||||
if (!ClassNames.LOGIN_LISTENER.isInstance(packetListener)) {
|
||||
// player is not in the login state, abort
|
||||
|
||||
// I would've liked to close the channel for security reasons, but our big friend
|
||||
// ProtocolSupport, who likes to break things, doesn't work otherwise
|
||||
ctx.pipeline().remove(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// set the player his GameProfile, we can't change the username without this
|
||||
GameProfile gameProfile = new GameProfile(
|
||||
// TODO testing only
|
||||
session.javaUuid(), session.javaUsername()
|
||||
);
|
||||
setValue(packetListener, ClassNames.LOGIN_PROFILE, gameProfile);
|
||||
|
||||
// we have to fake the offline player (login) cycle
|
||||
// just like on Spigot:
|
||||
|
||||
// LoginListener#initUUID
|
||||
// new LoginHandler().fireEvents();
|
||||
|
||||
// and the tick of LoginListener will do the rest
|
||||
|
||||
ClassNames.INIT_UUID.invoke(packetListener);
|
||||
|
||||
Object loginHandler = ClassNames.LOGIN_HANDLER_CONSTRUCTOR.newInstance(packetListener);
|
||||
ClassNames.FIRE_LOGIN_EVENTS.invoke(loginHandler);
|
||||
|
||||
ctx.pipeline().remove(this);
|
||||
return;
|
||||
}
|
||||
ctx.fireChannelRead(packet);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.command;
|
||||
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import me.lucko.commodore.Commodore;
|
||||
import me.lucko.commodore.CommodoreProvider;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin;
|
||||
|
||||
/**
|
||||
* Needs to be a separate class so pre-1.13 loads correctly.
|
||||
*/
|
||||
public final class GeyserBrigadierSupport {
|
||||
|
||||
public static void loadBrigadier(GeyserSpigotPlugin plugin, PluginCommand pluginCommand) {
|
||||
// Enable command completions if supported
|
||||
// This is beneficial because this is sent over the network and Bedrock can see it
|
||||
Commodore commodore = CommodoreProvider.getCommodore(plugin);
|
||||
LiteralArgumentBuilder<?> builder = LiteralArgumentBuilder.literal("geyser");
|
||||
for (String command : plugin.getGeyserCommandManager().getCommands().keySet()) {
|
||||
builder.then(LiteralArgumentBuilder.literal(command));
|
||||
}
|
||||
commodore.register(pluginCommand, builder);
|
||||
|
||||
try {
|
||||
Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent");
|
||||
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPaperCommandListener(), plugin);
|
||||
plugin.getGeyserLogger().debug("Successfully registered AsyncPlayerSendCommandsEvent listener.");
|
||||
} catch (ClassNotFoundException e) {
|
||||
plugin.getGeyserLogger().debug("Not registering AsyncPlayerSendCommandsEvent listener.");
|
||||
}
|
||||
}
|
||||
|
||||
private GeyserBrigadierSupport() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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.command;
|
||||
|
||||
import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
public final class GeyserPaperCommandListener implements Listener {
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
@EventHandler
|
||||
public void onCommandSend(AsyncPlayerSendCommandsEvent<?> event) {
|
||||
// Documentation says to check (event.isAsynchronous() || !event.hasFiredAsync()), but as of Paper 1.18.2
|
||||
// event.hasFiredAsync is never true
|
||||
if (event.isAsynchronous()) {
|
||||
CommandNode<?> geyserBrigadier = event.getCommandNode().getChild("geyser");
|
||||
if (geyserBrigadier != null) {
|
||||
Player player = event.getPlayer();
|
||||
boolean isJavaPlayer = isProbablyJavaPlayer(player);
|
||||
Map<String, Command> commands = GeyserImpl.getInstance().commandManager().getCommands();
|
||||
Iterator<? extends CommandNode<?>> it = geyserBrigadier.getChildren().iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
CommandNode<?> subnode = it.next();
|
||||
Command command = commands.get(subnode.getName());
|
||||
if (command != null) {
|
||||
if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.permission())) {
|
||||
// Remove this from the node as we don't have permission to use it
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This early on, there is a rare chance that Geyser has yet to process the connection. We'll try to minimize that
|
||||
* chance, though.
|
||||
*/
|
||||
private boolean isProbablyJavaPlayer(Player player) {
|
||||
if (GeyserImpl.getInstance().connectionByUuid(player.getUniqueId()) != null) {
|
||||
// For sure this is a Bedrock player
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GeyserImpl.getInstance().getConfig().isUseDirectConnection()) {
|
||||
InetSocketAddress address = player.getAddress();
|
||||
if (address != null) {
|
||||
return address.getPort() != 0;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.command;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandExecutor;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class GeyserSpigotCommandExecutor extends GeyserCommandExecutor implements TabExecutor {
|
||||
|
||||
public GeyserSpigotCommandExecutor(GeyserImpl geyser, Map<String, org.geysermc.geyser.api.command.Command> commands) {
|
||||
super(geyser, commands);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, String[] args) {
|
||||
SpigotCommandSource commandSender = new SpigotCommandSource(sender);
|
||||
GeyserSession session = getGeyserSession(commandSender);
|
||||
|
||||
if (args.length > 0) {
|
||||
GeyserCommand geyserCommand = getCommand(args[0]);
|
||||
if (geyserCommand != null) {
|
||||
if (!sender.hasPermission(geyserCommand.permission())) {
|
||||
String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale());
|
||||
|
||||
commandSender.sendMessage(ChatColor.RED + message);
|
||||
return true;
|
||||
}
|
||||
if (geyserCommand.isBedrockOnly() && session == null) {
|
||||
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale()));
|
||||
return true;
|
||||
}
|
||||
geyserCommand.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
|
||||
return true;
|
||||
} else {
|
||||
String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale());
|
||||
commandSender.sendMessage(ChatColor.RED + message);
|
||||
}
|
||||
} else {
|
||||
getCommand("help").execute(session, commandSender, new String[0]);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, String[] args) {
|
||||
if (args.length == 1) {
|
||||
return tabComplete(new SpigotCommandSource(sender));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.command;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class GeyserSpigotCommandManager extends GeyserCommandManager {
|
||||
|
||||
private static final CommandMap COMMAND_MAP;
|
||||
|
||||
static {
|
||||
CommandMap commandMap = null;
|
||||
try {
|
||||
// Paper-only
|
||||
Server.class.getMethod("getCommandMap");
|
||||
commandMap = Bukkit.getServer().getCommandMap();
|
||||
} catch (NoSuchMethodException e) {
|
||||
try {
|
||||
Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
|
||||
cmdMapField.setAccessible(true);
|
||||
commandMap = (CommandMap) cmdMapField.get(Bukkit.getServer());
|
||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
COMMAND_MAP = commandMap;
|
||||
}
|
||||
|
||||
public GeyserSpigotCommandManager(GeyserImpl geyser) {
|
||||
super(geyser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description(String command) {
|
||||
Command cmd = COMMAND_MAP.getCommand(command.replace("/", ""));
|
||||
return cmd != null ? cmd.getDescription() : "";
|
||||
}
|
||||
|
||||
public static CommandMap getCommandMap() {
|
||||
return COMMAND_MAP;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.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.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.platform.spigot.PaperAdventure;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
||||
public class SpigotCommandSource implements GeyserCommandSource {
|
||||
private final org.bukkit.command.CommandSender handle;
|
||||
|
||||
public SpigotCommandSource(org.bukkit.command.CommandSender handle) {
|
||||
this.handle = handle;
|
||||
// Ensure even Java players' languages are loaded
|
||||
GeyserLocale.loadGeyserLocale(locale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return handle.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NonNull String message) {
|
||||
handle.sendMessage(message);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void sendMessage(Component message) {
|
||||
if (PaperAdventure.canSendMessageUsingComponent()) {
|
||||
PaperAdventure.sendMessage(handle, message);
|
||||
return;
|
||||
}
|
||||
|
||||
// CommandSender#sendMessage(BaseComponent[]) is Paper-only
|
||||
handle.spigot().sendMessage(BungeeComponentSerializer.get().serialize(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConsole() {
|
||||
return handle instanceof ConsoleCommandSender;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public String locale() {
|
||||
if (this.handle instanceof Player player) {
|
||||
// getLocale() is deprecated on Paper, but not on Spigot
|
||||
return player.getLocale();
|
||||
}
|
||||
|
||||
return GeyserLocale.getDefaultLocale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
return handle.hasPermission(permission);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* 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.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.value.PistonValueType;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockPistonEvent;
|
||||
import org.bukkit.event.block.BlockPistonExtendEvent;
|
||||
import org.bukkit.event.block.BlockPistonRetractEvent;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.physics.Direction;
|
||||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.PistonCache;
|
||||
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class GeyserPistonListener implements Listener {
|
||||
private final GeyserImpl geyser;
|
||||
private final GeyserSpigotWorldManager worldManager;
|
||||
|
||||
public GeyserPistonListener(GeyserImpl geyser, GeyserSpigotWorldManager worldManager) {
|
||||
this.geyser = geyser;
|
||||
this.worldManager = worldManager;
|
||||
}
|
||||
|
||||
// The handlers' parent class cannot be registered
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPistonExtend(BlockPistonExtendEvent event) {
|
||||
onPistonAction(event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPistonRetract(BlockPistonRetractEvent event) {
|
||||
onPistonAction(event);
|
||||
}
|
||||
|
||||
private void onPistonAction(BlockPistonEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
World world = event.getBlock().getWorld();
|
||||
boolean isExtend = event instanceof BlockPistonExtendEvent;
|
||||
|
||||
Location location = event.getBlock().getLocation();
|
||||
Vector3i position = getVector(location);
|
||||
PistonValueType type = isExtend ? PistonValueType.PUSHING : PistonValueType.PULLING;
|
||||
boolean sticky = event.isSticky();
|
||||
|
||||
Object2IntMap<Vector3i> attachedBlocks = new Object2IntArrayMap<>();
|
||||
boolean blocksFilled = false;
|
||||
|
||||
for (Map.Entry<UUID, GeyserSession> entry : geyser.getSessionManager().getSessions().entrySet()) {
|
||||
Player player = Bukkit.getPlayer(entry.getKey());
|
||||
if (player == null || !player.getWorld().equals(world)) {
|
||||
continue;
|
||||
}
|
||||
GeyserSession session = entry.getValue();
|
||||
|
||||
int dX = Math.abs(location.getBlockX() - player.getLocation().getBlockX()) >> 4;
|
||||
int dZ = Math.abs(location.getBlockZ() - player.getLocation().getBlockZ()) >> 4;
|
||||
if ((dX * dX + dZ * dZ) > session.getServerRenderDistance() * session.getServerRenderDistance()) {
|
||||
// Ignore pistons outside the player's render distance
|
||||
continue;
|
||||
}
|
||||
|
||||
// Trying to grab the blocks from the world like other platforms would result in the moving piston block
|
||||
// being returned instead.
|
||||
if (!blocksFilled) {
|
||||
List<Block> blocks = isExtend ? ((BlockPistonExtendEvent) event).getBlocks() : ((BlockPistonRetractEvent) event).getBlocks();
|
||||
for (Block block : blocks) {
|
||||
Location attachedLocation = block.getLocation();
|
||||
int blockId = worldManager.getBlockNetworkId(block);
|
||||
// Ignore blocks that will be destroyed
|
||||
if (BlockStateValues.canPistonMoveBlock(blockId, isExtend)) {
|
||||
attachedBlocks.put(getVector(attachedLocation), blockId);
|
||||
}
|
||||
}
|
||||
blocksFilled = true;
|
||||
}
|
||||
|
||||
int pistonBlockId = worldManager.getBlockNetworkId(event.getBlock());
|
||||
// event.getDirection() is unreliable
|
||||
Direction orientation = BlockStateValues.getPistonOrientation(pistonBlockId);
|
||||
|
||||
session.executeInEventLoop(() -> {
|
||||
PistonCache pistonCache = session.getPistonCache();
|
||||
PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos ->
|
||||
new PistonBlockEntity(session, position, orientation, sticky, !isExtend));
|
||||
blockEntity.setAction(type, attachedBlocks);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3i getVector(Location location) {
|
||||
return Vector3i.from(location.getX(), location.getY(), location.getZ());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.world;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class GeyserSpigotBlockPlaceListener implements Listener {
|
||||
private final GeyserImpl geyser;
|
||||
private final GeyserSpigotWorldManager worldManager;
|
||||
|
||||
@EventHandler
|
||||
public void place(final BlockPlaceEvent event) {
|
||||
GeyserSession session = geyser.connectionByUuid(event.getPlayer().getUniqueId());
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
LevelSoundEventPacket placeBlockSoundPacket = new LevelSoundEventPacket();
|
||||
placeBlockSoundPacket.setSound(SoundEvent.PLACE);
|
||||
placeBlockSoundPacket.setPosition(Vector3f.from(event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ()));
|
||||
placeBlockSoundPacket.setBabySound(false);
|
||||
if (worldManager.isLegacy()) {
|
||||
placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(worldManager.getBlockAt(session,
|
||||
event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())));
|
||||
} else {
|
||||
String javaBlockId = event.getBlockPlaced().getBlockData().getAsString();
|
||||
placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().getOrDefault(javaBlockId, BlockStateValues.JAVA_AIR_ID)));
|
||||
}
|
||||
placeBlockSoundPacket.setIdentifier(":");
|
||||
session.sendUpstreamPacket(placeBlockSoundPacket);
|
||||
session.setLastBlockPlacePosition(null);
|
||||
session.setLastBlockPlacedId(null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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.world.manager;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.data.MappingData;
|
||||
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Used when block IDs need to be translated to the latest version
|
||||
*/
|
||||
public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorldManager {
|
||||
|
||||
private final Int2IntMap oldToNewBlockId;
|
||||
|
||||
public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin) {
|
||||
super(plugin);
|
||||
IntList allBlockStates = adapter.getAllBlockStates();
|
||||
oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size());
|
||||
ProtocolVersion serverVersion = plugin.getServerProtocolVersion();
|
||||
List<ProtocolPathEntry> protocolList = Via.getManager().getProtocolManager().getProtocolPath(GameProtocol.getJavaProtocolVersion(),
|
||||
serverVersion.getVersion());
|
||||
Objects.requireNonNull(protocolList, "protocolList cannot be null");
|
||||
for (int oldBlockId : allBlockStates) {
|
||||
int newBlockId = oldBlockId;
|
||||
// protocolList should *not* be null; we checked for that before initializing this class
|
||||
for (int i = protocolList.size() - 1; i >= 0; i--) {
|
||||
MappingData mappingData = protocolList.get(i).getProtocol().getMappingData();
|
||||
if (mappingData != null) {
|
||||
newBlockId = mappingData.getNewBlockStateId(newBlockId);
|
||||
}
|
||||
}
|
||||
oldToNewBlockId.put(oldBlockId, newBlockId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
||||
int nativeBlockId = super.getBlockAt(session, x, y, z);
|
||||
return oldToNewBlockId.getOrDefault(nativeBlockId, nativeBlockId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegacy() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.world.manager;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
|
||||
import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
|
||||
protected final SpigotWorldAdapter adapter;
|
||||
|
||||
public GeyserSpigotNativeWorldManager(Plugin plugin) {
|
||||
super(plugin);
|
||||
adapter = SpigotAdapters.getWorldAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
||||
Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername());
|
||||
if (player == null) {
|
||||
return BlockStateValues.JAVA_AIR_ID;
|
||||
}
|
||||
return adapter.getBlockAt(player.getWorld(), x, y, z);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String[] getBiomeIdentifiers(boolean withTags) {
|
||||
// Biome identifiers will basically always be the same for one server, since you have to re-send the
|
||||
// ClientboundLoginPacket to change the registry. Therefore, don't bother caching for each player.
|
||||
return adapter.getBiomeSuggestions(withTags);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* 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.world.manager;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.geysermc.erosion.bukkit.BukkitLecterns;
|
||||
import org.geysermc.erosion.bukkit.BukkitUtils;
|
||||
import org.geysermc.erosion.bukkit.PickBlockUtils;
|
||||
import org.geysermc.erosion.bukkit.SchedulerUtils;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.level.GameRule;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.BlockEntityUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* The base world manager to use when there is no supported NMS revision
|
||||
*/
|
||||
public class GeyserSpigotWorldManager extends WorldManager {
|
||||
private final Plugin plugin;
|
||||
private final BukkitLecterns lecterns;
|
||||
|
||||
public GeyserSpigotWorldManager(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.lecterns = new BukkitLecterns(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
|
||||
return BlockStateValues.JAVA_AIR_ID;
|
||||
}
|
||||
World world = bukkitPlayer.getWorld();
|
||||
if (!world.isChunkLoaded(x >> 4, z >> 4)) {
|
||||
// If the chunk isn't loaded, how could we even be here?
|
||||
return BlockStateValues.JAVA_AIR_ID;
|
||||
}
|
||||
|
||||
return getBlockNetworkId(world.getBlockAt(x, y, z));
|
||||
}
|
||||
|
||||
public int getBlockNetworkId(Block block) {
|
||||
if (SchedulerUtils.FOLIA && !Bukkit.isOwnedByCurrentRegion(block)) {
|
||||
// Terrible behavior, but this is basically what's always been happening behind the scenes anyway.
|
||||
CompletableFuture<String> blockData = new CompletableFuture<>();
|
||||
Bukkit.getRegionScheduler().execute(this.plugin, block.getLocation(), () -> blockData.complete(block.getBlockData().getAsString()));
|
||||
return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(blockData.join(), BlockStateValues.JAVA_AIR_ID);
|
||||
}
|
||||
return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(block.getBlockData().getAsString(), BlockStateValues.JAVA_AIR_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasOwnChunkCache() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLecternData(GeyserSession session, int x, int y, int z) {
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
|
||||
// Run as a task to prevent async issues
|
||||
SchedulerUtils.runTask(this.plugin, () -> sendLecternData(session, block, false), block);
|
||||
}
|
||||
|
||||
public void sendLecternData(GeyserSession session, int x, int z, List<BlockEntityInfo> blockEntityInfos) {
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
|
||||
return;
|
||||
}
|
||||
if (SchedulerUtils.FOLIA) {
|
||||
Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
Bukkit.getRegionScheduler().execute(this.plugin, bukkitPlayer.getWorld(), x, z, () ->
|
||||
sendLecternData(session, chunk, blockEntityInfos));
|
||||
} else {
|
||||
Bukkit.getScheduler().runTask(this.plugin, () -> {
|
||||
Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
sendLecternData(session, chunk, blockEntityInfos);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Chunk getChunk(World world, int x, int z) {
|
||||
if (!world.isChunkLoaded(x, z)) {
|
||||
return null;
|
||||
}
|
||||
return world.getChunkAt(x, z);
|
||||
}
|
||||
|
||||
private void sendLecternData(GeyserSession session, Chunk chunk, List<BlockEntityInfo> blockEntityInfos) {
|
||||
//noinspection ForLoopReplaceableByForEach - avoid constructing Iterator
|
||||
for (int i = 0; i < blockEntityInfos.size(); i++) {
|
||||
BlockEntityInfo info = blockEntityInfos.get(i);
|
||||
Block block = chunk.getBlock(info.getX(), info.getY(), info.getZ());
|
||||
sendLecternData(session, block, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendLecternData(GeyserSession session, Block block, boolean isChunkLoad) {
|
||||
NbtMap blockEntityTag = this.lecterns.getLecternData(block, isChunkLoad);
|
||||
if (blockEntityTag != null) {
|
||||
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, BukkitUtils.getVector(block.getLocation()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpectLecternHandled(GeyserSession session) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
|
||||
org.bukkit.GameRule<?> bukkitGameRule = org.bukkit.GameRule.getByName(gameRule.getJavaID());
|
||||
if (bukkitGameRule == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Unknown game rule " + gameRule.getJavaID());
|
||||
return gameRule.getDefaultBooleanValue();
|
||||
}
|
||||
|
||||
Player bukkitPlayer = Objects.requireNonNull(Bukkit.getPlayer(session.getPlayerEntity().getUuid()));
|
||||
Object value = bukkitPlayer.getWorld().getGameRuleValue(bukkitGameRule);
|
||||
if (value instanceof Boolean booleanValue) {
|
||||
return booleanValue;
|
||||
}
|
||||
GeyserImpl.getInstance().getLogger().debug("Expected a bool for " + gameRule + " but got " + value);
|
||||
return gameRule.getDefaultBooleanValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGameRuleInt(GeyserSession session, GameRule gameRule) {
|
||||
org.bukkit.GameRule<?> bukkitGameRule = org.bukkit.GameRule.getByName(gameRule.getJavaID());
|
||||
if (bukkitGameRule == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Unknown game rule " + gameRule.getJavaID());
|
||||
return gameRule.getDefaultIntValue();
|
||||
}
|
||||
Player bukkitPlayer = Objects.requireNonNull(Bukkit.getPlayer(session.getPlayerEntity().getUuid()));
|
||||
Object value = bukkitPlayer.getWorld().getGameRuleValue(bukkitGameRule);
|
||||
if (value instanceof Integer intValue) {
|
||||
return intValue;
|
||||
}
|
||||
GeyserImpl.getInstance().getLogger().debug("Expected an int for " + gameRule + " but got " + value);
|
||||
return gameRule.getDefaultIntValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameMode getDefaultGameMode(GeyserSession session) {
|
||||
return GameMode.byId(Bukkit.getDefaultGameMode().ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(GeyserSession session, String permission) {
|
||||
Player player = Bukkit.getPlayer(session.javaUuid());
|
||||
if (player != null) {
|
||||
return player.hasPermission(permission);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) {
|
||||
CompletableFuture<@Nullable CompoundTag> future = new CompletableFuture<>();
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
|
||||
future.complete(null);
|
||||
return future;
|
||||
}
|
||||
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
|
||||
// Paper 1.19.3 complains about async access otherwise.
|
||||
// java.lang.IllegalStateException: Tile is null, asynchronous access?
|
||||
SchedulerUtils.runTask(this.plugin, () -> future.complete(PickBlockUtils.pickBlock(block)), block);
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be set to true if we are post-1.13 but before the latest version, and we should convert the old block state id
|
||||
* to the current one.
|
||||
*
|
||||
* @return whether there is a difference between client block state and server block state that requires extra processing
|
||||
*/
|
||||
public boolean isLegacy() {
|
||||
return false;
|
||||
}
|
||||
}
|
16
bootstrap/spigot/base/src/main/resources/plugin.yml
Normal file
16
bootstrap/spigot/base/src/main/resources/plugin.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
main: org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin
|
||||
name: ${name}-Spigot
|
||||
author: ${author}
|
||||
website: ${url}
|
||||
version: ${version}
|
||||
softdepend: ["ViaVersion", "floodgate"]
|
||||
api-version: 1.13
|
||||
folia-supported: true
|
||||
commands:
|
||||
geyser:
|
||||
description: The main command for Geyser.
|
||||
usage: /geyser <subcommand>
|
||||
permission: geyser.command
|
||||
permissions:
|
||||
geyser.command:
|
||||
default: true
|
Loading…
Add table
Add a link
Reference in a new issue