commit 6628a748369fbe887061643ef257adbe5ba7515e Author: Garbage Mule Date: Mon May 30 07:11:25 2011 +0200 first commit diff --git a/src/com/garbagemule/MobArena/ArenaManager.java b/src/com/garbagemule/MobArena/ArenaManager.java new file mode 100644 index 0000000..cc59c15 --- /dev/null +++ b/src/com/garbagemule/MobArena/ArenaManager.java @@ -0,0 +1,421 @@ +package com.garbagemule.MobArena; + +import java.io.File; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Set; +import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; +import org.bukkit.World; +import org.bukkit.Server; +import org.bukkit.Material; +import org.bukkit.Location; +import org.bukkit.ChatColor; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.util.config.Configuration; +import org.bukkit.event.Event; +import org.bukkit.plugin.EventExecutor; + +public class ArenaManager +{ + // Convenience variables. + protected static MobArena plugin = null; + protected static Server server = null; + protected static World world = null; + protected static Location arenaLoc = null; + protected static Location lobbyLoc = null; + protected static Location spectatorLoc = null; + protected static boolean isRunning = false; + protected static boolean isSetup = false; + + // Location variables for the arena region. + protected static Location p1 = null; + protected static Location p2 = null; + + // Configuration + protected static Configuration config = null; + + // Spawn locations list and monster distribution fields. + protected static List spawnpoints = new ArrayList(); + protected static int dZombies, dSkeletons, dSpiders, dCreepers; + + // Set and Maps for storing players, their locations, items, armor, etc. + protected static Set playerSet = new HashSet(); + protected static Set readySet = new HashSet(); + protected static Map rewardMap = new HashMap(); + protected static Map locationMap = new HashMap(); + + // Maps for storing class items and armor. + protected static List classes = new ArrayList(); + protected static Map classMap = new HashMap(); + protected static Map classItemMap = new HashMap(); + protected static Map classArmorMap = new HashMap(); + + // Maps for rewards. + protected static Map everyWaveMap = new HashMap(); + protected static Map afterWaveMap = new HashMap(); + + // Entities and blocks on MobArena floor. + protected static Set monsterSet = new HashSet(); + protected static Set blockSet = new HashSet(); + + + + /* ///////////////////////////////////////////////////////////////////// // + + INITIALIZATION AND UPDATE METHODS + + // ///////////////////////////////////////////////////////////////////// */ + + /** + * Initializes the ArenaManager. + */ + public static void init(MobArena instance) + { + // If instance == null, simply update location variables. + if (instance != null) + { + // General variables. + config = MAUtils.getConfig(); + plugin = instance; + server = plugin.getServer(); + world = MAUtils.getWorld(); + + // Class list and maps. + classes = MAUtils.getClasses(); + classItemMap = MAUtils.getClassItems("items"); + classArmorMap = MAUtils.getClassItems("armor"); + + // Waves and rewards. + everyWaveMap = MAUtils.getWaveMap("every"); + afterWaveMap = MAUtils.getWaveMap("after"); + + // Monster distribution coefficients. + dZombies = MAUtils.getDistribution("zombies"); + dSkeletons = MAUtils.getDistribution("skeletons"); + dSpiders = MAUtils.getDistribution("spiders"); + dCreepers = MAUtils.getDistribution("creepers"); + } + + // Convenience variables. + arenaLoc = MAUtils.getCoords("arena"); + lobbyLoc = MAUtils.getCoords("lobby"); + spectatorLoc = MAUtils.getCoords("spectator"); + p1 = MAUtils.getCoords("p1"); + p2 = MAUtils.getCoords("p2"); + spawnpoints = MAUtils.getSpawnPoints(); + + // Set the boolean if all variables are valid. + ArenaManager.isSetup = MAUtils.verifyData(); + } + + /** + * Updates all relevant variables. + */ + public static void updateVariables() + { + init(null); + } + + + + /* ///////////////////////////////////////////////////////////////////// // + + ARENA METHODS + + // ///////////////////////////////////////////////////////////////////// */ + + /** + * Starts the current MobArena session. + */ + public static void startArena() + { + isRunning = true; + + for (Player p : playerSet) + { + p.teleport(arenaLoc); + rewardMap.put(p,""); + } + + MASpawnThread thread = new MASpawnThread(); + plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin,thread,100,400); + + tellAll("Let the slaughter begin!"); + } + + /** + * Ends the current MobArena session. + * Clears the arena floor, gives all the players their rewards, + * and stops the spawning of monsters. + */ + public static void endArena() + { + isRunning = false; + server.getScheduler().cancelTasks(plugin); + killMonsters(); + clearBlocks(); + giveRewards(); + + // TO-DO: Fix this, maybe add a Set dead + tellAll("Arena finished."); + } + + /** + * Attempts to let a player join the arena session. + * Players must have an empty inventory to join the arena. Their + * location will be stored for when they leave. + */ + public static void playerJoin(Player p) + { + if (!isSetup) + { + tellPlayer(p, "MobArena has not been set up yet!"); + return; + } + if (playerSet.contains(p)) + { + tellPlayer(p, "You are already playing!"); + return; + } + if (isRunning) + { + tellPlayer(p, "Arena in progress. Type /marena spectate to watch."); + return; + } + if (!MAUtils.hasEmptyInventory(p)) + { + tellPlayer(p, "You must empty your inventory to join the arena."); + return; + } + + playerSet.add(p); + + if (!locationMap.keySet().contains(p)) + { + locationMap.put(p, p.getLocation()); + } + + p.teleport(lobbyLoc); + + tellPlayer(p, "You joined the arena. Have fun!"); + } + + /** + * Attempts to remove a player from the arena session. + * The player is teleported back to his previous location, and + * is removed from all the sets and maps. + */ + public static void playerLeave(Player p) + { + if (!locationMap.keySet().contains(p)) + { + tellPlayer(p, "You are not in the arena."); + return; + } + + MAUtils.clearInventory(p); + + if (playerSet.contains(p)) + playerSet.remove(p); + + if (classMap.keySet().contains(p)) + classMap.remove(p); + + // This must occur after playerSet.remove(p) to avoid teleport block. + p.teleport(locationMap.remove(p)); + + if (isRunning && playerSet.isEmpty()) + endArena(); + + tellPlayer(p, "You left the arena. Thanks for playing!"); + } + + /** + * Adds a joined arena player to the set of ready players. + */ + public static void playerReady(Player p) + { + readySet.add(p); + + if (readySet.equals(playerSet)) + { + readySet.clear(); + startArena(); + } + } + + /** + * Removes a dead player from the arena session. + * The player is teleported safely back to the spectator area, + * and their health is restored. All sets and maps are updated. + * If this was the last player alive, the arena session ends. + */ + public static void playerDeath(Player p) + { + p.teleport(spectatorLoc); + MAUtils.clearInventory(p); + p.setHealth(20); + tellAll(p.getName() + " died!"); + + if (playerSet.contains(p)) + playerSet.remove(p); + + if (classMap.keySet().contains(p)) + classMap.remove(p); + + if (isRunning && playerSet.isEmpty()) + endArena(); + } + + /** + * Lets a player spectate the current arena session. + */ + public static void playerSpectate(Player p) + { + if (!playerSet.contains(p)) + { + p.teleport(spectatorLoc); + tellPlayer(p, "Enjoy the show!"); + } + else + { + tellPlayer(p, "Can't spectate when in the arena!"); + } + } + + /** + * Prints the list of players currently in the arena session. + */ + public static void playerList(Player p) + { + if (playerSet.isEmpty()) + { + tellPlayer(p, "There is no one in the arena right now."); + return; + } + + String list = ""; + for (Player player : playerSet) + { + list += player.getName() + ", "; + } + list = list.substring(0,list.length()-2); + + tellPlayer(p, "Survivors: " + list); + } + + + + /* ///////////////////////////////////////////////////////////////////// // + + CLEANUP METHODS + + // ///////////////////////////////////////////////////////////////////// */ + + /** + * Kills all monsters currently on the arena floor. + */ + public static void killMonsters() + { + // Remove all monsters, then clear the Set. + for (LivingEntity entity : monsterSet) + { + if (!entity.isDead()) + entity.remove(); + } + monsterSet.clear(); + } + + /** + * Removes all the blocks on the arena floor. + */ + public static void clearBlocks() + { + // Remove all blocks, then clear the Set. + for (Block b : blockSet) + { + b.setType(Material.AIR); + } + blockSet.clear(); + } + + + + /* ///////////////////////////////////////////////////////////////////// // + + CLASS AND REWARD METHODS + + // ///////////////////////////////////////////////////////////////////// */ + + /** + * Assigns a class to the player. + */ + public static void assignClass(Player p, String className) + { + classMap.put(p, className); + giveClassItems(p); + } + + /** + * Grant a player their class-specific items. + */ + public static void giveClassItems(Player p) + { + String className = classMap.get(p); + String classItems = classItemMap.get(className); + String classArmor = classArmorMap.get(className); + + MAUtils.giveItems(p, classItems, classArmor); + } + + /** + * Gives all the players the rewards they earned. + */ + public static void giveRewards() + { + for (Player p : rewardMap.keySet()) + { + String r = rewardMap.get(p); + if (r.equals("")) continue; + + tellPlayer(p, "You're gonna get some shit now, so be happy!"); + MAUtils.giveItems(p, r); + } + + rewardMap.clear(); + } + + + + /* ///////////////////////////////////////////////////////////////////// // + + MISC METHODS + + // ///////////////////////////////////////////////////////////////////// */ + + /** + * Sends a message to a player. + */ + public static void tellPlayer(Player p, String msg) + { + p.sendMessage(ChatColor.GREEN + "[MobArena] " + ChatColor.WHITE + msg); + } + + /** + * Sends a message to all players in the arena. + */ + public static void tellAll(String msg) + { + for (Player p : playerSet) + { + tellPlayer(p, msg); + } + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MABlockListener.java b/src/com/garbagemule/MobArena/MABlockListener.java new file mode 100644 index 0000000..839688a --- /dev/null +++ b/src/com/garbagemule/MobArena/MABlockListener.java @@ -0,0 +1,58 @@ +package com.garbagemule.MobArena; + +import org.bukkit.event.block.BlockListener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.BlockDamageEvent; + +/** + * This listener serves as a protection class. Blocks within + * the arena region cannot be destroyed, and blocks can only + * be placed by a participant in the current arena session. + * Any placed blocks will be removed by the cleanup method in + * ArenaManager when the session ends. + */ +public class MABlockListener extends BlockListener +{ + private MobArena plugin; + + public MABlockListener(MobArena instance) + { + plugin = instance; + } + + public void onBlockDamage(BlockDamageEvent event) + { + if (!ArenaManager.isSetup) + return; + + if (MAUtils.inRegion(event.getBlock().getLocation())) + event.setCancelled(true); + } + + public void onBlockBreak(BlockBreakEvent event) + { + if (!ArenaManager.isSetup) + return; + + if (MAUtils.inRegion(event.getBlock().getLocation())) + event.setCancelled(true); + } + + public void onBlockPlace(BlockPlaceEvent event) + { + if (!ArenaManager.isSetup) + return; + + if (MAUtils.inRegion(event.getBlock().getLocation())) + { + if (ArenaManager.isRunning && ArenaManager.playerSet.contains(event.getPlayer())) + { + ArenaManager.blockSet.add(event.getBlock()); + return; + } + + event.setCancelled(true); + } + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MACommands.java b/src/com/garbagemule/MobArena/MACommands.java new file mode 100644 index 0000000..3661d98 --- /dev/null +++ b/src/com/garbagemule/MobArena/MACommands.java @@ -0,0 +1,175 @@ +package com.garbagemule.MobArena; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.CommandExecutor; + +public class MACommands implements CommandExecutor +{ + /** + * Handles all command parsing. + * Unrecognized commands return false, giving the sender a list of + * valid commands (from plugin.yml). + */ + public boolean onCommand(CommandSender sender, Command command, String commandLabel, String[] args) + { + // Only accept commands from players. + if ((sender == null) || !(sender instanceof Player)) + { + System.out.println("Only players can use these commands, silly."); + return true; + } + + // Cast the sender to a Player object. + Player p = (Player) sender; + + /* If more than one argument, must be an advanced command. + * Only allow operators to access these commands. */ + if (args.length > 1) + { + if (p.isOp()) + return advancedCommands(p, args); + + ArenaManager.tellPlayer(p, "Must be operator for advanced commands."); + return true; + } + + // If not exactly one argument, must be an invalid command. + if (args.length != 1) + return false; + + // Exactly one argument, return whatever simpleCommands returns. + return basicCommands(p, args[0].toLowerCase()); + } + + /** + * Handles basic commands. + */ + private boolean basicCommands(Player p, String cmd) + { + if (cmd.equals("join") || cmd.equals("j")) + { + ArenaManager.playerJoin(p); + return true; + } + + if (cmd.equalsIgnoreCase("leave") || cmd.equalsIgnoreCase("l")) + { + ArenaManager.playerLeave(p); + return true; + } + + if (cmd.equalsIgnoreCase("list") || cmd.equalsIgnoreCase("who")) + { + ArenaManager.playerList(p); + return true; + } + + if (cmd.equalsIgnoreCase("spectate") || cmd.equalsIgnoreCase("spec")) + { + ArenaManager.playerSpectate(p); + return true; + } + + return false; + } + + /** + * Handles advanced commands, mainly for setting up the arena. + */ + private boolean advancedCommands(Player p, String[] args) + { + String cmd = args[0].toLowerCase(); + String arg = args[1].toLowerCase(); + + // ma setwarp arena/lobby/spectator + if (cmd.equals("setwarp")) + { + if (!arg.equals("arena") && !arg.equals("lobby") && !arg.equals("spectator")) + return false; + + // Write the coordinate data to the config-file. + MAUtils.setCoords(arg, p.getLocation()); + + ArenaManager.tellPlayer(p, "Warp point \"" + arg + "\" set."); + return true; + } + + // ma addspawn + if (cmd.equals("addspawn")) + { + // The name must start with a letter, followed by any letter(s) or number(s). + if (!arg.matches("[a-z]+([[0-9][a-z]])*")) + { + ArenaManager.tellPlayer(p, "Name must consist of only letters a-z and numbers 0-9"); + return true; + } + + // Write the coordinate data to the config-file. + MAUtils.setCoords("spawnpoints." + arg, p.getLocation()); + + ArenaManager.tellPlayer(p, "Spawn point with name \"" + arg + "\" added."); + return true; + } + + // ma delspawn + if (cmd.equals("delspawn")) + { + // The name must start with a letter, followed by any letter(s) or number(s). + if (!arg.matches("[a-z]+([[0-9][a-z]])*")) + { + ArenaManager.tellPlayer(p, "Name must consist of only letters a-z and numbers 0-9"); + return true; + } + + // If the spawnpoint does not exist, notify the player. + if (MAUtils.getCoords("spawnpoints." + arg) == null) + { + ArenaManager.tellPlayer(p, "Couldn't find spawnpoint \"" + arg + "\"."); + return true; + } + + MAUtils.delCoords("spawnpoints." + arg); + + ArenaManager.tellPlayer(p, "Spawn point with name \"" + arg + "\" removed."); + return true; + } + + // ma setregionpoint p1/p2 + if (cmd.equalsIgnoreCase("setregion")) + { + if (arg.equals("p1") || arg.equals("p2")) + { + MAUtils.setCoords(arg, p.getLocation()); + MAUtils.fixCoords(); + + ArenaManager.tellPlayer(p, "Region point \"" + arg + "\" set."); + return true; + } + ArenaManager.tellPlayer(p, "/ma setregionpoint p1, or /ma setregionpoint p2"); + return true; + } + + // ma expandregion up/down/out + if (cmd.equalsIgnoreCase("expandregion")) + { + if (arg.equals("up") || arg.equals("down") || arg.equals("out")) + { + if (args.length != 3 || !args[2].matches("[0-9]+")) + return false; + + int i = Integer.parseInt(args[2]); + MAUtils.expandRegion(arg, i); + + ArenaManager.tellPlayer(p, "Region expanded " + arg + " by " + i + " blocks."); + return true; + } + ArenaManager.tellPlayer(p, "Region point \"" + arg + "\" set."); + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MACreeperListener.java b/src/com/garbagemule/MobArena/MACreeperListener.java new file mode 100644 index 0000000..369c213 --- /dev/null +++ b/src/com/garbagemule/MobArena/MACreeperListener.java @@ -0,0 +1,24 @@ +package com.garbagemule.MobArena; + +import org.bukkit.event.entity.EntityListener; +import org.bukkit.event.entity.EntityExplodeEvent; + +/** + * Very simple listener for preventing Creeper explosions from + * damaging the blocks of the arena. + */ +public class MACreeperListener extends EntityListener +{ + private MobArena plugin; + + public MACreeperListener(MobArena instance) + { + plugin = instance; + } + + public void onEntityExplode(EntityExplodeEvent event) + { + if (MAUtils.inRegion(event.getLocation())) + event.setCancelled(true); + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MADamageListener.java b/src/com/garbagemule/MobArena/MADamageListener.java new file mode 100644 index 0000000..06e17da --- /dev/null +++ b/src/com/garbagemule/MobArena/MADamageListener.java @@ -0,0 +1,44 @@ +package com.garbagemule.MobArena; + +import org.bukkit.entity.Player; +import org.bukkit.event.entity.EntityListener; +import org.bukkit.event.entity.EntityDamageEvent; + +/** + * This listener acts as a type of death-listener. + * When a player is sufficiently low on health, and the next + * damaging blow will kill them, they are teleported to the + * spectator area, they have their hearts replenished, and all + * their items are stripped from them. + * By the end of the arena session, the rewards are given. + */ +// TO-DO: Perhaps implement TeamFluff's respawn-packet-code. +public class MADamageListener extends EntityListener +{ + private MobArena plugin; + + public MADamageListener(MobArena instance) + { + plugin = instance; + } + + public void onEntityDamage(EntityDamageEvent event) + { + if (!ArenaManager.isRunning) + return; + + if (!(event.getEntity() instanceof Player)) + return; + + Player p = (Player) event.getEntity(); + + if (!ArenaManager.playerSet.contains(p)) + return; + + if (p.getHealth() > event.getDamage()) + return; + + event.setCancelled(true); + ArenaManager.playerDeath(p); + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MADropListener.java b/src/com/garbagemule/MobArena/MADropListener.java new file mode 100644 index 0000000..0fbb69b --- /dev/null +++ b/src/com/garbagemule/MobArena/MADropListener.java @@ -0,0 +1,34 @@ +package com.garbagemule.MobArena; + +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerListener; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerBucketEmptyEvent; + +/** + * This listener prevents players from sharing class-specific + * items (read: cheating) before the arena session starts. + */ +// TO-DO: Merge with MASignListener and MAReadyListener into MALobbyListener +public class MADropListener extends PlayerListener +{ + private MobArena plugin; + + public MADropListener(MobArena instance) + { + plugin = instance; + } + + public void onPlayerDropItem(PlayerDropItemEvent event) + { + Player p = event.getPlayer(); + if (ArenaManager.playerSet.contains(p)) + { + if (ArenaManager.isRunning) + return; + + ArenaManager.tellPlayer(p, "No sharing before the arena starts!"); + event.setCancelled(true); + } + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MAReadyListener.java b/src/com/garbagemule/MobArena/MAReadyListener.java new file mode 100644 index 0000000..1ca0f4c --- /dev/null +++ b/src/com/garbagemule/MobArena/MAReadyListener.java @@ -0,0 +1,41 @@ +package com.garbagemule.MobArena; + +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerListener; +import org.bukkit.event.player.PlayerInteractEvent; + +/** + * This listener flags players as ready, if they are in the arena + * session, and hit an iron block (id: 42). + */ +// TO-DO: Merge with MASignListener and MADropListener into MALobbyListener +// TO-DO: Let server host decide which type of block is the "readyblock". +public class MAReadyListener extends PlayerListener +{ + private MobArena plugin; + + public MAReadyListener(MobArena instance) + { + plugin = instance; + } + + public void onPlayerInteract(PlayerInteractEvent event) + { + Player p = event.getPlayer(); + if (ArenaManager.playerSet.contains(p)) + { + if ((event.hasBlock()) && (event.getClickedBlock().getTypeId() == 42)) + { + if (ArenaManager.classMap.containsKey(p)) + { + ArenaManager.tellPlayer(p, "You have been flagged as ready!"); + ArenaManager.playerReady(p); + } + else + { + ArenaManager.tellPlayer(p, "You must first pick a class!"); + } + } + } + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MASignListener.java b/src/com/garbagemule/MobArena/MASignListener.java new file mode 100644 index 0000000..516a8a0 --- /dev/null +++ b/src/com/garbagemule/MobArena/MASignListener.java @@ -0,0 +1,62 @@ +package com.garbagemule.MobArena; + +import org.bukkit.block.Sign; +import org.bukkit.event.Event.Result; +import org.bukkit.event.block.Action; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerListener; +import org.bukkit.event.player.PlayerInteractEvent; + +/** + * This listener handles the class assignments. When a player in + * the arena session hits a class sign, they will be given their + * class-specific items. + */ +// TO-DO: Merge with MAReadyListener and MADropListener into MALobbyListener +public class MASignListener extends PlayerListener +{ + private MobArena plugin; + + public MASignListener(MobArena instance) + { + plugin = instance; + } + + public void onPlayerInteract(PlayerInteractEvent event) + { + // Only do these checks if the arena isn't running. + if (!ArenaManager.isRunning) + { + Action a = event.getAction(); + Player p = event.getPlayer(); + + // Check if player is trying to use an item. + // TO-DO: Find a way to allow right-click. Perhaps just remove the return; + if ((a == Action.RIGHT_CLICK_AIR) || (a == Action.RIGHT_CLICK_BLOCK)) + { + if (ArenaManager.playerSet.contains(p)) + event.setUseItemInHand(Result.DENY); + return; + } + + // Check if the clicked block is one of the class signs. + if ((event.hasBlock()) && (event.getClickedBlock().getState() instanceof Sign)) + { + // Cast the block to a sign to get the text on it. + Sign sign = (Sign) event.getClickedBlock().getState(); + + if (ArenaManager.playerSet.contains(p)) + { + // Check if the first line of the sign is a class name. + String className = sign.getLine(0); + if (ArenaManager.classes.contains(className)) + { + // Set the player's class. + ArenaManager.assignClass(p, className); + ArenaManager.tellPlayer(p, "You have chosen " + className + " as your class!"); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MASpawnThread.java b/src/com/garbagemule/MobArena/MASpawnThread.java new file mode 100644 index 0000000..c587522 --- /dev/null +++ b/src/com/garbagemule/MobArena/MASpawnThread.java @@ -0,0 +1,181 @@ +package com.garbagemule.MobArena; + +import java.util.List; +import java.util.Random; +import org.bukkit.Location; +import org.bukkit.entity.Slime; +import org.bukkit.entity.Player; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.CreatureType; + +/** + * Core class for handling wave spawning. + * Currently, every 4th wave is a special wave, and all other waves + * are default waves. The distribution coefficients are used to spread + * out the distribution of each default monster however the server + * host chooses. It is possible to create default waves that consist of + * only one type of monster, or ones that have no creepers, for example. + */ +// TO-DO: Allow custom special wave modulus. +// TO-DO: Allow custom special wave monsters. +// TO-DO: Allow additional "default" waves. +// TO-DO: +public class MASpawnThread implements Runnable +{ + private int wave, noOfSpawnPoints, noOfPlayers; + private int dZombies, dSkeletons, dSpiders, dCreepers; + private Random random; + + public MASpawnThread() + { + noOfSpawnPoints = ArenaManager.spawnpoints.size(); + noOfPlayers = ArenaManager.playerSet.size(); + wave = 1; + random = new Random(); + + // Set up the distribution variables for the random spawner. + dZombies = ArenaManager.dZombies; + dSkeletons = dZombies + ArenaManager.dSkeletons; + dSpiders = dSkeletons + ArenaManager.dSpiders; + dCreepers = dSpiders + ArenaManager.dCreepers; + } + + public void run() + { + String reward; + String currentRewards; + + // Check if we need to grant more rewards with the recurrent waves. + for (Integer i : ArenaManager.everyWaveMap.keySet()) + { + if (wave % i != 0) + continue; + + for (Player p : ArenaManager.playerSet) + { + currentRewards = ArenaManager.rewardMap.get(p); + reward = MAUtils.getRandomReward(ArenaManager.everyWaveMap.get(i)); + currentRewards += reward + ","; + ArenaManager.rewardMap.put(p, currentRewards); + ArenaManager.tellPlayer(p, "You just earned a reward: " + reward); + } + } + + // Same deal, this time with the one-time waves. + if (ArenaManager.afterWaveMap.containsKey(wave)) + { + for (Player p : ArenaManager.playerSet) + { + currentRewards = ArenaManager.rewardMap.get(p); + reward = MAUtils.getRandomReward(ArenaManager.afterWaveMap.get(wave)); + currentRewards += reward + ","; + ArenaManager.rewardMap.put(p, currentRewards); + ArenaManager.tellPlayer(p, "You just earned a reward: " + reward); + } + } + + // Check if this is a special wave. + // TO-DO: Get this value from the config-file. + if (wave % 4 == 0) + { + ArenaManager.tellAll("Get ready for wave #" + wave + "! [SPECIAL]"); + specialWave(); + } + else + { + ArenaManager.tellAll("Get ready for wave #" + wave + "!"); + defaultWave(); + } + + wave++; + } + + /** + * Spawns a default wave of monsters. + */ + private void defaultWave() + { + Location loc; + int ran; + + for (int i = 0; i < wave+noOfPlayers; i++) + { + loc = ArenaManager.spawnpoints.get(random.nextInt(noOfSpawnPoints)); + ran = random.nextInt(dCreepers); + CreatureType mob; + + /* Because of the nature of the if-elseif-else statement, + * we're able to evaluate the random number in this way. + * If dSpiders = 0, then dSpiders = dSkeletons, which + * means if the random number is below that value, we will + * spawn a skeleton and break out of the statement. */ + if (ran < dZombies) mob = CreatureType.ZOMBIE; + else if (ran < dSkeletons) mob = CreatureType.SKELETON; + else if (ran < dSpiders) mob = CreatureType.SPIDER; + else if (ran < dCreepers) mob = CreatureType.CREEPER; + else continue; + + LivingEntity e = ArenaManager.world.spawnCreature(loc,mob); + ArenaManager.monsterSet.add(e); + } + } + + /** + * Spawns a special wave of monsters. + */ + private void specialWave() + { + Location loc; + CreatureType mob; + + int count; + boolean lightning = false; + boolean slime = false; + + switch (random.nextInt(4)) + { + case 0: + mob = CreatureType.CREEPER; + count = noOfPlayers * 3; + lightning = true; + break; + case 1: + mob = CreatureType.PIG_ZOMBIE; + count = noOfPlayers * 2; + break; + case 2: + mob = CreatureType.SLIME; + count = noOfPlayers * 5; + slime = true; + break; + case 3: + mob = CreatureType.MONSTER; + count = Math.max(2, noOfPlayers); + break; + case 4: + mob = CreatureType.GHAST; + count = Math.max(1, noOfPlayers - 2); + break; + default: + mob = CreatureType.CHICKEN; + count = 50; + break; + } + + for (int i = 0; i < count; i++) + { + loc = ArenaManager.spawnpoints.get(random.nextInt(noOfSpawnPoints)); + + LivingEntity e = ArenaManager.world.spawnCreature(loc,mob); + if (lightning) + { + ArenaManager.world.strikeLightningEffect(loc); + e.setFireTicks(0); + e.setHealth(20); + } + if (slime) ((Slime)e).setSize(2); + + ArenaManager.monsterSet.add(e); + } + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MATeleportListener.java b/src/com/garbagemule/MobArena/MATeleportListener.java new file mode 100644 index 0000000..bb388b2 --- /dev/null +++ b/src/com/garbagemule/MobArena/MATeleportListener.java @@ -0,0 +1,40 @@ +package com.garbagemule.MobArena; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerListener; +import org.bukkit.event.player.PlayerTeleportEvent; + +/** + * This listener prevents players from warping out of the arena, if + * they are in the arena session. + */ +// TO-DO: Fix the bug that causes the message when people get stuck in walls. +public class MATeleportListener extends PlayerListener +{ + private MobArena plugin; + + public MATeleportListener(MobArena instance) + { + plugin = instance; + } + + public void onPlayerTeleport(PlayerTeleportEvent event) + { + Player p = event.getPlayer(); + if (ArenaManager.playerSet.contains(p)) + { + Location to = event.getTo(); + + if (ArenaManager.arenaLoc.equals(to) || + ArenaManager.lobbyLoc.equals(to) || + ArenaManager.spectatorLoc.equals(to)) + { + return; + } + + ArenaManager.tellPlayer(p, "Can't warp in arena! To leave, type /marena leave"); + event.setCancelled(true); + } + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MAUtils.java b/src/com/garbagemule/MobArena/MAUtils.java new file mode 100644 index 0000000..4bb2a05 --- /dev/null +++ b/src/com/garbagemule/MobArena/MAUtils.java @@ -0,0 +1,486 @@ +package com.garbagemule.MobArena; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.List; +import java.util.LinkedList; +import java.util.Map; +import java.util.HashMap; +import java.util.Random; +import org.bukkit.World; +import org.bukkit.Material; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.util.config.Configuration; + +public class MAUtils +{ + /* ///////////////////////////////////////////////////////////////////// // + + INVENTORY AND REWARD METHODS + + // ///////////////////////////////////////////////////////////////////// */ + + /* Clears the players inventory and armor slots. */ + public static PlayerInventory clearInventory(Player p) + { + PlayerInventory inv = p.getInventory(); + inv.clear(); + inv.setHelmet(null); + inv.setChestplate(null); + inv.setLeggings(null); + inv.setBoots(null); + return inv; + } + + /* Checks if all inventory and armor slots are empty. */ + public static boolean hasEmptyInventory(Player player) + { + ItemStack[] inventory = player.getInventory().getContents(); + ItemStack[] armor = player.getInventory().getArmorContents(); + + // For inventory, check for null + for (ItemStack stack : inventory) + if (stack != null) return false; + + // For armor, check for id 0, or AIR + for (ItemStack stack : armor) + if (stack.getTypeId() != 0) return false; + + return true; + } + + /* Gives all the items in the input string(s) to the player */ + public static void giveItems(Player p, String... strings) + { + // Variables used. + ItemStack stack; + int index, id, amount; + + PlayerInventory inv = clearInventory(p); + + for (String s : strings) + { + /* Trim the list, remove possible trailing commas, split by + * commas, and start the item loop. */ + s = s.trim(); + if (s.endsWith(",")) + s = s.substring(0, s.length()-1); + String[] items = s.split(","); + + // For every item in the list + for (String i : items) + { + /* Take into account possible amount, and if there is + * one, set the amount variable to that amount, else 1. */ + i = i.trim(); + String[] item = i.split(":"); + if (item.length == 2 && item[1].matches("[0-9]+")) + amount = Integer.parseInt(item[1]); + else + amount = 1; + + // Create ItemStack with appropriate constructor. + if (item[0].matches("[0-9]+")) + { + id = Integer.parseInt(item[0]); + stack = new ItemStack(id, amount); + } + else + { + stack = makeItemStack(item[0], amount); + if (stack == null) continue; + } + + // Put the item in the first empty inventory slot. + index = inv.firstEmpty(); + inv.setItem(index,stack); + } + } + } + + /* Helper method for grabbing a random reward */ + public static String getRandomReward(String rewardlist) + { + Random ran = new Random(); + + String[] rewards = rewardlist.split(","); + String item = rewards[ran.nextInt(rewards.length)]; + return item.trim(); + } + + /* Helper method for making an ItemStack out of a string */ + private static ItemStack makeItemStack(String s, int amount) + { + Material mat; + try + { + mat = Material.valueOf(s.toUpperCase()); + return new ItemStack(mat, amount); + } + catch (Exception e) + { + System.out.println("[MobArena] ERROR! Could not create item " + s + ". Check config.yml"); + return null; + } + } + + + + /* ///////////////////////////////////////////////////////////////////// // + + INITIALIZATION METHODS + + // ///////////////////////////////////////////////////////////////////// */ + + /** + * Creates a Configuration object from the config.yml file. + */ + public static Configuration getConfig() + { + new File("plugins/MobArena").mkdir(); + File configFile = new File("plugins/MobArena/config.yml"); + + if(!configFile.exists()) + { + try + { + configFile.createNewFile(); + } + catch(Exception e) + { + System.out.println("[MobArena] ERROR: Config file could not be created."); + return null; + } + } + + return new Configuration(configFile); + } + + /** + * Grabs the world from the config-file, or the "default" world + * from the list of worlds in the server object. + */ + public static World getWorld() + { + Configuration c = ArenaManager.config; + c.load(); + + String world = c.getString("world"); + + if (world == null) + return ArenaManager.server.getWorlds().get(0); + + return ArenaManager.server.getWorld(world); + } + + /** + * Grabs the list of classes from the config-file. If no list is + * found, generate a set of default classes. + */ + public static List getClasses() + { + Configuration c = ArenaManager.config; + c.load(); + + if (c.getKeys("classes") == null) + { + c.setProperty("classes.Archer.items", "wood_sword, bow, arrow:64, arrow:64, grilled_pork"); + c.setProperty("classes.Archer.armor", "298,299,300,301"); + c.setProperty("classes.Knight.items", "diamond_sword, grilled_pork"); + c.setProperty("classes.Knight.armor", "306,307,308,309"); + c.setProperty("classes.Tank.items", "iron_sword, grilled_pork:2"); + c.setProperty("classes.Tank.armor", "310,311,312,313"); + c.setProperty("classes.Oddjob.items", "stone_sword, flint_and_steal, netherrack:2, wood_door, fishing_rod, apple, grilled_pork:3"); + c.setProperty("classes.Oddjob.armor", "298,299,300,301"); + c.setProperty("classes.Chef.items", "stone_sword, bread:6, grilled_pork:4, mushroom_soup, cake:3, cookie:12"); + c.setProperty("classes.Chef.armor", "314,315,316,317"); + + c.save(); + } + + return c.getKeys("classes"); + } + + /** + * Generates a map of class names and class items based on the + * type of items ("items" or "armor") and the config-file. + * Will explode if the classes aren't well-defined. + */ + public static Map getClassItems(String type) + { + Configuration c = ArenaManager.config; + c.load(); + + Map result = new HashMap(); + + // Assuming well-defined classes. + List classes = c.getKeys("classes"); + for (String s : classes) + { + result.put(s, c.getString("classes." + s + "." + type, null)); + } + + return result; + } + + /** + * Generates a map of wave numbers and rewards based on the + * type of wave ("after" or "every") and the config-file. If + * no keys exist in the config-file, an empty map is returned. + */ + public static Map getWaveMap(String type) + { + Configuration c = ArenaManager.config; + c.load(); + + // Set up variables and resulting map. + Map result = new HashMap(); + int wave; + String rewards; + + /* Check if the keys exist in the config-file, if not, + * simply return the empty map. */ + List waves = c.getKeys("rewards.waves." + type); + if (waves == null) + return result; + + // Else, put all the rewards in the map. + for (String n : waves) + { + if (!n.matches("[0-9]+")) + continue; + + wave = Integer.parseInt(n); + rewards = c.getString("rewards.waves." + type + "." + n); + + result.put(wave,rewards); + } + + // And return the resulting map. + return result; + } + + /** + * Grabs all the spawnpoints from the config-file. IF no points + * are found, an empty list is returned. + */ + public static List getSpawnPoints() + { + Configuration c = ArenaManager.config; + c.load(); + + List spawnpoints = c.getKeys("coords.spawnpoints"); + if (spawnpoints == null) + return new LinkedList(); + + List result = new LinkedList(); + for (String s : spawnpoints) + { + Location loc = getCoords("spawnpoints." + s); + + if (loc != null) + result.add(loc); + } + + return result; + } + + /** + * Grabs the distribution coefficients from the config-file. If + * no coefficients are found, defaults (10) are added. + */ + public static int getDistribution(String monster) + { + Configuration c = ArenaManager.config; + c.load(); + + if (c.getInt("waves.default." + monster, -1) == -1) + { + c.setProperty("waves.default." + monster, 10); + c.save(); + } + + return c.getInt("waves.default." + monster, -1); + } + + + + /* ///////////////////////////////////////////////////////////////////// // + + REGION AND SETUP METHODS + + // ///////////////////////////////////////////////////////////////////// */ + + /** + * Checks if the Location object is within the arena region. + */ + public static boolean inRegion(Location loc) + { + Location p1 = ArenaManager.p1; + Location p2 = ArenaManager.p2; + + // Return false if the location is outside of the region. + if ((loc.getX() < p1.getX()) || (loc.getX() > p2.getX())) + return false; + + if ((loc.getZ() < p1.getZ()) || (loc.getZ() > p2.getZ())) + return false; + + if ((loc.getY() < p1.getY()) || (loc.getY() > p2.getY())) + return false; + + return true; + } + + /** + * Writes coordinate information to the config-file. + */ + public static void setCoords(String name, Location loc) + { + Configuration c = ArenaManager.config; + c.load(); + + c.setProperty("coords." + name + ".world", loc.getWorld().getName()); + c.setProperty("coords." + name + ".x", loc.getX()); + c.setProperty("coords." + name + ".y", loc.getY()); + c.setProperty("coords." + name + ".z", loc.getZ()); + c.setProperty("coords." + name + ".yaw", loc.getYaw()); + c.setProperty("coords." + name + ".pitch", loc.getPitch()); + + c.save(); + ArenaManager.updateVariables(); + } + + /** + * Removes coordinate information from the config-file. + */ + public static void delCoords(String name) + { + Configuration c = ArenaManager.config; + c.load(); + + c.removeProperty(name); + + c.save(); + ArenaManager.updateVariables(); + } + + /** + * Grabs coordinate information from the config-file. + */ + public static Location getCoords(String name) + { + Configuration c = ArenaManager.config; + c.load(); + + // Return null if coords aren't in the config file. + if (c.getKeys("coords." + name) == null) + return null; + + double x = c.getDouble("coords." + name + ".x", 0); + double y = c.getDouble("coords." + name + ".y", 0); + double z = c.getDouble("coords." + name + ".z", 0); + + return new Location(ArenaManager.world, x, y, z); + } + + /** + * Maintains the invariant that p1's coordinates are of lower + * values than their respective counter-parts of p2. Makes the + * inRegion()-method much faster/easier. + */ + public static void fixCoords() + { + Location p1 = getCoords("p1"); + Location p2 = getCoords("p2"); + double tmp; + + if (p1 == null || p2 == null) + return; + + if (p1.getX() > p2.getX()) + { + tmp = p1.getX(); + p1.setX(p2.getX()); + p2.setX(tmp); + } + + if (p1.getY() > p2.getY()) + { + tmp = p1.getY(); + p1.setY(p2.getY()); + p2.setY(tmp); + } + + if (p1.getZ() > p2.getZ()) + { + tmp = p1.getZ(); + p1.setZ(p2.getZ()); + p2.setZ(tmp); + } + + setCoords("p1", p1); + setCoords("p2", p2); + } + + /** + * Expands the arena region either upwards, downwards, or + * outwards (meaning on both the X and Z axes). + */ + public static void expandRegion(String direction, int i) + { + Location p1 = ArenaManager.p1; + Location p2 = ArenaManager.p2; + + if (direction.equals("up")) + p2.setY(p2.getY() + i); + else if (direction.equals("down")) + p1.setY(p1.getY() - i); + else if (direction.equals("out")) + { + p1.setX(p1.getX() - i); + p1.setZ(p1.getZ() - i); + p2.setX(p2.getX() + i); + p2.setZ(p2.getZ() + i); + } + + setCoords("p1", p1); + setCoords("p2", p2); + fixCoords(); + } + + + + /* ///////////////////////////////////////////////////////////////////// // + + VERIFICATION METHODS + + // ///////////////////////////////////////////////////////////////////// */ + + /** + * Verifies that all important variables are declared. Returns true + * if, and only if, the warppoints, region, distribution coefficients, + * classes and spawnpoints are all set up. + */ + public static boolean verifyData() + { + return ((ArenaManager.arenaLoc != null) && + (ArenaManager.lobbyLoc != null) && + (ArenaManager.spectatorLoc != null) && + (ArenaManager.p1 != null) && + (ArenaManager.p2 != null) && + (ArenaManager.dZombies != -1) && + (ArenaManager.dSkeletons != -1) && + (ArenaManager.dSpiders != -1) && + (ArenaManager.dCreepers != -1) && + (ArenaManager.classes.size() > 0) && + (ArenaManager.spawnpoints.size() > 0)); + + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MobArena.java b/src/com/garbagemule/MobArena/MobArena.java new file mode 100644 index 0000000..6fe8b63 --- /dev/null +++ b/src/com/garbagemule/MobArena/MobArena.java @@ -0,0 +1,65 @@ +package com.garbagemule.MobArena; + +import org.bukkit.event.Event; +import org.bukkit.event.Event.Priority; +import org.bukkit.event.block.BlockListener; +import org.bukkit.event.player.PlayerListener; +import org.bukkit.event.entity.EntityListener; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.PluginManager; + +/** + * MobArena + * + * @author garbagemule + */ +public class MobArena extends JavaPlugin +{ + public MobArena() + { + } + + public void onEnable() + { + PluginDescriptionFile pdfFile = this.getDescription(); + System.out.println(pdfFile.getName() + " v" + pdfFile.getVersion() + " enabled." ); + + // Initialize convenience variables in ArenaManager. + ArenaManager.init(this); + + // Bind the /ma and /marena commands to MACommands. + getCommand("ma").setExecutor(new MACommands()); + getCommand("marena").setExecutor(new MACommands()); + + // Create event listeners. + PluginManager pm = getServer().getPluginManager(); + PlayerListener signListener = new MASignListener(this); + PlayerListener dropListener = new MADropListener(this); + PlayerListener readyListener = new MAReadyListener(this); + PlayerListener teleportListener = new MATeleportListener(this); + BlockListener blockListener = new MABlockListener(this); + EntityListener creeperListener = new MACreeperListener(this); + EntityListener damageListener = new MADamageListener(this); + // TO-DO: PlayerListener to check for player logout during battle. + // TO-DO: PlayerListener to check for kills/deaths. + + // Register events. + pm.registerEvent(Event.Type.PLAYER_INTERACT, signListener, Priority.Normal, this); + pm.registerEvent(Event.Type.PLAYER_DROP_ITEM, dropListener, Priority.Normal, this); + pm.registerEvent(Event.Type.PLAYER_INTERACT, readyListener, Priority.Normal, this); + pm.registerEvent(Event.Type.PLAYER_TELEPORT, teleportListener, Priority.Normal, this); + pm.registerEvent(Event.Type.BLOCK_BREAK, blockListener, Priority.Normal, this); + pm.registerEvent(Event.Type.BLOCK_DAMAGE, blockListener, Priority.Normal, this); + pm.registerEvent(Event.Type.BLOCK_PLACE, blockListener, Priority.Normal, this); + pm.registerEvent(Event.Type.ENTITY_EXPLODE, creeperListener, Priority.Normal, this); + pm.registerEvent(Event.Type.ENTITY_DAMAGE, damageListener, Priority.Normal, this); + + System.out.println(pdfFile.getName() + " v" + pdfFile.getVersion() + " initialized." ); + } + + public void onDisable() + { + System.out.println("WAIT! WHAT ARE YOU DOING?!"); + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/b.jar b/src/com/garbagemule/MobArena/b.jar new file mode 100644 index 0000000..dbf7ed5 Binary files /dev/null and b/src/com/garbagemule/MobArena/b.jar differ diff --git a/src/com/garbagemule/MobArena/c.jar b/src/com/garbagemule/MobArena/c.jar new file mode 100644 index 0000000..bee8c11 Binary files /dev/null and b/src/com/garbagemule/MobArena/c.jar differ