de.themoep.simpleteampvp.games.SimpleTeamPvPGame.java Source code

Java tutorial

Introduction

Here is the source code for de.themoep.simpleteampvp.games.SimpleTeamPvPGame.java

Source

package de.themoep.simpleteampvp.games;

import de.themoep.servertags.bukkit.ServerInfo;
import de.themoep.simpleteampvp.LocationInfo;
import de.themoep.simpleteampvp.SimpleTeamPvP;
import de.themoep.simpleteampvp.TeamInfo;
import de.themoep.simpleteampvp.Utils;
import lombok.Getter;
import net.blitzcube.mlapi.MultiLineAPI;
import net.blitzcube.mlapi.tag.TagController;
import net.md_5.bungee.api.ChatColor;
import org.apache.commons.lang.Validate;
import org.bukkit.Color;
import org.bukkit.FireworkEffect;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.Chest;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Creature;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Firework;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
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.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.hanging.HangingBreakByEntityEvent;
import org.bukkit.event.hanging.HangingPlaceEvent;
import org.bukkit.event.inventory.CraftItemEvent;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.FireworkMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Score;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.stream.Collectors;

/**
 * SimpleTeamPvP
 * Copyright (C) 2015 Max Lee (https://github.com/Phoenix616/)
 * <p/>
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Mozilla Public License as published by
 * the Mozilla Foundation, version 2.
 * <p>
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Mozilla Public License v2.0 for more details.
 * <p>
 * You should have received a copy of the Mozilla Public License v2.0
 * along with this program. If not, see <http://mozilla.org/MPL/2.0/>.
 */
public abstract class SimpleTeamPvPGame implements Listener {

    protected final SimpleTeamPvP plugin;
    @Getter
    private final String name;
    @Getter
    private final GameConfig config;
    protected TagController tagController;
    private GameState state = GameState.CREATED;
    private GameTimer timer = null;
    private int regenId = -1;
    private BukkitTask teleportTask;
    private BukkitTask fwTask;
    private Objective pointObjective = null;
    private Objective killStreakObjectiveTab = null;
    private Objective killStreakObjectiveName = null;
    private List<String> highestKillStreakPlayers = new ArrayList<>();
    private int highestKillStreakScore = 0;

    private Objective playerKillsObjective = null;

    @Getter
    private Set<LocationInfo> pointBlockSet = new HashSet<>();
    private Map<String, Integer> teamScores = new HashMap<>();

    public SimpleTeamPvPGame(SimpleTeamPvP plugin, String name) {
        this.plugin = plugin;
        this.name = name.toLowerCase();

        plugin.getLogger().log(Level.INFO, "Initializing " + name + " game");

        ConfigurationSection game = plugin.getConfig().getConfigurationSection("games." + name);
        config = new GameConfig(game);

        if (plugin.useMultiLineApi()) {
            tagController = () -> 0;
            MultiLineAPI.register(tagController);
        }
    }

    public void loadConfig() {
        config.load();

        if (config.getPointItemChestLocation() != null) {
            Location loc = config.getPointItemChestLocation().getLocation();
            if (loc != null) {
                Block block = loc.getBlock();
                if (block.getState() instanceof Chest) {
                    Chest chest = (Chest) block.getState();
                    ItemStack item = null;
                    for (ItemStack i : chest.getBlockInventory().getContents()) {
                        if (i != null) {
                            item = i;
                            break;
                        }
                    }
                    if (item != null) {
                        config.setPointItem(item);
                        plugin.getLogger().log(Level.INFO, "Point item is " + config.getPointItem().getType());
                    }
                }
            } else {
                plugin.getLogger().log(Level.WARNING, "Could not get location for LocationInfo "
                        + config.getPointItemChestLocation() + ". Maybe the world doesn't exist anymore?");
            }
        }

        if (config.getPointItem() == null) {
            plugin.getLogger().log(Level.WARNING, "No point item configured!");
            config.setPointItem(new ItemStack(
                    config.getPointBlock() != Material.AIR ? config.getPointBlock() : Material.SLIME_BALL, 1));
            ItemMeta meta = config.getPointItem().getItemMeta();
            meta.setDisplayName("Point Item");
            config.getPointItem().setItemMeta(meta);
        }

        state = GameState.INITIATED;
    }

    /**
     * Join players into this game
     * @return <tt>true</tt> if game is in GameState.INITIATED and players can be joined
     */
    public boolean join() {
        if (getState() != GameState.INITIATED)
            return false;

        for (TeamInfo team : config.getTeams().values()) {
            team.init();
        }

        plugin.getServer().broadcastMessage(ChatColor.GREEN + "Spieler werden den Teams hinzugefgt...");

        state = GameState.JOINING;
        for (Player player : plugin.getServer().getOnlinePlayers()) {
            if (player.hasPermission(SimpleTeamPvP.BYPASS_PERM) || player.getGameMode() == GameMode.CREATIVE
                    || player.getGameMode() == GameMode.SPECTATOR)
                continue;

            if (getTeam(player) != null)
                continue;

            TeamInfo team = getTeamByJoinLocation(player.getLocation());
            if (team != null) {
                team.addPlayer(player);
            }
        }
        return true;
    }

