├── README.md ├── src └── main │ └── java │ └── com │ └── ultikits │ └── lib │ ├── common │ └── CheckResponse.java │ ├── annotations │ ├── RunAsync.java │ ├── CmdCD.java │ ├── CmdTarget.java │ ├── CmdSender.java │ ├── CmdSuggest.java │ ├── UsageLimit.java │ ├── CmdExecutor.java │ ├── CmdMapping.java │ └── CmdParam.java │ ├── managers │ └── CommandManager.java │ └── abstracts │ └── AbstractCommendExecutor.java ├── .gitignore └── pom.xml /README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/java/com/ultikits/lib/common/CheckResponse.java: -------------------------------------------------------------------------------- 1 | package com.ultikits.lib.common; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class CheckResponse { 7 | public static CheckResponse SUCCESS = new CheckResponse(); 8 | private final String message; 9 | private final boolean success; 10 | 11 | public CheckResponse() { 12 | this.message = ""; 13 | this.success = true; 14 | } 15 | 16 | public CheckResponse(String message) { 17 | this.message = message; 18 | this.success = false; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/ 8 | *.iws 9 | *.iml 10 | *.ipr 11 | 12 | ### Eclipse ### 13 | .apt_generated 14 | .classpath 15 | .factorypath 16 | .project 17 | .settings 18 | .springBeans 19 | .sts4-cache 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | 34 | ### Mac OS ### 35 | .DS_Store 36 | -------------------------------------------------------------------------------- /src/main/java/com/ultikits/lib/annotations/RunAsync.java: -------------------------------------------------------------------------------- 1 | package com.ultikits.lib.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Run async annotation. 10 | *

11 | * 指令异步注解。 12 | * 13 | * @see @RunAsync 14 | */ 15 | @Target(ElementType.METHOD) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface RunAsync { 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/ultikits/lib/annotations/CmdCD.java: -------------------------------------------------------------------------------- 1 | package com.ultikits.lib.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Command cooldown annotation. 10 | *

11 | * 指令冷却注解。 12 | * 13 | * @see Command cooldown 14 | */ 15 | @Target(ElementType.METHOD) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface CmdCD { 18 | /** 19 | * @return cooldown time in seconds
冷却时间(秒) 20 | */ 21 | int value() default 0; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/ultikits/lib/annotations/CmdTarget.java: -------------------------------------------------------------------------------- 1 | package com.ultikits.lib.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Command target annotation. 10 | *

11 | * 指令目标注解。 12 | * 13 | * @see @CmdTarget 14 | */ 15 | @Target({ElementType.TYPE, ElementType.METHOD}) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface CmdTarget { 18 | CmdTargetType value(); 19 | 20 | enum CmdTargetType { 21 | PLAYER, 22 | CONSOLE, 23 | BOTH 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/ultikits/lib/annotations/CmdSender.java: -------------------------------------------------------------------------------- 1 | package com.ultikits.lib.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Command sender annotation.
Support auto-cast to {@link org.bukkit.command.CommandSender} and {@link org.bukkit.entity.Player}. 10 | *

11 | * 指令发送者注解。
支持自动转换为 {@link org.bukkit.command.CommandSender} 和 {@link org.bukkit.entity.Player}。 12 | * 13 | * @see Command Excutor 14 | */ 15 | @Target(ElementType.PARAMETER) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface CmdSender { 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/ultikits/lib/annotations/CmdSuggest.java: -------------------------------------------------------------------------------- 1 | package com.ultikits.lib.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Command suggest annotation. 10 | *

11 | * 指令建议注解。 12 | * 13 | * @see @CmdSuggest 14 | */ 15 | @Target({ElementType.TYPE, ElementType.METHOD}) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface CmdSuggest { 18 | /** 19 | * @return suggest class
{@link CmdParam#suggest()} suggest method will be searched in this class. 20 | *

21 | * 建议类
{@link CmdParam#suggest()} 将在这个类中寻找建议方法。 22 | */ 23 | Class[] value(); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/ultikits/lib/annotations/UsageLimit.java: -------------------------------------------------------------------------------- 1 | package com.ultikits.lib.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Usage limit annotation. 10 | *

11 | * 指令使用限制注解。 12 | * 13 | * @see @UsageLimit 14 | */ 15 | @Target(ElementType.METHOD) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface UsageLimit { 18 | /** 19 | * @return limit type
限制类型 20 | */ 21 | LimitType value(); 22 | 23 | /** 24 | * @return whether to contain console
是否包含控制台 25 | */ 26 | boolean ContainConsole() default false; 27 | 28 | enum LimitType { 29 | NONE, 30 | SENDER, 31 | ALL 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/ultikits/lib/annotations/CmdExecutor.java: -------------------------------------------------------------------------------- 1 | package com.ultikits.lib.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Command executor annotation. 7 | *

8 | * 命令标记注释 9 | * 10 | * @author qianmo 11 | * @version 1.0.0 12 | * @see Command Excutor 13 | */ 14 | @Documented 15 | @Target(ElementType.TYPE) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface CmdExecutor { 18 | 19 | /** 20 | * @return permission
权限 21 | */ 22 | String permission() default ""; 23 | 24 | /** 25 | * @return description
描述 26 | */ 27 | String description() default ""; 28 | 29 | /** 30 | * @return command alias
别名 31 | */ 32 | String[] alias(); 33 | 34 | /** 35 | * @return if requires op
是否要求OP 36 | */ 37 | boolean requireOp() default false; 38 | 39 | /** 40 | * @return if it is manually register
是否手动注册 41 | */ 42 | boolean manualRegister() default false; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/ultikits/lib/annotations/CmdMapping.java: -------------------------------------------------------------------------------- 1 | package com.ultikits.lib.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Command mapping annotation. 10 | *

11 | * 指令映射注解。 12 | * 13 | * @see Command Excutor 14 | */ 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target(ElementType.METHOD) 17 | public @interface CmdMapping { 18 | /** 19 | * @return command format
20 | * For example: if command is "/test" then
"" stands for "/test";
"<player>" stands for "/test <player>";
"send <message>" stands for "/test send <message>" 21 | * 22 | *
指令格式
例如:如果指令前缀是"/test",那么
/test 的format是 "";
/test <player> 的format是 "<player>";
/test send <message> 的format是 "send <message>" 23 | */ 24 | String format(); 25 | 26 | /** 27 | * @return command permission
指令权限 28 | */ 29 | String permission() default ""; 30 | 31 | /** 32 | * @return if command requires op
指令是否需要op 33 | */ 34 | boolean requireOp() default false; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/ultikits/lib/annotations/CmdParam.java: -------------------------------------------------------------------------------- 1 | package com.ultikits.lib.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Command parameter annotation. 10 | *

11 | * 指令参数注解。 12 | * 13 | * @see Command Excutor 14 | */ 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target(ElementType.PARAMETER) 17 | public @interface CmdParam { 18 | /** 19 | * @return parameter name
参数名 20 | */ 21 | String value(); 22 | 23 | /** 24 | * @return parameter suggestion 25 | *
26 | * You can pass a method name or a string. UltiTools-API will search for the method in the same class first. 27 | * If the method is not found, it will search in the class which is indecated in {@link CmdSuggest}. 28 | * If the method is still not found, it will return the string as the suggestion (i18n supported). 29 | *
30 | * 参数补全建议 31 | *
32 | * 你可以传入一个方法名或一个字符串。UltiTools-API 会首先在同一个类中寻找这个方法。 33 | * 如果方法没有找到,它会在 {@link CmdSuggest} 中指定的类中寻找。 34 | * 如果方法仍然没有找到,它会将字符串作为建议返回(支持国际化)。 35 | * @see CmdSuggest 36 | */ 37 | String suggest() default ""; 38 | } 39 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.ultikits.lib 8 | UltiKits-Command 9 | 1.0.0 10 | 11 | 12 | 13 | wisdomme 14 | xia.erich03@gmail.com 15 | UltiKits 16 | +10 17 | 18 | 19 | 20 | 21 | 17 22 | 17 23 | UTF-8 24 | 5.8.25 25 | 26 | 27 | 28 | 29 | spigotmc-repo 30 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 31 | 32 | 33 | sonatype 34 | https://oss.sonatype.org/content/groups/public/ 35 | 36 | 37 | 38 | 39 | 40 | org.spigotmc 41 | spigot-api 42 | 1.19.3-R0.1-SNAPSHOT 43 | provided 44 | 45 | 46 | org.projectlombok 47 | lombok 48 | 1.18.32 49 | provided 50 | 51 | 52 | org.jetbrains 53 | annotations 54 | 24.0.1 55 | 56 | 57 | cn.hutool 58 | hutool-core 59 | ${hutool.version} 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/java/com/ultikits/lib/managers/CommandManager.java: -------------------------------------------------------------------------------- 1 | package com.ultikits.lib.managers; 2 | 3 | import com.ultikits.lib.abstracts.AbstractCommendExecutor; 4 | import lombok.Getter; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandMap; 8 | import org.bukkit.command.PluginCommand; 9 | import org.bukkit.plugin.Plugin; 10 | import org.bukkit.plugin.SimplePluginManager; 11 | 12 | import java.lang.reflect.Constructor; 13 | import java.lang.reflect.Field; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Command manager. 19 | *

20 | * 命令管理器 21 | */ 22 | public class CommandManager { 23 | private final List commandList = new ArrayList<>(); 24 | @Getter 25 | private final Plugin plugin; 26 | 27 | /** 28 | * Constructs a new CommandManager instance. 29 | * 30 | * @param plugin The plugin instance. 31 | */ 32 | public CommandManager(Plugin plugin) { 33 | this.plugin = plugin; 34 | } 35 | 36 | /** 37 | * Register command. 38 | *

39 | * 注册命令 40 | * 41 | * @param commandExecutor Command executor
命令执行器 42 | * @param permission Permission
权限 43 | * @param description Description
描述 44 | * @param aliases Command alias
命令别名 45 | */ 46 | private void register(AbstractCommendExecutor commandExecutor, String permission, String description, String... aliases) { 47 | commandExecutor.setPlugin(plugin); 48 | PluginCommand command = getCommand(aliases[0], plugin); 49 | commandList.add(command); 50 | } 51 | 52 | /** 53 | * @param name Command name
命令名 54 | */ 55 | public void unregister(String name) { 56 | PluginCommand command = getCommand(name, plugin); 57 | command.unregister(getCommandMap()); 58 | } 59 | 60 | /** 61 | * Unregister all commands registered by the plugin. 62 | *

63 | * 注销插件注册的所有命令 64 | */ 65 | public void unregisterAll() { 66 | for (Command command : commandList) { 67 | unregister(command.getName()); 68 | } 69 | } 70 | 71 | /** 72 | * Creates a new PluginCommand instance using reflection. 73 | * 74 | * @param name The name of the command. 75 | * @param plugin The plugin instance. 76 | * @return A new PluginCommand instance, or null if an error occurred. 77 | */ 78 | private PluginCommand getCommand(String name, Plugin plugin) { 79 | PluginCommand command = null; 80 | 81 | try { 82 | Constructor c = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); 83 | c.setAccessible(true); 84 | 85 | command = c.newInstance(name, plugin); 86 | } catch (Exception | Error e) { 87 | e.printStackTrace(); 88 | } 89 | 90 | return command; 91 | } 92 | 93 | /** 94 | * Retrieves the CommandMap instance using reflection. 95 | * 96 | * @return The CommandMap instance, or null if an error occurred. 97 | */ 98 | private CommandMap getCommandMap() { 99 | CommandMap commandMap = null; 100 | 101 | try { 102 | if (Bukkit.getPluginManager() instanceof SimplePluginManager) { 103 | Field f = SimplePluginManager.class.getDeclaredField("commandMap"); 104 | f.setAccessible(true); 105 | 106 | commandMap = (CommandMap) f.get(Bukkit.getPluginManager()); 107 | } 108 | } catch (Exception | Error e) { 109 | e.printStackTrace(); 110 | } 111 | 112 | return commandMap; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/ultikits/lib/abstracts/AbstractCommendExecutor.java: -------------------------------------------------------------------------------- 1 | package com.ultikits.lib.abstracts; 2 | 3 | import cn.hutool.core.util.ReflectUtil; 4 | import com.google.common.collect.BiMap; 5 | import com.google.common.collect.HashBiMap; 6 | import com.ultikits.lib.annotations.*; 7 | import com.ultikits.lib.common.CheckResponse; 8 | import lombok.Getter; 9 | import lombok.Setter; 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.ChatColor; 12 | import org.bukkit.Material; 13 | import org.bukkit.OfflinePlayer; 14 | import org.bukkit.command.Command; 15 | import org.bukkit.command.CommandSender; 16 | import org.bukkit.command.TabExecutor; 17 | import org.bukkit.entity.Player; 18 | import org.bukkit.plugin.Plugin; 19 | import org.bukkit.scheduler.BukkitRunnable; 20 | import org.jetbrains.annotations.NotNull; 21 | import org.jetbrains.annotations.Nullable; 22 | 23 | import java.lang.annotation.Annotation; 24 | import java.lang.reflect.Array; 25 | import java.lang.reflect.Method; 26 | import java.lang.reflect.Parameter; 27 | import java.util.*; 28 | import java.util.function.Function; 29 | 30 | /** 31 | * This abstract class represents a command executor. 32 | * It implements the TabExecutor interface from the Bukkit API. 33 | *

34 | * 这个抽象类代表了一个命令执行器。 35 | * 它实现了Bukkit API中的TabExecutor接口。 36 | * 37 | * @see TabExecutor 38 | */ 39 | public abstract class AbstractCommendExecutor implements TabExecutor { 40 | private final BiMap mappings = HashBiMap.create(); 41 | private final BiMap senderLock = HashBiMap.create(); 42 | private final BiMap serverLock = HashBiMap.create(); 43 | private final BiMap cmdCoolDown = HashBiMap.create(); 44 | @Getter 45 | private final Map>, Function> parsers = new HashMap<>(); 46 | @Setter 47 | private Plugin plugin; 48 | 49 | /** 50 | * Constructor that initializes parsers and scans command mappings. 51 | *

52 | * 构造函数,初始化解析器并扫描命令映射。 53 | */ 54 | public AbstractCommendExecutor() { 55 | initParsers(); 56 | scanCommandMappings(); 57 | } 58 | 59 | /** 60 | * Checks whether the sender is valid. 61 | *

62 | * 检查发送者是否有效。 63 | * 64 | * @param sender The sender of the command.
命令的发送者。 65 | * @param cmdTarget The method that matches the command.
匹配命令的方法。 66 | * @return Whether the sender is valid.
发送者是否有效。 67 | */ 68 | private CheckResponse checkCmdTargetType(CommandSender sender, CmdTarget cmdTarget) { 69 | if (cmdTarget.value().equals(CmdTarget.CmdTargetType.PLAYER) && !(sender instanceof Player)) { 70 | return new CheckResponse("This command can only be executed by a player."); 71 | } 72 | if (!cmdTarget.value().equals(CmdTarget.CmdTargetType.CONSOLE) || !(sender instanceof Player)) 73 | return CheckResponse.SUCCESS; 74 | return new CheckResponse("This command can only be executed by the console."); 75 | } 76 | 77 | /** 78 | * Gets a map of parameter match from a command. 79 | *

80 | * 从命令中获取参数匹配的映射。 81 | * 82 | * @param args The arguments of the command.
命令的参数。 83 | * @param format The format of the command.
命令的格式。 84 | * @return The map of the parameters.
参数的映射。 85 | */ 86 | private Map getParams(String[] args, String format) { 87 | if (args.length == 0) { 88 | return Collections.emptyMap(); 89 | } 90 | 91 | String[] formatArgs = format.split(" "); 92 | Map params = new HashMap<>(); 93 | List paramList = new ArrayList<>(); 94 | int index = 0; 95 | for (String arg : args) { 96 | String currentFormatArg = formatArgs[index]; 97 | if (currentFormatArg.startsWith("<")) { 98 | String paramName = currentFormatArg.substring(1, currentFormatArg.length() - (currentFormatArg.endsWith("...>") ? 4 : 1)); 99 | if (currentFormatArg.endsWith("...>")) { 100 | paramList.add(arg); 101 | } else { 102 | params.put(paramName, new String[]{arg}); 103 | } 104 | } 105 | index++; 106 | } 107 | 108 | if (!paramList.isEmpty()) { 109 | params.put(formatArgs[index].substring(1, formatArgs[index].length() - 1), paramList.toArray(new String[0])); 110 | } 111 | 112 | return params; 113 | } 114 | 115 | /** 116 | * Gets the instance of the command executor. 117 | *

118 | * 获取命令执行器的实例。 119 | * 120 | * @return The instance of the command executor.
命令执行器的实例。 121 | */ 122 | public AbstractCommendExecutor getInstance() { 123 | return this; 124 | } 125 | 126 | /** 127 | * Initializes the parsers. 128 | *

129 | * 初始化解析器。 130 | */ 131 | @SuppressWarnings("deprecation") 132 | private void initParsers() { 133 | parsers.put(Arrays.asList(Boolean[].class, Boolean.class, boolean[].class, boolean.class), Boolean::parseBoolean); 134 | parsers.put(Arrays.asList(Double[].class, Double.class, double[].class, double.class), Double::parseDouble); 135 | parsers.put(Arrays.asList(Integer[].class, Integer.class, int[].class, int.class), Integer::parseInt); 136 | parsers.put(Arrays.asList(Float[].class, Float.class, float[].class, float.class), Float::parseFloat); 137 | parsers.put(Arrays.asList(Short[].class, Short.class, short[].class, short.class), Short::parseShort); 138 | parsers.put(Arrays.asList(Short[].class, Short.class, short[].class, short.class), Byte::parseByte); 139 | parsers.put(Arrays.asList(OfflinePlayer[].class, OfflinePlayer.class), Bukkit::getOfflinePlayer); 140 | parsers.put(Arrays.asList(Long[].class, Long.class, long[].class, long.class), Long::parseLong); 141 | parsers.put(Arrays.asList(Material[].class, Material.class), Material::getMaterial); 142 | parsers.put(Arrays.asList(Player[].class, Player.class), Bukkit::getPlayerExact); 143 | parsers.put(Arrays.asList(UUID[].class, UUID.class), UUID::fromString); 144 | parsers.put(Arrays.asList(String[].class, String.class), s -> s); 145 | } 146 | 147 | /** 148 | * Gets the parser of the parameter. 149 | *

150 | * 获取参数的解析器。 151 | * 152 | * @param type The type of the parameter.
参数的类型。 153 | * @param The type of the parameter.
参数的类型。 154 | * @return The parser of the parameter.
参数的解析器。 155 | */ 156 | private Function getParser(Class type) { 157 | //noinspection unchecked 158 | return (Function) parsers.keySet().stream() 159 | .filter(classes -> classes.stream().anyMatch(clazz -> clazz.isAssignableFrom(type))) 160 | .findFirst() 161 | .map(parsers::get) 162 | .orElse(null); 163 | } 164 | 165 | /** 166 | * Scans the command mappings. 167 | *

168 | * 扫描命令映射。 169 | */ 170 | private void scanCommandMappings() { 171 | Class clazz = this.getClass(); 172 | Method[] methods = clazz.getDeclaredMethods(); 173 | for (Method method : methods) { 174 | if (method.isAnnotationPresent(CmdMapping.class)) { 175 | mappings.put(method.getAnnotation(CmdMapping.class).format(), method); 176 | } 177 | } 178 | } 179 | 180 | /** 181 | * Matches the command. 182 | *

183 | * 匹配命令。 184 | * 185 | * @param args The arguments of the command.
命令的参数。 186 | * @return The method that matches the command.
匹配命令的方法。 187 | */ 188 | private Method matchMethod(String[] args) { 189 | if (args.length == 0) { 190 | return mappings.getOrDefault("", null); 191 | } 192 | for (Map.Entry entry : mappings.entrySet()) { 193 | String format = entry.getKey(); 194 | String[] formatArgs = format.split(" "); 195 | 196 | boolean match = true; 197 | 198 | // 检查参数长度是否一致 199 | if (formatArgs.length != args.length) { 200 | // 参数长度不一致 取可配对参数的最小值 201 | int min = Math.min(formatArgs.length, args.length); 202 | 203 | // 逐个匹配 204 | for (int i = 0; i < min; i++) { 205 | if (!matchesArgument(formatArgs[i], args[i])) { 206 | match = false; 207 | break; 208 | } 209 | } 210 | 211 | // 如果所有参数都匹配,返回这个方法 212 | if (match) { 213 | return entry.getValue(); 214 | } 215 | 216 | // 如果不完全匹配,继续下一次循环 217 | continue; 218 | } 219 | 220 | for (int i = 0; i < formatArgs.length - 1; i++) { 221 | if (!matchesArgument(formatArgs[i], args[i])) { 222 | match = false; 223 | break; 224 | } 225 | } 226 | 227 | if (match && matchesLastArgument(formatArgs[formatArgs.length - 1], args[formatArgs.length - 1])) { 228 | return entry.getValue(); 229 | } 230 | } 231 | return null; 232 | } 233 | 234 | /** 235 | * Compare the actual argument with the format argument. 236 | *

237 | * 将实际参数与格式参数进行比较。 238 | * 239 | * @param formatArg The format argument.
格式参数。 240 | * @param actualArg The actual argument.
实际参数。 241 | * @return Whether the actual argument matches the format argument.
实际参数是否与格式参数匹配。 242 | */ 243 | private boolean matchesArgument(String formatArg, String actualArg) { 244 | return formatArg.startsWith("<") && formatArg.endsWith(">") || formatArg.equalsIgnoreCase(actualArg); 245 | } 246 | 247 | /** 248 | * Compare the actual argument with the format argument. 249 | *

250 | * 将实际参数与格式参数进行比较。 251 | * 252 | * @param formatArg The format argument.
格式参数。 253 | * @param actualArg The actual argument.
实际参数。 254 | * @return Whether the actual argument matches the format argument.
实际参数是否与格式参数匹配。 255 | */ 256 | private boolean matchesLastArgument(String formatArg, String actualArg) { 257 | if (formatArg.endsWith("...>")) { 258 | return true; 259 | } 260 | return matchesArgument(formatArg, actualArg); 261 | } 262 | 263 | /** 264 | * Checks whether the sender is valid. 265 | *

266 | * 检查发送者是否有效。 267 | * 268 | * @param sender The sender of the command.
命令的发送者。 269 | * @param method The method that matches the command.
匹配命令的方法。 270 | * @return Whether the sender is valid.
发送者是否有效。 271 | */ 272 | private CheckResponse checkSender(CommandSender sender, Method method) { 273 | if (!method.isAnnotationPresent(CmdTarget.class)) { 274 | Class clazz = this.getClass(); 275 | if (!clazz.isAnnotationPresent(CmdTarget.class)) { 276 | return CheckResponse.SUCCESS; 277 | } 278 | CmdTarget cmdTarget = clazz.getAnnotation(CmdTarget.class); 279 | return checkCmdTargetType(sender, cmdTarget); 280 | } 281 | CmdTarget cmdTarget = method.getAnnotation(CmdTarget.class); 282 | return checkCmdTargetType(sender, cmdTarget); 283 | } 284 | 285 | 286 | /** 287 | * Checks whether the sender has permission. 288 | *

289 | * 检查发送者是否有权限。 290 | * 291 | * @param sender The sender of the command.
命令的发送者。 292 | * @param method The method that matches the command.
匹配命令的方法。 293 | * @return Whether the sender has permission.
发送者是否有权限。 294 | */ 295 | private CheckResponse checkPermission(CommandSender sender, Method method) { 296 | if (!method.isAnnotationPresent(CmdMapping.class)) { 297 | Class clazz = this.getClass(); 298 | if (!clazz.isAnnotationPresent(CmdExecutor.class)) { 299 | return CheckResponse.SUCCESS; 300 | } 301 | CmdExecutor cmdExecutor = clazz.getAnnotation(CmdExecutor.class); 302 | String permission = cmdExecutor.permission(); 303 | if (sender.hasPermission(permission)) 304 | return CheckResponse.SUCCESS; 305 | return new CheckResponse(String.format("§7Executing this command requires §f%s §7permission", permission)); 306 | } 307 | CmdMapping cmdMapping = method.getAnnotation(CmdMapping.class); 308 | if (cmdMapping.permission().isEmpty()) { 309 | return CheckResponse.SUCCESS; 310 | } 311 | String permission = cmdMapping.permission(); 312 | if (sender.hasPermission(permission)) 313 | return CheckResponse.SUCCESS; 314 | return new CheckResponse(String.format("§7Executing this command requires §f%s §7permission", permission)); 315 | } 316 | 317 | /** 318 | * Checks whether the sender need to be an OP. 319 | *

320 | * 检查发送者是否需要是OP。 321 | * 322 | * @param sender The sender of the command.
命令的发送者。 323 | * @param method The method that matches the command.
匹配命令的方法。 324 | * @return Whether the sender need to be an OP.
发送者是否需要是OP。 325 | */ 326 | private CheckResponse checkOp(CommandSender sender, Method method) { 327 | if (!method.isAnnotationPresent(CmdMapping.class)) { 328 | Class clazz = this.getClass(); 329 | if (!clazz.isAnnotationPresent(CmdExecutor.class)) { 330 | return CheckResponse.SUCCESS; 331 | } 332 | CmdExecutor cmdExecutor = clazz.getAnnotation(CmdExecutor.class); 333 | if (cmdExecutor.requireOp() && sender.isOp()) 334 | return CheckResponse.SUCCESS; 335 | return new CheckResponse("You do not have permission to execute this command."); 336 | } 337 | CmdMapping cmdMapping = method.getAnnotation(CmdMapping.class); 338 | if (cmdMapping.requireOp() && sender.isOp()) 339 | return CheckResponse.SUCCESS; 340 | return new CheckResponse("You do not have permission to execute this command."); 341 | } 342 | 343 | /** 344 | * Checks whether the sender need to wait for the previous command to finish. 345 | *

346 | * 检查发送者是否需要等待上一条命令执行完毕。 347 | * 348 | * @param sender The sender of the command.
命令的发送者。 349 | * @param method The method that matches the command.
匹配命令的方法。 350 | * @return Whether the sender need to wait for the previous command to finish.
发送者是否需要等待上一条命令执行完毕。 351 | */ 352 | private CheckResponse checkNotLock(CommandSender sender, Method method) { 353 | if (!method.isAnnotationPresent(UsageLimit.class)) { 354 | return CheckResponse.SUCCESS; 355 | } 356 | if (method.getAnnotation(UsageLimit.class).value().equals(UsageLimit.LimitType.SENDER)) { 357 | if (!(sender instanceof Player || method.getAnnotation(UsageLimit.class).ContainConsole())) { 358 | return CheckResponse.SUCCESS; 359 | } 360 | if (sender instanceof Player && senderLock.get(((Player) sender).getUniqueId()).equals(method)) { 361 | return new CheckResponse("Please wait for the previous command to finish executing first!"); 362 | } 363 | } 364 | if (method.getAnnotation(UsageLimit.class).value().equals(UsageLimit.LimitType.ALL)) { 365 | if (!(sender instanceof Player || method.getAnnotation(UsageLimit.class).ContainConsole())) { 366 | return CheckResponse.SUCCESS; 367 | } 368 | if (sender instanceof Player && serverLock.get(((Player) sender).getUniqueId()).equals(method)) { 369 | return new CheckResponse("Please wait for the other players' commands to be executed first!"); 370 | } 371 | } 372 | return CheckResponse.SUCCESS; 373 | } 374 | 375 | /** 376 | * Checks whether the sender need to wait for the command cool down. 377 | *

378 | * 检查发送者是否需要等待命令冷却。 379 | * 380 | * @param sender The sender of the command.
命令的发送者。 381 | * @return Whether the sender need to wait for command cool down.
发送者是否需要等待命令冷却。 382 | */ 383 | private CheckResponse checkNotInCD(CommandSender sender) { 384 | if (!(sender instanceof Player player)) { 385 | return CheckResponse.SUCCESS; 386 | } 387 | if (cmdCoolDown.containsKey(player.getUniqueId())) 388 | return new CheckResponse("Too many operations. Please try again later."); 389 | return CheckResponse.SUCCESS; 390 | } 391 | 392 | /** 393 | * Builds the parameters of the command. 394 | *

395 | * 构建命令的参数。 396 | * 397 | * @param strings The arguments of the command.
命令的参数。 398 | * @param method The method that matches the command.
匹配命令的方法。 399 | * @param commandSender The sender of the command.
命令的发送者。 400 | * @return Parameters of the command.
命令的参数。 401 | */ 402 | private Object[] buildParams(String[] strings, Method method, CommandSender commandSender) { 403 | Map params = getParams(strings, mappings.inverse().get(method)); 404 | Parameter[] parameters = method.getParameters(); 405 | 406 | if (parameters.length == 0) { 407 | return new Object[0]; 408 | } 409 | 410 | List paramList = new ArrayList<>(); 411 | 412 | for (Parameter parameter : parameters) { 413 | Class paramType = parameter.getType(); 414 | 415 | if (paramType.equals(Player.class) || paramType.equals(CommandSender.class)) { 416 | boolean isCmdSenderAnnotationPresent = parameter.isAnnotationPresent(CmdSender.class); 417 | 418 | if (paramType.equals(Player.class) && commandSender instanceof Player) { 419 | paramList.add(isCmdSenderAnnotationPresent ? commandSender : null); 420 | } else if (paramType.equals(CommandSender.class)) { 421 | paramList.add(isCmdSenderAnnotationPresent ? commandSender : null); 422 | } 423 | 424 | continue; 425 | } 426 | 427 | if (parameter.isAnnotationPresent(CmdParam.class)) { 428 | CmdParam cmdParam = parameter.getAnnotation(CmdParam.class); 429 | String[] value = params.get(cmdParam.value()); 430 | try { 431 | paramList.add(parseType(value, paramType)); 432 | } catch (Exception | Error e) { 433 | commandSender.sendMessage(ChatColor.RED + e.getMessage()); 434 | //noinspection CallToPrintStackTrace 435 | e.printStackTrace(); 436 | return null; 437 | } 438 | } else { 439 | paramList.add(null); 440 | } 441 | } 442 | return paramList.toArray(); 443 | } 444 | 445 | /** 446 | * Parses the type of the parameter. 447 | *

448 | * 解析参数的类型。 449 | * 450 | * @param value The value of the parameter.
参数的值。 451 | * @param type The type of the parameter.
参数的类型。 452 | * @param The type of the parameter.
参数的类型。 453 | * @return The parsed parameter.
解析后的参数。 454 | */ 455 | private Object parseType(String[] value, Class type) { 456 | Function parser = getParser(type); 457 | if (type.isArray()) { 458 | Object array = Array.newInstance(type.getComponentType(), value.length); 459 | for (int i = 0; i < value.length; i++) { 460 | Array.set(array, i, parser.apply(value[i])); 461 | } 462 | return array; 463 | } else { 464 | return parser.apply(value[0]); 465 | } 466 | } 467 | 468 | /** 469 | * Sets the cool down of the command. 470 | *

471 | * 设置命令的冷却。 472 | * 473 | * @param commandSender The sender of the command.
命令的发送者。 474 | * @param method The method that matches the command.
匹配命令的方法。 475 | */ 476 | private void setCoolDown(CommandSender commandSender, Method method) { 477 | if (!(commandSender instanceof Player player)) { 478 | return; 479 | } 480 | CmdCD cmdCD = method.getAnnotation(CmdCD.class); 481 | if (cmdCD == null) { 482 | return; 483 | } 484 | if (cmdCD.value() == 0) { 485 | return; 486 | } 487 | cmdCoolDown.put(player.getUniqueId(), method); 488 | new BukkitRunnable() { 489 | int time = cmdCD.value(); 490 | 491 | @Override 492 | public void run() { 493 | if (time > 0) { 494 | time--; 495 | } else { 496 | cmdCoolDown.remove(player.getUniqueId(), method); 497 | this.cancel(); 498 | } 499 | } 500 | }.runTaskTimerAsynchronously(plugin, 0L, 20L); 501 | } 502 | 503 | 504 | /** 505 | * Abstract method that handles the help command. 506 | *

507 | * 处理帮助命令的抽象方法。 508 | * 509 | * @param sender The sender of the command.
命令的发送者。 510 | */ 511 | abstract protected void handleHelp(CommandSender sender); 512 | 513 | /** 514 | * Tab complete method. Returns a list of possible completions for the specified command string. 515 | * By rewriting this method, you can customize the tab completion of the command. 516 | *

517 | * 补全方法。返回指定命令字符串的可能补全列表。 518 | * 通过重写此方法,您可以自定义命令的补全。 519 | * 520 | * @param player The player who will see the suggestions.
看到补全的玩家 521 | * @param command The command that was typed in.
需要补全的命令 522 | * @param strings The arguments of the command that was typed in.
目前输入的命令参数 523 | * @return The suggestions.
补全的建议 524 | */ 525 | protected List suggest(Player player, Command command, String[] strings) { 526 | List completions = new ArrayList<>(); 527 | if (strings.length == 1) { 528 | for (Map.Entry entry : mappings.entrySet()) { 529 | Method method = entry.getValue(); 530 | String format = entry.getKey(); 531 | if (!checkPermission(player, method).isSuccess() || !checkOp(player, method).isSuccess()) { 532 | continue; 533 | } 534 | String arg = format.split(" ")[0]; 535 | if (arg.startsWith("<") && arg.endsWith(">")) { 536 | getArgSuggestion(player, command, strings, method, arg, completions); 537 | continue; 538 | } 539 | completions.add(arg); 540 | } 541 | return completions; 542 | } else { 543 | List methodsByArg = getMethodsByArg(player, String.join(" ", strings)); 544 | for (Method method : methodsByArg) { 545 | String formatByMethod = getFormatByMethod(method); 546 | String arg = getArgAt(formatByMethod, strings.length - 1); 547 | if (arg.startsWith("<") && arg.endsWith(">")) { 548 | getArgSuggestion(player, command, strings, method, arg, completions); 549 | } else { 550 | for (String format : mappings.keySet()) { 551 | String[] args = format.split(" "); 552 | if (args.length < strings.length) { 553 | continue; 554 | } 555 | String sug = args[strings.length - 1]; 556 | if (sug.startsWith("<") && sug.endsWith(">")) { 557 | continue; 558 | } 559 | completions.add(sug); 560 | } 561 | } 562 | } 563 | } 564 | return completions; 565 | } 566 | 567 | /** 568 | * Adds suggestion to the list of suggestions. This method is called by the suggest method. 569 | * Fetch the suggestions by invoking the given method. 570 | *

571 | * 将建议添加到建议列表中。此方法由suggest方法调用。 572 | * 通过调用给定的方法获取建议。 573 | * 574 | * @param player The player who will see the suggestions.
看到补全的玩家 575 | * @param command The command that was typed in.
需要补全的命令 576 | * @param strings The arguments of the command that was typed in.
目前输入的命令参数 577 | * @param method The method that matches the command.
匹配命令的方法。 578 | * @param arg The argument that needs to be completed.
需要补全的参数。 579 | * @param completions The suggestions.
补全的建议 580 | */ 581 | private void getArgSuggestion(Player player, Command command, String[] strings, Method method, String arg, List completions) { 582 | String suggestName = getSuggestName(method, arg.substring(1, arg.length() - 1)); 583 | if (suggestName == null) { 584 | return; 585 | } 586 | Method[] suggestMethod = getSuggestMethodByName(suggestName); 587 | if (suggestMethod == null || suggestMethod.length == 0) { 588 | completions.add(suggestName); 589 | return; 590 | } 591 | Class declaringClass = suggestMethod[0].getDeclaringClass(); 592 | Collection suggestObject; 593 | if (this.getClass() != declaringClass) { 594 | Object suggestInstance = ReflectUtil.newInstance(declaringClass); 595 | suggestObject = invokeSuggestMethod(suggestInstance, suggestMethod[0], player, command, strings); 596 | } else { 597 | suggestObject = invokeSuggestMethod(this, suggestMethod[0], player, command, strings); 598 | } 599 | if (suggestObject != null) { 600 | for (Object o : suggestObject) { 601 | completions.add(o.toString()); 602 | } 603 | } 604 | } 605 | 606 | /** 607 | * Gets the format of the command. 608 | *

609 | * 获取命令的格式。 610 | * 611 | * @param method The method that matches the command.
匹配命令的方法。 612 | * @return The format of the command.
命令的格式。 613 | */ 614 | private String getFormatByMethod(Method method) { 615 | return mappings.inverse().get(method); 616 | } 617 | 618 | /** 619 | * Gets the argument at the specified index. 620 | *

621 | * 获取指定索引处的参数。 622 | * 623 | * @param format The format of the command.
命令的格式。 624 | * @param index The index of the argument.
参数的索引。 625 | * @return The argument at the specified index.
指定索引处的参数。 626 | */ 627 | private String getArgAt(String format, int index) { 628 | String[] args = format.split(" "); 629 | if (args.length <= index) { 630 | return ""; 631 | } 632 | return args[index]; 633 | } 634 | 635 | /** 636 | * Gets the suggest method name of the parameter. 637 | *

638 | * 获取参数的建议方法名称。 639 | * 640 | * @param method The method that matches the command.
匹配命令的方法。 641 | * @param paramName The name of the parameter.
参数的名称。 642 | * @return The suggest method name of the parameter.
参数的建议方法名称。 643 | */ 644 | private String getSuggestName(Method method, String paramName) { 645 | Annotation[][] parameterAnnotations = method.getParameterAnnotations(); 646 | for (Annotation[] annotations : parameterAnnotations) { 647 | for (Annotation annotation : annotations) { 648 | if (annotation instanceof CmdParam cmdParam) { 649 | if (!paramName.equals(cmdParam.value())) { 650 | continue; 651 | } 652 | return cmdParam.suggest(); 653 | } 654 | } 655 | } 656 | return null; 657 | } 658 | 659 | /** 660 | * Gets the suggest method array by the suggest method name. 661 | * If the suggest method name ends with (), remove (). 662 | * Get the suggest method array from the current class first. 663 | * If the current class has the CmdSuggest annotation, 664 | * get the suggest method array from the CmdSuggest annotation. 665 | *

666 | * 通过建议方法名称获取建议方法数组。 667 | * 如果建议方法名称以()结尾,则去掉()。 668 | * 优先从当前类中获取建议方法集合。 669 | * 如果当前类上有CmdSuggest注解,则从CmdSuggest注解中获取建议方法集合。 670 | * 671 | * @param suggestName The name of the suggest method.
建议方法的名称 672 | * @return The suggest method array.
建议方法数组 673 | */ 674 | private Method[] getSuggestMethodByName(String suggestName) { 675 | if (suggestName.endsWith("()")) { 676 | suggestName = suggestName.substring(0, suggestName.length() - 2); 677 | } 678 | Method[] localSuggestMethod = getMethod(suggestName); 679 | if (localSuggestMethod != null && localSuggestMethod.length > 0) return localSuggestMethod; 680 | if (this.getClass().isAnnotationPresent(CmdSuggest.class)) { 681 | Class[] value = this.getClass().getAnnotation(CmdSuggest.class).value(); 682 | for (Class clazz : value) { 683 | Method[] method = getMethod(clazz, suggestName); 684 | if (method != null) { 685 | return method; 686 | } 687 | } 688 | } 689 | return null; 690 | } 691 | 692 | /** 693 | * Gets the suggest method array by the suggest method name. 694 | *

695 | * 通过建议方法名称获取建议方法数组。 696 | * 697 | * @param suggestName The name of the suggest method.
建议方法的名称 698 | * @return The suggest method array.
建议方法数组 699 | */ 700 | @Nullable 701 | private Method[] getMethod(String suggestName) { 702 | return getMethod(this.getClass(), suggestName); 703 | } 704 | 705 | /** 706 | * Gets the suggest method array by the suggest method name from another class. 707 | *

708 | * 从另一个类中通过方法名称获取方法数组。 709 | * 710 | * @param clazz The class of the suggest method.
建议方法的类 711 | * @param suggestName The name of the suggest method.
建议方法的名称 712 | * @return The suggest method array.
建议方法数组 713 | */ 714 | @Nullable 715 | private Method[] getMethod(Class clazz, String suggestName) { 716 | return ReflectUtil.getMethods(clazz, method -> method.getName().equals(suggestName)); 717 | } 718 | 719 | /** 720 | * Invokes the suggest method. Inject parameters by parameter type. 721 | *

722 | * 调用建议方法。按照参数类型注入参数。 723 | * 724 | * @param object The object that the method is invoked from.
方法所在的对象。 725 | * @param method The method that is invoked.
被调用的方法。 726 | * @param player The player who will see the suggestions.
看到补全的玩家 727 | * @param command The command that was typed in.
需要补全的命令 728 | * @param strings The arguments of the command that was typed in.
目前输入的命令参数 729 | * @return The return string list of suggestions of the method.
方法的返回字符串列表。 730 | */ 731 | private Collection invokeSuggestMethod(Object object, Method method, Player player, Command command, String[] strings) { 732 | Parameter[] parameters = method.getParameters(); 733 | Object[] params = new Object[parameters.length]; 734 | for (int i = 0; i < parameters.length; i++) { 735 | Parameter parameter = parameters[i]; 736 | Class type = parameter.getType(); 737 | if (type.equals(Player.class)) { 738 | params[i] = player; 739 | } else if (type.equals(Command.class)) { 740 | params[i] = command; 741 | } else if (type.equals(String[].class)) { 742 | params[i] = strings; 743 | } 744 | } 745 | return ReflectUtil.invoke(object, method, params); 746 | } 747 | 748 | /** 749 | * Gets the methods that match the command. 750 | * Get the perfect match method first. 751 | * If there is no perfect match method, get the partial match method. 752 | * If there is no perfect match method and partial match method, return an empty list. 753 | * If there is a perfect match method, only return the perfect match method. 754 | *

755 | * 获取匹配命令的方法。 756 | * 优先获取完全匹配的方法,如果没有完全匹配的方法,则获取部分匹配的方法。 757 | * 如果没有完全匹配的方法和部分匹配的方法,则返回空列表。 758 | * 如果有完全匹配的方法,则只返回完全匹配的方法。 759 | * 760 | * @param player The player who will see the suggestions.
看到补全的玩家 761 | * @param command The command that was typed in.
需要补全的命令 762 | * @return The suggestions.
补全的建议 763 | */ 764 | private List getMethodsByArg(Player player, String command) { 765 | List perfectMatch = new ArrayList<>(); 766 | List methods = new ArrayList<>(); 767 | for (Map.Entry entry : mappings.entrySet()) { 768 | Method method = entry.getValue(); 769 | String format = entry.getKey(); 770 | if (format.startsWith(command.substring(0, command.lastIndexOf(" ")))) { 771 | if (checkPermission(player, method).isSuccess() && checkOp(player, method).isSuccess()) { 772 | perfectMatch.add(method); 773 | } 774 | } else { 775 | String[] formatArgs = format.split(" "); 776 | String[] commandArgs = command.split(" "); 777 | boolean match = true; 778 | for (int i = 0; i < Math.min(commandArgs.length, formatArgs.length); i++) { 779 | if (!matchesArgument(formatArgs[i], commandArgs[i])) { 780 | match = false; 781 | break; 782 | } 783 | } 784 | if (match) { 785 | if (checkPermission(player, method).isSuccess() && checkOp(player, method).isSuccess()) { 786 | methods.add(method); 787 | } 788 | } 789 | } 790 | } 791 | if (!perfectMatch.isEmpty()) { 792 | return perfectMatch; 793 | } else { 794 | return methods; 795 | } 796 | } 797 | 798 | /** 799 | * @return The help command.
帮助命令。 800 | * @see #handleHelp(CommandSender) 801 | */ 802 | protected String getHelpCommand() { 803 | return "help"; 804 | } 805 | 806 | /** 807 | * Executes the command, returning its success. 808 | *

809 | * 执行命令,返回是否成功。 810 | * 811 | * @param commandSender Source of the command
命令的发送者 812 | * @param command Command which was executed
命令 813 | * @param s Alias of the command which was used
命令的别名 814 | * @param strings Passed command arguments
命令的参数 815 | * @return true if a valid command, otherwise false
如果是有效的命令则返回true,否则返回false 816 | */ 817 | @Override 818 | public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) { 819 | if (strings.length == 1 && getHelpCommand().equals(strings[0])) { 820 | handleHelp(commandSender); 821 | return true; 822 | } 823 | Method method = matchMethod(strings); 824 | if (method == null) { 825 | handleHelp(commandSender); 826 | return true; 827 | } 828 | // 检查参数长度 829 | CheckResponse checkResponse = checkParameters(strings, method, command); 830 | if (!checkResponse.isSuccess()) { 831 | handleParameterError(commandSender, checkResponse.getMessage()); 832 | return true; 833 | } 834 | CheckResponse checkSenderResponse = checkSender(commandSender, method); 835 | if (!checkSenderResponse.isSuccess()) { 836 | handleSenderError(commandSender, checkSenderResponse.getMessage()); 837 | return true; 838 | } 839 | CheckResponse checkPermissionResponse = checkPermission(commandSender, method); 840 | if (!checkPermissionResponse.isSuccess()) { 841 | handlePermissionError(commandSender, checkPermissionResponse.getMessage()); 842 | return true; 843 | } 844 | CheckResponse checkOpResponse = checkOp(commandSender, method); 845 | if (!checkOpResponse.isSuccess()) { 846 | handleOpError(commandSender, checkOpResponse.getMessage()); 847 | return true; 848 | } 849 | CheckResponse checkNotLockResponse = checkNotLock(commandSender, method); 850 | if (!checkNotLockResponse.isSuccess()) { 851 | handleNotLockError(commandSender, checkNotLockResponse.getMessage()); 852 | return true; 853 | } 854 | CheckResponse checkNotInCDResponse = checkNotInCD(commandSender); 855 | if (!checkNotInCDResponse.isSuccess()) { 856 | handleNotInCDError(commandSender, checkNotInCDResponse.getMessage()); 857 | return true; 858 | } 859 | Object[] params = buildParams(strings, method, commandSender); 860 | if (params == null) { 861 | return true; 862 | } 863 | BukkitRunnable bukkitRunnable = new BukkitRunnable() { 864 | @Override 865 | public void run() { 866 | UsageLimit usageLimit = method.getAnnotation(UsageLimit.class); 867 | 868 | if (usageLimit != null) { 869 | if (usageLimit.value().equals(UsageLimit.LimitType.ALL)) { 870 | serverLock.put(((Player) commandSender).getUniqueId(), method); 871 | } else if (usageLimit.value().equals(UsageLimit.LimitType.SENDER) && commandSender instanceof Player) { 872 | senderLock.put(((Player) commandSender).getUniqueId(), method); 873 | } 874 | } 875 | 876 | try { 877 | setCoolDown(commandSender, method); 878 | ReflectUtil.invoke(getInstance(), method, params); 879 | } finally { 880 | if (usageLimit != null) { 881 | if (usageLimit.value().equals(UsageLimit.LimitType.ALL)) { 882 | serverLock.remove(((Player) commandSender).getUniqueId()); 883 | } else if (usageLimit.value().equals(UsageLimit.LimitType.SENDER) && commandSender instanceof Player) { 884 | senderLock.remove(((Player) commandSender).getUniqueId()); 885 | } 886 | } 887 | } 888 | } 889 | }; 890 | if (method.isAnnotationPresent(RunAsync.class)) { 891 | bukkitRunnable.runTaskAsynchronously(plugin); 892 | } else { 893 | bukkitRunnable.runTask(plugin); 894 | } 895 | return true; 896 | } 897 | 898 | private CheckResponse checkParameters(String[] args, Method method, Command command) { 899 | // 从 mappings 中获取 method 对应的格式字符串 900 | String format = mappings.inverse().get(method); 901 | // 按空格分割格式字符串 902 | String[] formatArgs = format.split(" "); 903 | 904 | // 如果格式字符串中的参数数量与传入的参数数量不一致 905 | if (formatArgs.length != args.length) { 906 | int min = Math.min(formatArgs.length, args.length); 907 | 908 | // 如果传入的参数多于格式字符串中的参数 909 | if (formatArgs.length < args.length) { 910 | for (int i = 0; i < min; i++) { 911 | // 检查最后一个格式参数是否是变长参数 912 | if (formatArgs[formatArgs.length - 1].endsWith("...>")) { 913 | return CheckResponse.SUCCESS; 914 | } 915 | // 如果当前参数不匹配 916 | if (matchesArgument(formatArgs[i], args[i])) { 917 | // 拼接传入的参数字符串 918 | StringBuilder commandArgsStr = new StringBuilder(" "); 919 | for (int j = 0; j < min; j++) { 920 | commandArgsStr.append("§7").append(args[j]).append(" "); 921 | } 922 | // 告知错误位置 923 | return new CheckResponse( 924 | String.format( 925 | "§cArgument Error: §7/%s %s§c§n%s§r §c<--§o[Error Here]\b§eCorrect Usage: §7/%s %s", 926 | command.getName(), 927 | commandArgsStr, 928 | args[min], 929 | command.getName(), 930 | format 931 | ) 932 | ); 933 | } 934 | } 935 | } else { 936 | // 如果传入的参数少于格式字符串中的参数 937 | // 拼接传入的参数字符串 938 | StringBuilder commandArgsStr = new StringBuilder(" "); 939 | for (int j = 0; j < min; j++) { 940 | commandArgsStr.append("§7").append(args[j]).append(" "); 941 | } 942 | // 拼接缺少的参数字符串 943 | StringBuilder missingParameters = new StringBuilder(); 944 | for (int j = min; j < formatArgs.length; j++) { 945 | missingParameters.append("§c§n").append(formatArgs[j]).append(" "); 946 | } 947 | missingParameters = new StringBuilder(missingParameters.toString().trim()); 948 | // 告知缺少参数的位置 949 | return new CheckResponse( 950 | String.format( 951 | "§cMissing Parameters: §7/%s %s§c§n%s§r §c<--§o[Missing Part]\b§eCorrect Usage: §7/%s %s", 952 | command.getName(), 953 | commandArgsStr, 954 | missingParameters, 955 | command.getName(), 956 | format 957 | ) 958 | ); 959 | } 960 | } 961 | return CheckResponse.SUCCESS; 962 | } 963 | 964 | /** 965 | * Requests a list of possible completions for a command argument. 966 | *

967 | * 请求命令参数的可能补全列表。 968 | * 969 | * @param commandSender Source of the command. For players tab-completing a 970 | * command inside of a command block, this will be the player, not 971 | * the command block. 972 | * @param command Command which was executed 973 | * @param s Alias of the command which was used 974 | * @param strings The arguments passed to the command, including final 975 | * partial argument to be completed 976 | * @return A List of possible completions for the final argument, or null 977 | */ 978 | @Nullable 979 | @Override 980 | public List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) { 981 | Class clazz = this.getClass(); 982 | if (!(commandSender instanceof Player)) { 983 | return null; 984 | } 985 | if (!clazz.isAnnotationPresent(CmdTarget.class)) { 986 | return null; 987 | } 988 | CmdTarget cmdTarget = clazz.getAnnotation(CmdTarget.class); 989 | if (cmdTarget.value().equals(CmdTarget.CmdTargetType.CONSOLE)) { 990 | return null; 991 | } 992 | return suggest((Player) commandSender, command, strings); 993 | } 994 | 995 | /** 996 | * Handles the error when there is a parameter issue. 997 | * 998 | * @param sender The sender of the command. 999 | * @param message The error message to be sent. 1000 | */ 1001 | public void handleParameterError(CommandSender sender, String message) { 1002 | sender.sendMessage(ChatColor.RED + message); 1003 | } 1004 | 1005 | /** 1006 | * Handles the error when the sender is invalid. 1007 | * 1008 | * @param sender The sender of the command. 1009 | * @param message The error message to be sent. 1010 | */ 1011 | public void handleSenderError(CommandSender sender, String message) { 1012 | sender.sendMessage(ChatColor.RED + message); 1013 | } 1014 | 1015 | /** 1016 | * Handles the error when the sender lacks permission. 1017 | * 1018 | * @param sender The sender of the command. 1019 | * @param message The error message to be sent. 1020 | */ 1021 | public void handlePermissionError(CommandSender sender, String message) { 1022 | sender.sendMessage(ChatColor.RED + message); 1023 | } 1024 | 1025 | /** 1026 | * Handles the error when the sender is not an operator (OP). 1027 | * 1028 | * @param sender The sender of the command. 1029 | * @param message The error message to be sent. 1030 | */ 1031 | public void handleOpError(CommandSender sender, String message) { 1032 | sender.sendMessage(ChatColor.RED + message); 1033 | } 1034 | 1035 | /** 1036 | * Handles the error when the sender needs to wait for the previous command to finish. 1037 | * 1038 | * @param sender The sender of the command. 1039 | * @param message The error message to be sent. 1040 | */ 1041 | public void handleNotLockError(CommandSender sender, String message) { 1042 | sender.sendMessage(ChatColor.RED + message); 1043 | } 1044 | 1045 | /** 1046 | * Handles the error when the sender needs to wait for the command cooldown. 1047 | * 1048 | * @param sender The sender of the command. 1049 | * @param message The error message to be sent. 1050 | */ 1051 | public void handleNotInCDError(CommandSender sender, String message) { 1052 | sender.sendMessage(ChatColor.RED + message); 1053 | } 1054 | } 1055 | --------------------------------------------------------------------------------