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.bukkit.contexts.OnlinePlayer;
027import org.bukkit.Bukkit;
028import org.bukkit.ChatColor;
029import org.bukkit.Location;
030import org.bukkit.OfflinePlayer;
031import org.bukkit.World;
032import org.bukkit.command.BlockCommandSender;
033import org.bukkit.command.CommandSender;
034import org.bukkit.entity.Entity;
035import org.bukkit.entity.Player;
036import org.bukkit.inventory.PlayerInventory;
037import org.jetbrains.annotations.Contract;
038
039import java.util.HashSet;
040import java.util.Set;
041import java.util.UUID;
042import java.util.regex.Pattern;
043import java.util.stream.Collectors;
044import java.util.stream.Stream;
045
046import static co.aikar.commands.ACFBukkitUtil.isValidName;
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(), false));
055        registerContext(co.aikar.commands.contexts.OnlinePlayer.class, c -> {
056            OnlinePlayer onlinePlayer = getOnlinePlayer(c.getIssuer(), c.popFirstArg(), false);
057            return new co.aikar.commands.contexts.OnlinePlayer(onlinePlayer.getPlayer());
058        });
059        registerContext(OnlinePlayer[].class, (c) -> {
060            BukkitCommandIssuer issuer = c.getIssuer();
061            final String search = c.popFirstArg();
062            boolean allowMissing = c.hasFlag("allowmissing");
063            Set<OnlinePlayer> players = new HashSet<>();
064            Pattern split = ACFPatterns.COMMA;
065            String splitter = c.getFlagValue("splitter", (String) null);
066            if (splitter != null) {
067                split = Pattern.compile(Pattern.quote(splitter));
068            }
069            for (String lookup : split.split(search)) {
070                OnlinePlayer player = getOnlinePlayer(issuer, lookup, allowMissing);
071                if (player != null) {
072                    players.add(player);
073                }
074            }
075            if (players.isEmpty() && !c.hasFlag("allowempty")) {
076                issuer.sendError(MinecraftMessageKeys.NO_PLAYER_FOUND_SERVER,
077                        "{search}", search);
078
079                throw new InvalidCommandArgument(false);
080            }
081            return players.toArray(new OnlinePlayer[players.size()]);
082        });
083        registerIssuerAwareContext(World.class, (c) -> {
084            String firstArg = c.getFirstArg();
085            World world = firstArg != null ? Bukkit.getWorld(firstArg) : null;
086            if (world != null) {
087                c.popFirstArg();
088            }
089            if (world == null && c.getSender() instanceof Player) {
090                world = ((Entity) c.getSender()).getWorld();
091            }
092            if (world == null) {
093                throw new InvalidCommandArgument(MinecraftMessageKeys.INVALID_WORLD);
094            }
095            return world;
096        });
097        registerIssuerAwareContext(CommandSender.class, BukkitCommandExecutionContext::getSender);
098        registerIssuerAwareContext(Player.class, (c) -> {
099            boolean isOptional = c.isOptional();
100            CommandSender sender = c.getSender();
101            boolean isPlayerSender = sender instanceof Player;
102            if (!c.hasFlag("other")) {
103                Player player = isPlayerSender ? (Player) sender : null;
104                if (player == null && !isOptional) {
105                    throw new InvalidCommandArgument(MessageKeys.NOT_ALLOWED_ON_CONSOLE, false);
106                }
107                PlayerInventory inventory = player != null ? player.getInventory() : null;
108                if (inventory != null && c.hasFlag("itemheld") && !ACFBukkitUtil.isValidItem(inventory.getItem(inventory.getHeldItemSlot()))) {
109                    throw new InvalidCommandArgument(MinecraftMessageKeys.YOU_MUST_BE_HOLDING_ITEM, false);
110                }
111                return player;
112            } else {
113                String arg = c.popFirstArg();
114                if (arg == null && isOptional) {
115                    if (c.hasFlag("defaultself")) {
116                        if (isPlayerSender) {
117                            return (Player) sender;
118                        } else {
119                            throw new InvalidCommandArgument(MessageKeys.NOT_ALLOWED_ON_CONSOLE, false);
120                        }
121                    } else {
122                        return null;
123                    }
124                } else if (arg == null) {
125                    throw new InvalidCommandArgument();
126                }
127
128                OnlinePlayer onlinePlayer = getOnlinePlayer(c.getIssuer(), arg, false);
129                return onlinePlayer.getPlayer();
130            }
131        });
132        registerContext(OfflinePlayer.class, c -> {
133            String name = c.popFirstArg();
134            OfflinePlayer offlinePlayer;
135            if (c.hasFlag("uuid")) {
136                UUID uuid;
137                try {
138                    uuid = UUID.fromString(name);
139                } catch (IllegalArgumentException e) {
140                    throw new InvalidCommandArgument(MinecraftMessageKeys.NO_PLAYER_FOUND_OFFLINE,
141                            "{search}", name);
142                }
143                offlinePlayer = Bukkit.getOfflinePlayer(uuid);
144            } else {
145                offlinePlayer = Bukkit.getOfflinePlayer(name);
146            }
147            if (offlinePlayer == null || (!offlinePlayer.hasPlayedBefore() && !offlinePlayer.isOnline())) {
148                if (!c.hasFlag("uuid") && !manager.isValidName(name)) {
149                    throw new InvalidCommandArgument(MinecraftMessageKeys.IS_NOT_A_VALID_NAME, "{name}", name);
150                }
151                throw new InvalidCommandArgument(MinecraftMessageKeys.NO_PLAYER_FOUND_OFFLINE,
152                        "{search}", name);
153            }
154            return offlinePlayer;
155        });
156        registerContext(ChatColor.class, c -> {
157            String first = c.popFirstArg();
158            Stream<ChatColor> colors = Stream.of(ChatColor.values());
159            if (c.hasFlag("colorsonly")) {
160                colors = colors.filter(color -> color.ordinal() <= 0xF);
161            }
162            String filter = c.getFlagValue("filter", (String) null);
163            if (filter != null) {
164                filter = ACFUtil.simplifyString(filter);
165                String finalFilter = filter;
166                colors = colors.filter(color -> finalFilter.equals(ACFUtil.simplifyString(color.name())));
167            }
168
169            ChatColor match = ACFUtil.simpleMatch(ChatColor.class, first);
170            if (match == null) {
171                String valid = colors
172                        .map(color -> "<c2>" + ACFUtil.simplifyString(color.name()) + "</c2>")
173                        .collect(Collectors.joining("<c1>,</c1> "));
174
175                throw new InvalidCommandArgument(MessageKeys.PLEASE_SPECIFY_ONE_OF, "{valid}", valid);
176            }
177            return match;
178        });
179        registerContext(Location.class, c -> {
180            String input = c.popFirstArg();
181            CommandSender sender = c.getSender();
182            String[] split = ACFPatterns.COLON.split(input, 2);
183            if (split.length == 0) {
184                throw new InvalidCommandArgument(true);
185            }
186            if (split.length < 2 && !(sender instanceof Player) && !(sender instanceof BlockCommandSender)) {
187                throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_WORLD);
188            }
189            final String world;
190            final String rest;
191            Location sourceLoc = null;
192            if (split.length == 2) {
193                world = split[0];
194                rest = split[1];
195            } else if (sender instanceof Player) {
196                sourceLoc = ((Player) sender).getLocation();
197                world = sourceLoc.getWorld().getName();
198                rest = split[0];
199            } else if (sender instanceof BlockCommandSender) {
200                sourceLoc = ((BlockCommandSender) sender).getBlock().getLocation();
201                world = sourceLoc.getWorld().getName();
202                rest = split[0];
203            } else {
204                throw new InvalidCommandArgument(true);
205            }
206
207            boolean rel = rest.startsWith("~");
208            split = ACFPatterns.COMMA.split(rel ? rest.substring(1) : rest);
209            if (split.length < 3) {
210                throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ);
211            }
212
213            Double x = ACFUtil.parseDouble(split[0], rel ? 0.0D : null);
214            Double y = ACFUtil.parseDouble(split[1], rel ? 0.0D : null);
215            Double z = ACFUtil.parseDouble(split[2], rel ? 0.0D : null);
216
217            if (sourceLoc != null && rel) {
218                x += sourceLoc.getX();
219                y += sourceLoc.getY();
220                z += sourceLoc.getZ();
221            } else if (rel) {
222                throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_CONSOLE_NOT_RELATIVE);
223            }
224
225            if (x == null || y == null || z == null) {
226                throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ);
227            }
228
229            World worldObj = Bukkit.getWorld(world);
230            if (worldObj == null) {
231                throw new InvalidCommandArgument(MinecraftMessageKeys.INVALID_WORLD);
232            }
233
234            if (split.length >= 5) {
235                Float yaw = ACFUtil.parseFloat(split[3]);
236                Float pitch = ACFUtil.parseFloat(split[4]);
237
238                if (pitch == null || yaw == null) {
239                    throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ);
240                }
241                return new Location(worldObj, x, y, z, yaw, pitch);
242            } else {
243                return new Location(worldObj, x, y, z);
244            }
245        });
246
247        if (manager.mcMinorVersion >= 12) {
248            BukkitCommandContexts_1_12.register(this);
249        }
250    }
251
252    @Contract("_,_,false -> !null")
253    OnlinePlayer getOnlinePlayer(BukkitCommandIssuer issuer, String lookup, boolean allowMissing) throws InvalidCommandArgument {
254        Player player = ACFBukkitUtil.findPlayerSmart(issuer, lookup);
255        //noinspection Duplicates
256        if (player == null) {
257            if (allowMissing) {
258                return null;
259            }
260            throw new InvalidCommandArgument(false);
261        }
262        return new OnlinePlayer(player);
263    }
264}