diff --git a/bukkit/src/main/java/co/aikar/commands/ACFBukkitUtil.java b/bukkit/src/main/java/co/aikar/commands/ACFBukkitUtil.java index e4890d48..1a6e3c46 100644 --- a/bukkit/src/main/java/co/aikar/commands/ACFBukkitUtil.java +++ b/bukkit/src/main/java/co/aikar/commands/ACFBukkitUtil.java @@ -291,11 +291,11 @@ public class ACFBukkitUtil { } String name = ACFUtil.replace(search, ":confirm", ""); if (name.length() < 3) { - issuer.sendError(BukkitMessageKeys.USERNAME_TOO_SHORT); + issuer.sendError(MinecraftMessageKeys.USERNAME_TOO_SHORT); return null; } if (!isValidName(name)) { - issuer.sendError(BukkitMessageKeys.IS_NOT_A_VALID_NAME, "{name}", name); + issuer.sendError(MinecraftMessageKeys.IS_NOT_A_VALID_NAME, "{name}", name); return null; } @@ -306,19 +306,20 @@ public class ACFBukkitUtil { if (matches.size() > 1 || confirmList.size() > 1) { String allMatches = matches.stream().map(Player::getName).collect(Collectors.joining(", ")); - issuer.sendError(BukkitMessageKeys.MULTIPLE_PLAYERS_MATCH, + issuer.sendError(MinecraftMessageKeys.MULTIPLE_PLAYERS_MATCH, "{search}", name, "{all}", allMatches); return null; } + //noinspection Duplicates if (matches.isEmpty()) { if (confirmList.isEmpty()) { - issuer.sendError(BukkitMessageKeys.NO_PLAYER_FOUND_SERVER, + issuer.sendError(MinecraftMessageKeys.NO_PLAYER_FOUND_SERVER, "{search}", name); return null; } else { Player player = Iterables.getOnlyElement(confirmList); - issuer.sendInfo(BukkitMessageKeys.PLAYER_IS_VANISHED_CONFIRM, "{vanished}", player.getName()); + issuer.sendInfo(MinecraftMessageKeys.PLAYER_IS_VANISHED_CONFIRM, "{vanished}", player.getName()); return null; } } @@ -329,6 +330,7 @@ public class ACFBukkitUtil { private static void findMatches(String search, CommandSender requester, List matches, List confirmList) { // Remove vanished players from smart matching. Iterator iter = matches.iterator(); + //noinspection Duplicates while (iter.hasNext()) { Player player = iter.next(); if (requester instanceof Player && !((Player) requester).canSee(player)) { diff --git a/bukkit/src/main/java/co/aikar/commands/BukkitCommandContexts.java b/bukkit/src/main/java/co/aikar/commands/BukkitCommandContexts.java index aa7def37..bd32ad44 100644 --- a/bukkit/src/main/java/co/aikar/commands/BukkitCommandContexts.java +++ b/bukkit/src/main/java/co/aikar/commands/BukkitCommandContexts.java @@ -65,7 +65,7 @@ public class BukkitCommandContexts extends CommandContexts 1) { String allMatches = matches.stream().map(ProxiedPlayer::getName).collect(Collectors.joining(", ")); - issuer.sendError(BungeeMessageKeys.MULTIPLE_PLAYERS_MATCH, + issuer.sendError(MinecraftMessageKeys.MULTIPLE_PLAYERS_MATCH, "{search}", name, "{all}", allMatches); return null; } if (matches.isEmpty()) { - issuer.sendError(BungeeMessageKeys.NO_PLAYER_FOUND_SERVER, + issuer.sendError(MinecraftMessageKeys.NO_PLAYER_FOUND_SERVER, "{search}", name); return null; } diff --git a/bungee/src/main/java/co/aikar/commands/BungeeMessageKeys.java b/bungee/src/main/java/co/aikar/commands/MinecraftMessageKeys.java similarity index 84% rename from bungee/src/main/java/co/aikar/commands/BungeeMessageKeys.java rename to bungee/src/main/java/co/aikar/commands/MinecraftMessageKeys.java index e5f5a764..910fc5ba 100644 --- a/bungee/src/main/java/co/aikar/commands/BungeeMessageKeys.java +++ b/bungee/src/main/java/co/aikar/commands/MinecraftMessageKeys.java @@ -2,7 +2,7 @@ package co.aikar.commands; import co.aikar.locales.MessageKey; -public enum BungeeMessageKeys implements MessageKeyProvider { +public enum MinecraftMessageKeys implements MessageKeyProvider { USERNAME_TOO_SHORT, IS_NOT_A_VALID_NAME, MULTIPLE_PLAYERS_MATCH, diff --git a/sponge/src/main/java/co/aikar/commands/ACFSpongeUtil.java b/sponge/src/main/java/co/aikar/commands/ACFSpongeUtil.java new file mode 100644 index 00000000..02e942d9 --- /dev/null +++ b/sponge/src/main/java/co/aikar/commands/ACFSpongeUtil.java @@ -0,0 +1,102 @@ +package co.aikar.commands; + +import com.google.common.collect.Iterables; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +@SuppressWarnings("WeakerAccess") +public class ACFSpongeUtil { + public static Player findPlayerSmart(CommandIssuer issuer, String search) { + CommandSource requester = issuer.getIssuer(); + if (search == null) { + return null; + } + String name = ACFUtil.replace(search, ":confirm", ""); + if (name.length() < 3) { + issuer.sendError(MinecraftMessageKeys.USERNAME_TOO_SHORT); + return null; + } + if (!isValidName(name)) { + issuer.sendError(MinecraftMessageKeys.IS_NOT_A_VALID_NAME, "{name}", name); + return null; + } + + List matches = matchPlayer(name); + List confirmList = new ArrayList<>(); + findMatches(search, requester, matches, confirmList); + + + if (matches.size() > 1 || confirmList.size() > 1) { + String allMatches = matches.stream().map(Player::getName).collect(Collectors.joining(", ")); + issuer.sendError(MinecraftMessageKeys.MULTIPLE_PLAYERS_MATCH, + "{search}", name, "{all}", allMatches); + return null; + } + + if (matches.isEmpty()) { + if (confirmList.isEmpty()) { + issuer.sendError(MinecraftMessageKeys.NO_PLAYER_FOUND_SERVER, + "{search}", name); + return null; + } else { + Player player = Iterables.getOnlyElement(confirmList); + issuer.sendInfo(MinecraftMessageKeys.PLAYER_IS_VANISHED_CONFIRM, "{vanished}", player.getName()); + return null; + } + } + + return matches.get(0); + } + + private static void findMatches(String search, CommandSource requester, List matches, List confirmList) { + // Remove vanished players from smart matching. + Iterator iter = matches.iterator(); + //noinspection Duplicates + while (iter.hasNext()) { + Player player = iter.next(); + if (requester instanceof Player && !((Player) requester).canSee(player)) { + if (requester.hasPermission("acf.seevanish")) { + if (!search.endsWith(":confirm")) { + confirmList.add(player); + iter.remove(); + } + } else { + iter.remove(); + } + } + } + } + + public static List matchPlayer(String partialName) { + List matchedPlayers = new ArrayList(); + + for (Player iterPlayer : Sponge.getServer().getOnlinePlayers()) { + String iterPlayerName = iterPlayer.getName(); + + if (partialName.equalsIgnoreCase(iterPlayerName)) { + // Exact match + matchedPlayers.clear(); + matchedPlayers.add(iterPlayer); + break; + } + if (iterPlayerName.toLowerCase(java.util.Locale.ENGLISH).contains(partialName.toLowerCase(java.util.Locale.ENGLISH))) { + // Partial match + matchedPlayers.add(iterPlayer); + } + } + + return matchedPlayers; + } + + public static boolean isValidName(String name) { + return name != null && !name.isEmpty() && ACFPatterns.VALID_NAME_PATTERN.matcher(name).matches(); + } + +} diff --git a/sponge/src/main/java/co/aikar/commands/MinecraftMessageKeys.java b/sponge/src/main/java/co/aikar/commands/MinecraftMessageKeys.java new file mode 100644 index 00000000..e9c242a0 --- /dev/null +++ b/sponge/src/main/java/co/aikar/commands/MinecraftMessageKeys.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import co.aikar.locales.MessageKey; + +public enum MinecraftMessageKeys implements MessageKeyProvider { + INVALID_WORLD, + YOU_MUST_BE_HOLDING_ITEM, + PLAYER_IS_VANISHED_CONFIRM, + USERNAME_TOO_SHORT, + IS_NOT_A_VALID_NAME, + MULTIPLE_PLAYERS_MATCH, + NO_PLAYER_FOUND_SERVER, + NO_PLAYER_FOUND + ; + + private final MessageKey key = MessageKey.of(this.name().toLowerCase()); + public MessageKey getMessageKey() { + return key; + } +} diff --git a/sponge/src/main/java/co/aikar/commands/SpongeCommandContexts.java b/sponge/src/main/java/co/aikar/commands/SpongeCommandContexts.java index 9dd6b5bf..044a244e 100644 --- a/sponge/src/main/java/co/aikar/commands/SpongeCommandContexts.java +++ b/sponge/src/main/java/co/aikar/commands/SpongeCommandContexts.java @@ -23,7 +23,12 @@ package co.aikar.commands; +import co.aikar.commands.annotation.Optional; import co.aikar.commands.contexts.CommandResultSupplier; +import co.aikar.commands.contexts.OnlinePlayer; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; @SuppressWarnings("WeakerAccess") public class SpongeCommandContexts extends CommandContexts { @@ -32,5 +37,23 @@ public class SpongeCommandContexts extends CommandContexts new CommandResultSupplier()); + registerContext(OnlinePlayer.class, c -> getOnlinePlayer(c.getIssuer(), c.popFirstArg(), c.hasAnnotation(Optional.class))); + + + } + + @Nullable + OnlinePlayer getOnlinePlayer(SpongeCommandIssuer issuer, String lookup, boolean allowMissing) throws InvalidCommandArgument { + CommandSource sender = issuer.getIssuer(); + Player player = ACFSpongeUtil.findPlayerSmart(issuer, lookup); + //noinspection Duplicates + if (player == null) { + if (allowMissing) { + return null; + } + this.manager.sendMessage(sender, MessageType.ERROR, MessageKeys.COULD_NOT_FIND_PLAYER, "{search}", lookup); + throw new InvalidCommandArgument(false); + } + return new OnlinePlayer(player); } } diff --git a/sponge/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java b/sponge/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java new file mode 100644 index 00000000..7c2f9a55 --- /dev/null +++ b/sponge/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands.contexts; + +import org.spongepowered.api.entity.living.player.Player; + +import java.util.Objects; + +public class OnlinePlayer { + public final Player player; + + public OnlinePlayer(Player player) { + this.player = player; + } + + public Player getPlayer() { + return this.player; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OnlinePlayer that = (OnlinePlayer) o; + return Objects.equals(player, that.player); + } + + @Override + public int hashCode() { + return Objects.hash(player); + } + + @Override + public String toString() { + return "OnlinePlayer{" + + "player=" + player + + '}'; + } +}