001/*
002 * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License
003 *
004 *  Permission is hereby granted, free of charge, to any person obtaining
005 *  a copy of this software and associated documentation files (the
006 *  "Software"), to deal in the Software without restriction, including
007 *  without limitation the rights to use, copy, modify, merge, publish,
008 *  distribute, sublicense, and/or sell copies of the Software, and to
009 *  permit persons to whom the Software is furnished to do so, subject to
010 *  the following conditions:
011 *
012 *  The above copyright notice and this permission notice shall be
013 *  included in all copies or substantial portions of the Software.
014 *
015 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
016 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
017 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
018 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
019 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
020 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
021 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
022 */
023
024package co.aikar.commands;
025
026import co.aikar.commands.annotation.Optional;
027import co.aikar.commands.contexts.OnlinePlayer;
028import org.bukkit.Bukkit;
029import org.bukkit.ChatColor;
030import org.bukkit.Location;
031import org.bukkit.OfflinePlayer;
032import org.bukkit.World;
033import org.bukkit.command.BlockCommandSender;
034import org.bukkit.command.CommandSender;
035import org.bukkit.entity.Entity;
036import org.bukkit.entity.Player;
037import org.bukkit.inventory.PlayerInventory;
038import org.jetbrains.annotations.Nullable;
039
040import java.util.HashSet;
041import java.util.Set;
042import java.util.UUID;
043import java.util.regex.Matcher;
044import java.util.regex.Pattern;
045import java.util.stream.Collectors;
046import java.util.stream.Stream;
047
048@SuppressWarnings("WeakerAccess")
049public class BukkitCommandContexts extends CommandContexts<BukkitCommandExecutionContext> {
050
051    public BukkitCommandContexts(BukkitCommandManager manager) {
052        super(manager);
053
054        registerContext(OnlinePlayer.class, c -> getOnlinePlayer(c.getIssuer(), c.popFirstArg(), c.hasAnnotation(Optional.class)));
055        registerContext(OnlinePlayer[].class, (c) ->  {
056            BukkitCommandIssuer issuer = c.getIssuer();
057            final String search = c.popFirstArg();
058            boolean allowMissing = c.hasFlag("allowmissing");
059            Set<OnlinePlayer> players = new HashSet<>();
060            Pattern split = ACFPatterns.COMMA;
061            String splitter = c.getFlagValue("splitter", (String) null);
062            if (splitter != null) {
063                split = Pattern.compile(Pattern.quote(splitter));
064            }
065            for (String lookup : split.split(search)) {
066                OnlinePlayer player = getOnlinePlayer(issuer, lookup, allowMissing);
067                if (player != null) {
068                    players.add(player);
069                }
070            }
071            if (players.isEmpty() && !c.hasFlag("allowempty")) {
072                issuer.sendError(MinecraftMessageKeys.NO_PLAYER_FOUND_SERVER,
073                        "{search}", search);
074
075                throw new InvalidCommandArgument(false);
076            }
077            return players.toArray(new OnlinePlayer[players.size()]);
078        });
079        registerIssuerAwareContext(World.class, (c) -> {
080            String firstArg = c.getFirstArg();
081            World world = firstArg != null ? Bukkit.getWorld(firstArg) : null;
082            if (world != null) {
083                c.popFirstArg();
084            }
085            if (world == null && c.getSender() instanceof Player) {
086                world = ((Entity) c.getSender()).getWorld();
087            }
088            if (world == null) {
089                throw new InvalidCommandArgument(MinecraftMessageKeys.INVALID_WORLD);
090            }
091            return world;
092        });
093        registerIssuerAwareContext(CommandSender.class, BukkitCommandExecutionContext::getSender);
094        registerIssuerAwareContext(Player.class, (c) -> {
095            boolean isOptional = c.hasAnnotation(Optional.class);
096            CommandSender sender = c.getSender();
097            boolean isPlayerSender = sender instanceof Player;
098            if (!c.hasFlag("other")) {
099                Player player = isPlayerSender ? (Player) sender : null;
100                if (player == null && !isOptional) {
101                    throw new InvalidCommandArgument(MessageKeys.NOT_ALLOWED_ON_CONSOLE, false);
102                }
103                PlayerInventory inventory = player != null ? player.getInventory() : null;
104                if (inventory != null && c.hasFlag("itemheld") && !ACFBukkitUtil.isValidItem(inventory.getItem(inventory.getHeldItemSlot()))) {
105                    throw new InvalidCommandArgument(MinecraftMessageKeys.YOU_MUST_BE_HOLDING_ITEM, false);
106                }
107                return player;
108            } else {
109                String arg = c.popFirstArg();
110                if (arg == null && isOptional) {
111                    if (c.hasFlag("defaultself")) {
112                        if (isPlayerSender) {
113                            return (Player) sender;
114                        } else {
115                            throw new InvalidCommandArgument(MessageKeys.NOT_ALLOWED_ON_CONSOLE, false);
116                        }
117                    } else {
118                        return null;
119                    }
120                }
121                OnlinePlayer onlinePlayer = getOnlinePlayer(c.getIssuer(), arg, isOptional);
122                return onlinePlayer != null ? onlinePlayer.getPlayer() : null;
123            }
124        });
125        registerContext(OfflinePlayer.class, c -> {
126            String name = c.popFirstArg();
127            UUID uuid = null;
128            if (c.hasFlag("uuid")) {
129                uuid = UUID.fromString(name);
130            }
131            OfflinePlayer offlinePlayer = uuid != null ? Bukkit.getOfflinePlayer(uuid) : Bukkit.getOfflinePlayer(name);
132            if (offlinePlayer == null) {
133                throw new InvalidCommandArgument(MinecraftMessageKeys.NO_PLAYER_FOUND_OFFLINE,
134                        "{search}", name);
135            }
136            return offlinePlayer;
137        });
138        registerContext(ChatColor.class, c -> {
139            String first = c.popFirstArg();
140            Stream<ChatColor> colors = Stream.of(ChatColor.values());
141            if (c.hasFlag("colorsonly")) {
142                colors = colors.filter(color -> color.ordinal() <= 0xF);
143            }
144            String filter = c.getFlagValue("filter", (String) null);
145            if (filter != null) {
146                filter = ACFUtil.simplifyString(filter);
147                String finalFilter = filter;
148                colors = colors.filter(color -> finalFilter.equals(ACFUtil.simplifyString(color.name())));
149            }
150
151            ChatColor match = ACFUtil.simpleMatch(ChatColor.class, first);
152            if (match == null) {
153                String valid = colors
154                        .map(color -> "<c2>" + ACFUtil.simplifyString(color.name()) + "</c2>")
155                        .collect(Collectors.joining("<c1>,</c1> "));
156
157                throw new InvalidCommandArgument(MessageKeys.PLEASE_SPECIFY_ONE_OF, "{valid}", valid);
158            }
159            return match;
160        });
161        registerContext(Location.class, c -> {
162            String input = c.popFirstArg();
163            CommandSender sender = c.getSender();
164            String[] split = ACFPatterns.COLON.split(input, 2);
165            if (split.length == 0) {
166                throw new InvalidCommandArgument(true);
167            }
168            if (split.length < 2 && !(sender instanceof Player) && !(sender instanceof BlockCommandSender)) {
169                throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_WORLD);
170            }
171            final String world;
172            final String rest;
173            Location sourceLoc = null;
174            if (split.length == 2) {
175                world = split[0];
176                rest = split[1];
177            } else if (sender instanceof Player) {
178                sourceLoc = ((Player) sender).getLocation();
179                world = sourceLoc.getWorld().getName();
180                rest = split[0];
181            } else if (sender instanceof BlockCommandSender) {
182                sourceLoc = ((BlockCommandSender) sender).getBlock().getLocation();
183                world = sourceLoc.getWorld().getName();
184                rest = split[0];
185            } else {
186                throw new InvalidCommandArgument(true);
187            }
188
189            boolean rel = rest.startsWith("~");
190            split = ACFPatterns.COMMA.split(rel ? rest.substring(1) : rest);
191            if (split.length < 3) {
192                throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ);
193            }
194
195            Double x = ACFUtil.parseDouble(split[0]);
196            Double y = ACFUtil.parseDouble(split[1]);
197            Double z = ACFUtil.parseDouble(split[2]);
198
199            if (sourceLoc != null && rel) {
200                x += sourceLoc.getX();
201                y += sourceLoc.getY();
202                z += sourceLoc.getZ();
203            } else if (rel) {
204                throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_CONSOLE_NOT_RELATIVE);
205            }
206
207            if (x == null || y == null || z == null) {
208                throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ);
209            }
210
211            World worldObj = Bukkit.getWorld(world);
212            if (worldObj == null) {
213                throw new InvalidCommandArgument(MinecraftMessageKeys.INVALID_WORLD);
214            }
215
216            if (split.length >= 5) {
217                Float yaw = ACFUtil.parseFloat(split[3]);
218                Float pitch = ACFUtil.parseFloat(split[4]);
219
220                if (pitch == null || yaw == null) {
221                    throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ);
222                }
223                return new Location(worldObj, x, y, z, yaw, pitch);
224            } else {
225                return new Location(worldObj, x, y, z);
226            }
227        });
228        Pattern versionPattern = Pattern.compile("\\(MC: (\\d)\\.(\\d+)\\.?.*?\\)");
229        Matcher matcher = versionPattern.matcher(Bukkit.getVersion());
230        if (matcher.find()) {
231            int mcMajorVersion = ACFUtil.parseInt(matcher.toMatchResult().group(1), 0);
232            int mcMinorVersion = ACFUtil.parseInt(matcher.toMatchResult().group(2), 0);
233            manager.log(LogLevel.INFO, "Minecraft Version: " + mcMajorVersion + "." + mcMinorVersion);
234            if (mcMajorVersion >= 1 && mcMinorVersion >= 12) {
235                BukkitCommandContexts_1_12.register(this);
236            }
237        }
238    }
239
240    @Nullable
241    OnlinePlayer getOnlinePlayer(BukkitCommandIssuer issuer, String lookup, boolean allowMissing) throws InvalidCommandArgument {
242        Player player = ACFBukkitUtil.findPlayerSmart(issuer, lookup);
243        //noinspection Duplicates
244        if (player == null) {
245            if (allowMissing) {
246                return null;
247            }
248            throw new InvalidCommandArgument(false);
249        }
250        return new OnlinePlayer(player);
251    }
252}