├── 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 extends AbstractCommendExecutor> 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 extends AbstractCommendExecutor> 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 extends AbstractCommendExecutor> 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 extends AbstractCommendExecutor> 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 extends AbstractCommendExecutor> 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 |
--------------------------------------------------------------------------------