Initial Folia support (#2346)

Co-authored-by: Dan Mulloy <dev@dmulloy2.net>
This commit is contained in:
mani123 2023-06-11 02:08:11 +02:00 committed by GitHub
parent fbf6120876
commit 65a9ef5acf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 198 additions and 88 deletions

View File

@ -27,12 +27,10 @@ import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.metrics.Statistics; import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.updater.Updater; import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType; import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.ByteBuddyFactory; import com.comphenix.protocol.utility.*;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.Util;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet; import java.util.HashSet;
@ -44,6 +42,7 @@ import java.util.logging.LogRecord;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
@ -57,7 +56,6 @@ import org.bukkit.plugin.java.JavaPlugin;
* @author Kristian * @author Kristian
*/ */
public class ProtocolLib extends JavaPlugin { public class ProtocolLib extends JavaPlugin {
// Every possible error or warning report type // Every possible error or warning report type
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType( public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType(
"Cannot delete old ProtocolLib configuration."); "Cannot delete old ProtocolLib configuration.");
@ -490,9 +488,8 @@ public class ProtocolLib extends JavaPlugin {
} }
// Attempt to create task // Attempt to create task
this.packetTask = server.getScheduler().scheduleSyncRepeatingTask(this, () -> { this.packetTask = SchedulerUtil.scheduleSyncRepeatingTask(this, () -> {
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager(); AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
// We KNOW we're on the main thread at the moment // We KNOW we're on the main thread at the moment
manager.sendProcessedPackets(ProtocolLib.this.tickCounter++, true); manager.sendProcessedPackets(ProtocolLib.this.tickCounter++, true);
@ -567,7 +564,7 @@ public class ProtocolLib extends JavaPlugin {
// Clean up // Clean up
if (this.packetTask >= 0) { if (this.packetTask >= 0) {
this.getServer().getScheduler().cancelTask(this.packetTask); SchedulerUtil.cancelTask(this, packetTask);
this.packetTask = -1; this.packetTask = -1;
} }

View File

@ -2,21 +2,45 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * the License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * See the GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License along with this program; * You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.events; package com.comphenix.protocol.events;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.Util;
import net.bytebuddy.description.ByteCodeElement;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import org.bukkit.*;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.profile.PlayerProfile;
import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
@ -28,33 +52,9 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import org.bukkit.*;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.profile.PlayerProfile;
import net.bytebuddy.description.ByteCodeElement;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
/** /**
* Represents a player object that can be serialized by Java. * Represents a player object that can be serialized by Java.
* *
* @author Kristian * @author Kristian
*/ */
class SerializedOfflinePlayer implements OfflinePlayer, Serializable { class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
@ -65,7 +65,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
private static final long serialVersionUID = -2728976288470282810L; private static final long serialVersionUID = -2728976288470282810L;
private transient Location bedSpawnLocation; private transient Location bedSpawnLocation;
// Relevant data about an offline player // Relevant data about an offline player
private String name; private String name;
private UUID uuid; private UUID uuid;
@ -76,6 +76,8 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
private boolean playedBefore; private boolean playedBefore;
private boolean online; private boolean online;
private boolean whitelisted; private boolean whitelisted;
private long lastLogin;
private long lastSeen;
private static final Constructor<?> proxyPlayerConstructor = setupProxyPlayerConstructor(); private static final Constructor<?> proxyPlayerConstructor = setupProxyPlayerConstructor();
@ -85,9 +87,10 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
public SerializedOfflinePlayer() { public SerializedOfflinePlayer() {
// Do nothing // Do nothing
} }
/** /**
* Initialize this serializable offline player from another player. * Initialize this serializable offline player from another player.
*
* @param offline - another player. * @param offline - another player.
*/ */
public SerializedOfflinePlayer(OfflinePlayer offline) { public SerializedOfflinePlayer(OfflinePlayer offline) {
@ -100,8 +103,14 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
this.playedBefore = offline.hasPlayedBefore(); this.playedBefore = offline.hasPlayedBefore();
this.online = offline.isOnline(); this.online = offline.isOnline();
this.whitelisted = offline.isWhitelisted(); this.whitelisted = offline.isWhitelisted();
// TODO needs to be reflectively obtained
if (Util.isUsingFolia()) {
// this.lastSeen = offline.getLastSeen();
// this.lastLogin = offline.getLastLogin();
}
} }
@Override @Override
public boolean isOp() { public boolean isOp() {
return operator; return operator;
@ -122,49 +131,74 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return bedSpawnLocation; return bedSpawnLocation;
} }
// @Override
public long getLastLogin() {
return lastLogin;
}
// @Override
public long getLastSeen() {
return lastSeen;
}
// TODO do we need to implement this? // TODO do we need to implement this?
public void incrementStatistic(Statistic statistic) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic) throws IllegalArgumentException { } public void incrementStatistic(Statistic statistic) throws IllegalArgumentException {
}
public void incrementStatistic(Statistic statistic, int i) throws IllegalArgumentException { } public void decrementStatistic(Statistic statistic) throws IllegalArgumentException {
}
public void decrementStatistic(Statistic statistic, int i) throws IllegalArgumentException { } public void incrementStatistic(Statistic statistic, int i) throws IllegalArgumentException {
}
public void setStatistic(Statistic statistic, int i) throws IllegalArgumentException { } public void decrementStatistic(Statistic statistic, int i) throws IllegalArgumentException {
}
public void setStatistic(Statistic statistic, int i) throws IllegalArgumentException {
}
public int getStatistic(Statistic statistic) throws IllegalArgumentException { public int getStatistic(Statistic statistic) throws IllegalArgumentException {
return 0; return 0;
} }
public void incrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { } public void incrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException {
}
public void decrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { } public void decrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException {
}
public int getStatistic(Statistic statistic, Material material) throws IllegalArgumentException { public int getStatistic(Statistic statistic, Material material) throws IllegalArgumentException {
return 0; return 0;
} }
public void incrementStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException { } public void incrementStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException {
}
public void decrementStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException { } public void decrementStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException {
}
public void setStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException { } public void setStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException {
}
public void incrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { } public void incrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException {
}
public void decrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { } public void decrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException {
}
public int getStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { public int getStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException {
return 0; return 0;
} }
public void incrementStatistic(Statistic statistic, EntityType entityType, int i) throws IllegalArgumentException { } public void incrementStatistic(Statistic statistic, EntityType entityType, int i) throws IllegalArgumentException {
}
public void decrementStatistic(Statistic statistic, EntityType entityType, int i) { } public void decrementStatistic(Statistic statistic, EntityType entityType, int i) {
}
public void setStatistic(Statistic statistic, EntityType entityType, int i) { } public void setStatistic(Statistic statistic, EntityType entityType, int i) {
}
@Override @Override
public Location getLastDeathLocation() { public Location getLastDeathLocation() {
@ -187,7 +221,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
} }
@Override @Override
public PlayerProfile getPlayerProfile() { public @NotNull PlayerProfile getPlayerProfile() {
return null; return null;
} }
@ -195,7 +229,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
public String getName() { public String getName() {
return name; return name;
} }
@Override @Override
public boolean hasPlayedBefore() { public boolean hasPlayedBefore() {
return playedBefore; return playedBefore;
@ -209,7 +243,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
public void setBanned(boolean banned) { public void setBanned(boolean banned) {
this.banned = banned; this.banned = banned;
} }
@Override @Override
public boolean isOnline() { public boolean isOnline() {
return online; return online;
@ -227,14 +261,14 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
private void writeObject(ObjectOutputStream output) throws IOException { private void writeObject(ObjectOutputStream output) throws IOException {
output.defaultWriteObject(); output.defaultWriteObject();
// Serialize the bed spawn location // Serialize the bed spawn location
output.writeUTF(bedSpawnLocation.getWorld().getName()); output.writeUTF(bedSpawnLocation.getWorld().getName());
output.writeDouble(bedSpawnLocation.getX()); output.writeDouble(bedSpawnLocation.getX());
output.writeDouble(bedSpawnLocation.getY()); output.writeDouble(bedSpawnLocation.getY());
output.writeDouble(bedSpawnLocation.getZ()); output.writeDouble(bedSpawnLocation.getZ());
} }
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException { private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
input.defaultReadObject(); input.defaultReadObject();
@ -246,7 +280,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
input.readDouble() input.readDouble()
); );
} }
private World getWorld(String name) { private World getWorld(String name) {
try { try {
// Try to get the world at least // Try to get the world at least
@ -256,7 +290,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return null; return null;
} }
} }
@Override @Override
public Player getPlayer() { public Player getPlayer() {
try { try {
@ -266,11 +300,12 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return getProxyPlayer(); return getProxyPlayer();
} }
} }
/** /**
* Retrieve a player object that implements OfflinePlayer by referring to this object. * Retrieve a player object that implements OfflinePlayer by referring to this object.
* <p> * <p>
* All other methods cause an exception. * All other methods cause an exception.
*
* @return Proxy object. * @return Proxy object.
*/ */
public Player getProxyPlayer() { public Player getProxyPlayer() {
@ -285,8 +320,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
} }
} }
private static Constructor<? extends Player> setupProxyPlayerConstructor() private static Constructor<? extends Player> setupProxyPlayerConstructor() {
{
final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods(); final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
final String[] methodNames = new String[offlinePlayerMethods.length]; final String[] methodNames = new String[offlinePlayerMethods.length];
for (int idx = 0; idx < offlinePlayerMethods.length; ++idx) for (int idx = 0; idx < offlinePlayerMethods.length; ++idx)
@ -321,9 +355,9 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
}); });
final InvocationHandlerAdapter throwException = InvocationHandlerAdapter.of((obj, method, args) -> { final InvocationHandlerAdapter throwException = InvocationHandlerAdapter.of((obj, method, args) -> {
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for offline players."); "The method " + method.getName() + " is not supported for offline players.");
}); });
return ByteBuddyFactory.getInstance() return ByteBuddyFactory.getInstance()
.createSubclass(PlayerUnion.class, ConstructorStrategy.Default.NO_CONSTRUCTORS) .createSubclass(PlayerUnion.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
@ -354,7 +388,6 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
* This interface extends both OfflinePlayer and Player (in that order) so that the class generated by ByteBuddy * This interface extends both OfflinePlayer and Player (in that order) so that the class generated by ByteBuddy
* looks at OfflinePlayer's methods first while still being a Player. * looks at OfflinePlayer's methods first while still being a Player.
*/ */
private interface PlayerUnion extends OfflinePlayer, Player private interface PlayerUnion extends OfflinePlayer, Player {
{
} }
} }

View File

@ -1,24 +1,25 @@
/** /**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2 * Copyright (C) 2015 dmulloy2
* * <p>
* This program is free software; you can redistribute it and/or modify it under the terms of the * This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * the License, or (at your option) any later version.
* * <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * See the GNU General Public License for more details.
* * <p>
* You should have received a copy of the GNU General Public License along with this program; * You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.updater; package com.comphenix.protocol.updater;
import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.utility.Closer; import com.comphenix.protocol.utility.Closer;
import com.comphenix.protocol.utility.SchedulerUtil;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -42,7 +43,7 @@ public final class SpigotUpdater extends Updater {
@Override @Override
public void start(UpdateType type) { public void start(UpdateType type) {
waitForThread(); waitForThread();
this.type = type; this.type = type;
this.thread = new Thread(new SpigotUpdateRunnable()); this.thread = new Thread(new SpigotUpdateRunnable());
this.thread.start(); this.thread.start();
@ -80,7 +81,7 @@ public final class SpigotUpdater extends Updater {
} finally { } finally {
// Invoke the listeners on the main thread // Invoke the listeners on the main thread
for (Runnable listener : listeners) { for (Runnable listener : listeners) {
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, listener); SchedulerUtil.execute(plugin, listener);
} }
} }
} }

View File

@ -0,0 +1,72 @@
package com.comphenix.protocol.utility;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import java.util.function.Consumer;
public class SchedulerUtil {
private Object foliaScheduler;
private MethodAccessor runAtFixedRate;
private MethodAccessor cancelTasks;
private MethodAccessor execute;
private static SchedulerUtil getInstance() {
return Holder.INSTANCE;
}
private static class Holder {
private static final SchedulerUtil INSTANCE = new SchedulerUtil();
}
private SchedulerUtil() {
if (Util.isUsingFolia()) {
MethodAccessor getScheduler = Accessors.getMethodAccessor(Bukkit.getServer().getClass(), "getGlobalRegionScheduler");
foliaScheduler = getScheduler.invoke(Bukkit.getServer());
runAtFixedRate = Accessors.getMethodAccessor(foliaScheduler.getClass(), "runAtFixedRate", Plugin.class,
Consumer.class, long.class, long.class);
cancelTasks = Accessors.getMethodAccessor(foliaScheduler.getClass(), "cancelTasks", Plugin.class);
execute = Accessors.getMethodAccessor(foliaScheduler.getClass(), "execute", Plugin.class, Runnable.class);
}
}
public static int scheduleSyncRepeatingTask(Plugin plugin, Runnable runnable, long delay, long period) {
return getInstance().doScheduleSyncRepeatingTask(plugin, runnable, delay, period);
}
private int doScheduleSyncRepeatingTask(Plugin plugin, Runnable runnable, long delay, long period) {
if (Util.isUsingFolia()) {
runAtFixedRate.invoke(foliaScheduler, plugin, (Consumer<Object>)(task -> runnable.run()), delay, period);
return 1;
} else {
return plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, runnable, delay, period);
}
}
private void doCancelTask(Plugin plugin, int id) {
if (Util.isUsingFolia()) {
cancelTasks.invoke(foliaScheduler, plugin);
} else {
plugin.getServer().getScheduler().cancelTask(id);
}
}
public static void cancelTask(Plugin plugin, int id) {
getInstance().doCancelTask(plugin, id);
}
private void doExecute(Plugin plugin, Runnable runnable) {
if (Util.isUsingFolia()) {
execute.invoke(foliaScheduler, plugin, runnable);
} else {
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, runnable);
}
}
public static void execute(Plugin plugin, Runnable runnable) {
getInstance().doExecute(plugin, runnable);
}
}

View File

@ -22,6 +22,7 @@ package com.comphenix.protocol.utility;
public final class Util { public final class Util {
private static final boolean SPIGOT = classExists("org.spigotmc.SpigotConfig"); private static final boolean SPIGOT = classExists("org.spigotmc.SpigotConfig");
private static final boolean FOLIA = classExists("io.papermc.paper.threadedregions.RegionizedServer");
private static Class<?> cachedBundleClass; private static Class<?> cachedBundleClass;
public static boolean classExists(String className) { public static boolean classExists(String className) {
@ -43,6 +44,10 @@ public final class Util {
return SPIGOT; return SPIGOT;
} }
public static boolean isUsingFolia() {
return FOLIA;
}
/** /**
* Checks if the server is getting reloaded by walking down the current thread stack trace. * Checks if the server is getting reloaded by walking down the current thread stack trace.
* *

View File

@ -8,6 +8,8 @@ load: STARTUP
database: false database: false
api-version: "1.13" api-version: "1.13"
folia-supported: true
commands: commands:
protocol: protocol:
description: Performs administrative tasks regarding ProtocolLib. description: Performs administrative tasks regarding ProtocolLib.