package com.github.jamesnorris.ablockalypse.aspect; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.entity.Player; import com.github.jamesnorris.ablockalypse.Ablockalypse; import com.github.jamesnorris.ablockalypse.DataContainer; import com.github.jamesnorris.ablockalypse.behavior.GameAspect; import com.github.jamesnorris.ablockalypse.enumerated.Setting; import com.github.jamesnorris.ablockalypse.enumerated.ZASound; import com.github.jamesnorris.ablockalypse.event.GameEndEvent; import com.github.jamesnorris.ablockalypse.event.bukkit.PlayerJoin; import com.github.jamesnorris.ablockalypse.threading.inherent.MysteryBoxFakeBeaconTask; import com.github.jamesnorris.ablockalypse.threading.inherent.NextLevelTask; import com.github.jamesnorris.ablockalypse.utility.BukkitUtility; import com.github.jamesnorris.ablockalypse.utility.MathUtility; import com.github.jamesnorris.ablockalypse.utility.SerialLocation; import com.github.jamesnorris.ablockalypse.utility.SpawnUtility; public class Game extends PermanentAspect { private MysteryBox active; private MysteryBoxFakeBeaconTask beacons; private DataContainer data = Ablockalypse.getData(); private int level = 0, mobcount, startpoints, spawnedInThisRound = 0; private Teleporter mainframe; private GameScoreboard scoreBoard; private String name; private NextLevelTask nlt; private CopyOnWriteArrayList<GameAspect> objects = new CopyOnWriteArrayList<GameAspect>(); private HashMap<Integer, Integer> wolfLevels = new HashMap<Integer, Integer>(); private boolean wolfRound, armorRound, paused, started;// TODO armorRound not used (should it be?) private Random rand = new Random(); private UUID uuid = UUID.randomUUID(); public Game(Map<String, Object> savings) { this((String) savings.get("game_name")); // SerialLocation.returnLocation((SerialLocation) savings.get("active_chest_location")); active = null;// automatically set by loading the available mystery chests level = (Integer) savings.get("level"); mobcount = (Integer) savings.get("mob_count"); startpoints = (Integer) savings.get("starting_points"); @SuppressWarnings("unchecked") List<Map<String, Object>> savedVersions = (List<Map<String, Object>>) savings.get("game_objects"); for (Map<String, Object> save : savedVersions) { if (save == null) { continue; } try { PermanentAspect.load(Class.forName((String) save.get("saved_class_type")), save); } catch (ClassNotFoundException ex) { Ablockalypse.getTracker().error("The class designated to a saved object could not be found!", 20, ex); } } if (savings.get("mainframe_location") != null) { mainframe = data.getTeleporter(SerialLocation.returnLocation((SerialLocation) savings.get("mainframe_location"))); if (mainframe != null) { mainframe.refresh(); } } @SuppressWarnings("unchecked") HashMap<Integer, Integer> hashMap = (HashMap<Integer, Integer>) savings.get("wolf_levels"); wolfLevels = hashMap; wolfRound = (Boolean) savings.get("wolf_round"); armorRound = (Boolean) savings.get("armor_round"); paused = (Boolean) savings.get("paused"); started = (Boolean) savings.get("started"); spawnedInThisRound = (Integer) savings.get("mobs_spawned_this_round"); uuid = savings.get("uuid") == null ? uuid : (UUID) savings.get("uuid"); } /** * Creates a new instance of a game. * * @param name The name of the ZAGame */ public Game(String name) { this.name = name; paused = false; started = false; @SuppressWarnings("unchecked") ArrayList<String> wolfLevelsList = (ArrayList<String>) Setting.WOLF_LEVELS.getSetting(); for (String line : wolfLevelsList.toArray(new String[wolfLevelsList.size()])) { for (Integer level : MathUtility.parseIntervalNotation(line)) { wolfLevels.put(level, MathUtility.parsePercentage(line)); } } startpoints = (Integer) Setting.STARTING_POINTS.getSetting(); beacons = new MysteryBoxFakeBeaconTask(this, 200, (Boolean) Setting.BEACONS.getSetting()); scoreBoard = new GameScoreboard(this); data.objects.add(this); } /** * Attaches an area to this game. * * @param ga The area to load into this game */ public void addObject(GameAspect obj) { if (obj != null && !objects.contains(obj) && !overlapsAnotherObject(obj)) { objects.add(obj); } } /** * Adds a player to the game. * NOTE: This does not change a players' status at all, that must be done through the ZAPlayer instance. * * @param player The player to be added to the game */ public void addPlayer(Player player) { ZAPlayer zap = data.getZAPlayer(player, name, true); if (!player.isOnline() && !PlayerJoin.isQueued(zap)) { return; } addObject(zap); if (!data.isZAPlayer(player)) { zap.setPoints(startpoints); } if (!playerIsInGame(zap)) { broadcast(ChatColor.RED + player.getName() + ChatColor.GRAY + " has joined the game!", player); } if (paused) { pause(false); } if (!started) { start(); } } /** * Sends a message to all players in the game. * * @param message The message to send * @param exception A player to be excluded from the broadcast */ public void broadcast(String message, Player... exceptions) { for (ZAPlayer zap : getPlayers()) { boolean contained = false; if (exceptions != null) { for (Player except : exceptions) { if (zap.getPlayer() == except) { contained = true; } } } if (!contained) { zap.getPlayer().sendMessage(message); } } } /** * Sends all players in the game the points of all players. */ public void broadcastPoints() { for (ZAPlayer zap : getPlayers()) { zap.showPoints(); } } public void broadcastSound(ZASound sound, Player... exceptions) { for (ZAPlayer zap : getPlayers()) { boolean contained = false; if (exceptions != null) { for (Player except : exceptions) { if (zap.getPlayer() == except) { contained = true; } } } if (!contained) { sound.play(zap.getPlayer().getLocation()); } } } /** * Ends the game, repairs all barriers, closes all areas, and removes all players. * @param countQueue If the player queue for offline players (see PlayerJoin.java) should be * checked before ending the game. If the queue is not empty, the game will then not start. */ public void end(boolean countQueue) { if (started) { if (countQueue && PlayerJoin.getQueues(this).size() != 0) { return; } int points = 0; for (ZAPlayer zap : getObjectsOfType(ZAPlayer.class)) { points += zap.getPoints(); } GameEndEvent GEE = new GameEndEvent(this, points); Bukkit.getPluginManager().callEvent(GEE); if (!GEE.isCancelled()) { for (GameAspect obj : objects) { obj.onGameEnd(); } spawnedInThisRound = 0; paused = true; started = false; level = 1; if (nlt != null && nlt.isRunning()) { nlt.cancel(); } mobcount = 0; } } } /** * Gets the currently active chest for this game. * * @return The currently active chest for this game */ public MysteryBox getActiveMysteryChest() { return active; } public Player getClosestLivingPlayer(Location location) { if (getRemainingPlayers().size() >= 1) { Player closest = getRandomLivingPlayer(); Double distanceSquared = Double.MAX_VALUE; for (ZAPlayer zap : getPlayers()) { double currentDSq = zap.getPlayer().getLocation().distanceSquared(location); if (!zap.getPlayer().isDead() && !zap.isInLastStand() && !zap.isInLimbo() && currentDSq < distanceSquared) { closest = zap.getPlayer(); distanceSquared = currentDSq; } } return closest; } return null; } public MysteryBoxFakeBeaconTask getFakeBeaconThread() { return beacons; } public GameScoreboard getGameScoreboard() { return scoreBoard; } @Override public String getHeader() { return this.getClass().getSimpleName() + " <UUID: " + getUUID().toString() + ">"; } /** * Gets the current level that the game is on. * * @return The current level of the game */ public int getLevel() { return level; } /** * Gets the spawn location for this game. * * @return The location of the spawn */ public Teleporter getMainframe() { return mainframe; } /** * Gets the remaining custom mobs in the game. * * @return The amount of remaining mobs in this game */ public int getMobCount() { return mobcount; } public int getMobCountSpawnedInThisRound() { return spawnedInThisRound; } /** * Gets all mobs spawned in this game. * * @return All mobs currently alive in this game */ public List<ZAMob> getMobs() { return getObjectsOfType(ZAMob.class); } /** * Gets the name of this game. * * @return The name of the ZAGame */ public String getName() { return name; } public CopyOnWriteArrayList<GameAspect> getObjects() { return objects; } @SuppressWarnings("unchecked") public <T extends Object> List<T> getObjectsOfType(Class<T> type) { ArrayList<T> list = new ArrayList<T>(); for (Object obj : objects) { if (obj != null && type != null) { if (type.isAssignableFrom(obj.getClass())) { list.add((T) obj); } } } return list; } /** * Returns a list of players currently in the game. * * @return A list of player names that are involved in this game */ public List<ZAPlayer> getPlayers() { return getObjectsOfType(ZAPlayer.class); } /** * Gets a random living player. * Living is considered as not in limbo, last stand, respawn thread, or death. * * @return The random living player */ public Player getRandomLivingPlayer() { if (getRemainingPlayers().size() >= 1) { ArrayList<ZAPlayer> zaps = new ArrayList<ZAPlayer>(); for (ZAPlayer zap : getPlayers()) { if (zap.getPlayer() != null && !zap.getPlayer().isDead() && !zap.isInLastStand() && !zap.isInLimbo()) { zaps.add(zap); } } return zaps.get(rand.nextInt(zaps.size())).getPlayer(); } return null; } /** * Returns a random player from this game. * * @return The random player from this game */ public ZAPlayer getRandomPlayer() { return getPlayers().get(rand.nextInt(getPlayers().size())); } /** * Gets the players still in the game. * This only counts players that are living and not in last stand or limbo. * * @return How many living players are in the game */ public List<ZAPlayer> getRemainingPlayers() { List<ZAPlayer> remaining = new ArrayList<ZAPlayer>(); for (ZAPlayer zap : getPlayers()) { if (zap.getPlayer() != null && !zap.getPlayer().isDead() && !zap.isInLimbo() && !zap.isInLastStand()) { remaining.add(zap); } } return remaining; } @Override public Map<String, Object> getSave() { Map<String, Object> savings = new HashMap<String, Object>(); savings.put("uuid", getUUID()); savings.put("active_chest_location", active == null ? null : new SerialLocation(active.getLocation())); savings.put("level", level); savings.put("mob_count", mobcount); savings.put("starting_points", startpoints); savings.put("mobs_spawned_this_round", spawnedInThisRound); savings.put("mainframe_location", mainframe == null ? null : new SerialLocation(mainframe.getLocation())); savings.put("game_name", name); List<Map<String, Object>> savedVersions = new ArrayList<Map<String, Object>>(); for (PermanentAspect aspect : getObjectsOfType(PermanentAspect.class)) { Map<String, Object> save = aspect.getSave(); save.put("saved_class_type", aspect.getClass().getName()); savedVersions.add(save); } savings.put("game_objects", savedVersions); savings.put("wolf_levels", wolfLevels); savings.put("wolf_round", wolfRound); savings.put("armor_round", armorRound); savings.put("paused", paused); savings.put("started", started); savings.put("mobs_spawned_this_round", spawnedInThisRound); return savings; } @Override public UUID getUUID() { return uuid; } public int getWolfPercentage() { return getWolfPercentage(level); } public int getWolfPercentage(int level) { if (!wolfLevels.containsKey(level)) { return 0; } return wolfLevels.get(level); } public boolean hasMob(ZAMob mob) { return getMobs().contains(mob); } /** * Returns whether or not the game has started. * * @return Whether or not the game has been started, and mobs are spawning */ public boolean hasStarted() { return started; } public boolean isArmorRound() { return armorRound; } /** * Checks if the game is paused or not. * * @return Whether or not the game is paused */ public boolean isPaused() { return paused; } /** * Returns true if the current round is a wolf round. * * @return Whether or not the current round is a wolf round */ public boolean isWolfRound() { return wolfRound; } /** * Starts the next level for the game, and adds a level to all players in this game. * Then, spawns a wave of zombies, and starts the thread for the next level. */ public void nextLevel() { ++level; int maxWave = (Integer) Setting.MAX_WAVE.getSetting(); if (level >= maxWave && maxWave != -1) { end(false); return; } spawnedInThisRound = 0; mobcount = 0; if (!started) { level = 1; List<MysteryBox> chests = getObjectsOfType(MysteryBox.class); if (chests != null && chests.size() > 0) { MysteryBox mc = chests.get(rand.nextInt(chests.size())); setActiveMysteryChest(mc); } for (GameAspect obj : objects) { obj.onGameStart(); } started = true; } if (wolfLevels != null && wolfLevels.containsKey(level)) { wolfRound = true; } paused = false; nlt = new NextLevelTask(this, true); spawnWave(SpawnUtility.getCurrentSpawnAmount(this) - spawnedInThisRound); for (GameAspect obj : objects) { obj.onNextLevel(); } } public void organizeObjects() { CopyOnWriteArrayList<GameAspect> newObjects = new CopyOnWriteArrayList<GameAspect>(); int[][] priorities = new int[objects.size()][2]; for (int i = 0; i < priorities.length; i++) { GameAspect obj = objects.get(i); priorities[i][0] = i; priorities[i][1] = obj == null ? Integer.MAX_VALUE : obj.getLoadPriority(); } for (int j = 1; j < priorities.length; j++) { int[] temp = priorities[j]; int current = j - 1; while (current >= 0 && priorities[current][1] > temp[1]) { priorities[current + 1] = priorities[current]; current--; } priorities[current + 1] = temp; } for (int k = 0; k < priorities.length; k++) { newObjects.add(objects.get(priorities[k][0])); } objects = newObjects; } /** * Sets the game to pause or not. * * @param tf Whether or not to pause or un-pause the game */ public void pause(boolean tf) { if (tf && !paused) { // TODO freeze mobs, cancel damage, etc } else if (!tf && paused) { --level; nextLevel(); } paused = tf; } /** * Ends the game, removes all attached instances, and finalizes this instance. */ public void remove(boolean permanently) { end(false); for (GameAspect object : getObjects()) { object.remove(); } if (beacons != null) { beacons.cancel(); } File savedData = Ablockalypse.getExternal().getSavedDataFile(getName(), false); if (permanently && savedData != null) { savedData.delete(); } data.objects.remove(this); } /** * Removes an area from this game. * * @param ga The area to be unloaded from this game */ public void removeObject(GameAspect obj) { if (obj != null && objects.contains(obj)) { objects.remove(obj); } } /** * Removes a player from the game. * * @param player The player to be removed from the game */ public void removePlayer(Player player) { ZAPlayer zap = data.getZAPlayer(player); if (zap != null && objects.contains(zap)) { objects.remove(zap); zap.removeFromGame();// removes zap from data.objects if (getPlayers().isEmpty()) { pause(true); end(true); } } } /** * Sets the active chest that can be used during this game. * * @param mc The chest to be made active */ public void setActiveMysteryChest(MysteryBox mc) { if ((Boolean) Setting.MOVING_MYSTERY_BOXES.getSetting()) { active = mc; for (MysteryBox chest : getObjectsOfType(MysteryBox.class)) { chest.setActive(false); } if (!mc.isActive()) { mc.setActive(true); mc.setActiveUses(rand.nextInt(8) + 2); } } } public void setArmorRound(boolean tf) { armorRound = tf; } public void setFakeBeaconThread(MysteryBoxFakeBeaconTask thread) { beacons = thread; } public void setGameScoreboard(GameScoreboard scoreBoard) { this.scoreBoard = scoreBoard; } /** * Sets the game to the specified level. * * @param i The level the game will be set to */ public void setLevel(int i) { level = i; } /** * Sets the spawn location and central teleporter of the game. * * @param mf The mainframe to be set for the game */ public void setMainframe(Teleporter mf) { mainframe = mf; mf.refresh(); } /** * Sets the mob count. * If raised higher than the count currently is, this will require more mobs spawned. * If lower than the count currently is, this will require more mob removals. * * @param i The count to set the current amount of mobs to. */ public void setMobCount(int i) { mobcount = i; if (mobcount < 0) { mobcount = 0; } } public void setMobCountSpawnedInThisRound(int amt) { spawnedInThisRound = amt; } /** * Makes the game a wolf round. * * @param tf Whether or not the game should be a wolf round */ public void setWolfRound(boolean tf) { wolfRound = tf; } /** * Spawns a wave of mobs around random living players in this game. * If barriers are present and acessible, spawns the mobs at the barriers. */ public void spawnWave(int amount) { if (mainframe == null && getRandomLivingPlayer() != null) { Location playerLoc = getRandomLivingPlayer().getLocation(); mainframe = new Teleporter(this, playerLoc); } SpawnUtility.spawnWave(this, amount); started = true; } public void start() { started = false; level = 0; nextLevel(); } private boolean overlapsAnotherObject(GameAspect obj) { if (obj.getDefiningBlocks() == null) { return false; } for (GameAspect object : getObjects()) { if (object == null || object.getDefiningBlocks() == null) { continue; } for (Block block : object.getDefiningBlocks()) { if (block == null) { continue; } Location bLoc = block.getLocation(); for (Block otherBlock : obj.getDefiningBlocks()) { if (BukkitUtility.locationMatch(otherBlock.getLocation(), bLoc)) { return true; } } } } return false; } private boolean playerIsInGame(ZAPlayer zap) { for (ZAPlayer zap2 : getObjectsOfType(ZAPlayer.class)) { if (zap.getPlayer().getName().equalsIgnoreCase(zap2.getPlayer().getName())) { return true; } } return false; } }