CommandMap.java 8.73 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.*;

Jaida Wu's avatar
Jaida Wu committed
11
@SuppressWarnings({"UnusedReturnValue", "unused"})
KingRainbow44's avatar
KingRainbow44 committed
12
public final class CommandMap {
Jaida Wu's avatar
Jaida Wu committed
13
14
    private final Map<String, CommandHandler> commands = new HashMap<>();
    private final Map<String, Command> annotations = new HashMap<>();
AnimeGitB's avatar
AnimeGitB committed
15
16
    private final Map<String, Player> targetPlayers = new HashMap<>();
    private static final String consoleId = "console";
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.commands.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
62
        Grasscutter.getLogger().debug("Unregistered command: " + label);
        CommandHandler handler = this.commands.get(label);
Jaida Wu's avatar
Jaida Wu committed
63
64
        if (handler == null) return this;

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

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

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

KingRainbow44's avatar
KingRainbow44 committed
80
81
    /**
     * Returns a list of all registered commands.
Jaida Wu's avatar
Jaida Wu committed
82
     *
KingRainbow44's avatar
KingRainbow44 committed
83
84
     * @return All command handlers as a list.
     */
85
    public List<CommandHandler> getHandlersAsList() {
KingRainbow44's avatar
KingRainbow44 committed
86
87
88
        return new LinkedList<>(this.commands.values());
    }

Jaida Wu's avatar
Jaida Wu committed
89
90
91
    public HashMap<String, CommandHandler> getHandlers() {
        return new LinkedHashMap<>(this.commands);
    }
92

KingRainbow44's avatar
KingRainbow44 committed
93
94
    /**
     * Returns a handler by label/alias.
Jaida Wu's avatar
Jaida Wu committed
95
     *
KingRainbow44's avatar
KingRainbow44 committed
96
97
98
99
100
101
102
     * @param label The command label.
     * @return The command handler.
     */
    public CommandHandler getHandler(String label) {
        return this.commands.get(label);
    }

KingRainbow44's avatar
KingRainbow44 committed
103
104
    /**
     * Invoke a command handler with the given arguments.
Jaida Wu's avatar
Jaida Wu committed
105
106
     *
     * @param player     The player invoking the command or null for the server console.
KingRainbow44's avatar
KingRainbow44 committed
107
108
     * @param rawMessage The messaged used to invoke the command.
     */
AnimeGitB's avatar
AnimeGitB committed
109
    public void invoke(Player player, Player targetPlayer, String rawMessage) {
110
        rawMessage = rawMessage.trim();
BaiSugar's avatar
BaiSugar committed
111
        if (rawMessage.length() == 0) {
方块君's avatar
方块君 committed
112
            CommandHandler.sendMessage(player, Grasscutter.getLanguage().No_command_specified);
BaiSugar's avatar
BaiSugar committed
113
            return;
114
        }
Jaida Wu's avatar
Jaida Wu committed
115

KingRainbow44's avatar
KingRainbow44 committed
116
117
        // Parse message.
        String[] split = rawMessage.split(" ");
118
        List<String> args = new LinkedList<>(Arrays.asList(split));
KingRainbow44's avatar
KingRainbow44 committed
119
        String label = args.remove(0);
AnimeGitB's avatar
AnimeGitB committed
120
        String playerId = (player == null) ? consoleId : player.getAccount().getId();
AnimeGitB's avatar
AnimeGitB committed
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
        // Check for special cases - currently only target command
        String targetUidStr = null;
        if (label.startsWith("@")) {  // @[UID]
            targetUidStr = label.substring(1);
        } else if (label == "target") {  // target [[@]UID]
            targetUidStr = args.get(0);
            if (targetUidStr.startsWith("@")) {
                targetUidStr = targetUidStr.substring(1);
            }
        }
        if (targetUidStr == "") {  // Clears default targetPlayer
            targetPlayers.remove(playerId);
            CommandHandler.sendMessage(player, Grasscutter.getLanguage().Target_cleared);
            return;
        } else if (targetUidStr != null) {  // Sets default targetPlayer to the UID given
            try {
                int uid = Integer.parseInt(targetUidStr);
                targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid);
                if (targetPlayer == null) {
                    CommandHandler.sendMessage(player, Grasscutter.getLanguage().Player_not_found_or_offline);
                } else {
                    targetPlayers.put(playerId, targetPlayer);
                    CommandHandler.sendMessage(player, Grasscutter.getLanguage().Target_set.replace("{uid}", targetUidStr));
AnimeGitB's avatar
AnimeGitB committed
144
                }
AnimeGitB's avatar
AnimeGitB committed
145
146
            } catch (NumberFormatException e) {
                CommandHandler.sendMessage(player, Grasscutter.getLanguage().Invalid_UID);
AnimeGitB's avatar
AnimeGitB committed
147
148
149
            }
            return;
        } 
AnimeGitB's avatar
AnimeGitB committed
150

KingRainbow44's avatar
KingRainbow44 committed
151
152
        // Get command handler.
        CommandHandler handler = this.commands.get(label);
Jaida Wu's avatar
Jaida Wu committed
153
        if (handler == null) {
方块君's avatar
方块君 committed
154
            CommandHandler.sendMessage(player, Grasscutter.getLanguage().Unknown_command + label);
Jaida Wu's avatar
Jaida Wu committed
155
            return;
KingRainbow44's avatar
KingRainbow44 committed
156
        }
Jaida Wu's avatar
Jaida Wu committed
157

AnimeGitB's avatar
AnimeGitB committed
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
        // 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("@")) {
                continue;
            } else {
                arg = args.remove(i).substring(1);
                try {
                    int uid = Integer.parseInt(arg);
                    targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid);
                    if (targetPlayer == null) {
                        CommandHandler.sendMessage(player, Grasscutter.getLanguage().Player_not_found_or_offline);
                        return;
                    }
                    break;
                } catch (NumberFormatException e) {
                    CommandHandler.sendMessage(player, Grasscutter.getLanguage().Invalid_UID);
                    return;
                }
            }
        }
        // If there's still no targetPlayer at this point, use previously-set target
        if (targetPlayer == null) {
            targetPlayer = targetPlayers.getOrDefault(playerId, null);
        }

