CommandMap.java 10.4 KB
Newer Older
Jaida Wu's avatar
Jaida Wu committed
1
package emu.grasscutter.command;
KingRainbow44's avatar
KingRainbow44 committed
2
3

import emu.grasscutter.Grasscutter;
Melledy's avatar
Melledy committed
4
import emu.grasscutter.game.player.Player;
KingRainbow44's avatar
KingRainbow44 committed
5
6
7
8
import org.reflections.Reflections;

import java.util.*;

Jaida Wu's avatar
Jaida Wu committed
9
@SuppressWarnings({"UnusedReturnValue", "unused"})
KingRainbow44's avatar
KingRainbow44 committed
10
public final class CommandMap {
Jaida Wu's avatar
Jaida Wu committed
11
    private final Map<String, CommandHandler> commands = new HashMap<>();
12
    private final Map<String, CommandHandler> aliases = new HashMap<>();
Jaida Wu's avatar
Jaida Wu committed
13
    private final Map<String, Command> annotations = new HashMap<>();
AnimeGitB's avatar
AnimeGitB committed
14
    private final Map<String, Integer> targetPlayerIds = new HashMap<>();
AnimeGitB's avatar
AnimeGitB committed
15
    private static final String consoleId = "console";
4Benj_'s avatar
4Benj_ committed
16

Jaida Wu's avatar
Jaida Wu committed
17
18
19
20
21
22
23
24
    public CommandMap() {
        this(false);
    }

    public CommandMap(boolean scan) {
        if (scan) this.scan();
    }

KingRainbow44's avatar
KingRainbow44 committed
25
26
27
28
29
30
    public static CommandMap getInstance() {
        return Grasscutter.getGameServer().getCommandMap();
    }

    /**
     * Register a command handler.
Jaida Wu's avatar
Jaida Wu committed
31
32
     *
     * @param label   The command label.
KingRainbow44's avatar
KingRainbow44 committed
33
34
35
     * @param command The command handler.
     * @return Instance chaining.
     */
36
    @Deprecated(since = "1.2.1-dev")
KingRainbow44's avatar
KingRainbow44 committed
37
    public CommandMap registerCommand(String label, CommandHandler command) {
38
        Grasscutter.getLogger().debug("Registered command: " + label);
Jaida Wu's avatar
Jaida Wu committed
39

KingRainbow44's avatar
KingRainbow44 committed
40
        // Get command data.
41
        Command annotation = command.getClass().getAnnotation(Command.class);
42
        this.annotations.put(label, annotation);
KingRainbow44's avatar
KingRainbow44 committed
43
        this.commands.put(label, command);
Jaida Wu's avatar
Jaida Wu committed
44

KingRainbow44's avatar
KingRainbow44 committed
45
        // Register aliases.
Jaida Wu's avatar
Jaida Wu committed
46
        if (annotation.aliases().length > 0) {
KingRainbow44's avatar
KingRainbow44 committed
47
            for (String alias : annotation.aliases()) {
48
                this.aliases.put(alias, command);
49
                this.annotations.put(alias, annotation);
KingRainbow44's avatar
KingRainbow44 committed
50
            }
Jaida Wu's avatar
Jaida Wu committed
51
52
        }
        return this;
KingRainbow44's avatar
KingRainbow44 committed
53
54
55
56
    }

    /**
     * Removes a registered command handler.
Jaida Wu's avatar
Jaida Wu committed
57
     *
KingRainbow44's avatar
KingRainbow44 committed
58
59
60
61
     * @param label The command label.
     * @return Instance chaining.
     */
    public CommandMap unregisterCommand(String label) {
62
        Grasscutter.getLogger().debug("Unregistered command: " + label);
63

64
        CommandHandler handler = this.commands.get(label);
Jaida Wu's avatar
Jaida Wu committed
65
66
        if (handler == null) return this;

67
        Command annotation = handler.getClass().getAnnotation(Command.class);
68
        this.annotations.remove(label);
KingRainbow44's avatar
KingRainbow44 committed
69
        this.commands.remove(label);
Jaida Wu's avatar
Jaida Wu committed
70

KingRainbow44's avatar
KingRainbow44 committed
71
        // Unregister aliases.
Jaida Wu's avatar
Jaida Wu committed
72
        if (annotation.aliases().length > 0) {
KingRainbow44's avatar
KingRainbow44 committed
73
            for (String alias : annotation.aliases()) {
74
                this.aliases.remove(alias);
75
                this.annotations.remove(alias);
KingRainbow44's avatar
KingRainbow44 committed
76
77
            }
        }
Jaida Wu's avatar
Jaida Wu committed
78

79
        return this;
KingRainbow44's avatar
KingRainbow44 committed
80
81
    }

82
83
84
    public List<Command> getAnnotationsAsList() {
        return new LinkedList<>(this.annotations.values());
    }
85
86
87
88
89

