CommandMap.java 11.5 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
36
     * @param command The command handler.
     * @return Instance chaining.
     */
    public CommandMap registerCommand(String label, CommandHandler command) {
37
        Grasscutter.getLogger().debug("Registered command: " + label);
Jaida Wu's avatar
Jaida Wu committed
38

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

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

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

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

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

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

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

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

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

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

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

KingRainbow44's avatar
KingRainbow44 committed
102
103
    /**
     * Returns a handler by label/alias.
Jaida Wu's avatar
Jaida Wu committed
104
     *
KingRainbow44's avatar
KingRainbow44 committed
105
106
107
108
     * @param label The command label.
     * @return The command handler.
     */
    public CommandHandler getHandler(String label) {
Luke H-W's avatar
Luke H-W committed
109
110
111
112
113
114
        CommandHandler handler = this.commands.get(label);
        if (handler == null) {
            // Try getting by alias
            handler = this.aliases.get(label);
        }
        return handler;
KingRainbow44's avatar
KingRainbow44 committed
115
116
    }

117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
    private Player getTargetPlayer(String playerId, Player player, Player targetPlayer, List<String> args) {
        // Top priority: If any @UID argument is present, override targetPlayer with it.
        for (int i = 0; i < args.size(); i++) {
            String arg = args.get(i);
            if (arg.startsWith("@")) {
                arg = args.remove(i).substring(1);
                if (arg.equals("")) {
                    // This is a special case to target nothing, distinct from failing to assign a target.
                    // This is specifically to allow in-game players to run a command without targeting themselves or anyone else.
                    return null;
                }
                try {
                    int uid = Integer.parseInt(arg);
                    targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
                    if (targetPlayer == null) {
                        CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
                        throw new IllegalArgumentException();
                    }
                    return targetPlayer;
                } catch (NumberFormatException e) {
Luke H-W's avatar
Luke H-W committed
137
                    CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid");
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
                    throw new IllegalArgumentException();
                }
            }
        }

        // Next priority: If we invoked with a target, use that.
        // By default, this only happens when you message another player in-game with a command.
        if (targetPlayer != null) {
            return targetPlayer;
        }

        // Next priority: Use previously-set target. (see /target [[@]UID])
        if (targetPlayerIds.containsKey(playerId)) {
            targetPlayer = Grasscutter.getGameServer().getPlayerByUid(targetPlayerIds.get(playerId), true);
            // We check every time in case the target is deleted after being targeted
            if (targetPlayer == null) {
                CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
                throw new IllegalArgumentException();
            }
            return targetPlayer;
        }

        // Lowest priority: Target the player invoking the command. In the case of the console, this will return null.
        return player;
    }

    private boolean setPlayerTarget(String playerId, Player player, String targetUid) {
        if (targetUid.equals("")) { // Clears the default targetPlayer.
                targetPlayerIds.remove(playerId);
                CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target");
                return true;
        }
        
        // Sets default targetPlayer to the UID provided.
        try {
            int uid = Integer.parseInt(targetUid);
            Player targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
            if (targetPlayer == null) {
                CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
                return false;
            }

            targetPlayerIds.put(playerId, uid);
            CommandHandler.sendTranslatedMessage(player, "commands.execution.set_target", targetUid);
            CommandHandler.sendTranslatedMessage(player, targetPlayer.isOnline()? "commands.execution.set_target_online" : "commands.execution.set_target_offline", targetUid);
            return true;
        } catch (NumberFormatException e) {
Luke H-W's avatar
Luke H-W committed
185
            CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid");
186
187
188
189
            return false;
        }
    }

KingRainbow44's avatar
KingRainbow44 committed
190
191
    /**
     * Invoke a command handler with the given arguments.
Jaida Wu's avatar
Jaida Wu committed
192
193
     *
     * @param player     The player invoking the command or null for the server console.
KingRainbow44's avatar
KingRainbow44 committed
194
195
     * @param rawMessage The messaged used to invoke the command.
     */
AnimeGitB's avatar
AnimeGitB committed
196
    public void invoke(Player player, Player targetPlayer, String rawMessage) {
197
        rawMessage = rawMessage.trim();
BaiSugar's avatar
BaiSugar committed
198
        if (rawMessage.length() == 0) {
199
            CommandHandler.sendTranslatedMessage(player, "commands.generic.not_specified");
BaiSugar's avatar
BaiSugar committed
200
            return;
201
        }
Jaida Wu's avatar
Jaida Wu committed
202

KingRainbow44's avatar
KingRainbow44 committed
203
204
        // Parse message.
        String[] split = rawMessage.split(" ");
205
        List<String> args = new LinkedList<>(Arrays.asList(split));
KingRainbow44's avatar
KingRainbow44 committed
206
        String label = args.remove(0);
AnimeGitB's avatar
AnimeGitB committed
207
        String playerId = (player == null) ? consoleId : player.getAccount().getId();
208

209
210
        // Check for special cases - currently only target command.
        if (label.startsWith("@")) { // @[UID]
211
212
            this.setPlayerTarget(playerId, player, label.substring(1));
            return;
213
        } else if (label.equalsIgnoreCase("target")) { // target [[@]UID]
AnimeGitB's avatar
AnimeGitB committed
214
            if (args.size() > 0) {
215
                String targetUidStr = args.get(0);
AnimeGitB's avatar
AnimeGitB committed
216
217
218
                if (targetUidStr.startsWith("@")) {
                    targetUidStr = targetUidStr.substring(1);
                }
219
220
                this.setPlayerTarget(playerId, player, targetUidStr);
            return;
AnimeGitB's avatar
AnimeGitB committed
221
            } else {
222
223
                this.setPlayerTarget(playerId, player, "");
                return;
AnimeGitB's avatar
AnimeGitB committed
224
            }
AnimeGitB's avatar
AnimeGitB committed
225
        }
AnimeGitB's avatar
AnimeGitB committed
226

KingRainbow44's avatar
KingRainbow44 committed
227
        // Get command handler.
Luke H-W's avatar
Luke H-W committed
228
        CommandHandler handler = this.getHandler(label);
229

Luke H-W's avatar
Luke H-W committed
230
        // Check if the handler is null.
Jaida Wu's avatar
Jaida Wu committed
231
        if (handler == null) {
232
            CommandHandler.sendTranslatedMessage(player, "commands.generic.unknown_command", label);
Jaida Wu's avatar
Jaida Wu committed
233
            return;
KingRainbow44's avatar
KingRainbow44 committed
234
        }
Jaida Wu's avatar
Jaida Wu committed
235

236
237
238
        // Get the command's annotation.
        Command annotation = this.annotations.get(label);

239
240
241
242
243
        // Resolve targetPlayer
        try{
            targetPlayer = getTargetPlayer(playerId, player, targetPlayer, args);
        } catch (IllegalArgumentException e) {
            return;
AnimeGitB's avatar
AnimeGitB committed
244
245
        }

4Benj_'s avatar
4Benj_ committed
246
        // Check for permissions.
247
        if (!Grasscutter.getPermissionHandler().checkPermission(player, targetPlayer, annotation.permission(), this.annotations.get(label).permissionTargeted())) {
4Benj_'s avatar
4Benj_ committed
248
            return;
249
250
251
        }

        // Check if command has unfulfilled constraints on targetPlayer
252
        Command.TargetRequirement targetRequirement = annotation.targetRequirement();
253
254
        if (targetRequirement != Command.TargetRequirement.NONE) {
            if (targetPlayer == null) {
255
                CommandHandler.sendTranslatedMessage(null, "commands.execution.need_target");
256
257
                return;
            }
258

259
260
261
262
            if ((targetRequirement == Command.TargetRequirement.ONLINE) && !targetPlayer.isOnline()) {
                CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_online");
                return;
            }
263

264
265
            if ((targetRequirement == Command.TargetRequirement.OFFLINE) && targetPlayer.isOnline()) {
                CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_offline");
Jaida Wu's avatar
Jaida Wu committed
266
                return;
267
268
            }
        }
Jaida Wu's avatar
Jaida Wu committed
269

270
271
272
273
        // 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
274
        // Invoke execute method for handler.
275
276
        Runnable runnable = () -> handlerF.execute(player, targetPlayerF, args);
        if (annotation.threading()) {
277
278
            new Thread(runnable).start();
        } else {
BaiSugar's avatar
BaiSugar committed
279
280
            runnable.run();
        }
KingRainbow44's avatar
KingRainbow44 committed
281
282
283
284
285
286
287
    }

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

KingRainbow44's avatar
KingRainbow44 committed
290
291
        classes.forEach(annotated -> {
            try {
292
                Command cmdData = annotated.getAnnotation(Command.class);
293
                Object object = annotated.getDeclaredConstructor().newInstance();
KingRainbow44's avatar
KingRainbow44 committed
294
295
                if (object instanceof CommandHandler)
                    this.registerCommand(cmdData.label(), (CommandHandler) object);
296
297
298
299
                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
300
301
302
        });
    }
}