de.xaniox.heavyspleef.core.game.Game.java Source code

Java tutorial

Introduction

Here is the source code for de.xaniox.heavyspleef.core.game.Game.java

Source

/*
 * This file is part of HeavySpleef.
 * Copyright (c) 2014-2016 Matthias Werning
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package de.xaniox.heavyspleef.core.game;

import com.google.common.collect.*;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.EditSessionFactory;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.bukkit.BukkitUtil;
import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.CylinderRegion;
import com.sk89q.worldedit.regions.Polygonal2DRegion;
import com.sk89q.worldedit.regions.Region;
import de.xaniox.heavyspleef.core.HeavySpleef;
import de.xaniox.heavyspleef.core.MinecraftVersion;
import de.xaniox.heavyspleef.core.Permissions;
import de.xaniox.heavyspleef.core.config.*;
import de.xaniox.heavyspleef.core.event.*;
import de.xaniox.heavyspleef.core.extension.ExtensionManager;
import de.xaniox.heavyspleef.core.extension.GameExtension;
import de.xaniox.heavyspleef.core.flag.AbstractFlag;
import de.xaniox.heavyspleef.core.flag.FlagManager;
import de.xaniox.heavyspleef.core.floor.DefaultFloorRegenerator;
import de.xaniox.heavyspleef.core.floor.Floor;
import de.xaniox.heavyspleef.core.floor.FloorRegenerator;
import de.xaniox.heavyspleef.core.floor.RegenerationCause;
import de.xaniox.heavyspleef.core.hook.HookReference;
import de.xaniox.heavyspleef.core.hook.WorldEditHook;
import de.xaniox.heavyspleef.core.i18n.I18N;
import de.xaniox.heavyspleef.core.i18n.I18NManager;
import de.xaniox.heavyspleef.core.i18n.Messages;
import de.xaniox.heavyspleef.core.player.PlayerStateHolder;
import de.xaniox.heavyspleef.core.player.SpleefPlayer;
import de.xaniox.heavyspleef.core.script.Variable;
import de.xaniox.heavyspleef.core.script.VariableSuppliable;
import de.xaniox.heavyspleef.core.stats.StatisticRecorder;
import net.md_5.bungee.api.chat.*;
import org.apache.commons.lang.Validate;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.*;
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
import org.bukkit.event.player.*;
import org.bukkit.metadata.MetadataValue;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class Game implements VariableSuppliable {

    private static final String SPLEEF_COMMAND = "spleef";
    private static final int NO_BLOCK_LIMIT = -1;
    private static final int DEFAULT_COUNTDOWN = 10;
    private static final String HAS_FLAG_PREFIX = "has_flag";
    private static final String FLAG_VALUE_PREFIX = "flag_value";
    private static final String NOTE_PLING_SEARCH = "NOTE_PLING";
    private static final String[] LEVEL_UP_SEARCH = { "LEVEL_UP", "PLAYER_LEVELUP" };
    private static final Map<Class<? extends Region>, SpawnpointGenerator<?>> SPAWNPOINT_GENERATORS;

    static {
        SPAWNPOINT_GENERATORS = Maps.newHashMap();

        SPAWNPOINT_GENERATORS.put(CuboidRegion.class, new CuboidSpawnpointGenerator());
        SPAWNPOINT_GENERATORS.put(CylinderRegion.class, new CylinderSpawnpointGenerator());
        SPAWNPOINT_GENERATORS.put(Polygonal2DRegion.class, new Polygonal2DSpawnpointGenerator());
    }

    private final I18N i18n;

    private final EditSessionFactory editSessionFactory;
    private HeavySpleef heavySpleef;
    private EventBus eventBus;
    private Set<SpleefPlayer> ingamePlayers;
    private List<SpleefPlayer> deadPlayers;
    private List<SpleefPlayer> killedPlayers;
    private List<SpleefPlayer> killedLobbyPlayers;
    private BiMap<SpleefPlayer, Set<Block>> blocksBroken;
    private KillDetector killDetector;
    private JoinRequester joinRequester;
    private Queue<SpleefPlayer> queuedPlayers;
    private CountdownTask countdownTask;
    private StatisticRecorder statisticRecorder;
    private FloorRegenerator floorRegenerator;
    private Queue<Location> spawnLocationQueue;

    private String name;
    private World world;
    private com.sk89q.worldedit.world.World worldEditWorld;
    private FlagManager flagManager;
    private ExtensionManager extensionManager;
    private GameState gameState;
    private Map<String, Floor> floors;
    private Map<String, Region> deathzones;

    public Game(HeavySpleef heavySpleef, String name, World world) {
        this.heavySpleef = heavySpleef;
        this.name = name;
        this.world = world;
        this.worldEditWorld = new BukkitWorld(world);
        this.i18n = I18NManager.getGlobal();
        this.ingamePlayers = Sets.newLinkedHashSet();
        this.deadPlayers = Lists.newArrayList();
        this.eventBus = heavySpleef.getGlobalEventBus().newChildBus();
        this.statisticRecorder = new StatisticRecorder(heavySpleef, heavySpleef.getLogger());
        this.floorRegenerator = new DefaultFloorRegenerator();
        this.killedPlayers = Lists.newArrayList();
        this.killedLobbyPlayers = Lists.newArrayList();

        eventBus.registerListener(statisticRecorder);
        setGameState(GameState.WAITING);

        DefaultConfig configuration = heavySpleef.getConfiguration(ConfigType.DEFAULT_CONFIG);
        FlagManager.GamePropertyBundle defaults = new FlagManager.DefaultGamePropertyBundle(
                configuration.getProperties());

        this.flagManager = new FlagManager(heavySpleef.getPlugin(), defaults);
        this.extensionManager = heavySpleef.getExtensionRegistry().newManagerInstance(eventBus);
        this.deathzones = Maps.newHashMap();
        this.blocksBroken = HashBiMap.create();
        this.killDetector = new DefaultKillDetector();
        this.queuedPlayers = new LinkedList<SpleefPlayer>();
        this.spawnLocationQueue = new LinkedList<Location>();

        //Concurrent map for database schematics
        this.floors = new ConcurrentHashMap<String, Floor>();

        WorldEditHook hook = (WorldEditHook) heavySpleef.getHookManager().getHook(HookReference.WORLDEDIT);
        WorldEdit worldEdit = hook.getWorldEdit();

        this.editSessionFactory = worldEdit.getEditSessionFactory();

        GeneralSection generalSection = configuration.getGeneralSection();
        this.joinRequester = new JoinRequester(this, heavySpleef.getPvpTimerManager());
        this.joinRequester.setPvpTimerMode(generalSection.getPvpTimer() > 0);
    }

    public HeavySpleef getHeavySpleef() {
        return heavySpleef;
    }

    public void setHeavySpleef(HeavySpleef heavySpleef) {
        Validate.notNull(heavySpleef, "HeavySpleef instance cannot be null");
        this.heavySpleef = heavySpleef;
    }

    public String getName() {
        return name;
    }

    void setName(String newName) {
        String old = this.name;
        this.name = newName;

        GameRenameEvent event = new GameRenameEvent(this, old);
        eventBus.callEvent(event);
    }

    public World getWorld() {
        return world;
    }

    public GameState getGameState() {
        return gameState;
    }

    public EventBus getEventBus() {
        return eventBus;
    }

    public FlagManager getFlagManager() {
        return flagManager;
    }

    public Map<String, Region> getDeathzones() {
        return deathzones;
    }

    public List<SpleefPlayer> getDeadPlayers() {
        return deadPlayers;
    }

    public BiMap<SpleefPlayer, Set<Block>> getBlocksBroken() {
        return blocksBroken;
    }

    public JoinRequester getJoinRequester() {
        return joinRequester;
    }

    public CountdownTask getCountdownTask() {
        return countdownTask;
    }

    public StatisticRecorder getStatisticRecorder() {
        return statisticRecorder;
    }

    public FloorRegenerator getFloorRegenerator() {
        return floorRegenerator;
    }

    public void setFloorRegenerator(FloorRegenerator regenerator) {
        if (regenerator == null) {
            regenerator = new DefaultFloorRegenerator();
        }

        this.floorRegenerator = regenerator;
    }

    public boolean countdown() {
        GameCountdownEvent event = new GameCountdownEvent(this);
        eventBus.callEvent(event);

        if (event.isCancelled()) {
            return false;
        }

        // The player cannot play alone
        if (ingamePlayers.size() <= 1) {
            broadcast(i18n.getVarString(Messages.Player.NEED_MIN_PLAYERS).setVariable("amount", String.valueOf(2))
                    .toString());
            return false;
        }

        // Spleef cannot be started when there is no floor
        if (floors.size() == 0) {
            broadcast(i18n.getString(Messages.Broadcast.NEED_FLOORS));
            return false;
        }

        DefaultConfig config = heavySpleef.getConfiguration(ConfigType.DEFAULT_CONFIG);
        GeneralSection section = config.getGeneralSection();

        if (section.getBroadcastGameStart()) {
            List<String> blacklists = section.getBroadcastGameStartBlacklist();
            String message = i18n.getVarString(Messages.Broadcast.BROADCAST_GAME_START).setVariable("game", name)
                    .toString();

            Object baseComponent = null;

            if (MinecraftVersion.isSpigot()) {
                baseComponent = new ComponentBuilder(message)
                        .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
                                TextComponent.fromLegacyText(i18n.getString(Messages.Command.CLICK_TO_JOIN))))
                        .event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/spleef join " + name))
                        .color(net.md_5.bungee.api.ChatColor.GOLD).create();
            }

            for (Player globalPlayer : Bukkit.getOnlinePlayers()) {
                World globalPlayerWorld = globalPlayer.getWorld();

                if (blacklists.contains(globalPlayerWorld.getName())) {
                    continue;
                }

                if (MinecraftVersion.isSpigot()) {
                    globalPlayer.spigot().sendMessage((BaseComponent[]) baseComponent);
                } else {
                    globalPlayer.sendMessage(section.getSpleefPrefix() + message);
                }
            }
        }

        EditSession editSession = editSessionFactory.getEditSession(worldEditWorld, NO_BLOCK_LIMIT);

        // Regenerate all floors
        for (Floor floor : floors.values()) {
            floorRegenerator.regenerate(floor, editSession, RegenerationCause.COUNTDOWN);
        }

        // Generate a random spawnpoint
        Floor topFloor = null;
        for (Floor floor : floors.values()) {
            if (topFloor == null || floor.getRegion().getMaximumPoint().getBlockY() > topFloor.getRegion()
                    .getMaximumPoint().getBlockY()) {
                topFloor = floor;
            }
        }

        Region region = topFloor.getRegion();

        List<Location> randomLocations = Lists.newArrayList();
        generateSpawnpoints(region, randomLocations, ingamePlayers.size());

        int listIndex = 0;
        int randomIndex = 0;
        List<Location> spawnLocations = event.getSpawnLocations();
        Map<SpleefPlayer, Location> spawnLocationMap = event.getSpawnLocationMap();
        Map<SpleefPlayer, Location> finalSpawns = Maps.newHashMap();

        boolean isWarmupMode = config.getGeneralSection().isWarmupMode();

        for (SpleefPlayer player : ingamePlayers) {
            Location location;

            if (spawnLocationMap != null && spawnLocationMap.containsKey(player)) {
                location = spawnLocationMap.get(player);
            } else if (spawnLocations != null && !spawnLocations.isEmpty()) {
                location = spawnLocations.get(listIndex++);
                listIndex = listIndex >= spawnLocations.size() ? 0 : listIndex;
            } else {
                location = randomLocations.get(randomIndex++);
            }

            if (!isWarmupMode) {
                player.teleport(location);
            } else {
                finalSpawns.put(player, location);
            }
        }

        spawnLocationQueue.clear();
        if (spawnLocations != null) {
            for (int i = listIndex; i < spawnLocations.size(); i++) {
                Location next = spawnLocations.get(i);
                spawnLocationQueue.offer(next);
            }
        }

        final boolean countdownEnabled = event.isCountdownEnabled();
        final int countdownLength = event.getCountdownLength();

        if (isWarmupMode) {
            setGameState(GameState.WARMUP);
            int warmupTimeSeconds = config.getGeneralSection().getWarmupTime();
            long warmupTimePeriod = (warmupTimeSeconds * 20L) / finalSpawns.size();

            broadcast(BroadcastTarget.INGAME, i18n.getVarString(Messages.Broadcast.WARMUP_STARTED)
                    .setVariable("length", String.valueOf(warmupTimeSeconds)).toString());

            WarmupTeleportTask warmupTask = new WarmupTeleportTask(heavySpleef.getPlugin(), this, finalSpawns,
                    warmupTimePeriod);
            warmupTask.start();

            CountdownTask warmupCountdownTask = new CountdownTask(heavySpleef.getPlugin(), warmupTimeSeconds,
                    new CountdownTask.CountdownCallback() {
                        @Override
                        public void onCountdownCount(CountdownTask task) {
                            float percent = (float) task.getRemaining() / task.getLength();
                            for (SpleefPlayer player : ingamePlayers) {
                                player.getBukkitPlayer().setLevel(task.getRemaining());
                                player.getBukkitPlayer().setExp(percent);
                            }
                        }

                        @Override
                        public void onCountdownFinish(CountdownTask task) {
                            for (SpleefPlayer player : ingamePlayers) {
                                Player bukkitPlayer = player.getBukkitPlayer();

                                bukkitPlayer.setLevel(0);
                                bukkitPlayer.setExp(0f);
                            }

                            startCountdown(countdownEnabled, countdownLength);
                        }
                    });
            warmupCountdownTask.start();
        } else {
            startCountdown(countdownEnabled, countdownLength);
        }

        return true;
    }

    private void startCountdown(boolean countdownEnabled, int countdownLength) {
        if (countdownLength <= 0) {
            countdownLength = DEFAULT_COUNTDOWN;
        }

        setGameState(GameState.STARTING);

        if (countdownEnabled && countdownLength > 0) {
            countdownTask = new CountdownTask(heavySpleef.getPlugin(), countdownLength,
                    new CountdownTask.CountdownCallback() {

                        @Override
                        public void onCountdownFinish(CountdownTask task) {
                            for (SpleefPlayer player : ingamePlayers) {
                                Player bukkitPlayer = player.getBukkitPlayer();

                                bukkitPlayer.setLevel(0);
                                bukkitPlayer.setExp(0f);

                                Sound plingSound = getSoundEnumType(NOTE_PLING_SEARCH);
                                if (plingSound != null) {
                                    bukkitPlayer.playSound(bukkitPlayer.getLocation(), plingSound, 1.0f, 1.5f);
                                }
                            }

                            start();

                            countdownTask = null;
                        }

                        @Override
                        public void onCountdownCount(CountdownTask task) {
                            boolean broadcast = task.getRemaining() % 10 == 0 || task.getRemaining() <= 5;

                            GameCountdownChangeEvent event = new GameCountdownChangeEvent(Game.this, countdownTask,
                                    broadcast);
                            eventBus.callEvent(event);

                            float percent = (float) task.getRemaining() / task.getLength();
                            for (SpleefPlayer player : ingamePlayers) {
                                player.getBukkitPlayer().setLevel(task.getRemaining());
                                player.getBukkitPlayer().setExp(percent);
                            }

                            if (broadcast) {
                                broadcast(BroadcastTarget.INGAME,
                                        i18n.getVarString(Messages.Broadcast.GAME_COUNTDOWN_MESSAGE)
                                                .setVariable("remaining", String.valueOf(task.getRemaining()))
                                                .toString());

                                for (SpleefPlayer player : ingamePlayers) {
                                    Player bukkitPlayer = player.getBukkitPlayer();

                                    Sound plingSound = getSoundEnumType(NOTE_PLING_SEARCH);
                                    if (plingSound != null) {
                                        bukkitPlayer.playSound(bukkitPlayer.getLocation(), plingSound, 1.0f, 1.0f);
                                    }
                                }
                            }
                        }
                    });

            for (SpleefPlayer player : ingamePlayers) {
                player.getBukkitPlayer().setLevel(countdownLength);
                player.getBukkitPlayer().setExp(1.0f);
            }

            countdownTask.start();
        } else {
            //Countdown is not enabled so just start the game
            start();
        }
    }

    public static Sound getSoundEnumType(String... searchStrings) {
        Sound[] sounds = Sound.values();
        for (Sound sound : sounds) {
            for (String search : searchStrings) {
                if (sound.name().toUpperCase().contains(search)) {
                    return sound;
                }
            }
        }

        return null;
    }

    @SuppressWarnings("unchecked")
    private <T extends Region> void generateSpawnpoints(T region, List<Location> spawnpoints, int n) {
        World world = Bukkit.getWorld(region.getWorld().getName());

        SpawnpointGenerator<T> generator = (SpawnpointGenerator<T>) SPAWNPOINT_GENERATORS.get(region.getClass());
        generator.generateSpawnpoints(region, world, spawnpoints, n);
    }

    public void start() {
        GameStartEvent event = new GameStartEvent(this);
        eventBus.callEvent(event);

        DefaultConfig config = heavySpleef.getConfiguration(ConfigType.DEFAULT_CONFIG);
        boolean adventureMode = config.getGeneralSection().isAdventureMode();

        if (adventureMode) {
            for (SpleefPlayer player : ingamePlayers) {
                player.getBukkitPlayer().setGameMode(GameMode.SURVIVAL);
            }
        }

        setGameState(GameState.INGAME);
        broadcast(i18n.getVarString(Messages.Broadcast.GAME_STARTED).setVariable("game", name)
                .setVariable("count", String.valueOf(ingamePlayers.size())).toString());
    }

    public void stop() {
        //Create a copy of current ingame players to prevent
        //a ConcurrentModificationException
        Set<SpleefPlayer> ingamePlayersCopy = Sets.newHashSet(ingamePlayers);
        for (SpleefPlayer player : ingamePlayersCopy) {
            leave(player, QuitCause.STOP, true);
        }

        resetGame();

        GameEndEvent event = new GameEndEvent(this);
        eventBus.callEvent(event);

        broadcast(i18n.getVarString(Messages.Broadcast.GAME_STOPPED).setVariable("game", name).toString());
    }

    private void resetGame() {
        EditSession editSession = editSessionFactory.getEditSession(worldEditWorld, NO_BLOCK_LIMIT);
        for (Floor floor : floors.values()) {
            floorRegenerator.regenerate(floor, editSession, RegenerationCause.RESET);
        }

        blocksBroken.clear();
        deadPlayers.clear();
        spawnLocationQueue.clear();
        setGameState(GameState.WAITING);

        //Stop the countdown if necessary
        if (countdownTask != null) {
            countdownTask.cancel();
            countdownTask = null;
        }
    }

    public void flushQueue() {
        Queue<SpleefPlayer> failedToQueue = Lists.newLinkedList();

        //Flush the queue
        while (!queuedPlayers.isEmpty()) {
            SpleefPlayer player = queuedPlayers.poll();
            if (!player.isOnline()) {
                continue;
            }

            PlayerQueueFlushEvent event = new PlayerQueueFlushEvent(this, player);
            eventBus.callEvent(event);

            PlayerQueueFlushEvent.FlushResult result = event.getResult();
            if (result == PlayerQueueFlushEvent.FlushResult.ALLOW) {
                PlayerPreJoinGameEvent joinEvent = new PlayerPreJoinGameEvent(this, player);
                eventBus.callEvent(joinEvent);

                if (joinEvent.getJoinResult() != JoinResult.TEMPORARY_DENY) {
                    join(player, joinEvent);
                    continue;
                }
            }

            failedToQueue.offer(player);
        }

        queuedPlayers.addAll(failedToQueue);
    }

    public void disable() {
        if (gameState == GameState.DISABLED) {
            return;
        }

        if (gameState.isGameActive()) {
            //Stop this game before disabling it
            stop();
        } else if (gameState == GameState.LOBBY) {
            //Create a copy of current ingame players to prevent
            //a ConcurrentModificationException
            Set<SpleefPlayer> ingamePlayersCopy = Sets.newHashSet(ingamePlayers);
            for (SpleefPlayer player : ingamePlayersCopy) {
                leave(player);
            }
        }

        setGameState(GameState.DISABLED);

        GameDisableEvent event = new GameDisableEvent(this);
        eventBus.callEvent(event);
    }

    public void enable() {
        if (gameState.isGameEnabled()) {
            return;
        }

        setGameState(GameState.WAITING);

        GameEnableEvent event = new GameEnableEvent(this);
        eventBus.callEvent(event);
    }

    public JoinResult join(SpleefPlayer player) {
        return join(player, null);
    }

    private JoinResult join(SpleefPlayer player, PlayerPreJoinGameEvent event) {
        if (ingamePlayers.contains(player)) {
            return JoinResult.PERMANENT_DENY;
        }

        if (event == null) {
            //Only call this event if the caller didn't give us an event
            //This event is called before the player joins the game!
            event = new PlayerPreJoinGameEvent(this, player);
            eventBus.callEvent(event);
        }

        if (event.getLobbyTeleportationLocation() == null) {
            player.sendMessage(i18n.getString(Messages.Player.ERROR_NO_LOBBY_POINT_SET));
            return JoinResult.PERMANENT_DENY;
        }

        JoinResult result = event.getJoinResult();
        switch (result) {
        case ALLOW:
            //Go through
            break;
        case PERMANENT_DENY:
        case TEMPORARY_DENY:
            String denyMessage = event.getMessage();
            if (denyMessage != null) {
                player.sendMessage(denyMessage);
            }

            return result;
        case NOT_SPECIFIED:
            //Do a state check
            if (gameState == GameState.INGAME) {
                return JoinResult.TEMPORARY_DENY;
            }
            break;
        default:
            break;
        }

        ingamePlayers.add(player);

        if (gameState == GameState.WAITING) {
            setGameState(GameState.LOBBY);
        }

        Location location;

        if (gameState.isGameActive()) {
            location = spawnLocationQueue.poll();

            if (location == null) {
                // Generate a random spawnpoint
                Floor topFloor = null;
                for (Floor floor : floors.values()) {
                    if (topFloor == null || floor.getRegion().getMaximumPoint().getBlockY() > topFloor.getRegion()
                            .getMaximumPoint().getBlockY()) {
                        topFloor = floor;
                    }
                }

                Region region = topFloor.getRegion();

                if (event.getGameTeleportationLocation() != null) {
                    location = event.getGameTeleportationLocation();
                } else {
                    List<Location> randomLocations = Lists.newArrayList();
                    generateSpawnpoints(region, randomLocations, 1);

                    location = randomLocations.get(0);
                }
            }
        } else {
            location = event.getLobbyTeleportationLocation();
        }

        PlayerStateHolder holder = new PlayerStateHolder();
        holder.setLocation(player.getBukkitPlayer().getLocation());
        holder.setGamemode(player.getBukkitPlayer().getGameMode());

        //Firstly set the players gamemode and teleport him
        //to provide compatibility with inventory plugins such as MultiInv
        //and xInventories
        player.getBukkitPlayer().setGameMode(GameMode.SURVIVAL);
        player.teleport(location);

        //Store a reference to the player state
        holder.updateState(player.getBukkitPlayer(), false, holder.getGamemode());
        player.savePlayerState(this, holder);

        DefaultConfig config = heavySpleef.getConfiguration(ConfigType.DEFAULT_CONFIG);
        boolean adventureMode = config.getGeneralSection().isAdventureMode();

        PlayerStateHolder.applyDefaultState(player.getBukkitPlayer(), adventureMode);

        //This event is called when the player actually joins the game
        PlayerJoinGameEvent joinGameEvent = new PlayerJoinGameEvent(this, player);
        eventBus.callEvent(joinGameEvent);

        broadcast(i18n.getVarString(Messages.Broadcast.PLAYER_JOINED_GAME)
                .setVariable("player", player.getDisplayName()).toString());

        if (event.getMessage() != null) {
            player.sendMessage(event.getMessage());
        }

        if (joinGameEvent.getStartGame()) {
            countdown();
        }

        return JoinResult.ALLOW;
    }

    public boolean queue(SpleefPlayer player) {
        PlayerEnterQueueEvent event = new PlayerEnterQueueEvent(this, player);
        eventBus.callEvent(event);

        if (event.isCancelled()) {
            return false;
        }

        queuedPlayers.add(player);
        return true;
    }

    public boolean isQueued(SpleefPlayer player) {
        return queuedPlayers.contains(player);
    }

    public void unqueue(SpleefPlayer player) {
        PlayerLeaveQueueEvent event = new PlayerLeaveQueueEvent(this, player);
        eventBus.callEvent(event);

        queuedPlayers.remove(player);
    }

    public Queue<SpleefPlayer> getQueuedPlayers() {
        return queuedPlayers;
    }

    public void leave(SpleefPlayer player) {
        leave(player, QuitCause.SELF, true);
    }

    public void leave(SpleefPlayer player, QuitCause cause, boolean sendMessages, Object... args) {
        if (!ingamePlayers.contains(player)) {
            return;
        }

        ingamePlayers.remove(player);

        if (gameState == GameState.INGAME) {
            deadPlayers.add(player);
        }

        SpleefPlayer killer = null;
        if (cause == QuitCause.LOSE && args != null && args.length > 0 && args[0] != null
                && args[0] instanceof SpleefPlayer) {
            killer = (SpleefPlayer) args[0];
        }

        PlayerLeaveGameEvent event = new PlayerLeaveGameEvent(this, player, killer, cause);
        eventBus.callEvent(event);

        if (event.isCancelled()) {
            //Add the player again...
            ingamePlayers.add(player);
            deadPlayers.remove(player);
            return;
        }

        if (!event.isSendMessages()) {
            sendMessages = false;
        }

        Location tpLoc = event.getTeleportationLocation();

        //Receive the state back
        if (!player.getBukkitPlayer().isDead()) {
            PlayerStateHolder playerState = player.getPlayerState(this);
            if (playerState != null) {
                playerState.apply(player.getBukkitPlayer(), tpLoc == null);
                player.removePlayerState(this);
            } else {
                //Ugh, something went wrong
                player.sendMessage(i18n.getString(Messages.Player.ERROR_ON_INVENTORY_LOAD));
            }
        } else {
            //Keep track of this dead player to restore
            //his inventory on respawn
            killedPlayers.add(player);
        }

        if (tpLoc != null) {
            player.teleport(tpLoc);
        }

        if (ingamePlayers.size() == 0 && gameState == GameState.LOBBY) {
            setGameState(GameState.WAITING);
        }

        if (cause == QuitCause.WIN) {
            Sound levelUpSound = getSoundEnumType(LEVEL_UP_SEARCH);
            if (levelUpSound != null) {
                player.getBukkitPlayer().playSound(player.getBukkitPlayer().getLocation(), levelUpSound, 1.0f,
                        1.0f);
            }
        }

        if (sendMessages) {
            String broadcastMessage = i18n.getVarString(Messages.Broadcast.PLAYER_LEFT_GAME)
                    .setVariable("player", player.getDisplayName()).toString();
            String playerMessage = i18n.getString(Messages.Player.PLAYER_LEAVE);
            BroadcastTarget broadcastMessageTarget = BroadcastTarget.AROUND_GAME;

            if (event.getPlayerMessage() != null && event.getBroadcastMessage() != null) {
                playerMessage = event.getPlayerMessage();
                broadcastMessage = event.getBroadcastMessage();
            } else {
                switch (cause) {
                case KICK:
                    CommandSender clientPlayer = null;
                    String message = null;

                    int messageIndex = 0;
                    if (args != null && args.length > 0 && args[0] instanceof CommandSender) {
                        // Caller gave us a client player
                        clientPlayer = (CommandSender) args[0];
                        messageIndex = 1;
                    }

                    if (args != null && args.length > messageIndex && args[messageIndex] instanceof String) {
                        // Caller gave us a kick message
                        message = (String) args[1];
                    }

                    playerMessage = i18n.getVarString(Messages.Player.PLAYER_KICK)
                            .setVariable("message", message != null ? message : "No reason provided")
                            .setVariable("kicker", clientPlayer != null ? clientPlayer.getName() : "Unknown")
                            .toString();
                    break;
                case SELF:
                    playerMessage = i18n.getString(Messages.Player.PLAYER_LEAVE);
                    break;
                case STOP:
                    playerMessage = i18n.getString(Messages.Player.GAME_STOPPED);
                    break;
                case LOSE:
                    if (killer != null) {
                        broadcastMessage = i18n.getVarString(Messages.Broadcast.PLAYER_LOST_GAME)
                                .setVariable("player", player.getDisplayName())
                                .setVariable("killer", killer.getDisplayName()).toString();
                    } else {
                        broadcastMessage = i18n.getVarString(Messages.Broadcast.PLAYER_LOST_GAME_UNKNOWN_KILLER)
                                .setVariable("player", player.getDisplayName()).toString();
                    }

                    playerMessage = i18n.getString(Messages.Player.PLAYER_LOSE);
                    break;
                case WIN:
                    broadcastMessage = i18n.getVarString(Messages.Broadcast.PLAYER_WON_GAME)
                            .setVariable("player", player.getDisplayName()).toString();

                    DefaultConfig config = heavySpleef.getConfiguration(ConfigType.DEFAULT_CONFIG);
                    GeneralSection section = config.getGeneralSection();

                    if (section.isWinMessageToAllEnabled()) {
                        broadcastMessageTarget = BroadcastTarget.PARTICIPATED;
                    }

                    playerMessage = i18n.getString(Messages.Player.PLAYER_WIN);
                default:
                    break;
                }
            }

            broadcast(broadcastMessageTarget, broadcastMessage);
            player.sendMessage(playerMessage);
        }

        PlayerLeftGameEvent leftEvent = new PlayerLeftGameEvent(this, player, killer, cause);
        eventBus.callEvent(leftEvent);
    }

    public void requestLose(SpleefPlayer player, QuitCause cause) {
        Object[] args = null;

        if (cause == QuitCause.LOSE) {
            final SpleefPlayer killer = killDetector.detectKiller(this, player);
            args = new Object[] { killer };
        }

        requestLose(player, true, cause, args);
    }

    private void requestLose(SpleefPlayer player, boolean checkWin, QuitCause cause, Object... args) {
        if (!ingamePlayers.contains(player)) {
            return;
        }

        leave(player, cause == null ? QuitCause.LOSE : cause, true, args);

        if (gameState.isGameActive()) {
            if (ingamePlayers.size() == 1 && checkWin) {
                SpleefPlayer playerLeft = ingamePlayers.iterator().next();
                requestWin(playerLeft);
            } else if (ingamePlayers.size() == 0) {
                GameEndEvent event = new GameEndEvent(this);
                eventBus.callEvent(event);

                resetGame();
            }
        }
    }

    public void requestWin(SpleefPlayer... players) {
        requestWin(players, true);
    }

    public void requestWin(SpleefPlayer[] players, boolean sendMessages) {
        List<SpleefPlayer> winnerList = Arrays.asList(players);
        for (SpleefPlayer ingamePlayer : Sets.newHashSet(ingamePlayers)) {
            if (winnerList.contains(ingamePlayer)) {
                leave(ingamePlayer, QuitCause.WIN, sendMessages);
            } else {
                requestLose(ingamePlayer, sendMessages, QuitCause.LOSE);
            }
        }

        int toIndex = deadPlayers.size() - players.length;
        if (toIndex < 0) {
            toIndex = 0;
        }

        List<SpleefPlayer> dead = Lists.newArrayList(this.deadPlayers.subList(0, toIndex));
        Collections.reverse(dead);

        PlayerWinGameEvent event = new PlayerWinGameEvent(this, players, dead);
        eventBus.callEvent(event);

        resetGame();
        flushQueue();
    }

    public void kickPlayer(SpleefPlayer player, String message, CommandSender sender) {
        if (!ingamePlayers.contains(player)) {
            throw new IllegalArgumentException("Player must be in game to kick");
        }

        requestLose(player, true, QuitCause.KICK, sender, message);
    }

    public Set<SpleefPlayer> getPlayers() {
        return ingamePlayers;
    }

    public boolean isIngame(SpleefPlayer player) {
        return ingamePlayers.contains(player);
    }

    public void registerGameListener(SpleefListener listener) {
        eventBus.registerListener(listener);
    }

    public EditSession newEditSession() {
        return editSessionFactory.getEditSession(worldEditWorld, NO_BLOCK_LIMIT);
    }

    public void addFlag(AbstractFlag<?> flag) {
        addFlag(flag, true);
    }

    public void addFlag(AbstractFlag<?> flag, boolean initFlag) {
        flagManager.addFlag(flag);
        eventBus.registerListener(flag);

        if (initFlag) {
            flag.onFlagAdd(this);
        }
    }

    public void removeFlag(String path) {
        AbstractFlag<?> flag = flagManager.removeFlag(path);
        if (flag != null) {
            flag.onFlagRemove(this);

            eventBus.unregister(flag);
        }
    }

    public void removeFlag(Class<? extends AbstractFlag<?>> flagClass) {
        AbstractFlag<?> flag = flagManager.removeFlag(flagClass);
        if (flag != null) {
            flag.onFlagRemove(this);

            eventBus.unregister(flag);
        }
    }

    public boolean isFlagPresent(String path) {
        return flagManager.isFlagPresent(path);
    }

    public boolean isFlagPresent(Class<? extends AbstractFlag<?>> clazz) {
        return flagManager.isFlagPresent(clazz);
    }

    public <T extends AbstractFlag<?>> T getFlag(Class<T> flag) {
        return flagManager.getFlag(flag);
    }

    @SuppressWarnings("unchecked")
    public <T extends AbstractFlag<?>> T getFlag(String path) {
        return (T) flagManager.getFlag(path);
    }

    public void addExtension(GameExtension extension) {
        extension.setGame(this);
        extensionManager.addExtension(extension);
    }

    public void removeExtension(GameExtension extension) {
        extension.setGame(null);
        extensionManager.removeExtension(extension);
    }

    public Set<GameExtension> getExtensions() {
        return extensionManager.getExtensions();
    }

    public <T extends GameExtension> Set<T> getExtensionsByType(Class<T> extClass) {
        return getExtensionsByType(extClass, false);
    }

    public <T extends GameExtension> Set<T> getExtensionsByType(Class<T> extClass, boolean strict) {
        return extensionManager.getExtensionsByType(extClass, strict);
    }

    public void addFloor(Floor floor) {
        floors.put(floor.getName(), floor);
    }

    public Floor removeFloor(String name) {
        return floors.remove(name);
    }

    public boolean isFloorPresent(String name) {
        return floors.containsKey(name);
    }

    public Floor getFloor(String name) {
        return floors.get(name);
    }

    public Collection<Floor> getFloors() {
        return floors.values();
    }

    public boolean canSpleef(Block block) {
        if (block.getType() == Material.AIR) {
            //Player can not "spleef" an empty block
            return false;
        }

        if (gameState != GameState.INGAME) {
            //Players can't spleef while the game is not ingame
            return false;
        }

        boolean onFloor = false;
        for (Floor floor : getFloors()) {
            if (floor.contains(block)) {
                onFloor = true;
                break;
            }
        }

        return onFloor;
    }

    public void addDeathzone(String name, Region region) {
        deathzones.put(name, region);
    }

    public Region removeDeathzone(String name) {
        return deathzones.remove(name);
    }

    public Region getDeathzone(String name) {
        return deathzones.get(name);
    }

    public boolean isDeathzonePresent(String name) {
        return deathzones.containsKey(name);
    }

    @Override
    public void supply(Set<Variable> vars, Set<String> requested) {
        String gameStateName = gameState.name();
        gameStateName = Character.toUpperCase(gameStateName.charAt(0)) + gameStateName.substring(1);

        vars.add(new Variable("name", name));
        vars.add(new Variable("state", gameStateName));
        vars.add(new Variable("localized_state", gameState.getLocalizedName()));
        vars.add(new Variable("players", ingamePlayers.size()));
        vars.add(new Variable("dead", deadPlayers.size()));
        vars.add(new Variable("countdown", countdownTask != null ? countdownTask.getRemaining() : 0));
        vars.add(new Variable("is_countdown", countdownTask != null));

        for (String req : requested) {
            StringTokenizer tokenizer = new StringTokenizer(req, ":");

            String primaryReq = tokenizer.nextToken();

            boolean hasFlagRequest = primaryReq.equals(HAS_FLAG_PREFIX);
            boolean flagValueRequest = primaryReq.equals(FLAG_VALUE_PREFIX);

            if (hasFlagRequest || flagValueRequest) {
                if (!tokenizer.hasMoreTokens()) {
                    throw new IllegalStateException("Requested variable '" + req
                            + "' must be followed by a flag name ('<request>:<flagpath>')");
                }

                String flagPath = tokenizer.nextToken();

                if (hasFlagRequest) {
                    vars.add(new Variable(req, isFlagPresent(flagPath)));
                } else if (flagValueRequest) {
                    AbstractFlag<?> flag = getFlag(flagPath);

                    vars.add(new Variable(req, flag != null ? flag.getValue() : null));
                }
            }
        }
    }

    public void setGameState(GameState state) {
        GameState old = this.gameState;
        this.gameState = state;

        GameStateChangeEvent event = new GameStateChangeEvent(this, old);
        eventBus.callEvent(event);
    }

    @SuppressWarnings("unchecked")
    public synchronized <T> T getPropertyValue(GameProperty property) {
        return (T) flagManager.getProperty(property);
    }

    public void requestProperty(GameProperty property, Object value) {
        flagManager.requestProperty(property, value);
    }

    public void addBlockBroken(SpleefPlayer player, Block brokenBlock) {
        Set<Block> set = blocksBroken.get(player);
        if (set == null) {
            // Lazily initialize the set
            set = Sets.newHashSet();
            blocksBroken.put(player, set);
        }

        set.add(brokenBlock);
    }

    public void setKillDetector(KillDetector detector) {
        Validate.notNull(detector, "detector cannot be null");

        this.killDetector = detector;
    }

    public void broadcast(String message) {
        broadcast(BroadcastTarget.AROUND_GAME, message);
    }

    public void broadcast(BroadcastTarget target, String message) {
        switch (target) {
        case PARTICIPATED:
        case AROUND_GAME:
            //Use any floor as a fixpoint
            Iterator<Floor> iterator = floors.values().iterator();
            if (iterator.hasNext()) {
                Floor floor = iterator.next();

                Vector center = floor.getRegion().getCenter();
                int broadcastRadius = getPropertyValue(GameProperty.BROADCAST_RADIUS);

                for (Player player : Bukkit.getOnlinePlayers()) {
                    SpleefPlayer spleefPlayer = heavySpleef.getSpleefPlayer(player);
                    Vector playerVec = BukkitUtil.toVector(player.getLocation());

                    double distanceSq = center.distanceSq(playerVec);
                    if (distanceSq <= Math.pow(broadcastRadius, 2) || isIngame(spleefPlayer)
                            || (target == BroadcastTarget.PARTICIPATED && deadPlayers.contains(spleefPlayer))) {
                        player.sendMessage(heavySpleef.getSpleefPrefix() + message);
                    }
                }

                break;
            }

            //$FALL-THROUGH$
        case GLOBAL:
            Bukkit.broadcastMessage(heavySpleef.getSpleefPrefix() + message);
            break;
        case INGAME:
            for (SpleefPlayer player : ingamePlayers) {
                player.sendMessage(message);
            }
            break;
        default:
            break;
        }
    }

    /* Event hooks */
    @SuppressWarnings("deprecation")
    public void onPlayerInteract(PlayerInteractEvent event, SpleefPlayer player) {
        Block block = event.getClickedBlock();
        Action action = event.getAction();

        PlayerInteractGameEvent spleefEvent = new PlayerInteractGameEvent(this, player, block, action);
        eventBus.callEvent(spleefEvent);

        if (spleefEvent.isCancelled()) {
            event.setCancelled(true);
            return;
        }

        if (gameState != GameState.INGAME) {
            return;
        }

        boolean isInstantBreak = getPropertyValue(GameProperty.INSTANT_BREAK);
        boolean playBreakEffect = getPropertyValue(GameProperty.PLAY_BLOCK_BREAK);

        if (action == Action.LEFT_CLICK_BLOCK && isInstantBreak) {
            boolean breakBlock = false;

            for (Floor floor : floors.values()) {
                if (floor.contains(block)) {
                    breakBlock = true;
                    break;
                }
            }

            if (breakBlock) {
                PlayerBlockBreakEvent breakEvent = new PlayerBlockBreakEvent(this, player, block);
                eventBus.callEvent(breakEvent);

                if (breakEvent.isCancelled()) {
                    return;
                }

                Material blockMaterial = block.getType();
                block.setType(Material.AIR);

                if (playBreakEffect) {
                    block.getWorld().playEffect(block.getLocation(), Effect.STEP_SOUND, blockMaterial.getId());
                }

                addBlockBroken(player, block);
            }
        }
    }

    public void onPlayerBreakBlock(BlockBreakEvent event, SpleefPlayer player) {
        if (gameState != GameState.INGAME) {
            event.setCancelled(true);
            return;
        }

        Block block = event.getBlock();

        boolean onFloor = false;
        for (Floor floor : floors.values()) {
            if (floor.contains(block)) {
                onFloor = true;
                break;
            }
        }

        boolean disableBuild = getPropertyValue(GameProperty.DISABLE_BUILD);
        boolean disableFloorBreak = getPropertyValue(GameProperty.DISABLE_FLOOR_BREAK);

        if ((!onFloor && disableBuild) || disableFloorBreak) {
            event.setCancelled(true);
        } else {
            PlayerBlockBreakEvent spleefEvent = new PlayerBlockBreakEvent(this, player, event.getBlock());
            eventBus.callEvent(spleefEvent);

            if (spleefEvent.isCancelled()) {
                event.setCancelled(true);
                return;
            }

            addBlockBroken(player, block);
            //Prevent drops
            block.setType(Material.AIR);
        }
    }

    public void onPlayerPlaceBlock(BlockPlaceEvent event, SpleefPlayer player) {
        PlayerBlockPlaceEvent spleefEvent = new PlayerBlockPlaceEvent(this, player, event.getBlock());
        eventBus.callEvent(spleefEvent);

        if (spleefEvent.isCancelled()) {
            event.setCancelled(true);
            return;
        }

        boolean disableBuild = getPropertyValue(GameProperty.DISABLE_BUILD);

        if (disableBuild) {
            event.setCancelled(true);
        }
    }

    public void onPlayerPickupItem(PlayerPickupItemEvent event, SpleefPlayer player) {
        boolean disablePickup = getPropertyValue(GameProperty.DISABLE_ITEM_PICKUP);
        if (disablePickup) {
            event.setCancelled(true);
        }
    }

    public void onPlayerDropItem(PlayerDropItemEvent event, SpleefPlayer player) {
        boolean disableDrop = getPropertyValue(GameProperty.DISABLE_ITEM_DROP);
        if (disableDrop) {
            event.setCancelled(true);
        }
    }

    public void onPlayerFoodLevelChange(FoodLevelChangeEvent event, SpleefPlayer player) {
        boolean noHunger = getPropertyValue(GameProperty.DISABLE_HUNGER);
        if (noHunger) {
            event.setCancelled(true);
        }
    }

    public void onEntityByEntityDamageEvent(EntityDamageByEntityEvent event, SpleefPlayer damagedPlayer) {
        DefaultConfig config = heavySpleef.getConfiguration(ConfigType.DEFAULT_CONFIG);
        LobbySection lobbySection = config.getLobbySection();

        boolean disablePvp = getPropertyValue(GameProperty.DISABLE_PVP);
        boolean disableDamage = getPropertyValue(GameProperty.DISABLE_DAMAGE);
        boolean enableLobbyPvp = lobbySection.isEnablePvp();

        if (enableLobbyPvp && event.getDamager() instanceof Player && gameState == GameState.LOBBY) {
            return;
        }

        if (event.getDamager() instanceof Player && disablePvp
                || !(event.getDamager() instanceof Player) && disableDamage) {
            event.setCancelled(true);
        }
    }

    public void onEntityDamageEvent(EntityDamageEvent event, SpleefPlayer damaged) {
        boolean disableDamage = getPropertyValue(GameProperty.DISABLE_DAMAGE);

        if (event.getCause() != DamageCause.ENTITY_ATTACK && disableDamage) {
            event.setCancelled(true);
        }
    }

    public void onEntityTargetLivingEntity(EntityTargetLivingEntityEvent event, SpleefPlayer targetted) {
        boolean disableDamage = getPropertyValue(GameProperty.DISABLE_DAMAGE);

        if (disableDamage) {
            event.setCancelled(true);
        }
    }

    public void onPlayerQuit(PlayerQuitEvent event, SpleefPlayer quitter) {
        handleQuit(quitter);
    }

    public void onPlayerKick(PlayerKickEvent event, SpleefPlayer quitter) {
        handleQuit(quitter);
    }

    private void handleQuit(SpleefPlayer quitter) {
        if (ingamePlayers.contains(quitter)) {
            requestLose(quitter, QuitCause.SELF);
        } else if (isQueued(quitter)) {
            unqueue(quitter);
        }
    }

    public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event, SpleefPlayer typing) {
        boolean blockCommands = false;

        if (ingamePlayers.contains(typing)) {
            blockCommands = getPropertyValue(GameProperty.BLOCK_COMMANDS);
        } else if (isQueued(typing)) {
            DefaultConfig config = heavySpleef.getConfiguration(ConfigType.DEFAULT_CONFIG);
            QueueSection section = config.getQueueSection();

            blockCommands = !section.isCommandsInQueue();
        }

        if (!blockCommands || typing.hasPermission(Permissions.PERMISSION_COMMAND_BYPASS)) {
            return;
        }

        String message = event.getMessage();
        String[] components = message.split(" ");

        String command = components[0];
        command = command.substring(1);

        DefaultConfig config = heavySpleef.getConfiguration(ConfigType.DEFAULT_CONFIG);
        GeneralSection section = config.getGeneralSection();

        List<String> whitelistedCommands = section.getWhitelistedCommands();
        if (whitelistedCommands.contains(command) || command.equalsIgnoreCase(SPLEEF_COMMAND)) {
            return;
        }

        //Block this command
        event.setCancelled(true);
        typing.sendMessage(i18n.getString(Messages.Player.COMMAND_NOT_ALLOWED));
    }

    public void onPlayerDeath(PlayerDeathEvent event, SpleefPlayer dead) {
        if (isIngame(dead)) {
            if (gameState == GameState.LOBBY) {
                killedLobbyPlayers.add(dead);
            } else {
                requestLose(dead, QuitCause.LOSE);
            }

            event.getDrops().clear();
        }

        //Check if this player was queued
        if (isQueued(dead)) {
            unqueue(dead);
        }
    }

    public void onPlayerRespawn(PlayerRespawnEvent event, final SpleefPlayer respawning) {
        if (killedPlayers.contains(respawning)) {
            killedPlayers.remove(respawning);
            Bukkit.getScheduler().scheduleSyncDelayedTask(heavySpleef.getPlugin(), new Runnable() {

                @Override
                public void run() {
                    if (respawning.isOnline()) {
                        PlayerStateHolder playerState = respawning.getPlayerState(Game.this);
                        if (playerState != null) {
                            playerState.apply(respawning.getBukkitPlayer(), true);
                        } else {
                            //Ugh, something went wrong
                            respawning.sendMessage(i18n.getString(Messages.Player.ERROR_ON_INVENTORY_LOAD));
                        }
                    }
                }
            }, 5L);
        } else if (killedLobbyPlayers.contains(respawning)) {
            killedLobbyPlayers.remove(respawning);
            Bukkit.getScheduler().scheduleSyncDelayedTask(heavySpleef.getPlugin(), new Runnable() {
                @Override
                public void run() {
                    if (respawning.isOnline()) {
                        UpdateLobbyItemsEvent event = new UpdateLobbyItemsEvent(Game.this, respawning);
                        eventBus.callEvent(event);

                        Bukkit.getScheduler().scheduleSyncDelayedTask(heavySpleef.getPlugin(), new Runnable() {
                            @Override
                            public void run() {
                                respawning.getBukkitPlayer().updateInventory();
                            }
                        }, 15L);
                    }
                }
            }, 5L);
        }

    }

    public void onPlayerGameModeChange(PlayerGameModeChangeEvent event, SpleefPlayer player) {
        if (!gameState.isGameActive()) {
            return;
        }

        GameMode gameMode = event.getNewGameMode();
        if (gameMode != GameMode.CREATIVE) {
            return;
        }

        event.setCancelled(true);
        player.sendMessage(i18n.getString(Messages.Player.CANNOT_CHANGE_GAMEMODE));
    }

    public void onPlayerTeleport(PlayerTeleportEvent event, SpleefPlayer player) {
        Player bukkitPlayer = player.getBukkitPlayer();

        if (bukkitPlayer.hasMetadata(SpleefPlayer.ALLOW_NEXT_TELEPORT_KEY)) {
            List<MetadataValue> values = bukkitPlayer.getMetadata(SpleefPlayer.ALLOW_NEXT_TELEPORT_KEY);

            for (MetadataValue value : values) {
                if (value.getOwningPlugin() != heavySpleef.getPlugin()) {
                    continue;
                }

                if (value.asBoolean()) {
                    return;
                }
            }
        }

        event.setCancelled(true);
    }

    public enum JoinResult {

        TEMPORARY_DENY, PERMANENT_DENY, NOT_SPECIFIED, ALLOW;

    }

}