package com.supaham.commons.bukkit.players;
import static com.google.common.base.Preconditions.checkNotNull;
import com.supaham.commons.Uuidable;
import com.supaham.commons.bukkit.CommonPlugin;
import com.supaham.commons.utils.ThrowableUtils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Represents a player manager class for {@link CommonPlayer} instances. This manager includes methods
* such as {@link #createPlayer(Player)} and {@link #removePlayer(UUID)} that contains all the
* boilerplate code. This manager also registers a package-private {@link Listener} that handles all
* the {@link CommonPlayer} instances, such as when a player joins or leaves the server.
*
* @param <T> a class that extends {@link CommonPlayer}
*
* @since 0.1
*/
public class BukkitPlayerManager<T extends CommonPlayer> {
private final CommonPlugin plugin;
private final Class<T> playerClass;
private final PlayerListener listener;
private final Map<UUID, T> players = new HashMap<>();
private final Map<String, UUID> namesToUUID = new HashMap<>();
/**
* Converts a {@link Collection} of {@link CommonPlayer}s into a {@link List} of {@link Player}s.
*
* @param cPlayers players list to convert.
*
* @return the new {@link List} of {@link Player}s.
*/
public static <T extends CommonPlayer> List<Player> commonPlayersToPlayers(@Nonnull Collection<T> cPlayers) {
List<Player> list = new ArrayList<>();
for (T cPlayer : cPlayers) {
if (cPlayer.getPlayer() != null) {
list.add(cPlayer.getPlayer());
}
}
return list;
}
public BukkitPlayerManager(@Nonnull CommonPlugin plugin, @Nonnull Class<T> playerClass) {
checkNotNull(plugin, "plugin cannot be null.");
checkNotNull(playerClass, "player class cannot be null.");
this.plugin = plugin;
this.playerClass = playerClass;
plugin.registerEvents(this.listener = new PlayerListener());
}
public void unload() {
HandlerList.unregisterAll(this.listener);
}
/**
* Creates a new {@link CommonPlayer} out of a {@link Player}.
*
* @param player player to own the new {@link CommonPlayer} instance
*
* @return newly created instance of {@link CommonPlayer}
*
* @throws PlayerCreationException thrown if constructor invocation failed
*/
protected T createPlayer(Player player) throws PlayerCreationException {
T cPlayer = getPlayer(player);
if (cPlayer == null) {
try {
Constructor<T> ctor = this.playerClass.getConstructor(BukkitPlayerManager.class, Player.class);
cPlayer = ctor.newInstance(this, player);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException
| InstantiationException e) {
throw (PlayerCreationException) new PlayerCreationException(player).initCause(ThrowableUtils.getCause(e));
}
addPlayer(cPlayer);
}
cPlayer.join();
return cPlayer;
}
protected void addPlayer(@Nonnull T cPlayer) {
this.players.put(cPlayer.getUuid(), cPlayer);
this.namesToUUID.put(cPlayer.getName().toLowerCase(), cPlayer.getUuid());
}
/**
* Removes a {@link CommonPlayer} from this {@link BukkitPlayerManager} by {@link UUID}.
*
* @param uuid uuid that owns the {@link CommonPlayer} instance
*
* @return removed player instance
*/
@Nullable
public T removePlayer(@Nonnull UUID uuid) {
T removed = this.players.remove(uuid);
if (removed != null) {
this.namesToUUID.remove(removed.getName().toLowerCase());
}
return removed;
}
/**
* Gets a {@link CommonPlayer} from a {@link CommandSender}, assuming its a {@link Player}.
*
* @param sender sender to get {@link CommonPlayer} instance for
*
* @return the {@link CommonPlayer} instance if the {@code sender} is a {@link Player} and found,
* otherwise null.
*
* @see #getPlayer(UUID)
*/
@Nullable
public T getPlayer(@Nonnull CommandSender sender) {
if (!(sender instanceof Player)) {
return null;
}
return getPlayer(((Player) sender).getUniqueId());
}
/**
* Gets a {@link CommonPlayer} from a {@link UUID} represented as a {@link String}.
*
* @param name name of the player to get
*
* @return {@link CommonPlayer} if found, otherwise null.
*
* @see #getPlayer(UUID)
*/
@Nullable
public T getPlayer(@Nonnull String name) {
return getPlayer(namesToUUID.get(name.toLowerCase()));
}
/**
* Gets a {@link CommonPlayer} from a {@link UUID}.
*
* @param uuidable uuidable object to get {@link CommonPlayer} for
*
* @return {@link CommonPlayer} if found, otherwise null
*/
@Nullable
public T getPlayer(@Nullable Uuidable uuidable) {
return uuidable == null ? null : getPlayer(uuidable.getUuid());
}
/**
* Gets a {@link CommonPlayer} from a {@link UUID}.
*
* @param uuid uuid to get {@link CommonPlayer} for
*
* @return {@link CommonPlayer} if found, otherwise null
*/
@Nullable
public T getPlayer(@Nullable UUID uuid) {
return uuid == null ? null : this.players.get(uuid);
}
public CommonPlugin getPlugin() {
return this.plugin;
}
public Map<UUID, T> getPlayers() {
return this.players;
}
public Map<String, UUID> getNamesToUUID() {
return this.namesToUUID;
}
/**
* @since 0.2.7
*/
class PlayerListener implements Listener {
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerJoin(final PlayerJoinEvent event) {
createPlayer(event.getPlayer());
}
/*
* This listener's task is to trigger the player quitting but not removing the instance.
* Instance is removed in the HIGHEST priority listener of PlayerQuitEvent.
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerQuit(PlayerQuitEvent event) {
CommonPlayer commonPlayer = getPlayer(event.getPlayer());
if (commonPlayer != null) {
commonPlayer.disconnect();
}
}
/*
* This listener's task is to clear the player reference at the latest time possible.
* This is to allow other classes to be able to access the reference whilst the player is
* quitting.
*/
@EventHandler(priority = EventPriority.MONITOR)
public void removeReference(PlayerQuitEvent event) {
CommonPlayer removed = removePlayer(event.getPlayer().getUniqueId());
if (removed != null) {
// Set status here in case any code needs to check if a player's online.
// This is as late as we can set it, so accessors have a lot of time to do their checks.
removed.setStatus(PlayerStatus.OFFLINE);
}
}
}
}