diff --git a/.github/workflows/build-master.yml b/.github/workflows/build-master.yml index e503a9763ab..59e72511ce2 100644 --- a/.github/workflows/build-master.yml +++ b/.github/workflows/build-master.yml @@ -4,6 +4,7 @@ on: push: branches: - 2.x + - dev/* jobs: build: diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 1ed0c24e771..e8b9458f50b 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -9,6 +9,7 @@ on: branches: - 2.x - mc/* + - dev/* jobs: build: diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 39f0b0ad4b3..de8c1c27dc3 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -746,7 +746,7 @@ public boolean onCommandEssentials(final CommandSender cSender, final Command co if (getSettings().isCommandDisabled(commandLabel)) { if (getKnownCommandsProvider().getKnownCommands().containsKey(commandLabel)) { final Command newCmd = getKnownCommandsProvider().getKnownCommands().get(commandLabel); - if (!(newCmd instanceof PluginIdentifiableCommand) || ((PluginIdentifiableCommand) newCmd).getPlugin() != this) { + if (!(newCmd instanceof PluginIdentifiableCommand) || !isEssentialsPlugin(((PluginIdentifiableCommand) newCmd).getPlugin())) { return newCmd.execute(cSender, commandLabel, args); } } @@ -821,6 +821,10 @@ public boolean onCommandEssentials(final CommandSender cSender, final Command co } } + private boolean isEssentialsPlugin(Plugin plugin) { + return plugin.getDescription().getMain().contains("com.earth2me.essentials") || plugin.getDescription().getMain().contains("net.essentialsx"); + } + public void cleanupOpenInventories() { for (final User user : getOnlineUsers()) { if (user.isRecipeSee()) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsConf.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsConf.java deleted file mode 100644 index 611fe918972..00000000000 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsConf.java +++ /dev/null @@ -1,674 +0,0 @@ -package com.earth2me.essentials; - -import com.google.common.io.Files; -import net.ess3.api.InvalidWorldException; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.Server; -import org.bukkit.World; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.math.BigDecimal; -import java.math.MathContext; -import java.nio.Buffer; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CoderResult; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static com.earth2me.essentials.I18n.tl; - -public class EssentialsConf extends YamlConfiguration { - protected static final Logger LOGGER = Logger.getLogger("Essentials"); - protected static final Charset UTF8 = StandardCharsets.UTF_8; - private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor(); - protected final File configFile; - private final AtomicInteger pendingDiskWrites = new AtomicInteger(0); - private final AtomicBoolean transaction = new AtomicBoolean(false); - private final byte[] bytebuffer = new byte[1024]; - protected String templateName = null; - private Class resourceClass = EssentialsConf.class; - - public EssentialsConf(final File configFile) { - super(); - this.configFile = configFile.getAbsoluteFile(); - } - - public static BigDecimal toBigDecimal(final String input, final BigDecimal def) { - if (input == null || input.isEmpty()) { - return def; - } else { - try { - return new BigDecimal(input, MathContext.DECIMAL128); - } catch (final NumberFormatException | ArithmeticException e) { - return def; - } - } - } - - public synchronized void load() { - if (pendingDiskWrites.get() != 0) { - LOGGER.log(Level.INFO, "File {0} not read, because it''s not yet written to disk.", configFile); - return; - } - if (!configFile.getParentFile().exists()) { - if (!configFile.getParentFile().mkdirs()) { - LOGGER.log(Level.SEVERE, tl("failedToCreateConfig", configFile.toString())); - } - } - // This will delete files where the first character is 0. In most cases they are broken. - if (configFile.exists() && configFile.length() != 0) { - try { - final InputStream input = new FileInputStream(configFile); - try { - if (input.read() == 0) { - input.close(); - configFile.delete(); - } - } catch (final IOException ex) { - LOGGER.log(Level.SEVERE, null, ex); - } finally { - try { - input.close(); - } catch (final IOException ex) { - LOGGER.log(Level.SEVERE, null, ex); - } - } - } catch (final FileNotFoundException ex) { - LOGGER.log(Level.SEVERE, null, ex); - } - } - - if (!configFile.exists()) { - if (legacyFileExists()) { - convertLegacyFile(); - } else if (altFileExists()) { - convertAltFile(); - } else if (templateName != null) { - LOGGER.log(Level.INFO, tl("creatingConfigFromTemplate", configFile.toString())); - createFromTemplate(); - } else { - return; - } - } - - try { - try (final FileInputStream inputStream = new FileInputStream(configFile)) { - final long startSize = configFile.length(); - if (startSize > Integer.MAX_VALUE) { - throw new InvalidConfigurationException("File too big"); - } - ByteBuffer buffer = ByteBuffer.allocate((int) startSize); - int length; - while ((length = inputStream.read(bytebuffer)) != -1) { - if (length > buffer.remaining()) { - final ByteBuffer resize = ByteBuffer.allocate(buffer.capacity() + length - buffer.remaining()); - final int resizePosition = buffer.position(); - // Fix builds compiled against Java 9+ breaking on Java 8 - ((Buffer) buffer).rewind(); - resize.put(buffer); - resize.position(resizePosition); - buffer = resize; - } - buffer.put(bytebuffer, 0, length); - } - ((Buffer) buffer).rewind(); - final CharBuffer data = CharBuffer.allocate(buffer.capacity()); - CharsetDecoder decoder = UTF8.newDecoder(); - CoderResult result = decoder.decode(buffer, data, true); - if (result.isError()) { - ((Buffer) buffer).rewind(); - ((Buffer) data).clear(); - LOGGER.log(Level.INFO, "File " + configFile.getAbsolutePath() + " is not utf-8 encoded, trying " + Charset.defaultCharset().displayName()); - decoder = Charset.defaultCharset().newDecoder(); - result = decoder.decode(buffer, data, true); - if (result.isError()) { - throw new InvalidConfigurationException("Invalid Characters in file " + configFile.getAbsolutePath()); - } else { - decoder.flush(data); - } - } else { - decoder.flush(data); - } - final int end = data.position(); - ((Buffer) data).rewind(); - super.loadFromString(data.subSequence(0, end).toString()); - } - } catch (final IOException ex) { - LOGGER.log(Level.SEVERE, ex.getMessage(), ex); - } catch (final InvalidConfigurationException ex) { - final File broken = new File(configFile.getAbsolutePath() + ".broken." + System.currentTimeMillis()); - configFile.renameTo(broken); - LOGGER.log(Level.SEVERE, "The file " + configFile.toString() + " is broken, it has been renamed to " + broken.toString(), ex.getCause()); - } - } - - public boolean legacyFileExists() { - return false; - } - - public void convertLegacyFile() { - LOGGER.log(Level.SEVERE, "Unable to import legacy config file."); - } - - public boolean altFileExists() { - return false; - } - - public void convertAltFile() { - LOGGER.log(Level.SEVERE, "Unable to import alt config file."); - } - - private void createFromTemplate() { - InputStream istr = null; - OutputStream ostr = null; - try { - istr = resourceClass.getResourceAsStream(templateName); - if (istr == null) { - LOGGER.log(Level.SEVERE, tl("couldNotFindTemplate", templateName)); - return; - } - ostr = new FileOutputStream(configFile); - final byte[] buffer = new byte[1024]; - int length = 0; - length = istr.read(buffer); - while (length > 0) { - ostr.write(buffer, 0, length); - length = istr.read(buffer); - } - } catch (final IOException ex) { - LOGGER.log(Level.SEVERE, tl("failedToWriteConfig", configFile.toString()), ex); - } finally { - try { - if (istr != null) { - istr.close(); - } - } catch (final IOException ex) { - Logger.getLogger(EssentialsConf.class.getName()).log(Level.SEVERE, null, ex); - } - try { - if (ostr != null) { - ostr.close(); - } - } catch (final IOException ex) { - LOGGER.log(Level.SEVERE, tl("failedToCloseConfig", configFile.toString()), ex); - } - } - } - - public void setTemplateName(final String templateName) { - this.templateName = templateName; - } - - public File getFile() { - return configFile; - } - - public void setTemplateName(final String templateName, final Class resClass) { - this.templateName = templateName; - this.resourceClass = resClass; - } - - public void startTransaction() { - transaction.set(true); - } - - public void stopTransaction() { - transaction.set(false); - save(); - } - - public void save() { - try { - save(configFile); - } catch (final IOException ex) { - LOGGER.log(Level.SEVERE, ex.getMessage(), ex); - } - } - - public void saveWithError() throws IOException { - save(configFile); - } - - @Override - public synchronized void save(final File file) throws IOException { - if (!transaction.get()) { - delayedSave(file); - } - } - - //This may be aborted if there are stagnant requests sitting in queue. - //This needs fixed to discard outstanding save requests. - public synchronized void forceSave() { - try { - final Future future = delayedSave(configFile); - if (future != null) { - future.get(); - } - } catch (final InterruptedException | ExecutionException ex) { - LOGGER.log(Level.SEVERE, ex.getMessage(), ex); - } - } - - public synchronized void cleanup() { - forceSave(); - } - - private Future delayedSave(final File file) { - if (file == null) { - throw new IllegalArgumentException("File cannot be null"); - } - - final String data = saveToString(); - - if (data.length() == 0) { - return null; - } - - pendingDiskWrites.incrementAndGet(); - - return EXECUTOR_SERVICE.submit(new WriteRunner(configFile, data, pendingDiskWrites)); - } - - public boolean hasProperty(final String path) { - return isSet(path); - } - - public Location getLocation(final String path, final Server server) throws InvalidWorldException { - final String worldString = (path == null ? "" : path + ".") + "world"; - final String worldName = getString(worldString); - if (worldName == null || worldName.isEmpty()) { - return null; - } - final World world = server.getWorld(worldName); - if (world == null) { - throw new InvalidWorldException(worldName); - } - return new Location(world, getDouble((path == null ? "" : path + ".") + "x", 0), getDouble((path == null ? "" : path + ".") + "y", 0), getDouble((path == null ? "" : path + ".") + "z", 0), (float) getDouble((path == null ? "" : path + ".") + "yaw", 0), (float) getDouble((path == null ? "" : path + ".") + "pitch", 0)); - } - - public void setProperty(final String path, final Location loc) { - set((path == null ? "" : path + ".") + "world", loc.getWorld().getName()); - set((path == null ? "" : path + ".") + "x", loc.getX()); - set((path == null ? "" : path + ".") + "y", loc.getY()); - set((path == null ? "" : path + ".") + "z", loc.getZ()); - set((path == null ? "" : path + ".") + "yaw", loc.getYaw()); - set((path == null ? "" : path + ".") + "pitch", loc.getPitch()); - } - - @Override - public ItemStack getItemStack(final String path) { - final ItemStack stack = new ItemStack(Material.valueOf(getString(path + ".type", "AIR")), getInt(path + ".amount", 1), (short) getInt(path + ".damage", 0)); - final ConfigurationSection enchants = getConfigurationSection(path + ".enchant"); - if (enchants != null) { - for (final String enchant : enchants.getKeys(false)) { - final Enchantment enchantment = Enchantment.getByName(enchant.toUpperCase(Locale.ENGLISH)); - if (enchantment == null) { - continue; - } - final int level = getInt(path + ".enchant." + enchant, enchantment.getStartLevel()); - stack.addUnsafeEnchantment(enchantment, level); - } - } - return stack; - /* - * , - * (byte)getInt(path + ".data", 0) - */ - } - - public void setProperty(final String path, final ItemStack stack) { - final Map map = new HashMap<>(); - map.put("type", stack.getType().toString()); - map.put("amount", stack.getAmount()); - map.put("damage", stack.getDurability()); - final Map enchantments = stack.getEnchantments(); - if (!enchantments.isEmpty()) { - final Map enchant = new HashMap<>(); - for (final Map.Entry entry : enchantments.entrySet()) { - enchant.put(entry.getKey().getName().toLowerCase(Locale.ENGLISH), entry.getValue()); - } - map.put("enchant", enchant); - } - // getData().getData() is broken - //map.put("data", stack.getDurability()); - set(path, map); - } - - public void setProperty(final String path, final List object) { - set(path, new ArrayList(object)); - } - - public void setProperty(final String path, final Map object) { - set(path, new LinkedHashMap(object)); - } - - public Object getProperty(final String path) { - return get(path); - } - - public void setProperty(final String path, final BigDecimal bigDecimal) { - set(path, bigDecimal.toString()); - } - - public void setProperty(final String path, final Object object) { - set(path, object); - } - - public void removeProperty(final String path) { - set(path, null); - } - - @Override - public synchronized Object get(final String path) { - return super.get(path); - } - - @Override - public synchronized Object get(final String path, final Object def) { - return super.get(path, def); - } - - public synchronized BigDecimal getBigDecimal(final String path, final BigDecimal def) { - final String input = super.getString(path); - return toBigDecimal(input, def); - } - - @Override - public synchronized boolean getBoolean(final String path) { - return super.getBoolean(path); - } - - @Override - public synchronized boolean getBoolean(final String path, final boolean def) { - return super.getBoolean(path, def); - } - - @Override - public synchronized List getBooleanList(final String path) { - return super.getBooleanList(path); - } - - @Override - public synchronized List getByteList(final String path) { - return super.getByteList(path); - } - - @Override - public synchronized List getCharacterList(final String path) { - return super.getCharacterList(path); - } - - @Override - public synchronized ConfigurationSection getConfigurationSection(final String path) { - return super.getConfigurationSection(path); - } - - @Override - public synchronized double getDouble(final String path) { - return super.getDouble(path); - } - - @Override - public synchronized double getDouble(final String path, final double def) { - return super.getDouble(path, def); - } - - @Override - public synchronized List getDoubleList(final String path) { - return super.getDoubleList(path); - } - - @Override - public synchronized List getFloatList(final String path) { - return super.getFloatList(path); - } - - @Override - public synchronized int getInt(final String path) { - return super.getInt(path); - } - - @Override - public synchronized int getInt(final String path, final int def) { - return super.getInt(path, def); - } - - @Override - public synchronized List getIntegerList(final String path) { - return super.getIntegerList(path); - } - - @Override - public synchronized ItemStack getItemStack(final String path, final ItemStack def) { - return super.getItemStack(path, def); - } - - @Override - public synchronized Set getKeys(final boolean deep) { - return super.getKeys(deep); - } - - @Override - public synchronized List getList(final String path) { - return super.getList(path); - } - - @Override - public synchronized List getList(final String path, final List def) { - return super.getList(path, def); - } - - @Override - public synchronized long getLong(final String path) { - return super.getLong(path); - } - - @Override - public synchronized long getLong(final String path, final long def) { - return super.getLong(path, def); - } - - @Override - public synchronized List getLongList(final String path) { - return super.getLongList(path); - } - - public synchronized Map getMap() { - return map; - } - - @Override - public synchronized List> getMapList(final String path) { - return super.getMapList(path); - } - - @Override - public synchronized OfflinePlayer getOfflinePlayer(final String path) { - return super.getOfflinePlayer(path); - } - - @Override - public synchronized OfflinePlayer getOfflinePlayer(final String path, final OfflinePlayer def) { - return super.getOfflinePlayer(path, def); - } - - @Override - public synchronized List getShortList(final String path) { - return super.getShortList(path); - } - - @Override - public synchronized String getString(final String path) { - return super.getString(path); - } - - @Override - public synchronized String getString(final String path, final String def) { - return super.getString(path, def); - } - - @Override - public synchronized List getStringList(final String path) { - return super.getStringList(path); - } - - @Override - public synchronized Map getValues(final boolean deep) { - return super.getValues(deep); - } - - @Override - public synchronized Vector getVector(final String path) { - return super.getVector(path); - } - - @Override - public synchronized Vector getVector(final String path, final Vector def) { - return super.getVector(path, def); - } - - @Override - public synchronized boolean isBoolean(final String path) { - return super.isBoolean(path); - } - - @Override - public synchronized boolean isConfigurationSection(final String path) { - return super.isConfigurationSection(path); - } - - @Override - public synchronized boolean isDouble(final String path) { - return super.isDouble(path); - } - - @Override - public synchronized boolean isInt(final String path) { - return super.isInt(path); - } - - @Override - public synchronized boolean isItemStack(final String path) { - return super.isItemStack(path); - } - - @Override - public synchronized boolean isList(final String path) { - return super.isList(path); - } - - @Override - public synchronized boolean isLong(final String path) { - return super.isLong(path); - } - - @Override - public synchronized boolean isOfflinePlayer(final String path) { - return super.isOfflinePlayer(path); - } - - @Override - public synchronized boolean isSet(final String path) { - return super.isSet(path); - } - - @Override - public synchronized boolean isString(final String path) { - return super.isString(path); - } - - @Override - public synchronized boolean isVector(final String path) { - return super.isVector(path); - } - - @Override - public synchronized void set(final String path, final Object value) { - super.set(path, value); - } - - private static final class WriteRunner implements Runnable { - private final File configFile; - private final String data; - private final AtomicInteger pendingDiskWrites; - - private WriteRunner(final File configFile, final String data, final AtomicInteger pendingDiskWrites) { - this.configFile = configFile; - this.data = data; - this.pendingDiskWrites = pendingDiskWrites; - } - - @Override - public void run() { - //long startTime = System.nanoTime(); - synchronized (configFile) { - if (pendingDiskWrites.get() > 1) { - // Writes can be skipped, because they are stored in a queue (in the executor). - // Only the last is actually written. - pendingDiskWrites.decrementAndGet(); - //LOGGER.log(Level.INFO, configFile + " skipped writing in " + (System.nanoTime() - startTime) + " nsec."); - return; - } - try { - Files.createParentDirs(configFile); - - if (!configFile.exists()) { - try { - LOGGER.log(Level.INFO, tl("creatingEmptyConfig", configFile.toString())); - if (!configFile.createNewFile()) { - LOGGER.log(Level.SEVERE, tl("failedToCreateConfig", configFile.toString())); - return; - } - } catch (final IOException ex) { - LOGGER.log(Level.SEVERE, tl("failedToCreateConfig", configFile.toString()), ex); - return; - } - } - - try (final FileOutputStream fos = new FileOutputStream(configFile)) { - try (final OutputStreamWriter writer = new OutputStreamWriter(fos, UTF8)) { - writer.write(data); - } - } - } catch (final IOException e) { - LOGGER.log(Level.SEVERE, e.getMessage(), e); - } finally { - //LOGGER.log(Level.INFO, configFile + " written to disk in " + (System.nanoTime() - startTime) + " nsec."); - pendingDiskWrites.decrementAndGet(); - } - } - } - } -} diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java index 0fce2a5bf15..1a1de2a73e0 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java @@ -540,7 +540,7 @@ public void onPlayerTeleport(final PlayerTeleportEvent event) { return; } final User user = ess.getUser(player); - if (ess.getSettings().registerBackInListener() && user.isAuthorized("essentials.back.onteleport") && !player.hasMetadata("ess_ignore_teleport")) { + if (ess.getSettings().registerBackInListener() && user.isAuthorized("essentials.back.onteleport")) { user.setLastLocation(); } if (ess.getSettings().isTeleportInvulnerability()) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUserConf.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUserConf.java deleted file mode 100644 index 3ddf0852023..00000000000 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUserConf.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.earth2me.essentials; - -import com.google.common.base.Charsets; -import com.google.common.io.Files; -import org.bukkit.Bukkit; - -import java.io.File; -import java.io.IOException; -import java.util.Locale; -import java.util.UUID; -import java.util.logging.Level; - -public class EssentialsUserConf extends EssentialsConf { - public String username; - public final UUID uuid; - - public EssentialsUserConf(final String username, final UUID uuid, final File configFile) { - super(configFile); - this.username = username; - this.uuid = uuid; - } - - public void setUsername(String username) { - this.username = username; - } - - @Override - public boolean legacyFileExists() { - final File file = new File(configFile.getParentFile(), username + ".yml"); - return file.exists(); - } - - @Override - public void convertLegacyFile() { - final File file = new File(configFile.getParentFile(), username + ".yml"); - try { - Files.move(file, new File(configFile.getParentFile(), uuid + ".yml")); - } catch (final IOException ex) { - Bukkit.getLogger().log(Level.WARNING, "Failed to migrate user: " + username, ex); - } - - setProperty("lastAccountName", username); - } - - private File getAltFile() { - final UUID fn = UUID.nameUUIDFromBytes(("OfflinePlayer:" + username.toLowerCase(Locale.ENGLISH)).getBytes(Charsets.UTF_8)); - return new File(configFile.getParentFile(), fn.toString() + ".yml"); - } - - @Override - public boolean altFileExists() { - if (username.equals(username.toLowerCase())) { - return false; - } - return getAltFile().exists(); - } - - @Override - public void convertAltFile() { - try { - Files.move(getAltFile(), new File(configFile.getParentFile(), uuid + ".yml")); - } catch (final IOException ex) { - Bukkit.getLogger().log(Level.WARNING, "Failed to migrate user: " + username, ex); - } - } -} diff --git a/Essentials/src/main/java/com/earth2me/essentials/utils/LocationUtil.java b/Essentials/src/main/java/com/earth2me/essentials/utils/LocationUtil.java index f25d6277e63..ae84c2aa43c 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/utils/LocationUtil.java +++ b/Essentials/src/main/java/com/earth2me/essentials/utils/LocationUtil.java @@ -1,7 +1,6 @@ package com.earth2me.essentials.utils; import com.earth2me.essentials.IEssentials; -import com.google.common.primitives.Ints; import net.ess3.api.IUser; import org.bukkit.GameMode; import org.bukkit.Location; @@ -232,12 +231,12 @@ public static Location getSafeDestination(IEssentials ess, final Location loc) t i++; if (i >= VOLUME.length) { x = origX; - y = Ints.constrainToRange(origY + RADIUS, worldMinY, worldMaxY); + y = NumberUtil.constrainToRange(origY + RADIUS, worldMinY, worldMaxY); z = origZ; break; } x = origX + VOLUME[i].x; - y = Ints.constrainToRange(origY + VOLUME[i].y, worldMinY, worldMaxY); + y = NumberUtil.constrainToRange(origY + VOLUME[i].y, worldMinY, worldMaxY); z = origZ + VOLUME[i].z; } while (isBlockUnsafe(ess, world, x, y, z)) { @@ -267,6 +266,7 @@ public static boolean shouldFly(IEssentials ess, final Location loc) { int y = (int) Math.round(loc.getY()); final int z = loc.getBlockZ(); int count = 0; + // Check whether more than 2 unsafe block are below player. while (LocationUtil.isBlockUnsafe(ess, world, x, y, z) && y >= ess.getWorldInfoProvider().getMinHeight(world)) { y--; count++; @@ -275,7 +275,8 @@ public static boolean shouldFly(IEssentials ess, final Location loc) { } } - return y < 0; + // If not then check if player is in the void + return y < ess.getWorldInfoProvider().getMinHeight(world); } public static class Vector3D { diff --git a/Essentials/src/main/java/com/earth2me/essentials/utils/NumberUtil.java b/Essentials/src/main/java/com/earth2me/essentials/utils/NumberUtil.java index c9d8249140e..e1d376b7e8d 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/utils/NumberUtil.java +++ b/Essentials/src/main/java/com/earth2me/essentials/utils/NumberUtil.java @@ -119,4 +119,11 @@ public static boolean isPositiveInt(final String sInt) { } return Integer.parseInt(sInt) > 0; } + + /** + * Backport from Guava. + */ + public static int constrainToRange(int value, int min, int max) { + return Math.min(Math.max(value, min), max); + } } diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/events/discord/DiscordRelayEvent.java b/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/events/discord/DiscordRelayEvent.java new file mode 100644 index 00000000000..565202b061c --- /dev/null +++ b/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/events/discord/DiscordRelayEvent.java @@ -0,0 +1,111 @@ +package net.essentialsx.api.v2.events.discord; + +import net.essentialsx.api.v2.services.discord.InteractionChannel; +import net.essentialsx.api.v2.services.discord.InteractionMember; +import org.bukkit.Bukkit; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * Fired before a message is relayed to Minecraft. + *

+ * Note: This event has no guarantee of the thread it is fired on, please use {@link #isAsynchronous()}} to see if this event is off the main Bukkit thread. + */ +public class DiscordRelayEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final InteractionMember member; + private final InteractionChannel channel; + private final List groupNames; + private final String rawMessage; + private String formattedMessage; + private boolean cancelled = false; + + /** + * @param member The member that sent the message. + * @param channel The channel the message was sent in. + * @param groupNames The message type keys which will be used to determine which player group the message should be sent to. + * @param rawMessage The raw message sent from Discord. + * @param formattedMessage The formatted message that will be sent to Minecraft. + */ + public DiscordRelayEvent(final InteractionMember member, final InteractionChannel channel, final List groupNames, final String rawMessage, final String formattedMessage) { + super(!Bukkit.isPrimaryThread()); + this.member = member; + this.channel = channel; + this.groupNames = groupNames; + this.rawMessage = rawMessage; + this.formattedMessage = formattedMessage; + } + + /** + * Gets the Discord member that sent the message. + * @return The member that sent the message. + */ + public InteractionMember getMember() { + return member; + } + + /** + * Gets the Discord channel the message was sent in. + * @return The channel the message was sent in. + */ + public InteractionChannel getChannel() { + return channel; + } + + /** + * Gets the message type group keys. + * @return The message type group keys. + */ + public List getGroupNames() { + return groupNames; + } + + /** + * Gets the raw message sent from Discord. + * @return The raw message sent from Discord. + */ + public String getRawMessage() { + return rawMessage; + } + + /** + * Gets the formatted message that will be sent to Minecraft. + * @return The formatted message. + */ + public String getFormattedMessage() { + return formattedMessage; + } + + /** + * Sets the formatted message that will be sent to Minecraft. + * @param formattedMessage The formatted message. + */ + public void setFormattedMessage(final String formattedMessage) { + this.formattedMessage = formattedMessage; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java index 438f8455a47..fcc24e099ca 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java @@ -36,6 +36,13 @@ public void onEnable() { getLogger().log(Level.WARNING, tl("versionMismatchAll")); } + // JDK-8274349 - Mitigation for a regression in Java 17 on 1 core systems which was fixed in 17.0.2 + final String[] javaVersion = System.getProperty("java.version").split("\\."); + if (Runtime.getRuntime().availableProcessors() <= 1 && javaVersion[0].startsWith("17") && (javaVersion.length < 2 || (javaVersion[1].equals("0") && javaVersion[2].startsWith("1")))) { + logger.log(Level.INFO, "Essentials is mitigating JDK-8274349"); + System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "1"); + } + isPAPI = getServer().getPluginManager().getPlugin("PlaceholderAPI") != null; settings = new DiscordSettings(this); diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionEventImpl.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionEventImpl.java index f6b062227ef..37c6dfd405a 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionEventImpl.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionEventImpl.java @@ -3,6 +3,7 @@ import com.earth2me.essentials.utils.FormatUtil; import com.google.common.base.Joiner; import net.dv8tion.jda.api.MessageBuilder; +import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.essentialsx.api.v2.services.discord.InteractionChannel; @@ -33,7 +34,12 @@ public InteractionEventImpl(final SlashCommandEvent jdaEvent) { public void reply(String message) { message = FormatUtil.stripFormat(message).replace("ยง", ""); // Don't ask replyBuffer.add(message); - event.getHook().editOriginal(new MessageBuilder().setContent(Joiner.on('\n').join(replyBuffer)).setAllowedMentions(DiscordUtil.NO_GROUP_MENTIONS).build()) + String reply = Joiner.on('\n').join(replyBuffer); + reply = reply.substring(0, Math.min(Message.MAX_CONTENT_LENGTH, reply.length())); + event.getHook().editOriginal( + new MessageBuilder() + .setContent(reply) + .setAllowedMentions(DiscordUtil.NO_GROUP_MENTIONS).build()) .queue(null, error -> logger.log(Level.SEVERE, "Error while editing command interaction response", error)); } diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordListener.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordListener.java index cdb3746975c..616888c66ed 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordListener.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordListener.java @@ -8,12 +8,17 @@ import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.ess3.api.IUser; +import net.essentialsx.api.v2.events.discord.DiscordRelayEvent; import net.essentialsx.discord.JDADiscordService; +import net.essentialsx.discord.interactions.InteractionChannelImpl; +import net.essentialsx.discord.interactions.InteractionMemberImpl; import net.essentialsx.discord.util.DiscordUtil; import net.essentialsx.discord.util.MessageUtil; import org.apache.commons.lang.StringUtils; +import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; +import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -88,7 +93,7 @@ public void onGuildMessageReceived(@NotNull GuildMessageReceivedEvent event) { return; } - final String formattedMessage = EmojiParser.parseToAliases(MessageUtil.formatMessage(plugin.getPlugin().getSettings().getDiscordToMcFormat(), + String formattedMessage = EmojiParser.parseToAliases(MessageUtil.formatMessage(plugin.getPlugin().getSettings().getDiscordToMcFormat(), event.getChannel().getName(), user.getName(), user.getDiscriminator(), user.getAsTag(), effectiveName, DiscordUtil.getRoleColorFormat(member), finalMessage, DiscordUtil.getRoleFormat(member)), EmojiParser.FitzpatrickAction.REMOVE); @@ -99,6 +104,18 @@ public void onGuildMessageReceived(@NotNull GuildMessageReceivedEvent event) { } } + // Do not create the event specific objects if there are no listeners + if (DiscordRelayEvent.getHandlerList().getRegisteredListeners().length != 0) { + final DiscordRelayEvent relayEvent = new DiscordRelayEvent( + new InteractionMemberImpl(member), new InteractionChannelImpl(event.getChannel()), + Collections.unmodifiableList(keys), event.getMessage().getContentRaw(), formattedMessage); + Bukkit.getPluginManager().callEvent(relayEvent); + if (relayEvent.isCancelled()) { + return; + } + formattedMessage = relayEvent.getFormattedMessage(); + } + for (IUser essUser : plugin.getPlugin().getEss().getOnlineUsers()) { for (String group : keys) { final String perm = "essentials.discord.receive." + group; diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java index 91cc5f464c6..e6ce349c8b3 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java @@ -81,7 +81,6 @@ public void append(LogEvent event) { entry = "[" + event.getLoggerName() + "] " + entry; } - //noinspection UnstableApiUsage messageQueue.addAll(Splitter.fixedLength(Message.MAX_CONTENT_LENGTH - 2).splitToList( MessageUtil.formatMessage(jda.getSettings().getConsoleFormat(), TimeFormat.TIME_LONG.format(Instant.now()), diff --git a/README.md b/README.md index 15043cb068e..78ccff5d1d6 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ EssentialsX is almost a completely drop-in replacement for Essentials. However, * **EssentialsX requires Java 8 or higher.** On older versions, the plugin may not work properly. -* **EssentialsX supports Minecraft versions 1.8.8, 1.9.4, 1.10.2, 1.11.2, 1.12.2, 1.13.2, 1.14.4, 1.15.2, 1.16.5, 1.17.1, and 1.18.1** +* **EssentialsX supports Minecraft versions 1.8.8, 1.9.4, 1.10.2, 1.11.2, 1.12.2, 1.13.2, 1.14.4, 1.15.2, 1.16.5, 1.17.1, and 1.18.1.** Support @@ -63,11 +63,11 @@ Releases are hosted on the Maven repo at `https://repo.essentialsx.net/releases/ To add EssentialsX to your build system, you should use the following artifacts: -| Type | Group ID | Artifact ID | Version | -| :-------------- | :-------------- | :---------- | :-------------- | -| Latest release | net.essentialsx | EssentialsX | 2.19.0 -| Snapshots | net.essentialsx | EssentialsX | 2.19.1-SNAPSHOT | -| Older releases | net.ess3 | EssentialsX | 2.18.2 | +| Type | Group ID | Artifact ID | Version | +|:---------------|:----------------|:------------|:----------------| +| Latest release | net.essentialsx | EssentialsX | 2.19.2 | +| Snapshots | net.essentialsx | EssentialsX | 2.19.3-SNAPSHOT | +| Older releases | net.ess3 | EssentialsX | 2.18.2 | Note: up until `2.18.2`, EssentialsX used the `net.ess3` group ID, but starting with `2.19.0` snapshots, the group ID is now `net.essentialsx`. When updating your plugin, make sure you use the correct group ID. diff --git a/build.gradle b/build.gradle index 12ecf990c17..197c17b1dcb 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { } group = "net.essentialsx" -version = "2.19.1-SNAPSHOT" +version = "2.19.3-SNAPSHOT" project.ext { GIT_COMMIT = !indraGit.isPresent() ? "unknown" : indraGit.commit().abbreviate(7).name() diff --git a/providers/PaperProvider/build.gradle b/providers/PaperProvider/build.gradle index 9a483358e2f..af8d56f80d3 100644 --- a/providers/PaperProvider/build.gradle +++ b/providers/PaperProvider/build.gradle @@ -8,8 +8,8 @@ java { dependencies { implementation project(':providers:BaseProviders') - compileOnly 'io.papermc.paper:paper-api:1.17.1-R0.1-SNAPSHOT' - compileOnly 'io.papermc.paper:paper-mojangapi:1.17.1-R0.1-SNAPSHOT' + compileOnly 'io.papermc.paper:paper-api:1.18.1-R0.1-SNAPSHOT' + compileOnly 'io.papermc.paper:paper-mojangapi:1.18.1-R0.1-SNAPSHOT' } essentials { diff --git a/providers/PaperProvider/src/main/java/net/ess3/provider/providers/PaperAdvancementListenerProvider.java b/providers/PaperProvider/src/main/java/net/ess3/provider/providers/PaperAdvancementListenerProvider.java index c54811e2e6b..93848016f25 100644 --- a/providers/PaperProvider/src/main/java/net/ess3/provider/providers/PaperAdvancementListenerProvider.java +++ b/providers/PaperProvider/src/main/java/net/ess3/provider/providers/PaperAdvancementListenerProvider.java @@ -1,7 +1,11 @@ package net.ess3.provider.providers; import io.papermc.paper.advancement.AdvancementDisplay; +import io.papermc.paper.text.PaperComponents; import net.ess3.provider.AbstractAchievementEvent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.ComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -9,12 +13,25 @@ import org.bukkit.event.player.PlayerAdvancementDoneEvent; public class PaperAdvancementListenerProvider implements Listener { + private final ComponentSerializer serializer; + + public PaperAdvancementListenerProvider() { + ComponentSerializer yeOldSerializer; + try { + // This method is only available in Paper 1.18.1+ and replaces the old deprecated method below. + yeOldSerializer = PaperComponents.plainTextSerializer(); + } catch (NoSuchMethodError e) { + //noinspection deprecation + yeOldSerializer = PaperComponents.plainSerializer(); + } + this.serializer = yeOldSerializer; + } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onAdvancement(final PlayerAdvancementDoneEvent event) { final AdvancementDisplay display = event.getAdvancement().getDisplay(); if (display != null && display.doesAnnounceToChat()) { - //noinspection deprecation - Bukkit.getPluginManager().callEvent(new AbstractAchievementEvent(event.getPlayer(), Bukkit.getUnsafe().plainComponentSerializer().serialize(display.title()))); + Bukkit.getPluginManager().callEvent(new AbstractAchievementEvent(event.getPlayer(), serializer.serialize(display.title()))); } } }