diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index c3e650d19..796f694b2 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -43,11 +43,13 @@ import net.ess3.api.IEssentials; import net.ess3.api.IItemDb; import net.ess3.api.IJails; import net.ess3.api.ISettings; +import net.ess3.nms.refl.providers.ReflFormattedCommandAliasProvider; import net.ess3.nms.refl.providers.ReflKnownCommandsProvider; import net.ess3.nms.refl.providers.ReflServerStateProvider; import net.ess3.nms.refl.providers.ReflSpawnEggProvider; import net.ess3.nms.refl.providers.ReflSpawnerBlockProvider; import net.ess3.provider.ContainerProvider; +import net.ess3.provider.FormattedCommandAliasProvider; import net.ess3.provider.KnownCommandsProvider; import net.ess3.provider.MaterialTagProvider; import net.ess3.provider.PotionMetaProvider; @@ -140,6 +142,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { private transient ServerStateProvider serverStateProvider; private transient ContainerProvider containerProvider; private transient KnownCommandsProvider knownCommandsProvider; + private transient FormattedCommandAliasProvider formattedCommandAliasProvider; private transient ProviderListener recipeBookEventProvider; private transient MaterialTagProvider materialTagProvider; private transient Kits kits; @@ -348,6 +351,9 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { knownCommandsProvider = new ReflKnownCommandsProvider(); } + // Command aliases provider + formattedCommandAliasProvider = new ReflFormattedCommandAliasProvider(PaperLib.isPaper()); + //Material Tag Providers if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_13_0_R01)) { materialTagProvider = PaperLib.isPaper() ? new PaperMaterialTagProvider() : new BukkitMaterialTagProvider(); @@ -1057,6 +1063,11 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { return knownCommandsProvider; } + @Override + public FormattedCommandAliasProvider getFormattedCommandAliasProvider() { + return formattedCommandAliasProvider; + } + private AbstractItemDb getItemDbFromConfig() { final String setting = settings.getItemDbType(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java index d0c1b8675..e2ae8d1de 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java @@ -18,6 +18,8 @@ import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.FormattedCommandAlias; import org.bukkit.command.PluginCommand; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.HumanEntity; @@ -520,9 +522,30 @@ public class EssentialsPlayerListener implements Listener { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPlayerCommandPreprocess(final PlayerCommandPreprocessEvent event) { - final Player player = event.getPlayer(); final String cmd = event.getMessage().toLowerCase(Locale.ENGLISH).split(" ")[0].replace("/", "").toLowerCase(Locale.ENGLISH); + final int argStartIndex = event.getMessage().indexOf(" "); + final String args = argStartIndex == -1 ? "" // No arguments present + : event.getMessage().substring(argStartIndex); // arguments start at argStartIndex; substring from there. + // If the plugin command does not exist, check if it is an alias from commands.yml + if (ess.getServer().getPluginCommand(cmd) == null) { + final Command knownCommand = ess.getKnownCommandsProvider().getKnownCommands().get(cmd); + if (knownCommand instanceof FormattedCommandAlias) { + final FormattedCommandAlias command = (FormattedCommandAlias) knownCommand; + for (String fullCommand : ess.getFormattedCommandAliasProvider().createCommands(command, event.getPlayer(), args.split(" "))) { + handlePlayerCommandPreprocess(event, fullCommand); + } + return; + } + } + + // Handle the command given from the event. + handlePlayerCommandPreprocess(event, cmd + args); + } + + public void handlePlayerCommandPreprocess(final PlayerCommandPreprocessEvent event, final String effectiveCommand) { + final Player player = event.getPlayer(); + final String cmd = effectiveCommand.toLowerCase(Locale.ENGLISH).split(" ")[0].replace("/", "").toLowerCase(Locale.ENGLISH); final PluginCommand pluginCommand = ess.getServer().getPluginCommand(cmd); if (ess.getSettings().getSocialSpyCommands().contains(cmd) || ess.getSettings().getSocialSpyCommands().contains("*")) { @@ -575,12 +598,12 @@ public class EssentialsPlayerListener implements Listener { user.updateActivityOnInteract(broadcast); } - if (ess.getSettings().isCommandCooldownsEnabled() && pluginCommand != null + if (ess.getSettings().isCommandCooldownsEnabled() && !user.isAuthorized("essentials.commandcooldowns.bypass")) { - final int argStartIndex = event.getMessage().indexOf(" "); + final int argStartIndex = effectiveCommand.indexOf(" "); final String args = argStartIndex == -1 ? "" // No arguments present - : " " + event.getMessage().substring(argStartIndex); // arguments start at argStartIndex; substring from there. - final String fullCommand = pluginCommand.getName() + args; + : " " + effectiveCommand.substring(argStartIndex); // arguments start at argStartIndex; substring from there. + final String fullCommand = pluginCommand == null ? effectiveCommand : pluginCommand.getName() + args; // Used to determine whether a user already has an existing cooldown // If so, no need to check for (and write) new ones. diff --git a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java index 8626c2ce1..93e00c4ac 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java @@ -6,6 +6,7 @@ import com.earth2me.essentials.api.IWarps; import com.earth2me.essentials.perm.PermissionsHandler; import net.ess3.provider.MaterialTagProvider; import net.ess3.provider.ContainerProvider; +import net.ess3.provider.FormattedCommandAliasProvider; import net.ess3.provider.KnownCommandsProvider; import net.ess3.provider.ServerStateProvider; import net.ess3.provider.SpawnerBlockProvider; @@ -124,4 +125,6 @@ public interface IEssentials extends Plugin { ContainerProvider getContainerProvider(); KnownCommandsProvider getKnownCommandsProvider(); + + FormattedCommandAliasProvider getFormattedCommandAliasProvider(); } diff --git a/providers/BaseProviders/src/main/java/net/ess3/provider/FormattedCommandAliasProvider.java b/providers/BaseProviders/src/main/java/net/ess3/provider/FormattedCommandAliasProvider.java new file mode 100644 index 000000000..77fd89f39 --- /dev/null +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/FormattedCommandAliasProvider.java @@ -0,0 +1,15 @@ +package net.ess3.provider; + +import org.bukkit.command.CommandSender; +import org.bukkit.command.FormattedCommandAlias; + +import java.util.List; + +public interface FormattedCommandAliasProvider extends Provider { + + List createCommands(FormattedCommandAlias command, CommandSender sender, String[] args); + + String[] getFormatStrings(FormattedCommandAlias command); + + String buildCommand(FormattedCommandAlias command, CommandSender sender, String formatString, String[] args); +} diff --git a/providers/NMSReflectionProvider/src/main/java/net/ess3/nms/refl/providers/ReflFormattedCommandAliasProvider.java b/providers/NMSReflectionProvider/src/main/java/net/ess3/nms/refl/providers/ReflFormattedCommandAliasProvider.java new file mode 100644 index 000000000..e7563ca27 --- /dev/null +++ b/providers/NMSReflectionProvider/src/main/java/net/ess3/nms/refl/providers/ReflFormattedCommandAliasProvider.java @@ -0,0 +1,92 @@ +package net.ess3.nms.refl.providers; + +import net.ess3.nms.refl.ReflUtil; +import net.ess3.provider.FormattedCommandAliasProvider; +import org.bukkit.command.CommandSender; +import org.bukkit.command.FormattedCommandAlias; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class ReflFormattedCommandAliasProvider implements FormattedCommandAliasProvider { + + private final boolean paper; + private final Field formatStringsField; + private final MethodHandle buildCommandMethodHandle; + + public ReflFormattedCommandAliasProvider(boolean paper) { + this.paper = paper; + + final Class formattedCommandAliasClass; + Field formatStringsField = null; + MethodHandle buildCommandMethodHandle = null; + try { + formattedCommandAliasClass = FormattedCommandAlias.class; + formatStringsField = ReflUtil.getFieldCached(formattedCommandAliasClass, "formatStrings"); + + final Class[] parameterTypes; + if (paper) { + parameterTypes = new Class[] {CommandSender.class, String.class, String[].class}; + } else { + parameterTypes = new Class[] {String.class, String[].class}; + } + + final Method buildCommandMethod = ReflUtil.getMethodCached(formattedCommandAliasClass, "buildCommand", parameterTypes); + buildCommandMethod.setAccessible(true); + buildCommandMethodHandle = MethodHandles.lookup().unreflect(buildCommandMethod); + } catch (final Exception ex) { + ex.printStackTrace(); + } finally { + this.formatStringsField = formatStringsField; + this.buildCommandMethodHandle = buildCommandMethodHandle; + } + } + + @Override + public List createCommands(FormattedCommandAlias command, CommandSender sender, String[] args) { + final List commands = new ArrayList<>(); + for (String formatString : getFormatStrings(command)) { + final String cmd; + try { + cmd = buildCommand(command, sender, formatString, args); + } catch (Throwable th) { + continue; // Ignore, let server handle this. + } + + if (cmd == null) continue; + commands.add(cmd.trim()); + } + return commands; + } + + @Override + public String[] getFormatStrings(FormattedCommandAlias command) { + try { + return (String[]) formatStringsField.get(command); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException(ex); // If this happens we have bigger problems... + } + } + + @Override + public String buildCommand(FormattedCommandAlias command, CommandSender sender, String formatString, String[] args) { + try { + if (paper) { + return (String) buildCommandMethodHandle.invoke(command, sender, formatString, args); + } else { + return (String) buildCommandMethodHandle.invoke(command, formatString, args); + } + } catch (Throwable ex) { + throw new RuntimeException(ex); // If this happens we have bigger problems... + } + } + + @Override + public String getDescription() { + return "NMS Reflection Provider for FormattedCommandAlias methods"; + } +}