    /**
     * Balance the teams
     * @return <tt>true</tt> if game is in GameState.JOINING and players can be balanced
     */
    public boolean balance() {
        if (getState() != GameState.JOINING)
            return false;

        plugin.getServer()
                .broadcastMessage(ChatColor.GREEN + "Ausbalancieren und Auffllen der Teams gestartet...");

        Map<Player, String> beforeBalance = new HashMap<>();
        List<Player> playersToJoin = new ArrayList<>();
        for (Player player : plugin.getServer().getOnlinePlayers()) {
            if (player.hasPermission(SimpleTeamPvP.BYPASS_PERM) || player.getGameMode() == GameMode.CREATIVE
                    || player.getGameMode() == GameMode.SPECTATOR)
                continue;
            TeamInfo team = getTeam(player);
            if (team == null) {
                if (config.getRandomRegion() == null || config.getRandomRegion().contains(player.getLocation()))
                    playersToJoin.add(player);
                beforeBalance.put(player, "");
            } else {
                beforeBalance.put(player, team.getName());
            }
        }
        plugin.getLogger().log(Level.INFO, "Players to join: " + playersToJoin.size());

        int totalPlayers = playersToJoin.size();
        for (TeamInfo team : config.getTeams().values()) {
            totalPlayers += team.getSize();
        }
        plugin.getLogger().log(Level.INFO, "Number of teams: " + config.getTeams().size());
        double perfectSize = (double) totalPlayers / (double) config.getTeams().size();

        plugin.getLogger().log(Level.INFO, "perfectSize: " + perfectSize);

        if (plugin.getServerTags() != null) {
            // Team key -> Tag
            Map<String, String> teamTags = new HashMap<>();

            for (TeamInfo team : config.getTeams().values()) {

                Map<String, Integer> tags = new HashMap<>();
                for (String playerName : team.getScoreboardTeam().getEntries()) {
                    Player player = plugin.getServer().getPlayer(playerName);
                    if (player == null)
                        continue;

                    String tag = "no server";
                    ServerInfo serverInfo = plugin.getServerTags().getPlayerServer(player);
                    if (serverInfo != null) {
                        tag = serverInfo.getTag();
                    }
                    if (!tags.containsKey(tag)) {
                        tags.put(tag, 0);
                    }
                    tags.put(tag, tags.get(tag) + 1);
                }

                String teamTag = "no server";
                int tagCount = 0;
                for (Map.Entry<String, Integer> entry : tags.entrySet()) {
                    if (entry.getValue() > tagCount) {
                        tagCount = entry.getValue();
                        teamTag = entry.getKey();
                    }
                }

                teamTags.put(team.getName(), teamTag);
            }

            for (TeamInfo team : config.getTeams().values()) {
                // Filter out players that come from another server than the majority of the team
                // and remove them as long as the team is larger than the perfect size
                for (String playerName : team.getScoreboardTeam().getEntries()) {
                    if (team.getSize() <= perfectSize + 0.5)
                        break;

                    Player player = plugin.getServer().getPlayer(playerName);
                    if (player == null)
                        continue;

                    String tag = "no server";
                    ServerInfo serverInfo = plugin.getServerTags().getPlayerServer(player);
                    if (serverInfo != null) {
                        tag = serverInfo.getTag();
                    }

                    if (tag.equals(teamTags.get(team.getName())))
                        continue;

                    plugin.getLogger().log(Level.INFO,
                            "[ST] Removed " + player.getName() + " from " + team.getName() + " (Step 1)");

                    team.removePlayer(player);
                    playersToJoin.add(player);
                }

                // Team still larger than the perfect size? Remove last joined player
                Deque<String> teamMates = new ArrayDeque<>(team.getScoreboardTeam().getEntries());
                while (team.getSize() > perfectSize + 0.5) {
                    String name = teamMates.peekLast();
                    Player player = plugin.getServer().getPlayer(name);
                    if (player == null)
                        continue;

                    team.removePlayer(player);
                    plugin.getLogger().log(Level.INFO,
                            "[ST] Removed " + player.getName() + " from " + team.getName() + " (Step 2)");
                    teamMates.pollLast();
                    playersToJoin.add(player);
                }
            }

            // Add rest of players to teams from their server
            Iterator<Player> playerIterator = playersToJoin.iterator();
            while (playerIterator.hasNext()) {
                Player player = playerIterator.next();
                ServerInfo serverInfo = plugin.getServerTags().getPlayerServer(player);
                if (serverInfo != null && teamTags.containsValue(serverInfo.getTag())) {
                    for (TeamInfo team : config.getTeams().values()) {
                        if (team.getSize() < perfectSize - 0.5 && teamTags.containsKey(team.getName())
                                && teamTags.get(team.getName()).equals(serverInfo.getTag())) {
                            team.addPlayer(player);
                            plugin.getLogger().log(Level.INFO,
                                    "[ST] Added " + player.getName() + " to " + team.getName());
                            playerIterator.remove();
                            break;
                        }
                    }
                }
            }
            plugin.getLogger().log(Level.INFO, "Players to join after servertags: " + playersToJoin.size());
        }

        // Remove players from teams that have more than the perfect size
        for (TeamInfo team : config.getTeams().values()) {
            for (String playerName : team.getScoreboardTeam().getEntries()) {
                if (team.getSize() <= perfectSize + 0.5)
                    break;

                Player player = plugin.getServer().getPlayer(playerName);
                if (player == null)
                    continue;

                plugin.getLogger().log(Level.INFO, "Removed " + player.getName() + " from " + team.getName());

                team.removePlayer(player);
                playersToJoin.add(player);
            }
        }

        Iterator<Player> playerIterator = playersToJoin.iterator();
        for (TeamInfo team : config.getTeams().values()) {
            while (playerIterator.hasNext()) {
                if (team.getSize() >= perfectSize - 0.5)
                    break;

                Player player = playerIterator.next();
                team.addPlayer(player);
                plugin.getLogger().log(Level.INFO, "Added " + player.getName() + " to " + team.getName());
                playerIterator.remove();
            }
        }

        if (playerIterator.hasNext()) {
            plugin.getLogger().log(Level.INFO, "Adding " + playersToJoin.size()
                    + " remaining players to teams according to their player count:");

            List<TeamInfo> teams = new ArrayList<>(config.getTeams().values());
            teams.sort((t1, t2) -> Integer.compare(t2.getSize(), t1.getSize()));

            for (TeamInfo team : teams) {
                while (playerIterator.hasNext()) {
                    if (team.getSize() > perfectSize)
                        break;

                    Player player = playerIterator.next();
                    team.addPlayer(player);
                    plugin.getLogger().log(Level.INFO,
                            "Added remaining player " + player.getName() + " to " + team.getName());
                    playerIterator.remove();
                }
            }
        }

        if (playerIterator.hasNext()) {
            plugin.getLogger().log(Level.INFO,
                    "Adding " + playersToJoin.size() + " remaining players to totally random teams:");
            Random r = new Random();
            List<TeamInfo> teams = new ArrayList<>(config.getTeams().values());
            while (playerIterator.hasNext()) {
                Player player = playerIterator.next();
                TeamInfo team = teams.get(r.nextInt(teams.size()));
                team.addPlayer(player);
                plugin.getLogger().log(Level.INFO,
                        "Added player " + player.getName() + " to " + team.getName() + " by random");
                playerIterator.remove();
            }
        }
        plugin.getLogger().log(Level.INFO, "All players joined! (" + playersToJoin.size() + ")");

        for (Map.Entry<Player, String> entry : beforeBalance.entrySet()) {
            TeamInfo team = getTeam(entry.getKey());
            if (team != null && !team.getName().equals(entry.getValue())) {
                Player player = null;
                for (Iterator<String> it = team.getScoreboardTeam().getEntries().iterator(); player == null
                        && it.hasNext();) {
                    player = plugin.getServer().getPlayer(it.next());
                }
                if (player != null && team.getJoinRegion().contains(player.getLocation())) {
                    entry.getKey().teleport(player);
                } else {
                    entry.getKey().teleport(team.getJoinRegion().calculateMiddle().getLocation());
                }
            }
        }

        plugin.getServer().broadcastMessage(ChatColor.GREEN + "Teams ausbalanciert und aufgefllt!");

        state = GameState.WAITING;
        return true;
    }