184
        // Check for permission.
Jaida Wu's avatar
Jaida Wu committed
185
        if (player != null) {
186
            String permissionNode = this.annotations.get(label).permission();
AnimeGitB's avatar
AnimeGitB committed
187
            String permissionNodeTargeted = this.annotations.get(label).permissionTargeted();
188
            Account account = player.getAccount();
AnimeGitB's avatar
AnimeGitB committed
189
190
191
192
193
194
            if (player != targetPlayer) {  // Additional permission required for targeting another player
                if (!permissionNodeTargeted.isEmpty() && !account.hasPermission(permissionNodeTargeted)) {
                    CommandHandler.sendMessage(player, Grasscutter.getLanguage().You_not_permission_run_command);
                    return;
                }
            }
BaiSugar's avatar
BaiSugar committed
195
            if (!permissionNode.isEmpty() && !account.hasPermission(permissionNode)) {
方块君's avatar
方块君 committed
196
                CommandHandler.sendMessage(player, Grasscutter.getLanguage().You_not_permission_run_command);
Jaida Wu's avatar
Jaida Wu committed
197
                return;
198
199
            }
        }
Jaida Wu's avatar
Jaida Wu committed
200

KingRainbow44's avatar
KingRainbow44 committed
201
        // Invoke execute method for handler.
BaiSugar's avatar
BaiSugar committed
202
        boolean threading  = this.annotations.get(label).threading();
AnimeGitB's avatar
AnimeGitB committed
203
204
        final Player targetPlayerF = targetPlayer;  // Is there a better way to do this?
        Runnable runnable = () -> handler.execute(player, targetPlayerF, args);
BaiSugar's avatar
BaiSugar committed
205
206
207
208
209
210
211
        if(threading) {
            Thread command = new Thread(runnable);
            command.start();
        }
        else {
            runnable.run();
        }
KingRainbow44's avatar
KingRainbow44 committed
212
213
214
215
216
217
218
    }

    /**
     * Scans for all classes annotated with {@link Command} and registers them.
     */
    private void scan() {
        Reflections reflector = Grasscutter.reflector;
219
        Set<Class<?>> classes = reflector.getTypesAnnotatedWith(Command.class);
KingRainbow44's avatar
KingRainbow44 committed
220
221
        classes.forEach(annotated -> {
            try {
222
223
                Command cmdData = annotated.getAnnotation(Command.class);
                Object object = annotated.newInstance();
KingRainbow44's avatar
KingRainbow44 committed
224
225
                if (object instanceof CommandHandler)
                    this.registerCommand(cmdData.label(), (CommandHandler) object);
226
227
228
229
                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
230
231
232
        });
    }
}