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.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.Nullable;
038
039import java.util.HashSet;
040import java.util.Set;
041import java.util.regex.Matcher;
042import java.util.regex.Pattern;
043import java.util.stream.Collectors;
044import java.util.stream.Stream;
045
046@SuppressWarnings("WeakerAccess")
047public class BukkitCommandContexts extends CommandContexts<BukkitCommandExecutionContext> {
048
049    public BukkitCommandContexts(BukkitCommandManager manager) {
050        super(manager);
051
052        registerContext(OnlinePlayer.class, c -> getOnlinePlayer(c.getIssuer(), c.popFirstArg(), c.hasAnnotation(Optional.class)));
053        registerContext(OnlinePlayer[].class, (c) ->  {
054            BukkitCommandIssuer issuer = c.getIssuer();
055            final String search = c.popFirstArg();
056            boolean allowMissing = c.hasFlag("allowmissing");
057            Set<OnlinePlayer> players = new HashSet<>();
058            Pattern split = ACFPatterns.COMMA;
059            String splitter = c.getFlagValue("splitter", (String) null);
060            if (splitter != null) {
061                split = Pattern.compile(Pattern.quote(splitter));
062            }
063            for (String lookup : split.split(search)) {
064                OnlinePlayer player = getOnlinePlayer(issuer, lookup, allowMissing);
065                if (player != null) {
066                    players.add(player);
067                }
068            }
069            if (players.isEmpty() && !c.hasFlag("allowempty")) {
070                issuer.sendError(MinecraftMessageKeys.NO_PLAYER_FOUND_SERVER,
071                        "{search}", search);
072
073                throw new InvalidCommandArgument(false);
074            }
075            return players.toArray(new OnlinePlayer[players.size()]);
076        });
077        registerIssuerAwareContext(World.class, (c) -> {
078            String firstArg = c.getFirstArg();
079            World world = firstArg != null ? Bukkit.getWorld(firstArg) : null;
080            if (world != null) {
081                c.popFirstArg();
082            }
083            if (world == null && c.getSender() instanceof Player) {
084                world = ((Entity) c.getSender()).getWorld();
085            }
086            if (world == null) {
087                throw new InvalidCommandArgument(MinecraftMessageKeys.INVALID_WORLD);
088            }
089            return world;
090        });
091        registerIssuerAwareContext(CommandSender.class, BukkitCommandExecutionContext::getSender);
092        registerIssuerAwareContext(Player.class, (c) -> {
093            Player player = c.getSender() instanceof Player ? (Player) c.getSender() : null;
094            if (player == null && !c.hasAnnotation(Optional.class)) {
095                throw new InvalidCommandArgument(MessageKeys.NOT_ALLOWED_ON_CONSOLE, false);
096            }
097            PlayerInventory inventory = player != null ? player.getInventory() : null;
098            if (inventory != null && c.hasFlag("itemheld") && !ACFBukkitUtil.isValidItem(inventory.getItem(inventory.getHeldItemSlot()))) {
099                throw new InvalidCommandArgument(MinecraftMessageKeys.YOU_MUST_BE_HOLDING_ITEM, false);
100            }
101            return player;
102        });
103        registerContext(ChatColor.class, c -> {
104            String first = c.popFirstArg();
105            Stream<ChatColor> colors = Stream.of(ChatColor.values());
106            if (c.hasFlag("colorsonly")) {
107                colors = colors.filter(color -> color.ordinal() <= 0xF);
108            }
109            String filter = c.getFlagValue("filter", (String) null);
110            if (filter != null) {
111                filter = ACFUtil.simplifyString(filter);
112                String finalFilter = filter;
113                colors = colors.filter(color -> finalFilter.equals(ACFUtil.simplifyString(color.name())));
114            }
115
116            ChatColor match = ACFUtil.simpleMatch(ChatColor.class, first);
117            if (match == null) {
118                String valid = colors
119                        .map(color -> "<c2>" + ACFUtil.simplifyString(color.name()) + "</c2>")
120                        .collect(Collectors.joining("<c1>,</c1> "));
121
122                throw new InvalidCommandArgument(MessageKeys.PLEASE_SPECIFY_ONE_OF, "{valid}", valid);
123            }
124            return match;
125        });
126        registerContext(Location.class, c -> {
127            String input = c.popFirstArg();
128            CommandSender sender = c.getSender();
129            String[] split = ACFPatterns.COLON.split(input, 2);
130            if (split.length == 0) {
131                throw new InvalidCommandArgument(true);
132            }
133            if (split.length < 2 && !(sender instanceof Player) && !(sender instanceof BlockCommandSender)) {
134                throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_WORLD);
135            }
136            final String world;
137            final String rest;
138            Location sourceLoc = null;
139            if (split.length == 2) {
140                world = split[0];
141                rest = split[1];
142            } else if (sender instanceof Player) {
143                sourceLoc = ((Player) sender).getLocation();
144                world = sourceLoc.getWorld().getName();
145                rest = split[0];
146            } else if (sender instanceof BlockCommandSender) {
147                sourceLoc = ((BlockCommandSender) sender).getBlock().getLocation();
148                world = sourceLoc.getWorld().getName();
149                rest = split[0];
150            } else {
151                throw new InvalidCommandArgument(true);
152            }
153
154            boolean rel = rest.startsWith("~");
155            split = ACFPatterns.COMMA.split(rel ? rest.substring(1) : rest);
156            if (split.length < 3) {
157                throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ);
158            }
159
160            Double x = ACFUtil.parseDouble(split[0]);
161            Double y = ACFUtil.parseDouble(split[1]);
162            Double z = ACFUtil.parseDouble(split[2]);
163
164            if (sourceLoc != null && rel) {
165                x += sourceLoc.getX();
166                y += sourceLoc.getY();
167                z += sourceLoc.getZ();
168            } else if (rel) {
169                throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_CONSOLE_NOT_RELATIVE);
170            }
171
172            if (x == null || y == null || z == null) {
173                throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ);
174            }
175
176            World worldObj = Bukkit.getWorld(world);
177            if (worldObj == null) {
178                throw new InvalidCommandArgument(MinecraftMessageKeys.INVALID_WORLD);
179            }
180
181            if (split.length >= 5) {
182                Float yaw = ACFUtil.parseFloat(split[3]);
183                Float pitch = ACFUtil.parseFloat(split[4]);
184
185                if (pitch == null || yaw == null) {
186                    throw new InvalidCommandArgument(MinecraftMessageKeys.LOCATION_PLEASE_SPECIFY_XYZ);
187                }
188                return new Location(worldObj, x, y, z, yaw, pitch);
189            } else {
190                return new Location(worldObj, x, y, z);
191            }
192        });
193        Pattern versionPattern = Pattern.compile("\\(MC: (\\d)\\.(\\d+)\\.?.*?\\)");
194        Matcher matcher = versionPattern.matcher(Bukkit.getVersion());
195        if (matcher.find()) {
196            int mcMajorVersion = ACFUtil.parseInt(matcher.toMatchResult().group(1), 0);
197            int mcMinorVersion = ACFUtil.parseInt(matcher.toMatchResult().group(2), 0);
198            manager.log(LogLevel.INFO, "Minecraft Version: " + mcMajorVersion + "." + mcMinorVersion);
199            if (mcMajorVersion >= 1 && mcMinorVersion >= 12) {
200                BukkitCommandContexts_1_12.register(this);
201            }
202        }
203    }
204
205    @Nullable
206    OnlinePlayer getOnlinePlayer(BukkitCommandIssuer issuer, String lookup, boolean allowMissing) throws InvalidCommandArgument {
207        Player player = ACFBukkitUtil.findPlayerSmart(issuer, lookup);
208        //noinspection Duplicates
209        if (player == null) {
210            if (allowMissing) {
211                return null;
212            }
213            throw new InvalidCommandArgument(false);
214        }
215        return new OnlinePlayer(player);
216    }
217}