    /**
     * Starts the game and teleports all players into the arena
     * @return <tt>true</tt> if the game is in GameState.WAITING after players got joined
     */
    public boolean start() {
        if (getState() != GameState.WAITING)
            return false;

        pointObjective = plugin.getServer().getScoreboardManager().getMainScoreboard().getObjective("teamPoints");
        if (pointObjective != null) {
            try {
                pointObjective.unregister();
            } catch (IllegalStateException e) {
                plugin.getLogger().log(Level.WARNING, "Could not unregister point objective?", e);
            }
        }
        pointObjective = plugin.getServer().getScoreboardManager().getMainScoreboard()
                .registerNewObjective("teamPoints", "dummy");
        setObjectiveDisplay(config.getObjectiveDisplay());

        playerKillsObjective = plugin.getServer().getScoreboardManager().getMainScoreboard()
                .getObjective("playerKills");
        if (playerKillsObjective != null) {
            try {
                playerKillsObjective.unregister();
            } catch (IllegalStateException e) {
                plugin.getLogger().log(Level.WARNING, "Could not unregister player kills objective?", e);
            }
        }
        playerKillsObjective = plugin.getServer().getScoreboardManager().getMainScoreboard()
                .registerNewObjective("playerKills", "playerKillCount");

        if (config.isKillStreakDisplayTab()) {
            killStreakObjectiveTab = plugin.getServer().getScoreboardManager().getMainScoreboard()
                    .getObjective("killStreakTab");
            if (killStreakObjectiveTab != null) {
                try {
                    killStreakObjectiveTab.unregister();
                } catch (IllegalStateException e) {
                    plugin.getLogger().log(Level.WARNING, "Could not unregister kill streak tab objective?", e);
                }
            }
            killStreakObjectiveTab = plugin.getServer().getScoreboardManager().getMainScoreboard()
                    .registerNewObjective("killStreakTab", "playerKillCount");
            killStreakObjectiveTab.setDisplaySlot(DisplaySlot.PLAYER_LIST);
        }

        if (config.isKillStreakDisplayName()) {
            killStreakObjectiveName = plugin.getServer().getScoreboardManager().getMainScoreboard()
                    .getObjective("killStreakName");
            if (killStreakObjectiveName != null) {
                try {
                    killStreakObjectiveName.unregister();
                } catch (IllegalStateException e) {
                    plugin.getLogger().log(Level.WARNING, "Could not unregister kill streak name objective?", e);
                }
            }
            killStreakObjectiveName = plugin.getServer().getScoreboardManager().getMainScoreboard()
                    .registerNewObjective("killStreakName", "playerKillCount");
            killStreakObjectiveName.setDisplayName("Kills");
            killStreakObjectiveName.setDisplaySlot(DisplaySlot.BELOW_NAME);
        }

        plugin.getServer().getPluginManager().registerEvents(this, plugin);

        for (TeamInfo team : config.getTeams().values()) {

            plugin.getServer()
                    .broadcastMessage(ChatColor.GREEN + "Team " + team.getScoreboardTeam().getPrefix()
                            + team.getScoreboardTeam().getDisplayName() + team.getScoreboardTeam().getSuffix()
                            + ChatColor.GREEN + ":");
            showPlayerList(team);

            Location spawnLocation = team.getSpawn() != null ? team.getSpawn().getLocation()
                    : team.getPoint() != null ? team.getPoint().getLocation()
                            : plugin.getServer().getWorlds().get(0).getSpawnLocation();
            spawnLocation = spawnLocation.add(0, 1, 0);
            for (String name : team.getScoreboardTeam().getEntries()) {
                Player player = plugin.getServer().getPlayer(name);
                if (player != null) {
                    player.getInventory().clear();
                    player.getInventory().setHelmet(null);
                    player.getInventory().setChestplate(null);
                    player.getInventory().setLeggings(null);
                    player.getInventory().setBoots(null);
                    player.setLevel(0);
                    player.setExp(0);
                    player.setHealth(player.getMaxHealth());
                    player.updateInventory();
                    if (config.isUsingKits()) {
                        if (plugin.getKitMap().size() > 1) {
                            plugin.getKitGui().show(player);
                        } else {
                            plugin.applyKit(plugin.getKitMap().values().iterator().next(), player);
                        }
                    }
                    player.teleport(spawnLocation);
                    player.setBedSpawnLocation(spawnLocation, true);
                }
            }
            pointObjective.getScore(team.getColor() + team.getName()).setScore(0);
        }
        state = GameState.RUNNING;
        if (config.isShowScore()) {
            pointObjective.setDisplaySlot(DisplaySlot.SIDEBAR);
        }
        plugin.getServer().broadcastMessage(ChatColor.GREEN + "Spiel gestartet!");

        if (config.getDuration() > 0) {
            timer = new GameTimer(this);
            timer.start();
        }

        return true;
    }

