net.tbnr.gearz.game.GearzGame.java Source code

Java tutorial

Introduction

Here is the source code for net.tbnr.gearz.game.GearzGame.java

Source

/*
 * Copyright (c) 2014.
 * CogzMC LLC USA
 * All Right reserved
 *
 * This software is the confidential and proprietary information of Cogz Development, LLC.
 * ("Confidential Information").
 * You shall not disclose such Confidential Information and shall use it only in accordance
 * with the terms of the license agreement you entered into with Cogz LLC.
 */

package net.tbnr.gearz.game;

import com.comphenix.protocol.ProtocolLibrary;
import lombok.*;
import net.tbnr.gearz.Gearz;
import net.tbnr.gearz.GearzPlugin;
import net.tbnr.gearz.arena.Arena;
import net.tbnr.gearz.effects.EnderBar;
import net.tbnr.gearz.event.game.GameEndEvent;
import net.tbnr.gearz.event.game.GamePreStartEvent;
import net.tbnr.gearz.event.game.GameStartEvent;
import net.tbnr.gearz.event.player.*;
import net.tbnr.gearz.game.classes.GearzAbstractClass;
import net.tbnr.gearz.game.classes.GearzClassResolver;
import net.tbnr.gearz.game.classes.GearzClassable;
import net.tbnr.gearz.game.tracker.DeathMessageProcessor;
import net.tbnr.gearz.game.tracker.PlayerDeath;
import net.tbnr.gearz.network.GearzPlayerProvider;
import net.tbnr.gearz.player.GearzPlayer;
import net.tbnr.util.BlockRepair;
import net.tbnr.util.RandomUtils;
import net.tbnr.util.inventory.InventoryGUI;
import net.tbnr.util.inventory.base.BaseGUI;
import net.tbnr.util.inventory.base.GUICallback;
import net.tbnr.util.inventory.base.GUIItem;
import net.tbnr.util.player.TPlayer;
import net.tbnr.util.player.TPlayerStorable;
import org.apache.commons.lang.StringUtils;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.LeavesDecayEvent;
import org.bukkit.event.entity.*;
import org.bukkit.event.hanging.HangingBreakByEntityEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.*;
import org.bukkit.event.world.PortalCreateEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;

import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.logging.Logger;

/**
 * GearzGame is a class to represent a game.
 */
