CommandMap.java 9.66 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;
4
import emu.grasscutter.game.Account;
Melledy's avatar
Melledy committed
5
6
import emu.grasscutter.game.player.Player;

KingRainbow44's avatar
KingRainbow44 committed
7
8
9
10
import org.reflections.Reflections;

import java.util.*;

11
12
import static emu.grasscutter.utils.Language.translate;

Jaida Wu's avatar
Jaida Wu committed
13
@SuppressWarnings({"UnusedReturnValue", "unused"})
KingRainbow44's avatar
KingRainbow44 committed
14
public final class CommandMap {
Jaida Wu's avatar
Jaida Wu committed
15
16
    private final Map<String, CommandHandler> commands = new HashMap<>();
    private final Map<String, Command> annotations = new HashMap<>();
AnimeGitB's avatar
AnimeGitB committed
17
    private final Map<String, Integer> targetPlayerIds = new HashMap<>();
AnimeGitB's avatar
AnimeGitB committed
18
    private static final String consoleId = "console";
Jaida Wu's avatar
Jaida Wu committed
19
20
21
22
23
24
25
26
    public CommandMap() {
        this(false);
    }

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

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

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

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

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

    /**
     * Removes a registered command handler.
Jaida Wu's avatar
Jaida Wu committed
58
     *
KingRainbow44's avatar
KingRainbow44 committed
59
60
61
62
     * @param label The command label.
     * @return Instance chaining.
     */
    public CommandMap unregisterCommand(String label) {
63
64
        Grasscutter.getLogger().debug("Unregistered command: " + label);
        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.commands.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
85
86
87
    public List<Command> getAnnotationsAsList() { return new LinkedList<>(this.annotations.values()); }

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

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

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

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

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

KingRainbow44's avatar
KingRainbow44 committed
124
125
        // Parse message.
        String[] split = rawMessage.split(" ");
126
        List<String> args = new LinkedList<>(Arrays.asList(split));
KingRainbow44's avatar
KingRainbow44 committed
127
        String label = args.remove(0);
AnimeGitB's avatar
AnimeGitB committed
128
        String playerId = (player == null) ? consoleId : player.getAccount().getId();
129
130
        
        // Check for special cases - currently only target command.
AnimeGitB's avatar
AnimeGitB committed
131
        String targetUidStr = null;
132
        if (label.startsWith("@")) { // @[UID]
AnimeGitB's avatar
AnimeGitB committed
133
            targetUidStr = label.substring(1);
134
        } else if (label.equalsIgnoreCase("target")) { // target [[@]UID]
AnimeGitB's avatar
AnimeGitB committed
135
136
137
138
139
140
141
            if (args.size() > 0) {
                targetUidStr = args.get(0);
                if (targetUidStr.startsWith("@")) {
                    targetUidStr = targetUidStr.substring(1);
                }
            } else {
                targetUidStr = "";
AnimeGitB's avatar
AnimeGitB committed
142
143
            }
        }
AnimeGitB's avatar
AnimeGitB committed
144
        if (targetUidStr != null) {
145
            if (targetUidStr.equals("")) { // Clears the default targetPlayer.
AnimeGitB's avatar
AnimeGitB committed
146
                targetPlayerIds.remove(playerId);
147
148
                CommandHandler.sendMessage(player, translate("commands.execution.clear_target"));
            } else { // Sets default targetPlayer to the UID provided.
AnimeGitB's avatar
AnimeGitB committed
149
150
151
152
                try {
                    int uid = Integer.parseInt(targetUidStr);
                    targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid);
                    if (targetPlayer == null) {
153
                        CommandHandler.sendMessage(player, translate("commands.generic.execution.player_exist_offline_error"));
AnimeGitB's avatar
AnimeGitB committed
154
155
                    } else {
                        targetPlayerIds.put(playerId, uid);
156
                        CommandHandler.sendMessage(player, translate("commands.execution.set_target", targetUidStr));
AnimeGitB's avatar
AnimeGitB committed
157
158
                    }
                } catch (NumberFormatException e) {
159
                    CommandHandler.sendMessage(player, translate("commands.execution.uid_error"));
AnimeGitB's avatar
AnimeGitB committed
160
161
                }
            }
162
            return;
AnimeGitB's avatar
AnimeGitB committed
163
        }
AnimeGitB's avatar
AnimeGitB committed
164

KingRainbow44's avatar
KingRainbow44 committed
165
166
        // Get command handler.
        CommandHandler handler = this.commands.get(label);
Jaida Wu's avatar
Jaida Wu committed
167
        if (handler == null) {
168
            CommandHandler.sendMessage(player, translate("commands.generic.unknown_command", label));
Jaida Wu's avatar
Jaida Wu committed
169
            return;
KingRainbow44's avatar
KingRainbow44 committed
170
        }
Jaida Wu's avatar
Jaida Wu committed
171

172
        // If any @UID argument is present, override targetPlayer with it.
AnimeGitB's avatar
AnimeGitB committed
173
174
        for (int i = 0; i < args.size(); i++) {
            String arg = args.get(i);
175
            if (arg.startsWith("@")) {
AnimeGitB's avatar
AnimeGitB committed
176
177
178
179
180
                arg = args.remove(i).substring(1);
                try {
                    int uid = Integer.parseInt(arg);
                    targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid);
                    if (targetPlayer == null) {
181
                        CommandHandler.sendMessage(player, translate("commands.generic.execution.player_exist_offline_error"));
AnimeGitB's avatar
AnimeGitB committed
182
183
184
185
                        return;
                    }
                    break;
                } catch (NumberFormatException e) {
186
                    CommandHandler.sendMessage(player, translate("commands.execution.uid_error"));
AnimeGitB's avatar
AnimeGitB committed
187
188
189
190
                    return;
                }
            }
        }