    /**
     * Stop the game:
     * - Teleports every player to the world spawn
     * - Clears inventory
     * - Sets respawn point to world spawn
     * - Prints a list of all players in each team
     * - Removes all players from the teams
     */
    public void stop() {
        if (getState() == GameState.ENDED)
            return;

        if (timer != null) {
            timer.destroy();
        }

        state = GameState.ENDED;

        int maxScore = 0;

        final List<TeamInfo> winTeams = new ArrayList<>();
        for (TeamInfo team : config.getTeams().values()) {
            if (!team.isInitialised()) {
                continue;
            }
            int teamScore = getScore(team);
            if (teamScore > maxScore) {
                maxScore = teamScore;
                winTeams.clear();
            }
            if (teamScore >= maxScore) {
                winTeams.add(team);
            }
            plugin.getServer()
                    .broadcastMessage(ChatColor.GREEN + "Team " + team.getScoreboardTeam().getPrefix()
                            + team.getScoreboardTeam().getDisplayName() + team.getScoreboardTeam().getSuffix()
                            + ChatColor.GREEN
                            + (config.getWinScore() > 0 ? " - Score: " + ChatColor.RED + teamScore : ":"));
            showPlayerList(team);

            for (String entry : team.getScoreboardTeam().getEntries()) {
                Player player = plugin.getServer().getPlayer(entry);
                if (player != null && player.isDead()) {
                    player.spigot().respawn();
                }
                calculateHighestKillStreak(entry);
            }
        }
        plugin.getServer().broadcastMessage(ChatColor.GREEN + "Spiel beendet!");

        List<String> winTeamNames = new ArrayList<String>();
        for (TeamInfo team : winTeams) {
            winTeamNames.add(team.getColor() + team.getName());
        }

        plugin.getServer().broadcastMessage("");
        if (winTeamNames.size() == 1) {
            plugin.getServer().broadcastMessage(ChatColor.GREEN + "Team " + winTeamNames.get(0) + ChatColor.GREEN
                    + " hat das Spiel mit " + ChatColor.YELLOW
                    + (maxScore == 1 ? "einem Punkt" : maxScore + " Punkten") + ChatColor.GREEN + " gewonnen!");
        } else if (winTeamNames.size() > 1) {
            plugin.getServer().broadcastMessage(ChatColor.GREEN + "Teams "
                    + winTeamNames.stream().collect(Collectors.joining(ChatColor.GREEN + ", ")) + ChatColor.GREEN
                    + " haben das Spiel mit " + ChatColor.YELLOW
                    + (maxScore == 1 ? "einem Punkt" : maxScore + " Punkten") + ChatColor.GREEN + " gewonnen!");
        } else {
            plugin.getServer().broadcastMessage(ChatColor.GREEN + "Kein Team hat das Spiel gewonnen!");
        }
        plugin.getServer().broadcastMessage("");

        if (playerKillsObjective != null) {
            List<String> killScoreWinners = new ArrayList<>();
            int amount = 0;
            for (String entry : playerKillsObjective.getScoreboard().getEntries()) {
                Score killScore = playerKillsObjective.getScore(entry);
                if (killScore.getScore() >= amount) {
                    if (killScore.getScore() > amount) {
                        killScoreWinners.clear();
                        amount = killScore.getScore();
                    }
                    killScoreWinners.add(plugin.addServerTag(entry));
                }
            }
            if (amount > 0 && killScoreWinners.size() > 0) {
                plugin.getServer().broadcastMessage(ChatColor.GREEN + "Meiste Kills (" + ChatColor.YELLOW + amount
                        + ChatColor.GREEN + "): " + killScoreWinners.stream().collect(Collectors.joining(", ")));
            }
        }

        if (highestKillStreakScore > 0) {
            plugin.getServer()
                    .broadcastMessage(ChatColor.GREEN + "Hchste Killstreak (" + ChatColor.YELLOW
                            + highestKillStreakScore + ChatColor.GREEN + "): " + ChatColor.WHITE
                            + highestKillStreakPlayers.stream().collect(Collectors.joining(", ")));
        }

        fwTask = plugin.getServer().getScheduler().runTaskTimer(plugin, () -> {
            for (TeamInfo team : winTeams) {
                Color color = Utils.convertColor(team.getColor());
                FireworkMeta fwm = null;
                if (team.getPoint() != null) {
                    Firework fw = (Firework) team.getPoint().getLocation().getWorld()
                            .spawnEntity(team.getPoint().getLocation(), EntityType.FIREWORK);
                    fwm = fw.getFireworkMeta();
                    FireworkEffect effect = FireworkEffect.builder().flicker(true).withColor(color)
                            .with(FireworkEffect.Type.BALL).trail(true).build();
                    fwm.addEffect(effect);
                    fwm.setPower(0);
                    fw.setFireworkMeta(fwm);
                }
                if (team.getScoreboardTeam() != null) {
                    for (String entry : team.getScoreboardTeam().getEntries()) {
                        Player player = plugin.getServer().getPlayer(entry);
                        if (player != null && player.isOnline()) {
                            Firework fw = (Firework) player.getWorld().spawnEntity(player.getLocation(),
                                    EntityType.FIREWORK);
                            if (fwm == null) {
                                fwm = fw.getFireworkMeta();
                                FireworkEffect effect = FireworkEffect.builder().flicker(true).withColor(color)
                                        .with(FireworkEffect.Type.BALL).trail(true).build();
                                fwm.addEffect(effect);
                                fwm.setPower(0);
                            }
                            fw.setFireworkMeta(fwm);
                        }
                    }
                }
            }
        }, 0, 10);

        teleportTask = plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
            for (TeamInfo team : config.getTeams().values()) {
                if (team.getScoreboardTeam() != null) {
                    for (String name1 : team.getScoreboardTeam().getEntries()) {
                        Player player = plugin.getServer().getPlayer(name1);
                        if (player != null) {
                            plugin.getKitGui().close(player);
                            team.getScoreboardTeam().removeEntry(name1);
                            player.getInventory().clear();
                            player.getInventory().setHelmet(null);
                            player.getInventory().setChestplate(null);
                            player.getInventory().setLeggings(null);
                            player.getInventory().setBoots(null);
                            player.setLevel(0);
                            player.setExp(0);
                            player.setHealth(player.getMaxHealth());
                            player.updateInventory();
                            player.teleport(plugin.getServer().getWorlds().get(0).getSpawnLocation());
                            player.setBedSpawnLocation(plugin.getServer().getWorlds().get(0).getSpawnLocation(),
                                    true);
                        }
                        team.getScoreboardTeam().removeEntry(name1);
                    }
                }
            }
            plugin.getServer().broadcastMessage(ChatColor.GREEN + "Spieler zurck zum Spawn geportet!");

            HandlerList.unregisterAll(plugin.getGame());

            destroy();

            state = GameState.DESTROYED;
        }, 20 * 10);

        regenPointBlocks();
    }

    private void showPlayerList(TeamInfo team) {
        Validate.notNull(team.getScoreboardTeam(), "Team not initialised yet!");
        Set<String> teamPlayers = team.getScoreboardTeam().getEntries();
        if (plugin.getServerTags() != null) {
            teamPlayers = new LinkedHashSet<>();
            for (String name : team.getScoreboardTeam().getEntries()) {
                teamPlayers.add(plugin.addServerTag(name));
            }
        }
        plugin.getServer()
                .broadcastMessage(ChatColor.WHITE + teamPlayers.stream().collect(Collectors.joining(", ")));
    }

    public void destroy() {
        if (teleportTask != null) {
            teleportTask.cancel();
        }
        if (fwTask != null) {
            fwTask.cancel();
        }
        for (Objective o : new Objective[] { pointObjective, killStreakObjectiveTab, killStreakObjectiveName,
                playerKillsObjective }) {
            if (o != null) {
                try {
                    o.unregister();
                } catch (IllegalStateException e) {
                    plugin.getLogger().log(Level.WARNING, "Could not unregister objective " + o.getName() + " ?",
                            e);
                }
            }
        }
        for (TeamInfo team : config.getTeams().values()) {
            team.unregister();
        }
        HandlerList.unregisterAll(this);
        state = GameState.DESTROYED;
    }

    public GameState setState(GameState state) {
        GameState oldState = this.state;
        this.state = state;
        return oldState;
    }

    public GameState getState() {
        return state;
    }

    /**
     * Set whether or not this game should use kits
     * @param use
     */
    protected void useKits(boolean use) {
        config.setUsingKits(use);
    }

    /**
     * Get whether or not this game uses kits
     * @return Whether or not this game uses kits
     */
    protected boolean isUsingKits() {
        return config.isUsingKits();
    }

    @EventHandler(priority = EventPriority.LOW)
    public void onBlockBreak(BlockBreakEvent event) {
        if (event.getPlayer().hasPermission(SimpleTeamPvP.BYPASS_PERM))
            return;

        if (config.isStopBuild()) {
            event.setCancelled(true);
        }

        if (!event.isCancelled() && config.isFilterDrops()) {
            List<ItemStack> drops = event.getBlock().getDrops().stream().filter(this::isWhitelisted)
                    .collect(Collectors.toList());
            if (drops.size() != event.getBlock().getDrops().size()) {
                event.setCancelled(true);
                event.getBlock().setType(Material.AIR);
                for (ItemStack drop : drops) {
                    event.getBlock().getWorld().dropItem(event.getBlock().getLocation(), drop);
                }
            }
        }
    }

    @EventHandler(priority = EventPriority.LOW)
    public void onBlockPlace(BlockPlaceEvent event) {
        if (event.getPlayer().hasPermission(SimpleTeamPvP.BYPASS_PERM))
            return;

        if (config.isStopBuild() || config.isFilterBreak() && !isWhitelisted(event.getBlock())) {
            event.setCancelled(true);
        }
    }

    @EventHandler(priority = EventPriority.LOW)
    public void onHangingPlace(HangingPlaceEvent event) {
        if (event.getPlayer().hasPermission(SimpleTeamPvP.BYPASS_PERM))
            return;

        if (config.isStopBuild() || config.isFilterPlace() && !isWhitelisted(event.getBlock())) {
            event.setCancelled(true);
        }
    }

    @EventHandler(priority = EventPriority.LOW)
    public void onPlayerDamage(EntityDamageEvent event) {
        if (state != GameState.RUNNING && event.getEntity() instanceof Player) {
            event.setCancelled(true);
        }
    }

    @EventHandler(priority = EventPriority.LOW)
    public void onDamageEntity(EntityDamageByEntityEvent event) {
        if (!(event.getDamager() instanceof Player || event.getDamager() instanceof Projectile
                || event.getDamager() instanceof Firework)) {
            return;
        }

        if (event.getEntity() instanceof Creature || event.getEntity() instanceof Player) {
            return;
        }

        if (config.isStopInteract()) {
            if (event.getDamager() instanceof Player) {
                event.setCancelled(!event.getDamager().hasPermission(SimpleTeamPvP.BYPASS_PERM));
            } else {
                event.setCancelled(true);
            }
        }
    }

    @EventHandler(priority = EventPriority.LOW)
    public void onHangingBreakByEntity(HangingBreakByEntityEvent event) {
        if (event.getRemover().hasPermission(SimpleTeamPvP.BYPASS_PERM))
            return;

        if (config.isStopInteract() || config.isStopBuild()) {
            event.setCancelled(true);
        }
    }

    @EventHandler(priority = EventPriority.LOW)
    public void onEntityInteract(PlayerInteractEntityEvent event) {
        if (event.getPlayer().hasPermission(SimpleTeamPvP.BYPASS_PERM))
            return;

        if (config.isStopInteract()) {
            event.setCancelled(true);
        }
    }

    @EventHandler(priority = EventPriority.LOW)
    public void onInteract(PlayerInteractEvent event) {
        if (event.getPlayer().hasPermission(SimpleTeamPvP.BYPASS_PERM))
            return;

        if ((event.getAction() == Action.PHYSICAL) && event.getClickedBlock().getType() == Material.SOIL) {
            event.setCancelled(event.isCancelled() || !event.getPlayer().hasPermission(SimpleTeamPvP.BYPASS_PERM));
        }

        if (event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.LEFT_CLICK_BLOCK)
            return;

        if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
            if (config.isStopInteract() && event.getClickedBlock().getType() == Material.BED_BLOCK) {
                event.setCancelled(
                        event.isCancelled() || !event.getPlayer().hasPermission(SimpleTeamPvP.BYPASS_PERM));
            } else if (config.isStopContainerAccess()
                    && event.getClickedBlock().getState() instanceof InventoryHolder) {
                event.setCancelled(
                        event.isCancelled() || !event.getPlayer().hasPermission(SimpleTeamPvP.BYPASS_PERM));
            }
        }
    }

    @EventHandler
    public void onItemDrop(PlayerDropItemEvent event) {
        if (event.getPlayer().hasPermission(SimpleTeamPvP.BYPASS_PERM))
            return;

        if (config.isFilterDrops() && !isWhitelisted(event.getItemDrop().getItemStack())) {
            event.setCancelled(true);
        }
    }

    @EventHandler
    public void onInventoryClick(InventoryClickEvent event) {
        if (event.getWhoClicked().hasPermission(SimpleTeamPvP.BYPASS_PERM))
            return;

        if (config.isStopArmorChange() && event.getSlotType() == InventoryType.SlotType.ARMOR) {
            event.setCancelled(true);
        }

        if (event.getClickedInventory() != event.getWhoClicked().getInventory()
                || event.getAction() == InventoryAction.COLLECT_TO_CURSOR
                || event.getAction() == InventoryAction.MOVE_TO_OTHER_INVENTORY) {
            if (config.isFilterDrops()
                    && !(isWhitelisted(event.getCurrentItem()) && isWhitelisted(event.getCursor()))) {
                event.setCancelled(true);
            }
        }
    }

    @EventHandler
    public void onItemDrop(CraftItemEvent event) {
        if (event.getWhoClicked().hasPermission(SimpleTeamPvP.BYPASS_PERM))
            return;

        if (config.isFilterCrafting() && !isWhitelisted(event.getRecipe().getResult())) {
            event.setCancelled(true);
        }
    }

    @EventHandler
    public void onPlayerDeath(PlayerDeathEvent event) {
        Player player = event.getEntity();

        if (player.hasPermission(SimpleTeamPvP.BYPASS_PERM))
            return;

        TeamInfo team = getTeam(player);
        if (team == null)
            return;

        player.setBedSpawnLocation(team.getSpawn().getLocation().add(0, 1, 0), true);

        if (event.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent) {
            EntityDamageByEntityEvent lastDamageCause = (EntityDamageByEntityEvent) event.getEntity()
                    .getLastDamageCause();
            if (lastDamageCause.getDamager() instanceof Player && lastDamageCause.getDamager() != player) {
                for (ItemStack drop : getDeathDrops(team, player)) {
                    player.getWorld().dropItem(player.getLocation(), drop);
                }
            }
        }

        if (!event.getKeepLevel() && config.isShowScoreExp()) {
            event.setDroppedExp(0);
            event.setKeepLevel(true);
        }

        if (config.isFilterDrops()) {
            if (event.getKeepInventory()) {
                for (int i = 0; i < player.getInventory().getSize(); i++) {
                    ItemStack item = player.getInventory().getItem(i);
                    if (isWhitelisted(item)) {
                        player.getInventory().setItem(i, null);
                        player.getLocation().getWorld().dropItem(player.getLocation(), item);
                    }
                }
            } else {
                Iterator<ItemStack> dropIterator = event.getDrops().iterator();
                while (dropIterator.hasNext()) {
                    ItemStack drop = dropIterator.next();
                    if (!isWhitelisted(drop)) {
                        dropIterator.remove();
                    }
                }
            }
        }
    }

    @EventHandler
    public void onPlayerRespawn(PlayerRespawnEvent event) {
        if (event.getPlayer().hasPermission(SimpleTeamPvP.BYPASS_PERM))
            return;

        TeamInfo team = getTeam(event.getPlayer());
        if (team != null) {
            if (isUsingKits()) {
                final Player player = event.getPlayer();
                // We need to wait a tick after respawning to show a chest gui
                plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
                    if (plugin.getKitMap().size() > 1) {
                        plugin.getKitGui().show(player);
                    } else {
                        plugin.applyKit(plugin.getKitMap().values().iterator().next(), player);
                    }
                }, 1L);
            }
            if (config.isShowScoreExp()) {
                event.getPlayer().setLevel(getScore(team));
            }
            if (config.getRespawnResistance() > 0) {
                event.getPlayer().addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE,
                        config.getRespawnResistance() * 20, 5, true));
            }

            calculateHighestKillStreak(event.getPlayer());
            if (config.isKillStreakDisplayName() && killStreakObjectiveName != null) {
                killStreakObjectiveName.getScore(event.getPlayer().getName()).setScore(0);
            }
            if (config.isKillStreakDisplayTab() && killStreakObjectiveTab != null) {
                killStreakObjectiveTab.getScore(event.getPlayer().getName()).setScore(0);
            }
        }
    }

    @EventHandler(priority = EventPriority.MONITOR)
    public void logPointBlockBreak(BlockBreakEvent event) {
        if (event.getPlayer().hasPermission(SimpleTeamPvP.BYPASS_PERM))
            return;

        if (event.getBlock().getType() == config.getPointBlock()) {
            addToPointBlockSet(event.getBlock().getLocation());
        }
    }

    public abstract SimpleTeamPvPGame clone();

    private int getScore(TeamInfo team) {
        if (teamScores.containsKey(team.getName())) {
            return teamScores.get(team.getName());
        }
        return 0;
    }

    public int incrementScore(TeamInfo team) {
        return incrementScore(team, 1);
    }

    public int incrementScore(TeamInfo team, int i) {
        return setScore(team, getScore(team) + i);
    }

    public int decrementScore(TeamInfo team) {
        return decrementScore(team, 1);
    }

    public int decrementScore(TeamInfo team, int i) {
        return setScore(team, getScore(team) - i);
    }

    private int setScore(TeamInfo team, int i) {
        teamScores.put(team.getName(), i);
        if (config.isShowScoreExp()) {
            for (String entry : team.getScoreboardTeam().getEntries()) {
                Player player = plugin.getServer().getPlayer(entry);
                if (player != null && player.isOnline()) {
                    player.setLevel(i);
                }
            }
        }
        Score score = pointObjective.getScore(team.getColor() + team.getName());
        score.setScore(i);

        if (config.getWinScore() > 0 && i >= config.getWinScore()) {
            stop();
        }

        return i;
    }

    public boolean isWhitelisted(ItemStack item) {
        return item == null || isWhitelisted(item.getType(), item.getData().getData());
    }

    private boolean isWhitelisted(Block block) {
        return block == null || isWhitelisted(block.getType(), block.getState().getData().getData());
    }

    public boolean isWhitelisted(Material type, int data) {
        return type == Material.AIR || config.getItemWhitelist().contains(type.toString())
                || config.getItemWhitelist().contains(type.toString() + ":" + data);
    }

    public List<ItemStack> getDeathDrops() {
        return config.getDeathDrops();
    }

    public List<ItemStack> getDeathDrops(TeamInfo team, Player player) {
        List<ItemStack> drops = new ArrayList<>();
        for (ItemStack dropTemplate : getDeathDrops()) {
            ItemStack drop = dropTemplate.clone();
            if (drop.hasItemMeta()) {
                ItemMeta meta = drop.getItemMeta();

                if (meta.hasDisplayName()) {
                    meta.setDisplayName(meta.getDisplayName().replace("%teamColor%", team.getColor().toString())
                            .replace("%teamName%", team.getName()).replace("%playerName%", player.getName()));
                }

                if (meta.hasLore()) {
                    List<String> lore = new ArrayList<>();
                    for (String loreText : meta.getLore()) {
                        lore.add(loreText.replace("%teamColor%", team.getColor().toString())
                                .replace("%teamName%", team.getName()).replace("%playerName%", player.getName()));
                    }
                    meta.setLore(lore);
                }
                drop.setItemMeta(meta);
            }
            drops.add(drop);
        }
        return drops;
    }

    public void setDuration(int duration) {
        config.setDuration(duration);
        if (timer != null) {
            timer.setTime(duration * 60);
        } else if (duration > 0 && state == GameState.RUNNING) {
            timer = new GameTimer(this);
            timer.start();
        }
    }

    public void setObjectiveDisplay(String format) {
        config.setObjectiveDisplay(format);
        setTimerDisplay(config.getDuration());
    }

    public void setTimerDisplay(int seconds) {
        pointObjective.setDisplayName(config.getObjectiveDisplay()
                .replace("%winscore%", config.getWinScore() > 0 ? Integer.toString(config.getWinScore()) : "")
                .replace("%time%", seconds >= 0 ? Utils.formatTime(seconds, TimeUnit.SECONDS) : ""));
    }

    public void addToPointBlockSet(LocationInfo loc) {
        pointBlockSet.add(loc);
    }

    public void addToPointBlockSet(Location loc) {
        pointBlockSet.add(new LocationInfo(loc));
    }

    public int regenPointBlocks() {
        final List<LocationInfo> fLocs = new ArrayList<LocationInfo>(pointBlockSet);
        pointBlockSet.clear();
        regenId = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, () -> {
            if (fLocs.size() > 0) {
                Iterator<LocationInfo> locIt = fLocs.iterator();
                if (locIt.hasNext()) {
                    Location loc = locIt.next().getLocation();
                    Block block = loc.getBlock();
                    if (block.getType() == Material.AIR)
                        block.setType(config.getPointBlock());
                    locIt.remove();
                    return;
                }
            }
            plugin.getServer().getScheduler().cancelTask(regenId);
            regenId = -1;
        }, 1L, 1L);
        return fLocs.size();
    }

    private void calculateHighestKillStreak(Player player) {
        calculateHighestKillStreak(player.getName());
    }

    private void calculateHighestKillStreak(String name) {
        Objective killStreakObjective = null;
        if (config.isKillStreakDisplayName() && killStreakObjectiveName != null) {
            killStreakObjective = killStreakObjectiveName;
        } else if (config.isKillStreakDisplayTab() && killStreakObjectiveTab != null) {
            killStreakObjective = killStreakObjectiveTab;
        }
        if (killStreakObjective != null) {
            Score score = killStreakObjective.getScore(name);
            if (score != null && score.getScore() >= highestKillStreakScore) {
                if (score.getScore() > highestKillStreakScore) {
                    highestKillStreakPlayers.clear();
                    highestKillStreakScore = score.getScore();
                }
                highestKillStreakPlayers.add(plugin.addServerTag(name));
            }
        }
    }

    public String toString() {
        return "Game{key=" + name + ",state=" + state + ",config=" + config + "}";
    }

    public boolean setRandom(String type, LocationInfo loc) {
        switch (type) {
        case "pos1":
            config.getRandomRegion().setPos1(loc);
            break;
        case "pos2":
            config.getRandomRegion().setPos2(loc);
            break;
        default:
            return false;
        }
        plugin.getConfig().set("game." + name.toLowerCase() + "random." + type, loc.serialize());
        plugin.saveConfig();
        return true;
    }

    public TeamInfo addTeam(TeamInfo teamInfo) {
        plugin.getLogger().log(Level.INFO, "Added team " + teamInfo.getName());
        return config.getTeams().put(teamInfo.getName().toLowerCase(), teamInfo);
    }

    public TeamInfo getTeam(String teamName) {
        return config.getTeams().get(teamName.toLowerCase());
    }

    /**
     * Get the team by a block or item
     * @return The team, null of none found
     * @param mat
     * @param data
     */
    public TeamInfo getTeam(Material mat, byte data) {
        for (TeamInfo team : config.getTeams().values()) {
            if (team.getBlockMaterial() == mat && team.getBlockData() == data) {
                return team;
            }
        }
        return null;
    }

    /**
     * Get a team by its region
     * @param location The location to get the team by
     * @return The team, null of none found
     */
    public TeamInfo getTeamByRegion(Location location) {
        for (TeamInfo team : config.getTeams().values()) {
            if (team.getRegion().contains(location)) {
                return team;
            }
        }
        return null;
    }

    /**
     * Get the team by a block
     * @return The team, null of none found
     * @param block
     */
    public TeamInfo getTeam(Block block) {
        return getTeam(block.getType(), block.getState().getData().getData());
    }

    /**
     * Get the team by an item
     * @return The team, null of none found
     * @param item
     */
    public TeamInfo getTeam(ItemStack item) {
        return getTeam(item.getType(), item.getData().getData());
    }

    /**
     * Get the team a player is in
     * @param player
     * @return The team the player is in or null if he doesn't have one
     */
    public TeamInfo getTeam(Player player) {
        for (TeamInfo team : config.getTeams().values()) {
            if (team.inTeam(player)) {
                return team;
            }
        }
        return null;
    }

    /**
     * Get a team by its join region
     * @param location The location to get the team by
     * @return The team, null of none found
     */
    public TeamInfo getTeamByJoinLocation(Location location) {
        for (TeamInfo team : config.getTeams().values()) {
            if (team.getJoinRegion().contains(location)) {
                return team;
            }
        }
        return null;
    }

    public TeamInfo removeTeam(TeamInfo teamInfo) {
        String teamName = teamInfo.getName();
        config.getConfig().set("teams." + teamName, null);
        for (String entry : teamInfo.getScoreboardTeam().getEntries()) {
            Player player = plugin.getServer().getPlayer(entry);
            if (player != null && player.isOnline()) {
                player.sendMessage(ChatColor.GOLD + "Your team has been removed!");
            }
        }
        teamInfo.getScoreboardTeam().unregister();
        plugin.getLogger().log(Level.INFO, "Removed team " + teamName);
        plugin.saveConfig();
        return config.getTeams().remove(teamName.toLowerCase());
    }

    public Map<String, TeamInfo> getTeamMap() {
        return config.getTeams();
    }

    /**
     * Write the team info to the config
     * @param teamInfo The info to write
     */
    public void toConfig(TeamInfo teamInfo) {
        toConfig(teamInfo, true);
    }

    /**
     * Write the team info to the config
     * @param teamInfo The info to write
     * @param save     Whether or not we should write the config to disk
     */
    public void toConfig(TeamInfo teamInfo, boolean save) {
        plugin.getConfig().set("games." + getName() + ".teams." + teamInfo.getName(), teamInfo.serialize());
        plugin.getLogger().log(Level.INFO, "Saved team " + teamInfo.getName() + " to config!");
        if (save)
            plugin.saveConfig();
    }
}