@SuppressWarnings({ "NullArgumentToVariableArgMethod", "UnusedDeclaration" })
@EqualsAndHashCode(of = { "id", "arena", "players" }, doNotUseGetters = true, callSuper = false)
@ToString(of = { "arena", "id", "running", "players", "spectators", "gameMeta" })
public abstract class GearzGame<PlayerType extends GearzPlayer, AbstractClassType extends GearzAbstractClass<PlayerType>>
        extends GameDelegate<PlayerType> implements Listener, GearzClassable<PlayerType, AbstractClassType> {
    private final Arena arena;
    private final Set<PlayerType> players;
    private final Set<PlayerType> spectators;
    @Getter(AccessLevel.PROTECTED)
    private final Set<PlayerType> addedPlayers;
    @Getter(AccessLevel.PROTECTED)
    private final Set<PlayerType> endedPlayers;
    @Getter(AccessLevel.PROTECTED)
    private final InventoryGUI spectatorGui;
    private final GameMeta gameMeta;
    private final GearzPlugin<PlayerType, AbstractClassType> plugin;
    private final Integer id;
    private final GearzMetrics<PlayerType> metrics;
    @Getter(AccessLevel.PROTECTED)
    private final PvPTracker<PlayerType> tracker;
    @Getter(AccessLevel.PROTECTED)
    private final GearzPlayerProvider<PlayerType> playerProvider;
    @Getter
    private boolean running;
    @Getter
    private boolean hideStream;
    private static final ChatColor[] progressiveWinColors = { ChatColor.DARK_GREEN, ChatColor.GREEN,
            ChatColor.DARK_AQUA, ChatColor.AQUA, ChatColor.DARK_BLUE, ChatColor.BLUE, ChatColor.DARK_RED,
            ChatColor.RED, ChatColor.DARK_PURPLE, ChatColor.LIGHT_PURPLE, ChatColor.DARK_GRAY, ChatColor.GRAY };

    private static enum NumberSuffixes {
        ONE('1', "st"), TWO('2', "nd"), THREE('3', "rd"), OTHER('*', "th");

        private final char numberCharacter;
        private final String suffix;

        NumberSuffixes(char numberCharacter, String suffix) {
            this.suffix = suffix;
            this.numberCharacter = numberCharacter;
        }

        public static NumberSuffixes valueOf(char chat) {
            for (NumberSuffixes numberSuffixes : NumberSuffixes.values()) {
                if (numberSuffixes.getChar() == chat)
                    return numberSuffixes;
            }
            return NumberSuffixes.OTHER;
        }

        public char getChar() {
            return this.numberCharacter;
        }

        public String getSuffix() {
            return this.suffix;
        }

        public static NumberSuffixes getForString(String string) {
            return valueOf(string.charAt(string.length() - 1));
        }
    }

    public enum Explosion {
        NORMAL, NO_BLOCK_DROP, NO_BLOCK_DAMAGE, NO_BLOCK_DAMAGE_AND_NO_DROP, REPAIR_BLOCK_DAMAGE, REPAIR_BLOCK_DAMAGE_AND_NO_DROP, NONE
    }

    //CLASS SECTION
    @Getter
    private final Map<PlayerType, AbstractClassType> classInstances;

    /**
     * New game in this arena
     *
     * @param players The players in this game
     * @param arena   The Arena that the game is in.
     * @param plugin  The plugin that handles this Game.
     * @param meta    The meta of the game.
     */
    public GearzGame(List<PlayerType> players, Arena arena, GearzPlugin<PlayerType, AbstractClassType> plugin,
            GameMeta meta, Integer id, final GearzPlayerProvider<PlayerType> playerProvider) {
        this.playerProvider = playerProvider;
        this.arena = arena;
        this.players = new HashSet<>();
        this.addedPlayers = new HashSet<>();
        for (PlayerType player : players) {
            if (player.isValid())
                this.players.add(player);
        }

        for (PlayerType player : players) {
            Gearz.getInstance()
                    .debug("GEARZ DEBUG ---<GearzGame|73>--------< <init> / player loop has been CAUGHT for: "
                            + player.getUsername());
        }
        this.tracker = new PvPTracker<>(this);
        this.spectators = new HashSet<>();
        this.endedPlayers = new HashSet<>();
        this.classInstances = new HashMap<>();
        this.plugin = plugin;
        this.gameMeta = meta;
        this.id = id;
        this.hideStream = false;
        this.metrics = GearzMetrics.beginTracking(this);
        this.spectatorGui = new InventoryGUI(getPlayersForMenu(), ChatColor.RED + "Spectator menu.",
                new GUICallback() {
                    @Override
                    public void onItemSelect(BaseGUI gui, GUIItem item, Player player) {
                        Player target = Bukkit.getServer().getPlayerExact(item.getName());
                        if (target == null)
                            return;
                        player.teleport(target.getLocation());
                        player.closeInventory();
                        player.sendMessage(
                                getFormat("spectator-tp", new String[] { "<player>", target.getName() }));
                        TPlayer tPlayer = resolvePlayer(player).getTPlayer();
                        tPlayer.playSound(Sound.ENDERMAN_TELEPORT);
                        tPlayer.playSound(Sound.ARROW_HIT);
                    }

                    @Override
                    public void onGUIOpen(BaseGUI gui, Player player) {
                        gui.updateContents(getPlayersForMenu());
                    }

                    @Override
                    public void onGUIClose(BaseGUI gui, Player player) {
                    }
                });
    }

    /**
     * Starts the game, called publicly to support required logic before telling the game itself that we're starting :D
     */
    public final void startGame() {
        //Required Logic stuff :D
        if (this.running) {
            return;
        }
        this.running = true;
        GamePreStartEvent gamePreStartEvent = new GamePreStartEvent(this);
        Bukkit.getPluginManager().callEvent(gamePreStartEvent);
        if (gamePreStartEvent.isCancelled()) {
            broadcast(getFormat("game-cancelled",
                    new String[] { "<reason>", gamePreStartEvent.getReasonCancelled() }));
            return;
        }
        this.gamePreStart();
        HashSet<PlayerType> players1 = this.getPlayers();
        for (PlayerType player : players1) {
            Bukkit.getPluginManager().callEvent(new PlayerGameEnterEvent(this, player));
            makePlayer(player);
        }
        broadcast(getFormat("game-start"));
        this.metrics.startGame();
        try {
            this.gameStarting();
        } catch (Throwable t) {
            t.printStackTrace();
            for (OfflinePlayer p : Bukkit.getOperators()) {
                if (!p.isOnline()) {
                    continue;
                }
                PlayerType gearzPlayer = resolvePlayer(p.getPlayer());
                gearzPlayer.sendException(t);
            }
        }
        for (PlayerType player2 : players1) {
            try {
                Location location = playerRespawn(player2);
                player2.getTPlayer().teleport(location);
                callActivatePlayer(player2);
            } catch (Throwable ignored) {
            }
        }
        //Ghosting player fix hopefully
        List<Player> playerEntityList = getPlayerEntityList(players1);
        for (Player player : playerEntityList) {
            ProtocolLibrary.getProtocolManager().updateEntity(player, playerEntityList);
        }

        for (Entity e : this.arena.getWorld().getEntitiesByClasses(LivingEntity.class)) {
            if (e instanceof Player) {
                continue;
            }
            if (!allowEntitySpawn(e)) {
                e.remove();
            }
        }
        this.arena.cleanupDrops();
        this.tracker.startGame();
        Bukkit.getPluginManager().callEvent(new GameStartEvent(this));
    }

    private void callActivatePlayer(PlayerType player) {
        AbstractClassType classFor = this.getClassFor(player);
        if (classFor != null)
            classFor.onPlayerActivate();
        this.activatePlayer(player);
    }

    public final boolean isIngame(PlayerType player) {
        return this.allPlayers().contains(player);
    }

    protected final void stopGameForPlayer(PlayerType player, GameStopCause cause) {
        if (this.endedPlayers.contains(player)) {
            return;
        }
        Bukkit.getPluginManager().callEvent(new PlayerGameLeaveEvent(player, this));
        player.getTPlayer().resetPlayer();
        AbstractClassType classFor = getClassFor(player);
        if (classFor != null) {
            classFor.deregisterClass();
            classFor.onClassDeactivate();
            classFor.onGameEndForPlayer();
            getClassResolver().playerUsedClassFully(player, classFor, this);
        }
        onPlayerGameEnd(player, cause);
        this.endedPlayers.add(player);
    }

    private void stopGame(GameStopCause cause) {
        if (!this.running) {
            return;
        }
        this.running = false;
        this.gameEnding();
        for (PlayerType player : allPlayers()) {
            stopGameForPlayer(player, cause);
        }
        broadcast(getFormat("game-ending"));
        this.metrics.finishGame();
        Bukkit.getPluginManager().callEvent(new GameEndEvent(this));
        HandlerList.unregisterAll(this);
        this.plugin.getGameManager().gameEnded(this);
        this.tracker.saveKills();
    }

    private List<Player> getPlayerEntityList(HashSet<PlayerType> players) {
        ArrayList<Player> players1 = new ArrayList<>();
        for (PlayerType player : players) {
            players1.add(player.getPlayer());
        }
        return players1;
    }

    public final void disable() {
        if (!this.running) {
            return;
        }
        this.running = false;
        this.metrics.finishGame();
        Bukkit.getPluginManager().callEvent(new GameEndEvent(this));
    }

    /**
     * Stops the current game ~ forcefully
     */
    public final void stopGame() {
        stopGame(GameStopCause.FORCED);
    }

    /**
     * Stop the current game ~ normally on finish
     */
    protected final void finishGame() {
        this.finishGame(false);
    }

    /**
     * Stop the current game ~ normally on finish
     *
     * @param hideStream whether or not to hide the join/leave stream
     */
    protected final void finishGame(boolean hideStream) {
        this.hideStream = hideStream;
        stopGame(GameStopCause.GAME_END);
    }

    @EventHandler
    public final void onExplosion(EntityExplodeEvent event) {
        if (getExplosionType() == Explosion.NO_BLOCK_DAMAGE
                || getExplosionType() == Explosion.NO_BLOCK_DAMAGE_AND_NO_DROP
                || getExplosionType() == Explosion.NONE) {
            event.setCancelled(true);
        }

        if (getExplosionType() == Explosion.NO_BLOCK_DROP
                || getExplosionType() == Explosion.NO_BLOCK_DAMAGE_AND_NO_DROP
                || getExplosionType() == Explosion.REPAIR_BLOCK_DAMAGE_AND_NO_DROP
                || getExplosionType() == Explosion.NONE) {
            event.setYield(0F);
        }

        if (getExplosionType() == Explosion.REPAIR_BLOCK_DAMAGE
                || getExplosionType() == Explosion.REPAIR_BLOCK_DAMAGE_AND_NO_DROP) {
            final Location center = event.getLocation();
            final List<Block> oldBlocks = event.blockList();
            final List<BlockState> toRemove = new ArrayList<>();

            for (Block block : oldBlocks) {
                if (block.getType() != Material.TNT) {
                    toRemove.add(block.getState());
                }
            }

            BlockRepair.performRegen(toRemove, center, 16, 40L);
        }
    }

    @EventHandler
    public final void onLeafDecay(LeavesDecayEvent event) {
        if (!canLeafsDecay()) {
            event.setCancelled(true);
        }
    }

    /**
     * Gets the players which are playing the game
     *
     * @return List<GearzPlayer> ~ List of players
     */
    public final HashSet<PlayerType> getPlayers() {
        HashSet<PlayerType> players = new HashSet<>();
        players.addAll(this.players);
        return players;
    }

    /**
     * Gets current instance of game
     *
     * @return Arena ~ current instance of game
     */
    public final Arena getArena() {
        return this.arena;
    }

    /**
     * Gets the players which are spectating
     *
     * @return List<PlayerType> ~ List of Players
     */
    public final HashSet<PlayerType> getSpectators() {
        HashSet<PlayerType> spectators = new HashSet<>();
        for (PlayerType player : this.spectators) {
            if (player.getPlayer() != null) {
                spectators.add(player);
            }
        }
        return spectators;
    }

    public final void addPlayer(PlayerType player) {
        Gearz.getInstance().debug(
                "GEARZ DEBUG ---<GearzGame|334>--------< addPlayer has been CAUGHT for: " + player.getUsername());
        this.addedPlayers.add(player);
        this.endedPlayers.add(player);
        makeSpectator(player);
    }

    /**
     * Checks whether a player is playing
     *
     * @param player ~ (in PlayerType wrapper) to be checked
     * @return boolean ~ true or false whether the player is playing
     */
    public final boolean isPlaying(PlayerType player) {
        return this.players.contains(player);
    }

    public final boolean isSpectating(PlayerType player) {
        return this.spectators.contains(player);
    }

    /**
     * Turns a player into a spectator
     *
     * @param player ~ (in PlayerType wrapper) to become a spectator
     */
    protected final void makeSpectator(PlayerType player) {
        player.getTPlayer().resetPlayer();
        this.spectators.add(player);
        Bukkit.getPluginManager().callEvent(new PlayerBeginSpectateEvent(player, this));
        player.getTPlayer().sendMessage(getFormat("begin-spectating"));
        Player player2 = player.getPlayer();
        player2.setGameMode(GameMode.ADVENTURE);
        player2.setAllowFlight(true);
        player2.setFlying(true);
        hideFromAll(player);
        player.getTPlayer().playSound(Sound.FIZZ);
        if (isPlaying(player)) {
            this.players.remove(player);
        } else {
            try {
                player.getTPlayer().teleport(playerRespawn(player));
            } catch (Throwable t) {
                t.printStackTrace();
                player.sendException(t);
            }
        }
        for (PlayerType player1 : spectators) {
            Player player3 = player1.getPlayer();
            if (player3 == null) {
                continue;
            }
            if (!player3.isOnline()) {
                continue;
            }
            if (!player2.isOnline()) {
                continue;
            }
            player2.hidePlayer(player3);
        }
        this.classInstances.remove(player);
        onPlayerBecomeSpectator(player);
        player.getTPlayer().giveItem(Material.BOOK, 1, (short) 0, getFormat("spectator-chooser"));
        spectatorGui.updateContents(getPlayersForMenu());
        RandomUtils.setPlayerCollision(player.getPlayer(), false);
    }

    protected final ArrayList<GUIItem> getPlayersForMenu() {
        ArrayList<GUIItem> items = new ArrayList<>();
        try {
            for (PlayerType player : getPlayers()) {
                Gearz.getInstance().debug(
                        "GEARZ DEBUG ---<GearzGame|399>--------< getPlayersForMenu / player loop has been CAUGHT for: "
                                + player.toString());

                if (!player.isValid()) {
                    continue;
                }

                ItemStack stack = new ItemStack(Material.SKULL_ITEM, 1, (short) SkullType.PLAYER.ordinal());
                ItemMeta itemMeta = stack.getItemMeta();
                String name = player.getPlayer().getName();
                itemMeta.setDisplayName(getGameMeta().mainColor() + name);
                stack.setItemMeta(itemMeta);
                items.add(new GUIItem(stack, player.getUsername()));
            }
        } catch (NullPointerException npe) {
            Gearz.getInstance().debug(
                    "GEARZ DEBUG ---<GearzGame|417>--------< getPlayersForMenu / player loop has thrown a npe: "
                            + npe.getCause());
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return items;
    }

    /**
     * Hides a player from all other players in the game (inc spectators)
     *
     * @param player ~ (in PlayerType wrapper) to be hidden
     */
    private void hideFromAll(PlayerType player) {
        for (PlayerType player1 : allPlayers()) {
            if (player1.getPlayer() == null) {
                continue;
            }
            if (!player1.getPlayer().isOnline()) {
                continue;
            }
            if (player.getPlayer() == null) {
                continue;
            }
            if (!player.getPlayer().isOnline()) {
                continue;
            }
            player1.getPlayer().hidePlayer(player.getPlayer());
        }
    }

    private void showForAll(PlayerType player) {
        for (PlayerType player1 : allPlayers()) {
            if (player1.getPlayer() == null) {
                continue;
            }
            if (!player1.getPlayer().isOnline()) {
                continue;
            }
            if (player.getPlayer() == null) {
                continue;
            }
            if (!player.getPlayer().isOnline()) {
                continue;
            }
            player1.getPlayer().showPlayer(player.getPlayer());
        }
    }

    protected final void fakeDeath(final PlayerType player) {
        player.getTPlayer().resetPlayer();
        PlayerGameDeathEvent event = new PlayerGameDeathEvent(this, player);
        Bukkit.getPluginManager().callEvent(event);
        if (!canPlayerRespawn(player)) {
            Bukkit.getScheduler().runTaskLater(getPlugin(), new Runnable() {
                @Override
                public void run() {
                    makeSpectator(player);
                }
            }, 1);
            return;
        }
        player.getTPlayer().teleport(playerRespawn(player));
        player.getPlayer().playNote(player.getPlayer().getLocation(), Instrument.PIANO, Note.sharp(1, Note.Tone.F));
        final PlayerGameRespawnEvent respawnEvent = new PlayerGameRespawnEvent(player, this);
        Bukkit.getScheduler().runTaskLater(getPlugin(), new Runnable() {
            @Override
            public void run() {
                callActivatePlayer(player);
                Bukkit.getPluginManager().callEvent(respawnEvent);
            }
        }, 1L);
    }

    protected final void makePlayer(PlayerType player) {
        if (this.endedPlayers.contains(player)) {
            throw new IllegalStateException("You cannot restart a game for a player whom is ended");
        }
        if (!this.players.contains(player)) {
            this.players.add(player);
        }
        if (this.spectators.contains(player)) {
            this.spectators.remove(player);
        }
        player.getTPlayer().resetPlayer();
        showForAll(player);
        onPlayerBecomePlayer(player);
        if (this.getClassResolver() != null) {
            try {
                updateClassFor(player);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException
                    | InstantiationException e) {
                e.printStackTrace();
            }
        }
        player.setGame(this);
    }

    protected abstract AbstractClassType constructClassType(Class<? extends AbstractClassType> classType,
            PlayerType player)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException;

    /**
     * Removes a player from the game, depending on if there spectating or not it will give different GameStopCause
     *
     * @param player ~ (in PlayerType wrapper) to be removed
     */
    public final void removePlayer(PlayerType player) {
        if (!this.isRunning()) {
            return;
        }
        Gearz.getInstance().debug("GEARZ DEBUG ---<GearzGAme|483>--------< removePlayer has been CAUGHT for: "
                + player.getUsername());
        GameStopCause cause = isSpectating(player) ? GameStopCause.GAME_END : GameStopCause.FORCED;
        playerLeft(player);
        stopGameForPlayer(player, cause);
        plugin.getGameManager().spawn(player);
    }

    public final void playerLeft(PlayerType player) {
        Gearz.getInstance().debug(
                "GEARZ DEBUG ---<GearzGAme|490>--------< playerLeft has been CAUGHT for: " + player.getUsername());
        this.players.remove(player);
        this.spectators.remove(player);
        player.setGame(null);
        if (this.players.size() < 2) {
            stopGame(GameStopCause.FORCED);
        }
        removePlayerFromGame(player);
        RandomUtils.setPlayerCollision(player.getPlayer(), true);
    }

    public final String getFormatBase(String format) {
        return formatUsingMeta(this.gameMeta, Gearz.getInstance().getFormat("game-strings." + format));
    }

    public static String formatUsingMeta(GameMeta meta, String string) {
        String[][] array = { new String[] { "<p>", meta.mainColor().toString() },
                new String[] { "<s>", meta.secondaryColor().toString() },
                new String[] { "<gname>", meta.longName() }, new String[] { "<gn>", meta.shortName() },
                new String[] { "<version>", meta.version() }, new String[] { "<description>", meta.description() },
                new String[] { "<key>", meta.key() }, new String[] { "<max>", String.valueOf(meta.maxPlayers()) },
                new String[] { "<min>", String.valueOf(meta.minPlayers()) } };
        String finalS = string;
        for (String[] element : array) {
            finalS = finalS.replaceAll(element[0], element[1]);
        }
        return (finalS == null ? string : finalS);
    }

    public final String getFormat(String format, String[]... args) {
        String formatBase = getFormatBase(format);
        if (args != null) {
            for (String[] arg : args) {
                if (arg.length < 2) {
                    continue;
                }
                formatBase = formatBase.replaceAll(arg[0], arg[1]);
            }
        }
        return getFormatBase("prefix") + formatBase;
    }

    public final String getFormat(String format) {
        return this.getFormat(format, null);
    }

    public final String getPluginFormat(String format, boolean prefix, String[]... args) {
        String format1 = plugin.getFormat(format);
        if (args != null) {
            for (String[] arg : args) {
                if (arg.length < 2) {
                    continue;
                }
                format1 = format1.replaceAll(arg[0], arg[1]);
            }
        }
        format1 = formatUsingMeta(this.gameMeta, format1);
        if (prefix) {
            format1 = getFormatBase("prefix") + format1;
        }
        return (format1 == null ? format : format1);
    }

    public final String getPluginFormat(String format) {
        return getPluginFormat(format, true);
    }

    public final String getPluginFormat(String format, boolean prefix) {
        return getPluginFormat(format, prefix, null);
    }

    /**
     * Broadcasts message to all players in the game
     *
     * @param message ~ The message to Broadcast
     */
    public final void broadcast(String message) {
        for (PlayerType player : allPlayers()) {
            try {
                player.getTPlayer().sendMessage(message);
            } catch (Exception ignored) {
            }
        }
    }

    /**
     * Gets all the players including spectators
     *
     * @return List<PlayerType> List of players (in GearzPlayer wrapper) inc. Spectators
     */
    public final HashSet<PlayerType> allPlayers() {
        HashSet<PlayerType> allPlayers = new HashSet<>();
        allPlayers.addAll(this.getPlayers());
        allPlayers.addAll(this.getSpectators());
        return allPlayers;
    }

    /**
     * Gets the Plugin
     *
     * @return Plugin ~ the plugin instance
     */
    public final GearzPlugin<PlayerType, AbstractClassType> getPlugin() {
        return this.plugin;

    }

    /**
     * Gets the id of the game
     *
     * @return Integer ~ Id of game
     */
    public final Integer getId() {
        return id;
    }

    /*
     * Spectator implementation
     * This code will prevent spectators from doing bad stuff.
     */
    @EventHandler
    public final void onPlayerInteract(PlayerInteractEvent event) {
        PlayerType player = resolvePlayer(event.getPlayer());
        if (!isIngame(player)) {
            return;
        }
        Block type = event.getClickedBlock();
        if (type != null && type.getType() == Material.BED_BLOCK) {
            event.setCancelled(true);
            return;
        }
        if (isSpectating(player)) {
            if (event.getPlayer().getItemInHand().getType() == Material.BOOK
                    && event.getAction() != Action.PHYSICAL) {
                spectatorGui.open(player.getPlayer());
                event.setCancelled(true);
            }
            event.setCancelled(true);
            return;
        }
        if (event.getPlayer().getItemInHand().getType() == Material.BOW) {
            if (!canDrawBow(player)) {
                player.getTPlayer().sendMessage(getFormat("no-bow"));
                event.setCancelled(true);
            }
            return;
        }
        if (event.getPlayer().getItemInHand().getType() == Material.POTION
                || event.getPlayer().getItemInHand().getType() == Material.EXP_BOTTLE) {
            if (!canUsePotion(player)) {
                player.getTPlayer().sendMessage(getFormat("no-potion"));
                event.setCancelled(true);
            }
            return;
        }
        if (!canUse(player)) {
            if (event.getAction() != Action.PHYSICAL) {
                player.getTPlayer().sendMessage(getFormat("no-interact"));
            }
            event.setCancelled(true);
        }
    }

    @EventHandler
    public final void onProjectileLaunch(ProjectileLaunchEvent event) {
        if ((event.getEntity().getType() != EntityType.SNOWBALL)) {
            return;
        }
        if (!(event.getEntity().getShooter() instanceof Player)) {
            return;
        }
        PlayerType player = resolvePlayer((Player) event.getEntity().getShooter());
        onSnowballThrow(player);
    }

    @EventHandler
    public final void onEntityInteract(EntityInteractEvent event) {
        onEntityInteract(event.getEntity(), event);
    }

    @EventHandler
    public final void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
        PlayerType player = resolvePlayer(event.getPlayer());
        if (!isIngame(player)) {
            return;
        }
        if (canBuild(player) && !isSpectating(player)) {
            return;
        }
        player.getTPlayer().sendMessage(getFormat("not-allowed-spectator"));
        event.setCancelled(true);
    }

    @EventHandler
    public final void onHangingDestroy(HangingBreakByEntityEvent event) {
        if (!(event.getEntity() instanceof Player)) {
            return;
        }
        PlayerType player = resolvePlayer((Player) event.getEntity());
        if (!isIngame(player)) {
            return;
        }
        if (canBuild(player) && !isSpectating(player)) {
            return;
        }
        player.getTPlayer().sendMessage(getFormat("not-allowed-spectator"));
        event.setCancelled(true);
    }

    @EventHandler
    public final void onEntityAttack(EntityDamageByEntityEvent event) {
        Entity eventDamager = event.getDamager();
        Entity eventTarget = event.getEntity();
        onDamage(eventDamager, eventTarget, event);
        if (!((eventDamager instanceof Player) || (eventTarget instanceof Player) || (eventDamager instanceof Arrow)
                || (eventDamager instanceof ThrownPotion))) {
            return;
        }
        if (eventDamager instanceof Player || eventDamager instanceof Arrow
                || eventDamager instanceof ThrownPotion) {
            if (eventDamager instanceof Arrow) {
                eventDamager = (Entity) ((Arrow) eventDamager).getShooter();
                if (!(eventDamager instanceof Player)) {
                    return;
                }
            }
            if (eventDamager instanceof ThrownPotion) {
                eventDamager = (Entity) ((ThrownPotion) eventDamager).getShooter();
                if (!(eventDamager instanceof Player)) {
                    return;
                }
                if (eventDamager.equals(eventTarget)) {
                    return;
                }
            }
            PlayerType damager = resolvePlayer((Player) eventDamager);
            if (this.gameMeta.pvpMode() == GameMeta.PvPMode.NoPvP) {
                damager.getTPlayer().sendMessage(getFormat("no-pvp-allowed"));
                event.setCancelled(true);
                return;
            }
            if (isSpectating(damager)) {
                damager.getTPlayer().sendMessage(getFormat("not-allowed-spectator"));
                event.setCancelled(true);
                return;
            }
            if (eventTarget instanceof Player) {
                PlayerType target = resolvePlayer((Player) eventTarget);
                double damage = damageForHit(damager, target, event.getDamage());
                if (isSpectating(target)) {
                    event.setCancelled(true);
                    return;
                }
                if (damage != -1) {
                    event.setDamage(damage);
                }
                if (useEnderBar(damager)) {
                    EnderBar.setHealthPercent(damager,
                            ((float) target.getPlayer().getHealth() / (float) target.getPlayer().getMaxHealth()));
                }
                if (!canPvP(damager, target)) {
                    damager.getTPlayer().sendMessage(
                            getFormat("no-pvp", new String[] { "<player>", target.getPlayer().getName() }));
                    event.setCancelled(true);
                    return;
                }
                PlayerGameDamageEvent callingEvent = new PlayerGameDamageEvent(this, target, event.getDamage(),
                        false);
                Bukkit.getPluginManager().callEvent(callingEvent);
                if (callingEvent.isCancelled()) {
                    event.setCancelled(true);
                    return;
                }
                event.setDamage(callingEvent.getDamage());
                PlayerGameDamageByPlayerEvent callingEvent2 = new PlayerGameDamageByPlayerEvent(callingEvent,
                        damager);
                Bukkit.getPluginManager().callEvent(callingEvent2);
                if (callingEvent2.isCancelled()) {
                    event.setCancelled(true);
                    return;
                }
                event.setDamage(callingEvent2.getDamage());
            }
        } else if (eventDamager instanceof LivingEntity) {
            PlayerType target = resolvePlayer((Player) eventTarget);
            PlayerGameDamageEvent callingEvent = new PlayerGameDamageEvent(this, target, event.getDamage(), false);
            Bukkit.getPluginManager().callEvent(callingEvent);
            if (callingEvent.isCancelled()) {
                event.setCancelled(true);
            }
        }
    }

    @EventHandler
    public void playerDied(PlayerDeathEvent event) {
        event.getEntity().setHealth(event.getEntity().getHealthScale());
        Player deadPlayer = event.getEntity();
        final PlayerType dead = resolvePlayer(deadPlayer);
        List<ItemStack> drops = event.getDrops();
        ItemStack[] itemStacks = drops.toArray(new ItemStack[drops.size()]);
        for (ItemStack stack : itemStacks) {
            if (!canDropItem(dead, stack)) {
                event.getDrops().remove(stack);
            }
        }
        DeathMessageProcessor processor = new DeathMessageProcessor(event, this);
        final PlayerDeath death = processor.processDeath();
        final Logger logger = Gearz.getInstance().getLogger();
        if (death.getCredited() != null) {
            final PlayerType player = resolvePlayer(death.getCredited());
            Bukkit.getScheduler().runTaskLater(getPlugin(), new Runnable() {
                @Override
                public void run() {
                    playerKilledPlayer(player, dead);
                }
            }, 2L);
        } else {
            Bukkit.getScheduler().runTaskLater(getPlugin(), new Runnable() {
                @Override
                public void run() {
                    playerKilled(dead, death.getCredited());
                    fakeDeath(dead);
                }
            }, 2L);

        }
        broadcast(death.getDeathMessage());
    }

    private void playerKilledPlayer(final PlayerType damager, final PlayerType target) {
        this.tracker.trackKill(damager, target);
        fakeDeath(target);
        this.playerKilled(target, damager);
        PlayerGameKillEvent event = new PlayerGameKillEvent(this, target, damager);
        Bukkit.getPluginManager().callEvent(event);
    }

    @EventHandler
    public final void onEntityDamage(EntityDamageEvent event) {
        if (!(event.getEntity() instanceof Player)) {
            return;
        }
        if ((event instanceof EntityDamageByEntityEvent)) {
            Entity damager = ((EntityDamageByEntityEvent) event).getDamager();
            if (damager instanceof Arrow || damager instanceof Player) {
                return;
            }
            if (damager instanceof ThrownPotion && !event.getEntity().equals(damager)) {
                return;
            }
        }
        if (event.getCause() == EntityDamageEvent.DamageCause.ENTITY_ATTACK) {
            return;
        }
        PlayerType player = resolvePlayer((Player) event.getEntity());
        if (!isIngame(player)) {
            return;
        }
        if (event.getCause() == EntityDamageEvent.DamageCause.SUFFOCATION) {
            player.getTPlayer().teleport(player.getPlayer().getLocation().add(0, 1, 0));
        }
        if (isSpectating(player)) {
            event.setCancelled(true);
            return;
        }
        if (onFallDamage(player, event) && event.getCause() == EntityDamageEvent.DamageCause.FALL) {
            event.setCancelled(true);
            return;
        }
        PlayerGameDamageEvent callingEvent = new PlayerGameDamageEvent(this, player, event.getDamage(), false);
        Bukkit.getPluginManager().callEvent(callingEvent);
        if (callingEvent.isCancelled()) {
            event.setCancelled(true);
        }
    }

    @SafeVarargs
    protected final void displayWinners(PlayerType... players) {
        List<String> strings = new ArrayList<>();
        String line = ChatColor.GOLD.toString() + ChatColor.STRIKETHROUGH + StringUtils.repeat(" ", 64);
        strings.add(line);
        for (int x = 0, l = progressiveWinColors.length; x < players.length; x++) {
            int place = x + 1;
            float percentage = x == 0 ? 0f : (float) x / players.length;
            int index = Double.valueOf(Math.floor(l * percentage)).intValue();
            ChatColor color = progressiveWinColors[index];
            strings.add(
                    "  " + color + players[x].getUsername() + ChatColor.GRAY + " - " + color + String.valueOf(place)
                            + NumberSuffixes.getForString(String.valueOf(place)).getSuffix() + " place.");
        }
        strings.add(line);
        for (PlayerType player : allPlayers()) {
            TPlayer tPlayer = player.getTPlayer();
            for (String s : strings) {
                tPlayer.sendMessage(s);
            }
        }
    }

    @EventHandler
    public final void onArrow(EntityShootBowEvent event) {
        if (!(event.getEntity() instanceof Player)) {
            return;
        }
        PlayerType shooter = resolvePlayer((Player) event.getEntity());
        if (!isIngame(shooter)) {
            return;
        }
        if (isSpectating(shooter)) {
            shooter.getTPlayer().sendMessage(getFormat("not-allowed-spectator"));
            event.setCancelled(true);
            return;
        }
        if (!canUse(shooter)) {
            shooter.getTPlayer().sendMessage(getFormat("no-shoot"));
            event.setCancelled(true);
        }
    }

    @EventHandler
    public final void onBlockBreak(BlockBreakEvent event) {
        PlayerType player = resolvePlayer(event.getPlayer());
        if (!isIngame(player)) {
            return;
        }
        if (isSpectating(player)) {
            player.getTPlayer().sendMessage(getFormat("not-allowed-spectator"));
            event.setCancelled(true);
            return;
        }
        if (!canBreak(player, event.getBlock())) {
            Material type = event.getBlock().getType();
            if (!(type == Material.LONG_GRASS || type == Material.TNT || type == Material.CROPS)) {
                player.getTPlayer().sendMessage(getFormat("no-break"));
            }
            event.setCancelled(true);
        }
    }

    @EventHandler
    public final void onBlockPlace(BlockPlaceEvent event) {
        PlayerType player = resolvePlayer(event.getPlayer());
        if (!isIngame(player))
            return;
        if (isSpectating(player)) {
            player.getTPlayer().sendMessage(getFormat("not-allowed-spectator"));
            event.setCancelled(true);
            return;
        }
        if (!canPlace(player, event.getBlockPlaced())) {
            player.getTPlayer().sendMessage(getFormat("no-place"));
            event.setCancelled(true);
        }
    }

    @EventHandler
    public void onMove(PlayerMoveEvent event) {
        PlayerType player = resolvePlayer(event.getPlayer());
        if (!isIngame(player)) {
            return;
        }
        if (!canMove(player)) {
            if (event.getTo().getBlock().getX() != event.getFrom().getBlock().getX()
                    || event.getTo().getBlock().getZ() != event.getFrom().getBlock().getZ()) {
                event.setTo(event.getFrom());
            }
        }
    }

    @EventHandler
    public void onPlayerDeath(PlayerDeathEvent event) {
        event.setDeathMessage(null);
    }

    @EventHandler(priority = EventPriority.HIGHEST)
    public void onPlayerRespawn(PlayerRespawnEvent event) {
        PlayerType player = resolvePlayer(event.getPlayer());
        if (!isIngame(player)) {
            return;
        }
        if (!canPlayerRespawn(player)) {
            makeSpectator(player);
            return;
        }
        event.setRespawnLocation(playerRespawn(player));
    }

    @EventHandler(priority = EventPriority.HIGHEST)
    public void onItemDrop(PlayerDropItemEvent event) {
        PlayerType player = resolvePlayer(event.getPlayer());
        if (!isIngame(player))
            return;
        if (isSpectating(player) || !canDropItem(player, event.getItemDrop().getItemStack()))
            event.setCancelled(true);
    }

    @EventHandler(priority = EventPriority.HIGHEST)
    public void onHunger(FoodLevelChangeEvent event) {
        if (!(event.getEntity() instanceof Player))
            return;
        PlayerType player = resolvePlayer((Player) event.getEntity());
        if (!isIngame(player))
            return;
        if (isSpectating(player))
            event.setCancelled(true);
        if (!allowHunger(player))
            event.setCancelled(true);
    }

    @EventHandler(priority = EventPriority.HIGHEST)
    public final void onItemPickup(PlayerPickupItemEvent event) {
        PlayerType player = resolvePlayer(event.getPlayer());
        if (isSpectating(player)) {
            event.setCancelled(true);
            return;
        }
        if (!this.canPickup(player, event.getItem())) {
            event.setCancelled(true);
        }
    }

    @EventHandler
    public final void onCreatureSpawn(CreatureSpawnEvent event) {
        LivingEntity entity = event.getEntity();
        if (this.getArena().getWorld().equals(entity.getLocation().getWorld()) && !allowEntitySpawn(entity)) {
            event.getEntity().remove();
        }
    }

    @EventHandler
    public final void onEggThrow(PlayerEggThrowEvent event) {
        onEggThrow(resolvePlayer(event.getPlayer()), event);
    }

    @EventHandler
    public final void onInventoryChange(InventoryClickEvent event) {
        if (!this.allowInventoryChange()) {
            event.setCancelled(true);
        }
    }

    @EventHandler
    public final void onPortalCreate(PortalCreateEvent event) {
        event.setCancelled(!canCreatePortal());
    }

    @EventHandler
    public final void onEXPChange(PlayerExpChangeEvent event) {
        PlayerType player = resolvePlayer(event.getPlayer());
        if (!isIngame(player)) {
            return;
        }
        if (isSpectating(player) || !canPickupEXP(player)) {
            event.setAmount(0);
        }
    }

    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onBoat(EntityDamageByEntityEvent event) {
        if (!(event.getDamager() instanceof Player))
            return;
        PlayerType damager = resolvePlayer((Player) event.getDamager());
        if (!isSpectating(damager))
            return;
        event.setCancelled(true);
        damager.getTPlayer().sendMessage(getFormat("not-allowed-spectator"));
    }

    protected final void addWin(PlayerType gearzPlayer) {
        TPlayer tPlayer = gearzPlayer.getTPlayer();
        Integer gameWins = (Integer) tPlayer.getStorable(getPlugin(), new GameWins(this));
        if (gameWins == null) {
            gameWins = 0;
        }
        gameWins += 1;
        GameWins newGameWins = new GameWins(this);
        newGameWins.setWins(gameWins);
        tPlayer.store(getPlugin(), newGameWins);
    }

    public final boolean hasEnded(PlayerType player) {
        return this.endedPlayers.contains(player);
    }

    public final void registerExternalListeners(Listener listener) {
        getPlugin().registerEvents(listener);
    }

    protected final PlayerType resolvePlayer(Player player) {
        return this.playerProvider.getPlayerFromPlayer(player);
    }

    /**
     * Gets the games meta
     *
     * @return GameMeta ~ the game's meta
     */
    public GameMeta getGameMeta() {
        return this.gameMeta;
    }

    @Data
    private final class GameWins implements TPlayerStorable {
        @NonNull
        private final GearzGame<PlayerType, AbstractClassType> game;
        private Integer wins;

        @Override
        public String getName() {
            return game.getGameMeta().key() + "_" + "wins";
        }

        @Override
        public Object getValue() {
            return wins == null ? 0 : wins;
        }
    }

    @Data
    private final class SpectatorReminder implements Runnable {
        @NonNull
        private final GearzGame<PlayerType, AbstractClassType> game;

        @Override
        public void run() {
            for (PlayerType player : game.getSpectators()) {
                if (game.hasEnded(player)) {
                    continue;
                }
                player.getTPlayer().sendMessage(game.getFormat("spectator-ingame"));
            }
        }
    }

    @Override
    public AbstractClassType getClassFor(PlayerType player) {
        return this.classInstances.get(player);
    }

    protected void updateClassFor(PlayerType player) throws InvocationTargetException, NoSuchMethodException,
            InstantiationException, IllegalAccessException {
        AbstractClassType classFor = getClassFor(player);
        if (classFor != null) {
            classFor.deregisterClass();
            classFor.onClassDeactivate();
        }
        if (this.getClassResolver() != null) {
            AbstractClassType abstractClassType = constructClassType(
                    getClassResolver().getClassForPlayer(player, this), player);
            this.classInstances.put(player, abstractClassType);
            abstractClassType.registerClass();
            abstractClassType.onClassActivate();
            if (classFor == null)
                abstractClassType.onGameStart();
            abstractClassType.onPlayerActivate();
        }
    }

    @Override
    public GearzClassResolver<PlayerType, AbstractClassType> getClassResolver() {
        return getPlugin().getClassResolver();
    }
}