191
        
AnimeGitB's avatar
AnimeGitB committed
192
193
        // If there's still no targetPlayer at this point, use previously-set target
        if (targetPlayer == null) {
AnimeGitB's avatar
AnimeGitB committed
194
195
196
            if (targetPlayerIds.containsKey(playerId)) {
                targetPlayer = Grasscutter.getGameServer().getPlayerByUid(targetPlayerIds.get(playerId));  // We check every time in case the target goes offline after being targeted
                if (targetPlayer == null) {
197
                    CommandHandler.sendMessage(player, translate("commands.generic.execution.player_exist_offline_error"));
AnimeGitB's avatar
AnimeGitB committed
198
199
200
                    return;
                }
            } else {
201
202
                // If there's still no targetPlayer at this point, use executor.
                targetPlayer = player;
AnimeGitB's avatar
AnimeGitB committed
203
            }
AnimeGitB's avatar
AnimeGitB committed
204
205
        }

206
        // Check for permission.
Jaida Wu's avatar
Jaida Wu committed
207
        if (player != null) {
208
            String permissionNode = this.annotations.get(label).permission();
AnimeGitB's avatar
AnimeGitB committed
209
            String permissionNodeTargeted = this.annotations.get(label).permissionTargeted();
210
            Account account = player.getAccount();
AnimeGitB's avatar
AnimeGitB committed
211
212
            if (player != targetPlayer) {  // Additional permission required for targeting another player
                if (!permissionNodeTargeted.isEmpty() && !account.hasPermission(permissionNodeTargeted)) {
213
                    CommandHandler.sendMessage(player, translate("commands.generic.permission_error"));
AnimeGitB's avatar
AnimeGitB committed
214
215
216
                    return;
                }
            }
BaiSugar's avatar
BaiSugar committed
217
            if (!permissionNode.isEmpty() && !account.hasPermission(permissionNode)) {
218
                CommandHandler.sendMessage(player, translate("commands.generic.permission_error"));
Jaida Wu's avatar
Jaida Wu committed
219
                return;
220
221
            }
        }
Jaida Wu's avatar
Jaida Wu committed
222

KingRainbow44's avatar
KingRainbow44 committed
223
        // Invoke execute method for handler.
BaiSugar's avatar
BaiSugar committed
224
        boolean threading  = this.annotations.get(label).threading();
AnimeGitB's avatar
AnimeGitB committed
225
226
        final Player targetPlayerF = targetPlayer;  // Is there a better way to do this?
        Runnable runnable = () -> handler.execute(player, targetPlayerF, args);
BaiSugar's avatar
BaiSugar committed
227
        if(threading) {
228
229
            new Thread(runnable).start();
        } else {
BaiSugar's avatar
BaiSugar committed
230
231
            runnable.run();
        }
KingRainbow44's avatar
KingRainbow44 committed
232
233
234
235
236
237
238
    }

    /**
     * Scans for all classes annotated with {@link Command} and registers them.
     */
    private void scan() {
        Reflections reflector = Grasscutter.reflector;
239
        Set<Class<?>> classes = reflector.getTypesAnnotatedWith(Command.class);
KingRainbow44's avatar
KingRainbow44 committed
240
241
        classes.forEach(annotated -> {
            try {
242
243
                Command cmdData = annotated.getAnnotation(Command.class);
                Object object = annotated.newInstance();
KingRainbow44's avatar
KingRainbow44 committed
244
245
                if (object instanceof CommandHandler)
                    this.registerCommand(cmdData.label(), (CommandHandler) object);
246
247
248
249
                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
250
251
252
        });
    }
}