package com.supaham.commons.bukkit.players; import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.Math.PI; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.supaham.commons.utils.RandomUtils; import com.supaham.commons.utils.StringUtils; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.Item; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Random; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * Utility methods for working with {@link Player} instances. This class contains methods such as * {@link #dropItem(Player, ItemStack)} and more. * * @since 0.1 */ public class Players { public static final float DEFAULT_FLY_SPEED = 0.1f; public static final float DEFAULT_WALK_SPEED = 0.2f; private static final Predicate<Object> IS_PLAYER = Predicates.instanceOf(Player.class); private static final PlayersSupplier SERVER_SUPPLIER = new ServerSupplier(Bukkit.getServer()); public static Item dropItem(@Nonnull Player player, @Nonnull ItemStack itemStack) { return dropItem(player, itemStack, false); } public static Item dropItem(@Nonnull Player player, @Nonnull ItemStack itemStack, boolean death) { Item item = player.getWorld().dropItem(player.getLocation(), itemStack); dropItem(player, item, death); return item; } public static void dropItem(@Nonnull Player player, @Nonnull Item item) { dropItem(player, item, false); } public static void dropItem(@Nonnull Player player, @Nonnull Item item, boolean death) { Location l = player.getLocation(); Vector v; Random random = RandomUtils.getRandom(); if (death) { double d = random.nextDouble() * 0.5; double d1 = random.nextDouble() * PI * 2.0; v = new Vector(-Math.sin(d1) * d, 0.2, Math.cos(d1) * d); } else { double d = 0.3F; v = new Vector((-Math.sin(l.getYaw() / 180.0 * PI)) * Math.cos(l.getPitch() / 180.0 * PI * d), -Math.sin(l.getPitch() / 180.0 * PI) * d + 0.1, (Math.cos(l.getYaw() / 180.0 * PI)) * Math.cos(l.getPitch() / 180.0 * PI * d)); double d1 = random.nextDouble() * PI * 2.0; d = 0.02 * random.nextDouble(); v.setX(v.getX() + (Math.cos(d1) * d)); v.setY(v.getY() + (random.nextDouble() - random.nextDouble()) * 0.1F); v.setZ(v.getZ() + (Math.sin(d1) * d)); } item.setVelocity(v); } /** * Returns whether a {@link Player} is vanished. * * @param player player to check * * @return whether the {@code player} is vanished */ public static boolean isVanished(@Nonnull Player player) { Preconditions.checkNotNull(player, "player cannot be null."); return player.hasMetadata("vanished") && ((Boolean) player.getMetadata("vanished").get(0).value()); } /** * Gets a constant Predicate which returns true if the passed object is instance of {@link * Player}. * * @return predicate */ public static Predicate<Object> isPlayer() { return IS_PLAYER; } private static Function<Entity, Player> entityToPlayerFunction() { return EntityToPlayer.INSTANCE; } /** * Gets a new {@link PlayersSupplier} which returns all players in a {@link Chunk}. * * @param chunk chunk to get entities from * * @return entities supplier * * @see #multiChunkPlayers(Collection) */ public static PlayersSupplier singleChunkPlayers(@Nonnull Chunk chunk) { return multiChunkPlayers(chunk); } /** * Gets a new {@link PlayersSupplier} which returns all players in an array of {@link Chunk}s. * * @param chunks chunks to get entities from * * @return entities supplier * * @see #multiChunkPlayers(Collection) */ public static PlayersSupplier multiChunkPlayers(@Nonnull Chunk... chunks) { return new ChunkSupplier(Arrays.asList(chunks)); } /** * Gets a new {@link PlayersSupplier} which returns all players in a collection of {@link * Chunk}s. * * @param chunks chunks to get entities from * * @return entities supplier */ public static PlayersSupplier multiChunkPlayers(@Nonnull Collection<Chunk> chunks) { return new ChunkSupplier(chunks); } /** * Gets a new {@link PlayersSupplier} which returns all players in a {@link World}. * * @param world world to get entities from * * @return entities supplier */ public static PlayersSupplier worldPlayers(@Nonnull World world) { return new WorldSupplier(world); } /** * Gets a new {@link PlayersSupplier} which returns all players in a {@link Server}. * * @param server server to get players from * * @return entities supplier */ public static PlayersSupplier serverPlayers(@Nonnull Server server) { return new ServerSupplier(server); } /** * Gets a new {@link PlayersSupplier} which returns all players in a {@link Bukkit#getServer()}. * * @return entities supplier */ public static PlayersSupplier serverPlayers() { return SERVER_SUPPLIER; } /** * Gets a new {@link PlayerSupplier} which returns a matched player by name. This is equivalent * to calling {@code playerByName(}{@link #serverPlayers()},{@code String)}. * * @return entities supplier * * @see #playerByName(PlayersSupplier, String) */ public static PlayerSupplier playerByName(@Nonnull String name) { return new PlayerByNameSupplier(serverPlayers(), name); } /** * Gets a new {@link PlayersSupplier} which returns all players in a {@link Bukkit#getServer()}. * * @return entities supplier */ public static PlayerSupplier playerByName(@Nonnull PlayersSupplier supplier, @Nonnull String name) { return new PlayerByNameSupplier(supplier, name); } /** * Gets a new {@link PlayersSupplierFor} which returns all players within a radius of a * {@link Location}. Although this utilizes a {@link PlayersSupplier} it returns a new * {@link ArrayList} as opposed to a {@link Collection} of the players within range. * <p /> * If the {@code supplier} is null, {@link #worldPlayers(World)} is called during each call. * * @param supplier players to check radius against, nullable * @param radius radius of the players to return * * @return players supplier */ public static PlayersSupplierFor<Location> playersByRadius(@Nullable PlayersSupplier supplier, double radius) { return new PlayersRadiusSupplier(supplier, radius); } private Players() {} private static class EntityToPlayer implements Function<Entity, Player> { private static final EntityToPlayer INSTANCE = new EntityToPlayer(); @Nullable @Override public Player apply(Entity input) { if (isPlayer().apply(input)) { return ((Player) input); } return null; } } public interface PlayerSupplier extends Supplier<Player> {} public interface PlayersSupplier extends Supplier<Collection<Player>> {} /** * Represents a {@link Player} supplier for a specific type. * * @param <T> type used to supply the player */ public interface PlayerSupplierFor<T> { Player get(T t); } /** * Represents a {@link Player} collection supplier for a specific type. * * @param <T> type used to supply the players collection */ public interface PlayersSupplierFor<T> { Collection<Player> get(T t); } private static class ChunkSupplier implements PlayersSupplier { private final Collection<Chunk> chunks; public ChunkSupplier(@Nonnull Collection<Chunk> chunks) { this.chunks = checkNotNull(chunks, "chunk cannot be null."); } @Override public Collection<Player> get() { ArrayList<Player> players = new ArrayList<>(); for (Chunk chunk : this.chunks) { players.addAll(Collections2.transform(Arrays.asList(chunk.getEntities()), entityToPlayerFunction())); } return players; } } private static class WorldSupplier implements PlayersSupplier { private final World world; public WorldSupplier(World world) { this.world = checkNotNull(world, "world cannot be null."); } @Override public Collection<Player> get() { return Collections2.filter( Collections2.transform(this.world.getEntities(), entityToPlayerFunction()), Predicates.notNull()); } } private static class ServerSupplier implements PlayersSupplier { private final Server server; public ServerSupplier(Server server) { this.server = checkNotNull(server, "server cannot be null."); } @Override public Collection<Player> get() { return ((Collection<Player>) this.server.getOnlinePlayers()); } } private static class PlayerByNameSupplier implements PlayerSupplier { private final PlayersSupplier supplier; private final String name; public PlayerByNameSupplier(@Nonnull PlayersSupplier supplier, @Nonnull String name) { this.supplier = checkNotNull(supplier, "supplier cannot be null."); this.name = StringUtils.checkNotNullOrEmpty(name, "name"); } @Override public Player get() { for (Player player : this.supplier.get()) { if (player.getName().equalsIgnoreCase(this.name)) { return player; } } return null; } } private static class PlayersRadiusSupplier implements PlayersSupplierFor<Location> { private final PlayersSupplier supplier; private final double radius; public PlayersRadiusSupplier(PlayersSupplier supplier, double radius) { Preconditions.checkArgument(radius >= 0, "radius cannot be smaller than 1."); this.supplier = supplier; this.radius = radius; } @Override public List<Player> get(Location location) { PlayersSupplier supplier = this.supplier == null ? worldPlayers(location.getWorld()) : this.supplier; List<Player> players = Lists.reverse(new ArrayList<>(supplier.get())); for (int i = 0; i < players.size(); i++) { if (players.get(i).getLocation().distanceSquared(location) > radius * radius) { players.remove(i); } } return players; } } }