    public HashMap<String, Command> getAnnotations() {
        return new LinkedHashMap<>(this.annotations);
    }

KingRainbow44's avatar
KingRainbow44 committed
90
91
    /**
     * Returns a list of all registered commands.
Jaida Wu's avatar
Jaida Wu committed
92
     *
KingRainbow44's avatar
KingRainbow44 committed
93
94
     * @return All command handlers as a list.
     */
95
    public List<CommandHandler> getHandlersAsList() {
KingRainbow44's avatar
KingRainbow44 committed
96
97
98
        return new LinkedList<>(this.commands.values());
    }

Jaida Wu's avatar
Jaida Wu committed
99
100
101
    public HashMap<String, CommandHandler> getHandlers() {
        return new LinkedHashMap<>(this.commands);
    }
102

KingRainbow44's avatar
KingRainbow44 committed
103
104
    /**
     * Returns a handler by label/alias.
Jaida Wu's avatar
Jaida Wu committed
105
     *
KingRainbow44's avatar
KingRainbow44 committed
106
107
108
109
110
111
112
     * @param label The command label.
     * @return The command handler.
     */
    public CommandHandler getHandler(String label) {
        return this.commands.get(label);
    }

KingRainbow44's avatar
KingRainbow44 committed
113
114
    /**
     * Invoke a command handler with the given arguments.
Jaida Wu's avatar
Jaida Wu committed
115
116
     *
     * @param player     The player invoking the command or null for the server console.
KingRainbow44's avatar
KingRainbow44 committed
117
118
     * @param rawMessage The messaged used to invoke the command.
     */
AnimeGitB's avatar
AnimeGitB committed
119
    public void invoke(Player player, Player targetPlayer, String rawMessage) {
120
        rawMessage = rawMessage.trim();
BaiSugar's avatar
BaiSugar committed
121
        if (rawMessage.length() == 0) {
122
            CommandHandler.sendTranslatedMessage(player, "commands.generic.not_specified");
BaiSugar's avatar
BaiSugar committed
123
            return;
124
        }
Jaida Wu's avatar
Jaida Wu committed
125

KingRainbow44's avatar
KingRainbow44 committed
126
127
        // Parse message.
        String[] split = rawMessage.split(" ");
128
        List<String> args = new LinkedList<>(Arrays.asList(split));
KingRainbow44's avatar
KingRainbow44 committed
129
        String label = args.remove(0);
AnimeGitB's avatar
AnimeGitB committed
130
        String playerId = (player == null) ? consoleId : player.getAccount().getId();
131

132
        // Check for special cases - currently only target command.
AnimeGitB's avatar
AnimeGitB committed
133
        String targetUidStr = null;
134
        if (label.startsWith("@")) { // @[UID]
AnimeGitB's avatar
AnimeGitB committed
135
            targetUidStr = label.substring(1);
136
        } else if (label.equalsIgnoreCase("target")) { // target [[@]UID]
AnimeGitB's avatar
AnimeGitB committed
137
138
139
140
141
142
143
            if (args.size() > 0) {
                targetUidStr = args.get(0);
                if (targetUidStr.startsWith("@")) {
                    targetUidStr = targetUidStr.substring(1);
                }
            } else {
                targetUidStr = "";
AnimeGitB's avatar
AnimeGitB committed
144
145
            }
        }
AnimeGitB's avatar
AnimeGitB committed
146
        if (targetUidStr != null) {
147
            if (targetUidStr.equals("")) { // Clears the default targetPlayer.
148
                this.targetPlayerIds.remove(playerId);
149
                CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target");
150
            } else { // Sets default targetPlayer to the UID provided.
AnimeGitB's avatar
AnimeGitB committed
151
152
                try {
                    int uid = Integer.parseInt(targetUidStr);
153
                    targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
AnimeGitB's avatar
AnimeGitB committed
154
                    if (targetPlayer == null) {
155
                        CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
AnimeGitB's avatar
AnimeGitB committed
156
                    } else {
157
                        this.targetPlayerIds.put(playerId, uid);
158
                        CommandHandler.sendTranslatedMessage(player, "commands.execution.set_target", targetUidStr);
159
                        CommandHandler.sendTranslatedMessage(player, targetPlayer.isOnline() ? "commands.execution.set_target_online" : "commands.execution.set_target_offline", targetUidStr);
AnimeGitB's avatar
AnimeGitB committed
160
161
                    }
                } catch (NumberFormatException e) {
162
                    CommandHandler.sendTranslatedMessage(player, "commands.execution.uid_error");
AnimeGitB's avatar
AnimeGitB committed
163
164
                }
            }
165
            return;
AnimeGitB's avatar
AnimeGitB committed
166
        }
AnimeGitB's avatar
AnimeGitB committed
167

KingRainbow44's avatar
KingRainbow44 committed
168
169
        // Get command handler.
        CommandHandler handler = this.commands.get(label);
170
171
172
173
174
        if(handler == null)
            // Try to get the handler by alias.
            handler = this.aliases.get(label);

        // Check if the handler is still null.
Jaida Wu's avatar
Jaida Wu committed
175
        if (handler == null) {
176
            CommandHandler.sendTranslatedMessage(player, "commands.generic.unknown_command", label);
Jaida Wu's avatar
Jaida Wu committed
177
            return;
KingRainbow44's avatar
KingRainbow44 committed
178
        }
Jaida Wu's avatar
Jaida Wu committed
179

180
181
182
        // Get the command's annotation.
        Command annotation = this.annotations.get(label);

183
        // If any @UID argument is present, override targetPlayer with it.
AnimeGitB's avatar
AnimeGitB committed
184
185
        for (int i = 0; i < args.size(); i++) {
            String arg = args.get(i);
186
            if (arg.startsWith("@")) {
AnimeGitB's avatar
AnimeGitB committed
187
188
189
                arg = args.remove(i).substring(1);
                try {
                    int uid = Integer.parseInt(arg);
190
                    targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
AnimeGitB's avatar
AnimeGitB committed
191
                    if (targetPlayer == null) {
192
                        CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
AnimeGitB's avatar
AnimeGitB committed
193
194
195
196
                        return;
                    }
                    break;
                } catch (NumberFormatException e) {
197
                    CommandHandler.sendTranslatedMessage(player, "commands.execution.uid_error");
AnimeGitB's avatar
AnimeGitB committed
198
199
200
201
                    return;
                }
            }
        }
202

AnimeGitB's avatar
AnimeGitB committed
203
204
        // If there's still no targetPlayer at this point, use previously-set target
        if (targetPlayer == null) {
205
206
            if (this.targetPlayerIds.containsKey(playerId)) {
                targetPlayer = Grasscutter.getGameServer().getPlayerByUid(this.targetPlayerIds.get(playerId), true);  // We check every time in case the target is deleted after being targeted
AnimeGitB's avatar
AnimeGitB committed
207
                if (targetPlayer == null) {
208
                    CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
AnimeGitB's avatar
AnimeGitB committed
209
210
211
                    return;
                }
            } else {
212
213
                // If there's still no targetPlayer at this point, use executor.
                targetPlayer = player;
AnimeGitB's avatar
AnimeGitB committed
214
            }
AnimeGitB's avatar
AnimeGitB committed
215
216
        }

4Benj_'s avatar
4Benj_ committed
217
        // Check for permissions.
218
        if (!Grasscutter.getPermissionHandler().checkPermission(player, targetPlayer, annotation.permission(), this.annotations.get(label).permissionTargeted())) {
4Benj_'s avatar
4Benj_ committed
219
            return;
220
221
222
        }

        // Check if command has unfulfilled constraints on targetPlayer
223
        Command.TargetRequirement targetRequirement = annotation.targetRequirement();
224
225
        if (targetRequirement != Command.TargetRequirement.NONE) {
            if (targetPlayer == null) {
226
                CommandHandler.sendTranslatedMessage(null, "commands.execution.need_target");
227
228
                return;
            }
229

230
231
232
233
            if ((targetRequirement == Command.TargetRequirement.ONLINE) && !targetPlayer.isOnline()) {
                CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_online");
                return;
            }
234

235
236
            if ((targetRequirement == Command.TargetRequirement.OFFLINE) && targetPlayer.isOnline()) {
                CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_offline");
Jaida Wu's avatar
Jaida Wu committed
237
                return;
238
239
            }
        }
Jaida Wu's avatar
Jaida Wu committed
240

241
242
243
244
        // Copy player and handler to final properties.
        final Player targetPlayerF = targetPlayer; // Is there a better way to do this?
        final CommandHandler handlerF = handler; // Is there a better way to do this?

KingRainbow44's avatar
KingRainbow44 committed
245
        // Invoke execute method for handler.
246
247
        Runnable runnable = () -> handlerF.execute(player, targetPlayerF, args);
        if (annotation.threading()) {
248
249
            new Thread(runnable).start();
        } else {
BaiSugar's avatar
BaiSugar committed
250
251
            runnable.run();
        }
KingRainbow44's avatar
KingRainbow44 committed
252
253
254
255
256
257
258
    }

    /**
     * Scans for all classes annotated with {@link Command} and registers them.
     */
    private void scan() {
        Reflections reflector = Grasscutter.reflector;
259
        Set<Class<?>> classes = reflector.getTypesAnnotatedWith(Command.class);
260

KingRainbow44's avatar
KingRainbow44 committed
261
262
        classes.forEach(annotated -> {
            try {
263
                Command cmdData = annotated.getAnnotation(Command.class);
264
                Object object = annotated.getDeclaredConstructor().newInstance();
KingRainbow44's avatar
KingRainbow44 committed
265
266
                if (object instanceof CommandHandler)
                    this.registerCommand(cmdData.label(), (CommandHandler) object);
267
268
269
270
                else Grasscutter.getLogger().error("Class " + annotated.getName() + " is not a CommandHandler!");
            } catch (Exception exception) {
                Grasscutter.getLogger().error("Failed to register command handler for " + annotated.getSimpleName(), exception);
            }
KingRainbow44's avatar
KingRainbow44 committed
271
272
273
        });
    }
}