From 110db86ab9f6caa304dd3ef738440e97cb0bae4b Mon Sep 17 00:00:00 2001 From: Garbage Mule Date: Sun, 3 Jul 2011 12:21:34 +0200 Subject: [PATCH] v0.92 - First commit to dev branch! --- MobArena.jar | Bin 45208 -> 80337 bytes bin/plugin.yml | 11 +- src/com/garbagemule/MobArena/Arena.java | 1135 ++++++++++++++ src/com/garbagemule/MobArena/ArenaMaster.java | 334 ++++ src/com/garbagemule/MobArena/Lobby.java | 26 + .../garbagemule/MobArena/MABlockListener.java | 62 +- src/com/garbagemule/MobArena/MACommands.java | 1080 ++++++++++--- .../MobArena/MAEntityListener.java | 65 + .../garbagemule/MobArena/MAInventoryItem.java | 22 + src/com/garbagemule/MobArena/MAMessages.java | 232 +++ .../MobArena/MAPlayerListener.java | 87 ++ .../garbagemule/MobArena/MASpawnThread.java | 233 +-- src/com/garbagemule/MobArena/MAUtils.java | 1385 ++++++++++------- src/com/garbagemule/MobArena/MobArena.java | 240 ++- .../garbagemule/MobArena/MobArenaHandler.java | 113 +- .../MobArena/MobArenaListener.java | 7 +- 16 files changed, 4001 insertions(+), 1031 deletions(-) create mode 100644 src/com/garbagemule/MobArena/Arena.java create mode 100644 src/com/garbagemule/MobArena/ArenaMaster.java create mode 100644 src/com/garbagemule/MobArena/Lobby.java create mode 100644 src/com/garbagemule/MobArena/MAEntityListener.java create mode 100644 src/com/garbagemule/MobArena/MAInventoryItem.java create mode 100644 src/com/garbagemule/MobArena/MAMessages.java create mode 100644 src/com/garbagemule/MobArena/MAPlayerListener.java diff --git a/MobArena.jar b/MobArena.jar index 6a73e4bb310cb67318330b51151ad40724dd9286..ed7f409b0102ba17c619cb882cb989a04b65f616 100644 GIT binary patch delta 77414 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 delta 42564 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

nKMy7$a0Peod(EWiA}kG^pGcJ;Ag<%RqFi(@%#^ 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 spawnpoints; + + // Wave/reward fields + protected int spawnTaskId, waveDelay, waveInterval, specialModulo, spawnMonstersInt, maxIdleTime; + protected MASpawnThread spawnThread; + protected Map> everyWaveMap, afterWaveMap; + protected Map distDefault, distSpecial; + protected Map classMap; + protected Map> classItems, classArmor; + protected Map> rewardMap; + + // Arena sets/maps + protected Set livePlayers, deadPlayers, readyPlayers, specPlayers; + protected Set monsters; + protected Set blocks; + protected Set pets; + protected Map petMap; + protected Map kills; + protected List repairList; + + // Spawn overriding + protected int spawnMonsters; + protected boolean allowMonsters, allowAnimals; + + // Global settings + protected int repairDelay; + protected List classes = new LinkedList(); + protected Map locations = new HashMap(); + + /** + * Primary constructor. Requires a name and a world. + */ + public Arena(String name, World world) + { + this.name = name; + this.world = world; + plugin = (MobArena) Bukkit.getServer().getPluginManager().getPlugin("MobArena"); + + livePlayers = new HashSet(); + deadPlayers = new HashSet(); + readyPlayers = new HashSet(); + specPlayers = new HashSet(); + monsters = new HashSet(); + blocks = new HashSet(); + pets = new HashSet(); + petMap = new HashMap(); + classMap = new HashMap(); + rewardMap = new HashMap>(); + kills = new HashMap(); + repairList = new LinkedList(); + + running = false; + edit = false; + + allowMonsters = world.getAllowMonsters(); + allowAnimals = world.getAllowAnimals(); + spawnMonsters = ((net.minecraft.server.World) ((CraftWorld) world).getHandle()).spawnMonsters; + } + + public Arena(String name, World world, ArenaMaster am) + { + this(name, world); + //classItems = am.classItems; + //classArmor = am.classArmor; + } + + public void startArena() + { + if (running) + return; + + if (!softRestore && forceRestore && !serializeRegion()) + return; + + // Set the spawn flags to enable monster spawning. + //MAUtils.setSpawnFlags(plugin, world, 1, true, true); + MAUtils.setSpawnFlags(plugin, world, 1, allowMonsters, allowAnimals); + + // Teleport players. + for (Player p : livePlayers) + { + p.teleport(arenaLoc); + p.setHealth(20); + rewardMap.put(p, new LinkedList()); + } + + running = true; + + // Spawn pets. + for (Map.Entry entry : petMap.entrySet()) + { + Player p = entry.getKey(); + for (int i = 0; i < entry.getValue(); i++) + { + Wolf wolf = (Wolf) world.spawnCreature(p.getLocation(), CreatureType.WOLF); + wolf.setTamed(true); + wolf.setOwner(p); + wolf.setHealth(20); + if (hellhounds) + wolf.setFireTicks(32768); + pets.add(wolf); + } + } + + // Start the spawnThread. + spawnThread = new MASpawnThread(this); + spawnTaskId = Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, spawnThread, waveDelay, waveInterval); + + readyPlayers.clear(); + MAUtils.tellAll(this, MAMessages.get(Msg.ARENA_START)); + } + + /** + * End this arena's session. + */ + public void endArena() + { + running = false; + + // If the arena was actually ever started, cancel the spawnthread. + if (spawnThread != null) + { + Bukkit.getServer().getScheduler().cancelTask(spawnThread.taskId); + Bukkit.getServer().getScheduler().cancelTask(spawnTaskId); + } + + if (!emptyInv) + for (Player p : deadPlayers) + MAUtils.restoreInventory(p); + + // Clean up the arena floor and give rewards + cleanup(); + giveRewards(); + + // Clear all the sets and maps. + livePlayers.clear(); + deadPlayers.clear(); + //specPlayers.clear(); + pets.clear(); + classMap.clear(); + rewardMap.clear(); + + if (softRestore) + for (int[] buffer : repairList) + world.getBlockAt(buffer[0], buffer[1], buffer[2]).setTypeIdAndData(buffer[3], (byte) buffer[4], false); + else if (forceRestore) + deserializeRegion(); + + // Set the spawn flags to restore monster spawning. + MAUtils.setSpawnFlags(plugin, world, spawnMonsters, allowMonsters, allowAnimals); + MAUtils.tellAll(this, MAMessages.get(Msg.ARENA_END)); + } + + /** + * Force an arena start by forcing all not-ready players to leave. + * @precondition - The arena musn't be running, and readyPlayers must not be empty. + */ + public void forceStart() + { + // Set operations. + Set tmp = new HashSet(); + tmp.addAll(livePlayers); + tmp.removeAll(readyPlayers); + + // Force leave. + for (Player p : tmp) + playerLeave(p); + } + + /** + * Force an arena end by forcing all players to leave. + * @precondition - livePlayers must not be empty. + */ + public void forceEnd() + { + // Force leave. + for (Player p : getAllPlayers()) + playerLeave(p); + } + + /** + * Warp the player to the arena lobby and add to the set of live players. + */ + public void playerJoin(Player p, Location loc) + { + if (!locations.containsKey(p)) + locations.put(p,loc); + + MAUtils.sitPets(p); + livePlayers.add(p); + p.teleport(lobbyLoc); + } + + /** + * Add the player to the set of ready players. + * If every is ready, the arena starts. + */ + public void playerReady(Player p) + { + readyPlayers.add(p); + + if (readyPlayers.equals(livePlayers)) + startArena(); + } + + /** + * Remove the player from all the player sets, and clear his inventory if necessary. + * If the set of live players becomes empty, end the arena. + * If the set of ready players becomes equal to the set of live players, start the arena. + */ + public void playerLeave(Player p) + { + boolean clear = false; + + p.teleport(locations.get(p)); + locations.remove(p); // get, then remove, because of Teleport Event + + // Only clear the inventory if the player has class items. + if (readyPlayers.remove(p)) clear = true; + if (livePlayers.remove(p)) clear = true; + if (deadPlayers.remove(p)) clear = false; + if (specPlayers.remove(p)) clear = false; + + if (clear) + MAUtils.clearInventory(p); + MAUtils.restoreInventory(p); + + if (running && livePlayers.isEmpty()) + endArena(); + else if (!readyPlayers.isEmpty() && readyPlayers.equals(livePlayers)) + startArena(); + } + + public void playerQuit(Player p) + { + p.teleport(locations.get(p)); + readyPlayers.remove(p); + livePlayers.remove(p); + deadPlayers.remove(p); + specPlayers.remove(p); + + if (running && livePlayers.isEmpty()) + endArena(); + else if (!readyPlayers.isEmpty() && readyPlayers.equals(livePlayers)) + startArena(); + } + + public void playerDeath(final Player p) + { + p.teleport(spectatorLoc); + p.setFireTicks(0); + p.setHealth(20); + + // Add to the list of dead players. + livePlayers.remove(p); + deadPlayers.add(p); + + // Has to be delayed for TombStone not to fuck shit up. + if (running && livePlayers.isEmpty()) + Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, + new Runnable() + { + public void run() + { + MAUtils.restoreInventory(p); + endArena(); + } + }, 10); + } + + public void playerSpec(Player p, Location loc) + { + if (!locations.containsKey(p)) + locations.put(p,loc); + + MAUtils.sitPets(p); + specPlayers.add(p); + p.teleport(spectatorLoc); + } + + public void playerKill(Player p, LivingEntity e) + { + kills.put(p, kills.get(p) + 1); + } + + + + /*//////////////////////////////////////////////////////////////////// + // + // Items & Cleanup + // + ////////////////////////////////////////////////////////////////////*/ + + public void assignClass(Player p, String className) + { + petMap.remove(p); + classMap.put(p, className); + MAUtils.clearInventory(p); + MAUtils.giveItems(p, classItems.get(className), autoEquip); + MAUtils.giveItems(p, classArmor.get(className), autoEquip); + + int pets = MAUtils.getPetAmount(p); + if (pets > 0) petMap.put(p, pets); + } + + private void giveRewards() + { + for (Map.Entry> entry : rewardMap.entrySet()) + { + MAUtils.tellPlayer(entry.getKey(), MAMessages.get(Msg.REWARDS_GIVE)); + MAUtils.giveRewards(entry.getKey(), entry.getValue()); + } + } + + private void cleanup() + { + removeMonsters(); + removeBlocks(); + removePets(); + removeEntities(); + monsters.clear(); + blocks.clear(); + pets.clear(); + } + + private void removeMonsters() + { + for (LivingEntity e : monsters) + e.remove(); + } + + private void removeBlocks() + { + for (Block b : blocks) + b.setTypeId(0); + } + + private void removePets() + { + for (Wolf w : pets) + w.remove(); + } + + private void removeEntities() + { + Chunk c1 = world.getChunkAt(p1); + Chunk c2 = world.getChunkAt(p2); + + for (int i = c1.getX(); i <= c2.getX(); i++) + for (int j = c1.getZ(); j <= c2.getZ(); j++) + for (Entity e : world.getChunkAt(i,j).getEntities()) + if ((e instanceof Item || e instanceof Slime) && inRegion(e.getLocation())) + e.remove(); + } + + + + /*//////////////////////////////////////////////////////////////////// + // + // Initialization & Checks + // + ////////////////////////////////////////////////////////////////////*/ + + public void load(Configuration config) + { + config.load(); + + String arenaPath = "arenas." + MAUtils.nameArenaToConfig(name) + ".settings."; + String configName = MAUtils.nameArenaToConfig(name); + + enabled = config.getBoolean(arenaPath + "enabled", true); + protect = config.getBoolean(arenaPath + "protect", true); + autoEquip = config.getBoolean(arenaPath + "auto-equip-armor", true); + waveClear = config.getBoolean(arenaPath + "wave-clear", false); + detCreepers = config.getBoolean(arenaPath + "detonate-creepers", false); + detDamage = config.getBoolean(arenaPath + "detonate-damage", false); + lightning = config.getBoolean(arenaPath + "lightning", true); + forceRestore = config.getBoolean(arenaPath + "force-restore", false); + softRestore = config.getBoolean(arenaPath + "soft-restore", false); + emptyInv = config.getBoolean(arenaPath + "require-empty-inventory", false); + hellhounds = config.getBoolean(arenaPath + "hellhounds", false); + pvp = config.getBoolean(arenaPath + "pvp-enabled", false); + monsterInfight = config.getBoolean(arenaPath + "monster-infight", false); + waveDelay = config.getInt(arenaPath + "first-wave-delay", 5) * 20; + waveInterval = config.getInt(arenaPath + "wave-interval", 20) * 20; + specialModulo = config.getInt(arenaPath + "special-modulo", 4); + maxIdleTime = config.getInt(arenaPath + "max-idle-time", 0); + + distDefault = MAUtils.getArenaDistributions(config, configName, "default"); + distSpecial = MAUtils.getArenaDistributions(config, configName, "special"); + everyWaveMap = MAUtils.getArenaRewardMap(config, configName, "every"); + afterWaveMap = MAUtils.getArenaRewardMap(config, configName, "after"); + + p1 = MAUtils.getArenaCoord(config, world, configName, "p1"); + p2 = MAUtils.getArenaCoord(config, world, configName, "p2"); + arenaLoc = MAUtils.getArenaCoord(config, world, configName, "arena"); + lobbyLoc = MAUtils.getArenaCoord(config, world, configName, "lobby"); + spectatorLoc = MAUtils.getArenaCoord(config, world, configName, "spectator"); + spawnpoints = MAUtils.getArenaSpawnpoints(config, world, configName); + + classes = plugin.getAM().classes; + classItems = plugin.getAM().classItems; + classArmor = plugin.getAM().classArmor; + repairDelay = plugin.getAM().repairDelay; + + // Determine if the arena is properly set up. Then add the to arena list. + setup = MAUtils.verifyData(this); + } + + public void serializeConfig() + { + String coords = "arenas." + configName() + ".coords."; + Configuration config = plugin.getConfig(); + + config.setProperty("arenas." + configName() + ".settings.enabled", enabled); + config.setProperty("arenas." + configName() + ".settings.protect", protect); + if (p1 != null) config.setProperty(coords + "p1", MAUtils.makeCoord(p1)); + if (p2 != null) config.setProperty(coords + "p2", MAUtils.makeCoord(p2)); + if (arenaLoc != null) config.setProperty(coords + "arena", MAUtils.makeCoord(arenaLoc)); + if (lobbyLoc != null) config.setProperty(coords + "lobby", MAUtils.makeCoord(lobbyLoc)); + if (spectatorLoc != null) config.setProperty(coords + "spectator", MAUtils.makeCoord(spectatorLoc)); + for (Map.Entry entry : spawnpoints.entrySet()) + config.setProperty(coords + "spawnpoints." + entry.getKey(), MAUtils.makeCoord(entry.getValue())); + + config.save(); + } + + public void deserializeConfig() + { + Configuration config = plugin.getConfig(); + config.load(); + load(config); + } + + public boolean serializeRegion() + { + int x1 = (int) p1.getX(); + int y1 = (int) p1.getY(); + int z1 = (int) p1.getZ(); + int x2 = (int) p2.getX(); + int y2 = (int) p2.getY(); + int z2 = (int) p2.getZ(); + + HashSet set = new HashSet(); + int[] buffer; + for (int i = x1; i <= x2; i++) + { + for (int j = y1; j <= y2; j++) + { + for (int k = z1; k <= z2; k++) + { + buffer = new int[4]; + buffer[0] = i; + buffer[1] = j; + buffer[2] = k; + buffer[3] = world.getBlockAt(i,j,k).getTypeId(); + set.add(buffer); + } + } + } + + try + { + new File(plugin.getDataFolder() + File.separator + "regions").mkdir(); + File regionFile = new File(plugin.getDataFolder() + File.separator + "regions" + File.separator + configName() + ".tmp"); + if (regionFile.exists()) + regionFile.createNewFile(); + + FileOutputStream fos = new FileOutputStream(regionFile); + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(set); + oos.close(); + } + catch (Exception e) + { + System.out.println("[MobArena] ERROR! Could not create region file. The arena will not be started!"); + e.printStackTrace(); + return false; + } + return true; + } + + @SuppressWarnings("unchecked") + public boolean deserializeRegion() + { + HashSet set = new HashSet(); + try + { + File regionFile = new File(plugin.getDataFolder() + File.separator + "regions" + File.separator + configName() + ".tmp"); + if (!regionFile.exists()) + return false; + + FileInputStream fis = new FileInputStream(regionFile); + ObjectInputStream ois = new ObjectInputStream(fis); + set = (HashSet) ois.readObject(); + ois.close(); + } + catch (Exception e) + { + System.out.println("[MobArena] ERROR! Could not find region file. The arena cannot be restored!"); + e.printStackTrace(); + return false; + } + + for (int[] buffer : set) + world.getBlockAt(buffer[0], buffer[1], buffer[2]).setTypeId(buffer[3]); + + return true; + } + + /** + * Check if a location is inside of the cuboid region + * that p1 and p2 span. + */ + public boolean inRegion(Location loc) + { + if (!loc.getWorld().getName().equals(world.getName())) + return false; + + if (!setup) + return false; + + // Returns false if the location is outside of the region. + return ((loc.getX() >= p1.getX() && loc.getX() <= p2.getX()) && + (loc.getZ() >= p1.getZ() && loc.getZ() <= p2.getZ()) && + (loc.getY() >= p1.getY() && loc.getY() <= p2.getY())); + } + + /** + * Check if a location is inside of the arena region, expanded + * by 'radius' blocks. Used with explosions. + */ + public boolean inRegionRadius(Location loc, int radius) + { + if (!loc.getWorld().getName().equals(world.getName()) || !setup) + return false; + + return ((loc.getX() + radius >= p1.getX() && loc.getX() - radius <= p2.getX()) && + (loc.getZ() + radius >= p1.getZ() && loc.getZ() - radius <= p2.getZ()) && + (loc.getY() + radius >= p1.getY() && loc.getY() - radius <= p2.getY())); + } + + + + /*//////////////////////////////////////////////////////////////////// + // + // EventListener methods + // + ////////////////////////////////////////////////////////////////////*/ + + // Block Listener + public void onBlockBreak(BlockBreakEvent event) + { + if (edit || !inRegion(event.getBlock().getLocation())) + return; + + Block b = event.getBlock(); + if (blocks.remove(b) || b.getType() == Material.TNT) + return; + + event.setCancelled(true); + } + + public void onBlockPlace(BlockPlaceEvent event) + { + // If in edit mode or the event didn't happen in this region, return. + if (edit || !inRegion(event.getBlock().getLocation())) + return; + + Block b = event.getBlock(); + if (running && livePlayers.contains(event.getPlayer())) + { + blocks.add(b); + Material mat = b.getType(); + + if (mat == Material.WOODEN_DOOR || mat == Material.IRON_DOOR_BLOCK) + blocks.add(b.getRelative(0,1,0)); + return; + } + + // If the arena isn't running, or if the player isn't in the arena, cancel. + event.setCancelled(true); + } + + // Monster Listener + public void onCreatureSpawn(CreatureSpawnEvent event) + { + if (!inRegion(event.getLocation())) + return; + + // If running == true, setCancelled(false), and vice versa. + event.setCancelled(!running); + } + + public void onEntityExplode(EntityExplodeEvent event) + { + if (!monsters.contains(event.getEntity()) && !inRegionRadius(event.getLocation(), 10)) + return; + + // If the arena isn't running + if (!running || repairDelay == 0) + { + event.setCancelled(true); + return; + } + + // Uncancel, just in case. + event.setCancelled(false); + event.setYield(0); + monsters.remove(event.getEntity()); + + int[] buffer; + final HashMap blockMap = new HashMap(); + for (Block b : event.blockList()) + { + Material mat = b.getType(); + + if (mat == Material.WOODEN_DOOR || mat == Material.IRON_DOOR_BLOCK || mat == Material.FIRE || mat == Material.CAKE_BLOCK || mat == Material.WATER || mat == Material.LAVA) + { + blocks.remove(b); + } + else if (blocks.remove(b)) + { + world.dropItemNaturally(b.getLocation(), new ItemStack(b.getTypeId(), 1)); + } + else if (softRestore) + { + buffer = new int[5]; + buffer[0] = b.getX(); + buffer[1] = b.getY(); + buffer[2] = b.getZ(); + buffer[3] = b.getTypeId(); + buffer[4] = (int) b.getData(); + repairList.add(buffer); + blockMap.put(b, b.getTypeId() + (b.getData() * 1000)); + } + else + { + blockMap.put(b, b.getTypeId() + (b.getData() * 1000)); + } + } + + if (!protect || softRestore) + return; + + Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, + new Runnable() + { + public void run() + { + for (Map.Entry entry : blockMap.entrySet()) + { + Block b = entry.getKey(); + int type = entry.getValue(); + + b.getLocation().getBlock().setTypeId(type % 1000); + + if (type > 1000) + b.getLocation().getBlock().setData((byte) (type / 1000)); + } + } + }, repairDelay); + } + + public void onEntityCombust(EntityCombustEvent event) + { + if (monsters.contains(event.getEntity())) + event.setCancelled(true); + } + + public void onEntityTarget(EntityTargetEvent event) + { + if (!running) return; + + if (pets.contains(event.getEntity())) + { + if (event.getReason() != TargetReason.TARGET_ATTACKED_OWNER && event.getReason() != TargetReason.OWNER_ATTACKED_TARGET) + return; + + if (!(event.getTarget() instanceof Player)) + return; + + // If the target is a player, cancel. + event.setCancelled(true); + return; + } + + if (monsters.contains(event.getEntity())) + { + if (event.getReason() == TargetReason.FORGOT_TARGET) + { + event.setTarget(MAUtils.getClosestPlayer(event.getEntity(), this)); + return; + } + + if (event.getReason() == TargetReason.TARGET_DIED) + { + event.setTarget(MAUtils.getClosestPlayer(event.getEntity(), this)); + return; + } + + if (event.getReason() == TargetReason.CLOSEST_PLAYER) + if (!livePlayers.contains(event.getTarget())) + event.setCancelled(true); + return; + } + } + + // Death Listener + public void onEntityRegainHealth(EntityRegainHealthEvent event) + { + if (!running) return; + + if (!(event.getEntity() instanceof Player) || !livePlayers.contains((Player)event.getEntity())) + return; + + if (event.getRegainReason() == RegainReason.REGEN) + event.setCancelled(true); + } + + public void onEntityDeath(EntityDeathEvent event) + { + if (!running) return; + + if (event.getEntity() instanceof Player) + { + Player p = (Player) event.getEntity(); + + if (!livePlayers.contains(p)) + return; + + event.getDrops().clear(); + playerDeath(p); + p.getInventory().clear(); // For TombStone + return; + } + + if (monsters.remove(event.getEntity())) + { + event.getDrops().clear(); + resetIdleTimer(); + return; + } + } + + public void onEntityDamage(EntityDamageEvent event) + { + if (!running) return; + + EntityDamageByEntityEvent e = (event instanceof EntityDamageByEntityEvent) ? (EntityDamageByEntityEvent) event : null; + Entity damager = (e != null) ? e.getDamager() : null; + Entity damagee = event.getEntity(); + + // Damagee - Pet Wolf - cancel all damage. + if (damagee instanceof Wolf && pets.contains(damagee)) + { + if (event.getCause() == DamageCause.FIRE_TICK) + { + damagee.setFireTicks(32768); // For mcMMO + event.setCancelled(true); + } + + event.setDamage(0); + return; + } + + // Damager - Pet Wolf - lower damage + if (e != null && damager instanceof Wolf && pets.contains(damager)) + { + event.setDamage(1); + return; + } + + // Damagee & Damager - Player - cancel if pvp disabled + if (damagee instanceof Player && damager instanceof Player) + { + if (livePlayers.contains(damagee) && !pvp) + event.setCancelled(true); + + return; + } + + // Damagee & Damager - Monsters - cancel if no monsterInfight + if (e != null && monsters.contains(damagee) && monsters.contains(damager)) + { + if (!monsterInfight) + event.setCancelled(true); + + return; + } + + // Creeper detonations + if (inRegion(damagee.getLocation())) + { + if (!detDamage || !(damagee instanceof Player) || !livePlayers.contains((Player) damagee)) + return; + + if (event.getCause() == DamageCause.BLOCK_EXPLOSION) + event.setCancelled(true); + + return; + } + } + + // Lobby Listener + public void onPlayerDropItem(PlayerDropItemEvent event) + { + if (running) return; + + Player p = event.getPlayer(); + if (!inRegion(p.getLocation()) || !livePlayers.contains(p)) + return; + + MAUtils.tellPlayer(p, MAMessages.get(Msg.LOBBY_DROP_ITEM)); + event.setCancelled(true); + } + + public void onPlayerBucketEmpty(PlayerBucketEmptyEvent event) + { + if (!readyPlayers.contains(event.getPlayer()) && !livePlayers.contains(event.getPlayer())) + return; + + if (!running) + { + event.getBlockClicked().getFace(event.getBlockFace()).setTypeId(0); + event.setCancelled(true); + return; + } + + Block liquid = event.getBlockClicked().getFace(event.getBlockFace()); + blocks.add(liquid); + } + + public void onPlayerInteract(PlayerInteractEvent event) + { + if (running || !livePlayers.contains(event.getPlayer())) + return; + + Player p = event.getPlayer(); + Action a = event.getAction(); + if ((a == Action.RIGHT_CLICK_AIR) || (a == Action.RIGHT_CLICK_BLOCK)) + { + event.setUseItemInHand(Result.DENY); + event.setCancelled(true); + } + + // Iron block + if (event.hasBlock() && event.getClickedBlock().getTypeId() == 42) + { + if (classMap.containsKey(p)) + { + MAUtils.tellPlayer(p, MAMessages.get(Msg.LOBBY_PLAYER_READY)); + playerReady(p); + } + else + { + MAUtils.tellPlayer(p, MAMessages.get(Msg.LOBBY_PICK_CLASS)); + } + return; + } + + // Sign + if (event.hasBlock() && event.getClickedBlock().getState() instanceof Sign) + { + if (a == Action.RIGHT_CLICK_BLOCK) + { + MAUtils.tellPlayer(p, MAMessages.get(Msg.LOBBY_RIGHT_CLICK)); + return; + } + + // Cast the block to a sign to get the text on it. + Sign sign = (Sign) event.getClickedBlock().getState(); + + // Check if the first line of the sign is a class name. + String className = sign.getLine(0); + if (!classes.contains(className)) + return; + + // Set the player's class. + assignClass(p, className); + MAUtils.tellPlayer(p, MAMessages.get(Msg.LOBBY_CLASS_PICKED, className)); + return; + } + } + + // Disconnect Listener + public void onPlayerQuit(PlayerQuitEvent event) + { + Player p = event.getPlayer(); + if (!enabled || !getAllPlayers().contains(p)) + return; + + MAUtils.clearInventory(p); + playerQuit(p); + } + + public void onPlayerKick(PlayerKickEvent event) + { + Player p = event.getPlayer(); + if (!enabled || !getAllPlayers().contains(p)) + return; + + MAUtils.clearInventory(p); + playerQuit(p); + } + + // Teleport Listener + public void onPlayerTeleport(PlayerTeleportEvent event) + { + if (edit || !enabled || !setup) + return; + + if (!inRegion(event.getTo()) && !inRegion(event.getFrom())) + return; + + Player p = event.getPlayer(); + Location old = locations.get(p); + Location to = event.getTo(); + Location from = event.getFrom(); + + if (livePlayers.contains(p)) + { + if (inRegion(from)) + { + if (to.equals(arenaLoc) || to.equals(lobbyLoc) || to.equals(spectatorLoc) || to.equals(old)) + return; + + MAUtils.tellPlayer(p, MAMessages.get(Msg.WARP_FROM_ARENA)); + event.setCancelled(true); + return; + } + + if (inRegion(to)) + { + if (to.equals(arenaLoc) || to.equals(lobbyLoc) || to.equals(spectatorLoc) || to.equals(old)) + return; + + MAUtils.tellPlayer(p, MAMessages.get(Msg.WARP_TO_ARENA)); + event.setCancelled(true); + return; + } + + return; + } + + if (running && inRegion(to)) + { + MAUtils.tellPlayer(p, MAMessages.get(Msg.WARP_TO_ARENA)); + event.setCancelled(true); + return; + } + } + + // Command Listener + public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) + { + Player p = event.getPlayer(); + + if (!livePlayers.contains(p)) + return; + + String[] args = event.getMessage().split(" "); + + if ((args.length > 1 && MACommands.COMMANDS.contains(args[1].trim())) || + MACommands.ALLOWED_COMMANDS.contains(event.getMessage().substring(1).trim()) || + MACommands.ALLOWED_COMMANDS.contains(args[0])) + return; + + event.setCancelled(true); + MAUtils.tellPlayer(p, MAMessages.get(Msg.MISC_COMMAND_NOT_ALLOWED)); + } + + + /*//////////////////////////////////////////////////////////////////// + // + // Getters & Misc + // + ////////////////////////////////////////////////////////////////////*/ + + public String configName() + { + return MAUtils.nameArenaToConfig(name); + } + + public String arenaName() + { + return name; + } + + public List getAllPlayers() + { + List result = new LinkedList(); + result.addAll(livePlayers); + result.addAll(deadPlayers); + result.addAll(specPlayers); + return result; + } + + public List getLivingPlayers() + { + List result = new LinkedList(); + result.addAll(livePlayers); + return result; + } + + public List getDeadPlayers() + { + List result = new LinkedList(); + result.addAll(deadPlayers); + return result; + } + + public void resetIdleTimer() + { + if (maxIdleTime <= 0) + return; + + // Reset the previousSize, cancel the previous timer, and start the new timer. + spawnThread.previousSize = monsters.size(); + Bukkit.getServer().getScheduler().cancelTask(spawnThread.taskId); + spawnThread.taskId = Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, + new Runnable() + { + public void run() + { + // Make sure to remove any dead/removed entities first. + List tmp = new LinkedList(monsters); + for (Entity e : tmp) + if (e.isDead()) + monsters.remove(e); + + // Compare the current size with the previous size. + if (monsters.size() != spawnThread.previousSize || spawnThread.previousSize == 0) + return; + + // Clear all player inventories, and "kill" all players. + for (Player p : livePlayers) + { + MAUtils.clearInventory(p); + playerDeath(p); + MAUtils.tellPlayer(p, MAMessages.get(Msg.FORCE_END_IDLE)); + } + } + }, maxIdleTime*20); + } + + /** + * The "perfect equals method" cf. "Object-Oriented Design and Patterns" + * by Cay S. Horstmann. + */ + public boolean equals(Object other) + { + if (this == other) return true; + if (other == null) return false; + if (getClass() != other.getClass()) return false; + + // Arenas must have different names. + if (other instanceof Arena && ((Arena)other).name.equals(name)) + return true; + + return false; + } + + public String toString() + { + return ((enabled && setup) ? ChatColor.GREEN : ChatColor.GRAY) + configName(); + } +} diff --git a/src/com/garbagemule/MobArena/ArenaMaster.java b/src/com/garbagemule/MobArena/ArenaMaster.java new file mode 100644 index 0000000..3501d96 --- /dev/null +++ b/src/com/garbagemule/MobArena/ArenaMaster.java @@ -0,0 +1,334 @@ +package com.garbagemule.MobArena; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.config.Configuration; + +public class ArenaMaster +{ + private MobArena plugin; + private Configuration config; + protected Arena selectedArena; + protected Lobby masterLobby; + + // Settings + protected boolean enabled, updateNotify, autoEquip, emptyInvs, hellhounds; + protected int repairDelay; + + // Classes + protected List classes; + protected Map> classItems, classArmor; + protected Map arenaMap; + + // Location map + protected Map locations = new HashMap(); + + // Arena list + protected List arenas; + + // Listeners + protected Set listeners = new HashSet(); + + + + /** + * Default constructor. + */ + public ArenaMaster(MobArena instance) + { + plugin = instance; + config = plugin.getConfig(); + } + + + + /*///////////////////////////////////////////////////////////////////////// + // + // Arena getters + // + /////////////////////////////////////////////////////////////////////////*/ + + public Arena getArenaInLocation(Location loc) + { + for (Arena arena : arenas) + if (arena.inRegion(loc)) + return arena; + return null; + } + + public List getArenasInWorld(World world) + { + List result = new LinkedList(); + for (Arena arena : arenas) + if (arena.world.equals(world)) + result.add(arena); + return result; + } + + public Arena getArenaWithPlayer(Player p) + { + return arenaMap.get(p); + } + + public Arena getArenaWithPlayer(String s) + { + for (Map.Entry entry : arenaMap.entrySet()) + { + if (entry.getKey().getName().equals(s)) + return entry.getValue(); + } + return null; + } + + public Arena getArenaWithMonster(Entity e) + { + for (Arena arena : arenas) + if (arena.monsters.contains(e)) + return arena; + return null; + } + + public Arena getArenaWithPet(Entity e) + { + for (Arena arena : arenas) + if (arena.pets.contains(e)) + return arena; + return null; + } + + public Arena getArenaWithName(String configName) + { + for (Arena arena : arenas) + if (arena.configName().equals(configName)) + return arena; + return null; + } + + + + /*///////////////////////////////////////////////////////////////////////// + // + // Initialization + // + /////////////////////////////////////////////////////////////////////////*/ + + public void initialize() + { + config.load(); + loadSettings(); + loadClasses(); + loadArenas(); + config.save(); + } + + /** + * Load the global settings. + */ + public void loadSettings() + { + if (config.getKeys("global-settings") == null) + { + config.setProperty("global-settings.enabled", true); + config.setProperty("global-settings.update-notification", true); + config.setProperty("global-settings.repair-delay", 5); + } + + enabled = config.getBoolean("global-settings.enabled", true); + updateNotify = config.getBoolean("global-settings.update-notification", true); + repairDelay = config.getInt("global-settings.repair-delay", 5); + } + + /** + * Load all class-related stuff. + */ + public void loadClasses() + { + if (config.getKeys("classes") == null) + { + config.setProperty("classes.Archer.items", "wood_sword, bow, arrow:128, grilled_pork"); + config.setProperty("classes.Archer.armor", "298,299,300,301"); + config.setProperty("classes.Knight.items", "diamond_sword, grilled_pork:2"); + config.setProperty("classes.Knight.armor", "306,307,308,309"); + config.setProperty("classes.Tank.items", "iron_sword, grilled_pork:3, apple"); + config.setProperty("classes.Tank.armor", "310,311,312,313"); + config.setProperty("classes.Oddjob.items", "stone_sword, flint_and_steel, netherrack:2, wood_pickaxe, tnt:4, fishing_rod, apple, grilled_pork:3"); + config.setProperty("classes.Oddjob.armor", "298,299,300,301"); + config.setProperty("classes.Chef.items", "stone_sword, bread:6, grilled_pork:4, mushroom_soup, cake:3, cookie:12"); + config.setProperty("classes.Chef.armor", "314,315,316,317"); + } + classes = config.getKeys("classes"); + classItems = MAUtils.getClassItems(config, "items"); + classArmor = MAUtils.getClassItems(config, "armor"); + } + + /** + * Load all arena-related stuff. + */ + public void loadArenas() + { + arenas = new LinkedList(); + + if (config.getKeys("arenas") == null) + createArenaNode("default", Bukkit.getServer().getWorlds().get(0)); + + for (String configName : config.getKeys("arenas")) + { + String arenaPath = "arenas." + configName + "."; + String worldName = config.getString(arenaPath + "settings.world"); + World world; + if (worldName == null) + { + System.out.println("[MobArena] ERROR! Could not find the world for arena '" + configName + "'. Using default world! Check the config-file!"); + world = Bukkit.getServer().getWorlds().get(0); + } + else + { + world = Bukkit.getServer().getWorld(worldName); + } + + Arena arena = new Arena(MAUtils.nameConfigToArena(configName), world, this); + arena.load(config); + arenas.add(arena); + } + + arenaMap = new HashMap(); + selectedArena = arenas.get(0); + } + + public Arena createArenaNode(String configName, World world) + { + config.setProperty("arenas." + configName + ".settings.world", world.getName()); + config.save(); + config.load(); + config.setProperty("arenas." + configName + ".settings.enabled", true); + config.save(); + config.load(); + config.setProperty("arenas." + configName + ".settings.protect", true); + config.save(); + config.load(); + config.setProperty("arenas." + configName + ".settings.wave-clear", false); + config.setProperty("arenas." + configName + ".settings.detonate-creepers", false); + config.setProperty("arenas." + configName + ".settings.detonate-damage", false); + config.setProperty("arenas." + configName + ".settings.lightning", true); + config.setProperty("arenas." + configName + ".settings.auto-equip-armor", true); + config.setProperty("arenas." + configName + ".settings.force-restore", false); + config.setProperty("arenas." + configName + ".settings.soft-restore", false); + config.setProperty("arenas." + configName + ".settings.require-empty-inventory", false); + config.setProperty("arenas." + configName + ".settings.hellhounds", false); + config.setProperty("arenas." + configName + ".settings.pvp-enabled", false); + config.setProperty("arenas." + configName + ".settings.monster-infight", false); + config.save(); + config.load(); + config.setProperty("arenas." + configName + ".settings.first-wave-delay", 5); + config.setProperty("arenas." + configName + ".settings.wave-interval", 20); + config.setProperty("arenas." + configName + ".settings.special-modulo", 4); + config.setProperty("arenas." + configName + ".settings.max-idle-time", 0); + config.save(); + config.load(); + + Arena arena = new Arena(MAUtils.nameConfigToArena(configName), world, this); + arena.load(config); + return arena; + } + + public void removeArenaNode(String configName) + { + config.removeProperty("arenas." + configName); + config.save(); + } + + + + /*///////////////////////////////////////////////////////////////////////// + // + // Update and serialization methods + // + /////////////////////////////////////////////////////////////////////////*/ + + /** + * Update one, two or all three of global settings, classes + * and arenas (arenas with deserialization). + */ + public void update(boolean settings, boolean classes, boolean arenalist) + { + boolean tmp = enabled; + enabled = false; + + for (Arena arena : arenas) + arena.forceEnd(); + + config.load(); + if (settings) loadSettings(); + if (classes) loadClasses(); + if (arenalist) deserializeArenas(); + config.save(); + + enabled = tmp; + } + + /** + * Serialize the global settings. + */ + public void serializeSettings() + { + String settings = "global-settings."; + config.setProperty(settings + "enabled", enabled); + config.save(); + } + + /** + * Serialize all arena configs. + */ + public void serializeArenas() + { + for (Arena arena : arenas) + arena.serializeConfig(); + } + + /** + * Deserialize all arena configs. Updates the arena list to + * include only the current arenas (not ones added in the + * actual file) that are also in the config-file. + */ + public void deserializeArenas() + { + // Get only the arenas in the config. + List strings = config.getKeys("arenas"); + if (strings == null) + return; + + // Get their Arena objects. + List configArenas = new LinkedList(); + for (String s : strings) + if (getArenaWithName(s) != null) + configArenas.add(getArenaWithName(s)); + + // Remove all Arenas no longer in the config. + arenas.retainAll(configArenas); + //for (Arena arena : arenas) + // arena.serializeConfig(); + + for (Arena arena : arenas) + arena.deserializeConfig(); + + // Make sure to update the selected arena to a valid one. + if (!arenas.contains(selectedArena) && arenas.size() >= 1) + selectedArena = arenas.get(0); + } + + public void updateSettings() { update(true, false, false); } + public void updateClasses() { update(false, true, false); } + public void updateArenas() { update(false, false, true); } + public void updateAll() { update(true, true, true); } +} diff --git a/src/com/garbagemule/MobArena/Lobby.java b/src/com/garbagemule/MobArena/Lobby.java new file mode 100644 index 0000000..6740175 --- /dev/null +++ b/src/com/garbagemule/MobArena/Lobby.java @@ -0,0 +1,26 @@ +package com.garbagemule.MobArena; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public class Lobby +{ + protected Arena arena; + protected ArenaMaster am; + protected Location warp, l1, l2; + + public Lobby(Arena arena) + { + this.arena = arena; + } + public Lobby() + { + this(null); + } + + public void playerJoin(Player p) + { + p.teleport(warp); + } +} diff --git a/src/com/garbagemule/MobArena/MABlockListener.java b/src/com/garbagemule/MobArena/MABlockListener.java index 1f80376..fef7a65 100644 --- a/src/com/garbagemule/MobArena/MABlockListener.java +++ b/src/com/garbagemule/MobArena/MABlockListener.java @@ -1,69 +1,27 @@ package com.garbagemule.MobArena; -import org.bukkit.Material; -import org.bukkit.block.Block; 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 -{ - public MABlockListener(MobArena instance) +{ + private ArenaMaster am; + + public MABlockListener(ArenaMaster am) { + this.am = am; } - /** - * Prevents blocks from breaking if block protection is on. - */ public void onBlockBreak(BlockBreakEvent event) { - if (!ArenaManager.isSetup || !ArenaManager.isProtected) - return; - - Block b = event.getBlock(); - - if (ArenaManager.blockSet.remove(b) || b.getType() == Material.TNT) - return; - - if (MAUtils.inRegion(b.getLocation())) - event.setCancelled(true); + for (Arena arena : am.arenas) + arena.onBlockBreak(event); } - - /** - * Adds player-placed blocks to a set for removal and item - * drop purposes. If the block is placed within the arena - * region, cancel the event if protection is on. - */ + public void onBlockPlace(BlockPlaceEvent event) { - if (!ArenaManager.isSetup || !ArenaManager.isProtected) - return; - - Block b = event.getBlock(); - - if (!MAUtils.inRegion(b.getLocation())) - return; - - if (ArenaManager.isRunning && ArenaManager.playerSet.contains(event.getPlayer())) - { - ArenaManager.blockSet.add(b); - Material type = b.getType(); - - // Make sure to add the top parts of doors. - if (type == Material.WOODEN_DOOR || type == Material.IRON_DOOR_BLOCK) - ArenaManager.blockSet.add(b.getRelative(0,1,0)); - - return; - } - - event.setCancelled(true); + for (Arena arena : am.arenas) + arena.onBlockPlace(event); } } \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MACommands.java b/src/com/garbagemule/MobArena/MACommands.java index da7a001..0dd9fb8 100644 --- a/src/com/garbagemule/MobArena/MACommands.java +++ b/src/com/garbagemule/MobArena/MACommands.java @@ -1,320 +1,884 @@ package com.garbagemule.MobArena; -import java.util.Arrays; +import java.util.List; +import java.util.LinkedList; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Server; import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.CommandExecutor; +import org.bukkit.command.ConsoleCommandSender; + +import com.garbagemule.MobArena.MAMessages.Msg; public class MACommands implements CommandExecutor { + public static List ALLOWED_COMMANDS = new LinkedList(); + public static final List COMMANDS = new LinkedList(); + static + { + COMMANDS.add("j"); // Join + COMMANDS.add("join"); // Join + COMMANDS.add("l"); // Leave + COMMANDS.add("leave"); // Leave + COMMANDS.add("notready"); // List of players who aren't ready + COMMANDS.add("spec"); // Watch arena + COMMANDS.add("spectate"); // Watch arena + COMMANDS.add("arenas"); // List of arenas + COMMANDS.add("list"); // List of players + COMMANDS.add("players"); // List of players + COMMANDS.add("restore"); // Restore inventory + COMMANDS.add("enable"); // Enabling + COMMANDS.add("disable"); // Disabling + COMMANDS.add("protect"); // Protection on/off + COMMANDS.add("force"); // Force start/end + COMMANDS.add("config"); // Reload config + COMMANDS.add("arena"); // Current arena + COMMANDS.add("setarena"); // Set current arena + COMMANDS.add("addarena"); // Add a new arena + COMMANDS.add("delarena"); // Delete current aren + COMMANDS.add("editarena"); // Editing + COMMANDS.add("setregion"); // Set a region point + COMMANDS.add("setwarp"); // Set arena/lobby/spec + COMMANDS.add("spawnpoints"); // List spawnpoints + COMMANDS.add("addspawn"); // Add a spawnpoint + COMMANDS.add("delspawn"); // Delete a spawnpoint + COMMANDS.add("expandregion"); // Expand the region + COMMANDS.add("reset"); // Reset arena coordinates + COMMANDS.add("auto-generate"); // Auto-generate arena + COMMANDS.add("auto-degenerate"); // Restore cuboid + } + private boolean player, op, console, meanAdmins; + private Server server; + private MobArena plugin; + private ArenaMaster am; + + public MACommands(MobArena plugin, ArenaMaster am) + { + this.plugin = plugin; + this.am = am; + server = Bukkit.getServer(); + meanAdmins = (server.getPluginManager().getPlugin("Mean Admins") != null); + ALLOWED_COMMANDS = MAUtils.getAllowedCommands(plugin.getConfig()); + } + /** * 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) + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - // Check if the server is also running Mean Admins. - Plugin ma = ArenaManager.server.getPluginManager().getPlugin("Mean Admins"); - if (ma != null && !Arrays.asList(ArenaManager.plugin.COMMANDS).contains(args[0].toLowerCase())) + // Play nice with Mean Admins. + if (meanAdmins && !COMMANDS.contains(args[0].toLowerCase())) { - ma.onCommand(sender, command, commandLabel, args); - return true; - } - - // Only accept commands from players. - if ((sender == null) || !(sender instanceof Player)) - { - System.out.println("Only players can use these commands, silly."); + server.getPluginManager().getPlugin("Mean Admins").onCommand(sender, command, label, args); return true; } - // Cast the sender to a Player object. - Player p = (Player) sender; + // Determine if the sender is a player (and an op), or the console. + player = (sender instanceof Player); + op = player && ((Player) sender).isOp(); + console = (sender instanceof ConsoleCommandSender); - /* 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; - } + // Cast the sender to Player if possible. + Player p = (player) ? (Player)sender : null; - // If not exactly one argument, must be an invalid command. - if (args.length != 1) + if (args.length == 0) 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.equals("leave") || cmd.equals("l")) - { - ArenaManager.playerLeave(p); - return true; - } - - if (cmd.equals("list") || cmd.equals("who")) - { - ArenaManager.playerList(p); - return true; - } + // Grab the command base and any arguments. + String base = args[0].toLowerCase(); + String arg1 = (args.length >= 2) ? args[1].toLowerCase() : null; + String arg2 = (args.length >= 3) ? args[2].toLowerCase() : null; + //String arg3 = (args.length >= 4) ? args[3].toLowerCase() : null; - if (cmd.equals("spectate") || cmd.equals("spec")) - { - ArenaManager.playerSpectate(p); - return true; - } - - if (cmd.equals("ready") || cmd.equals("notready")) - { - ArenaManager.notReadyList(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 enabled [true|false] - if (cmd.equals("enabled")) - { - if (!arg.equals("true") && !arg.equals("false")) + /*//////////////////////////////////////////////////////////////// + // + // Basics + // + ////////////////////////////////////////////////////////////////*/ + + /* + * Player join + */ + if (base.equals("join") || base.equals("j")) + { + if (!player) { - ArenaManager.tellPlayer(p, "/ma enabled [true|false]"); - return true; - } - - // Set the boolean - ArenaManager.isEnabled = Boolean.valueOf(arg); - ArenaManager.tellPlayer(p, "Enabled: " + arg); - return true; - } - - if (cmd.equals("check")) - { - if (!arg.equals("updates")) - { - ArenaManager.tellPlayer(p, "/ma check updates"); - return true; - } - - MAUtils.checkForUpdates(p, true); - return true; - } - - // ma force [start|end] - if (cmd.equals("force")) - { - // Start arena. - if (arg.equals("start")) - { - ArenaManager.forceStart(p); + MAUtils.tellPlayer(sender, "Players only."); return true; } - // End the arena. - if (arg.equals("end")) + boolean error; + + if (arg1 != null) { - ArenaManager.forceEnd(p); + Arena arena = am.getArenaWithName(arg1); + + // Crap-load of sanity-checks. + if (!am.enabled) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_NOT_ENABLED)); + else if (arena == null) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + else if (am.arenaMap.containsKey(p) && am.arenaMap.get(p).livePlayers.contains(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_IN_OTHER_ARENA)); + else if (!arena.enabled) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_ARENA_NOT_ENABLED)); + else if (!arena.setup) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_ARENA_NOT_SETUP)); + else if (arena.running) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_ARENA_IS_RUNNING)); + else if (arena.livePlayers.contains(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_ALREADY_PLAYING)); + else if (arena.emptyInv && !MAUtils.hasEmptyInventory(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_EMPTY_INV)); + else if (!arena.emptyInv && !MAUtils.storeInventory(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_STORE_INV_FAIL)); + else error = false; + + // If there was an error, don't join. + if (error) + return true; + + am.arenaMap.put(p,arena); + arena.playerJoin(p, p.getLocation()); + + MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_PLAYER_JOINED)); return true; } - ArenaManager.tellPlayer(p, "/ma force [start|end]"); + if (arg1 == null) + { + if (am.arenas.size() < 1) + { + MAUtils.tellPlayer(sender, "There are no arenas loaded. Check your config-file."); + return true; + } + + Arena arena = am.arenas.get(0); + + if (!am.enabled) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_NOT_ENABLED)); + else if (am.arenas.size() > 1) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_ARG_NEEDED)); + else if (arena == null) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + else if (am.arenaMap.containsKey(p) && am.arenaMap.get(p).livePlayers.contains(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_IN_OTHER_ARENA)); + else if (!arena.enabled) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_ARENA_NOT_ENABLED)); + else if (!arena.setup) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_ARENA_NOT_SETUP)); + else if (arena.running) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_ARENA_IS_RUNNING)); + else if (arena.livePlayers.contains(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_ALREADY_PLAYING)); + else if (arena.emptyInv && !MAUtils.hasEmptyInventory(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_EMPTY_INV)); + else if (!arena.emptyInv && !MAUtils.storeInventory(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_STORE_INV_FAIL)); + else error = false; + + // If there was an error, don't join. + if (error) + return true; + + am.arenaMap.put(p,arena); + arena.playerJoin(p, p.getLocation()); + + MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_PLAYER_JOINED)); + return true; + } + } + + /* + * Player leave + */ + if (base.equals("leave") || base.equals("l")) + { + if (!player) + { + MAUtils.tellPlayer(sender, "Players only."); + return true; + } + + if (!am.arenaMap.containsKey(p)) + { + MAUtils.tellPlayer(p, MAMessages.get(Msg.LEAVE_NOT_PLAYING)); + return true; + } + + Arena arena = am.arenaMap.remove(p); + arena.playerLeave(p); + MAUtils.tellPlayer(p, MAMessages.get(Msg.LEAVE_PLAYER_LEFT)); return true; } - // ma config reload - if (cmd.equals("config")) + /* + * Player spectate + */ + if (base.equals("spectate") || base.equals("spec")) { - if (!arg.equals("reload")) + if (!player) { - ArenaManager.tellPlayer(p, "/ma config reload"); + MAUtils.tellPlayer(sender, "Players only."); return true; } - - // End the arena. - ArenaManager.init(ArenaManager.plugin); - ArenaManager.tellPlayer(p, "Config-file was reloaded."); - return true; - } - - // ma setwarp [arena|lobby|spectator] - if (cmd.equals("setwarp")) - { - if (!arg.equals("arena") && !arg.equals("lobby") && !arg.equals("spectator")) - { - ArenaManager.tellPlayer(p, "/ma setwarp [arena|lobby|spectator]"); - return true; - } - - // Write the coordinate data to the config-file. - MAUtils.setCoords(arg, p.getLocation().getBlock().getRelative(0,1,0).getLocation()); - ArenaManager.tellPlayer(p, "Warp point \"" + arg + "\" set."); - MAUtils.notifyIfSetup(p); + boolean error; + Arena arena = null; + + if (arg1 != null) + { + arena = am.getArenaWithName(arg1); + + if (!am.enabled) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_NOT_ENABLED)); + else if (am.arenaMap.containsKey(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.SPEC_ALREADY_PLAYING)); + else if (arena == null) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + /*else if (!arena.running) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.SPEC_NOT_RUNNING)); + else if (!MAUtils.hasEmptyInventory(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.SPEC_EMPTY_INV));*/ + else error = false; + + if (error) + return true; + } + else + { + arena = am.arenas.get(0); + + if (!am.enabled) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_NOT_ENABLED)); + else if (am.arenaMap.containsKey(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.SPEC_ALREADY_PLAYING)); + else if (am.arenas.size() > 1) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_ARG_NEEDED)); + else if (arena == null) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + /*else if (!arena.running) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.SPEC_NOT_RUNNING)); + else if (!MAUtils.hasEmptyInventory(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.SPEC_EMPTY_INV));*/ + else error = false; + + if (error) + return true; + } + + am.arenaMap.put(p,arena); + arena.playerSpec(p, p.getLocation()); + MAUtils.tellPlayer(p, MAMessages.get(Msg.SPEC_PLAYER_SPECTATE)); return true; } - // ma addspawn - if (cmd.equals("addspawn")) + /* + * Prints a list of all arenas. + */ + if (base.equals("arenas")) { - // The name must start with a letter, followed by any letter(s) or number(s). - if (!arg.matches("[a-z]+([[0-9][a-z]])*")) + String list = MAUtils.listToString(am.arenas); + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_LIST_ARENAS, list)); + return true; + } + + /* + * Prints a list of all live players in all arenas, or live players in a specific arena. + */ + if (base.equals("players") || base.equals("list")) + { + if (arg1 != null) { - ArenaManager.tellPlayer(p, "Name must consist of only letters a-z and numbers 0-9"); + Arena arena = am.getArenaWithName(arg1); + if (arena == null) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + return true; + } + + String list = MAUtils.listToString(arena.getLivingPlayers()); + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_LIST_PLAYERS, list)); + } + else + { + StringBuffer buffy = new StringBuffer(); + for (Arena arena : am.arenas) + buffy.append(MAUtils.listToString(arena.getLivingPlayers())); + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_LIST_PLAYERS, buffy.toString())); + } + return true; + } + + /* + * Restore a player's inventory. + */ + if (base.equals("restore")) + { + if (arg1 == null && player) + { + if (am.getArenaWithPlayer((Player) sender) != null) + { + MAUtils.tellPlayer(sender, "You must first leave the current arena."); + return true; + } + + if (MAUtils.restoreInventory(p)) + MAUtils.tellPlayer(sender, "Restored your inventory!"); return true; } - - // Write the coordinate data to the config-file. - MAUtils.setCoords("spawnpoints." + arg, p.getLocation().getBlock().getRelative(0,1,0).getLocation()); - - ArenaManager.tellPlayer(p, "Spawn point with name \"" + arg + "\" added."); - MAUtils.notifyIfSetup(p); - 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]])*")) + if (arg1 != null && (op || console)) { - 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 + "\"."); - ArenaManager.tellPlayer(p, "Spawnpoints: " + MAUtils.spawnList()); - return true; - } - - MAUtils.delCoords("coords.spawnpoints." + arg); - - ArenaManager.tellPlayer(p, "Spawn point with name \"" + arg + "\" removed."); - MAUtils.notifyIfSetup(p); - return true; - } - - // ma setregion [p1|p2] - if (cmd.equals("setregion")) - { - if (!arg.equals("p1") && !arg.equals("p2")) - { - ArenaManager.tellPlayer(p, "/ma setregion [p1|p2]"); - return true; - } - - MAUtils.setCoords(arg, p.getLocation()); - MAUtils.fixCoords(); - - ArenaManager.tellPlayer(p, "Region point \"" + arg + "\" set."); - MAUtils.notifyIfSetup(p); - return true; - } - - // ma expandregion [up|down|out] - if (cmd.equals("expandregion")) - { - if (ArenaManager.p1 == null || ArenaManager.p2 == null) - { - ArenaManager.tellPlayer(p, "Set up region points first: /ma setregion [p1|p2]"); - return true; - } - if (!arg.equals("up") && !arg.equals("down") && !arg.equals("out")) - { - ArenaManager.tellPlayer(p, "/ma expandregion [up|down|out] "); - return true; - } - - 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; - } - - // ma reset coords - if (cmd.equals("reset")) - { - if (!arg.equals("coords")) - return false; - - MAUtils.delCoords("coords"); - ArenaManager.tellPlayer(p, "All arena coords have been reset."); - return true; - } - - // ma protect [true|false] - if (cmd.equals("protect")) - { - if (!arg.equals("true") && !arg.equals("false")) - return false; - - // Set the boolean - ArenaManager.isProtected = Boolean.valueOf(arg); - - ArenaManager.tellPlayer(p, "Region protection: " + arg); - return true; - } - - // ma dooooo it hippie monster - if (cmd.equals("dooooo")) - { - if (args.length != 4) - return false; - - if (args[1].equals("it") && args[2].equals("hippie") && args[3].equals("monster")) - { - MAUtils.DoooooItHippieMonster(p.getLocation(), 13); - ArenaManager.tellPlayer(p, "Auto-generated a working MobArena!"); + if (am.getArenaWithPlayer(arg1) != null) + { + MAUtils.tellPlayer(sender, "Player is currently in an arena."); + return true; + } + + if (MAUtils.restoreInventory(Bukkit.getServer().getPlayer(arg1))); + MAUtils.tellPlayer(sender, "Restored " + arg1 + "'s inventory!"); return true; } } - // ma undo it hippie monster - if (cmd.equals("undo")) + + + /*//////////////////////////////////////////////////////////////// + // + // Setup & Reload + // + ////////////////////////////////////////////////////////////////*/ + + /* + * Enable or disable arena(s) + */ + if ((base.equals("enable") || base.equals("disable"))) { - if (args.length != 4) - return false; - - if (args[1].equals("it") && args[2].equals("hippie") && args[3].equals("monster")) + if (!(op || console)) { - MAUtils.UnDoooooItHippieMonster(); - ArenaManager.tellPlayer(p, "Restored your precious little patch >_>"); + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + + if (arg1 != null) + { + Arena arena = am.getArenaWithName(arg1); + if (arena != null) + { + arena.enabled = base.equals("enable"); + arena.serializeConfig(); + MAUtils.tellPlayer(sender, "Arena '" + arena.configName() + "' " + ((arena.enabled) ? ChatColor.GREEN : ChatColor.RED) + base + "d"); + } + else + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + } + } + else + { + am.enabled = base.equals("enable"); + am.serializeSettings(); + MAUtils.tellPlayer(sender, "All arenas " + ((am.enabled) ? ChatColor.GREEN : ChatColor.RED) + base + "d"); + } + return true; + } + + /* + * Enable or disable protection + */ + if (base.equals("protect")) + { + if (!(op || console)) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1 == null || !arg1.matches("^[a-zA-Z][a-zA-Z0-9_]*$") || arg2 == null || !(arg2.equals("true") || arg2.equals("false"))) + { + MAUtils.tellPlayer(sender, "Usage: /ma protect [true|false]"); + return true; + } + + Arena arena = am.getArenaWithName(arg1); + if (arena == null) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + return true; + } + + arena.protect = arg2.equals("true"); + arena.serializeConfig(); + arena.load(plugin.getConfig()); + MAUtils.tellPlayer(sender, "Protection for arena '" + arg1 + "' set to " + arg2); + return true; + } + + /* + * Force start/end arenas. + */ + if (base.equals("force")) + { + if (!(op || console)) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1 == null || !arg1.matches("^[a-zA-Z][a-zA-Z0-9_]*$")) + { + MAUtils.tellPlayer(sender, "Usage: /ma force [start|end] ()"); + return true; + } + + if (arg1.equals("end")) + { + if (arg2 == null) + { + for (Arena arena : am.arenas) + arena.forceEnd(); + + am.arenaMap.clear(); + return true; + } + if (!arg2.matches("^[a-zA-Z][a-zA-Z0-9_]*$")) + { + MAUtils.tellPlayer(sender, "Usage: /ma force end ()"); + return true; + } + + Arena arena = am.getArenaWithName(arg2); + if (arena == null) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + return true; + } + + // The arena exists. + if (arena.livePlayers.isEmpty()) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.FORCE_END_EMPTY)); + return true; + } + + arena.forceEnd(); + MAUtils.tellPlayer(sender, MAMessages.get(Msg.FORCE_END_ENDED)); + return true; + } + + if (arg1.equals("start")) + { + if (arg2 == null || !arg2.matches("^[a-zA-Z][a-zA-Z0-9_]*$")) + { + MAUtils.tellPlayer(sender, "Usage: /ma force start "); + return true; + } + + Arena arena = am.getArenaWithName(arg2); + if (arena == null) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + return true; + } + + // The arena exists. + if (arena.running) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.FORCE_START_RUNNING)); + return true; + } + if (arena.readyPlayers.isEmpty()) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.FORCE_START_NOT_READY)); + return true; + } + + arena.forceStart(); + MAUtils.tellPlayer(sender, MAMessages.get(Msg.FORCE_START_STARTED)); return true; } } - return false; + /* + * Reload the config-file. + */ + if (base.equals("config") && arg1 != null) + { + if (!(op || console)) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + + if (!arg1.equals("reload")) + { + MAUtils.tellPlayer(sender, "Usage: /ma config reload"); + return true; + } + + am.updateAll(); + MAUtils.tellPlayer(sender, "Config reloaded."); + return true; + } + + /* + * Get the current arena, and list all other arenas. + */ + if (base.equals("arena")) + { + if (!(op || console)) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + + MAUtils.tellPlayer(sender, "Currently selected arena: " + ChatColor.GREEN + am.selectedArena.configName()); + + StringBuffer buffy = new StringBuffer(); + if (am.arenas.size() > 1) + { + for (Arena arena : am.arenas) + if (!arena.equals(am.selectedArena)) + buffy.append(arena.configName() + " "); + } + else buffy.append(MAMessages.get(Msg.MISC_NONE)); + + MAUtils.tellPlayer(sender, "Other arenas: " + buffy.toString()); + return true; + } + + /* + * Set the current arena + */ + if (base.equals("setarena")) + { + if (!(op || console)) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1 == null || !arg1.matches("^[a-zA-Z][a-zA-Z0-9_]*$")) + { + MAUtils.tellPlayer(sender, "Usage: /ma setarena "); + return true; + } + + Arena arena = am.getArenaWithName(arg1); + if (arena != null) + am.selectedArena = arena; + else + MAUtils.tellPlayer(sender, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + return true; + } + + /* + * Create a new arena, and set the current arena to this new arena. + */ + if (base.equals("addarena")) + { + if (!op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1 == null || !arg1.matches("^[a-zA-Z][a-zA-Z0-9_]*$")) + { + MAUtils.tellPlayer(sender, "Usage: /ma addarena "); + return true; + } + + Arena arena = am.getArenaWithName(arg1); + if (arena != null) + { + MAUtils.tellPlayer(sender, "An arena with that name already exists."); + return true; + } + + arena = am.createArenaNode(arg1, p.getWorld()); + am.arenas.add(arena); + am.selectedArena = arena; + + MAUtils.tellPlayer(sender, "New arena with name '" + arg1 + "' created!"); + return true; + } + + if (base.equals("delarena")) + { + if (!(op || console)) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1 == null || !arg1.matches("^[a-zA-Z][a-zA-Z0-9_]*$")) + { + MAUtils.tellPlayer(sender, "Usage: /ma delarena "); + return true; + } + if (am.arenas.size() == 1) + { + MAUtils.tellPlayer(sender, "At least one arena must exist."); + return true; + } + + Arena arena = am.getArenaWithName(arg1); + if (arena == null) + { + MAUtils.tellPlayer(sender, "There is no arena with that name."); + return true; + } + + am.removeArenaNode(arg1); + am.arenas.remove(arena); + am.selectedArena = (am.selectedArena.equals(arena)) ? am.arenas.get(0) : am.selectedArena; + + MAUtils.tellPlayer(sender, "Arena '" + arena.configName() + "' deleted."); + return true; + } + + if (base.equals("editarena")) + { + if (!(op || console)) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1 == null || !arg1.matches("^[a-zA-Z][a-zA-Z0-9_]*$") || arg2 == null || (!arg2.equals("true") && !arg2.equals("false"))) + { + MAUtils.tellPlayer(sender, "Usage: /ma editarena [true|false]"); + return true; + } + + Arena arena = am.getArenaWithName(arg1); + if (arena == null) + { + MAUtils.tellPlayer(sender, "There is no arena with that name."); + return true; + } + + arena.edit = arg2.equals("true"); + MAUtils.tellPlayer(sender, "Edit mode for arena '" + arg1 + "': " + ((arena.edit) ? ChatColor.GREEN + "true" : ChatColor.RED + "false")); + if (arena.edit) MAUtils.tellPlayer(sender, "Remember to turn it back off after editing!"); + return true; + } + + /* + * Set region points [p1|p2] for the current arena. + */ + if (base.equals("setregion")) + { + if (!op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + + if (arg1 == null || !(arg1.equals("p1") || arg1.equals("p2"))) + { + MAUtils.tellPlayer(sender, "Usage: /ma setregion [p1|p2]"); + return true; + } + + MAUtils.setArenaCoord(plugin.getConfig(), am.selectedArena, arg1, p.getLocation()); + MAUtils.tellPlayer(sender, "Set region point " + arg1 + " for arena '" + am.selectedArena.configName() + "'"); + return true; + } + + /* + * Expand the region (arg1) [up|down|out] + */ + if (base.equals("expandregion")) + { + if (!(op || console)) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (args.length != 3 || !arg1.matches("[0-9]+")) + { + MAUtils.tellPlayer(sender, "Usage: /ma expandregion [up|down|out]"); + return true; + } + + if (arg2.equals("up")) + { + am.selectedArena.p2.setY(Math.min(127, am.selectedArena.p2.getY() + Integer.parseInt(arg1))); + } + else if (arg2.equals("down")) + { + am.selectedArena.p1.setY(Math.max(0, am.selectedArena.p1.getY() - Integer.parseInt(arg1))); + } + else if (arg2.equals("out")) + { + am.selectedArena.p1.setX(am.selectedArena.p1.getX() - Integer.parseInt(arg1)); + am.selectedArena.p1.setZ(am.selectedArena.p1.getZ() - Integer.parseInt(arg1)); + am.selectedArena.p2.setX(am.selectedArena.p2.getX() + Integer.parseInt(arg1)); + am.selectedArena.p2.setZ(am.selectedArena.p2.getZ() + Integer.parseInt(arg1)); + } + else + { + MAUtils.tellPlayer(sender, "Usage: /ma expandregion [up|down|out]"); + return true; + } + + MAUtils.tellPlayer(sender, "Region for '" + am.selectedArena.configName() + "' expanded " + arg2 + " by " + arg1 + " blocks."); + am.selectedArena.serializeConfig(); + am.selectedArena.load(plugin.getConfig()); + return true; + } + + /* + * Set warp points [arena|lobby|spectator] for the current arena. + */ + if (base.equals("setwarp")) + { + if (!op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1 == null || !(arg1.equals("arena") || arg1.equals("lobby") || arg1.equals("spectator"))) + { + MAUtils.tellPlayer(sender, "Usage: /ma setwarp [arena|lobby|spectator]"); + return true; + } + + MAUtils.setArenaCoord(plugin.getConfig(), am.selectedArena, arg1, p.getLocation().getBlock().getRelative(0,1,0).getLocation()); + MAUtils.tellPlayer(sender, "Set warp point " + arg1 + " for arena '" + am.selectedArena.configName() + "'"); + return true; + } + + /* + * List all the current spawnpoints for the current arena. + */ + if (base.equals("spawnpoints")) + { + if (!(op || console)) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + + StringBuffer buffy = new StringBuffer(); + List spawnpoints = plugin.getConfig().getKeys("arenas." + am.selectedArena.configName() + ".coords.spawnpoints"); + + if (spawnpoints != null) + { + for (String s : spawnpoints) + { + buffy.append(s); + buffy.append(" "); + } + } + else + { + buffy.append(MAMessages.get(Msg.MISC_NONE)); + } + + MAUtils.tellPlayer(sender, "Spawnpoints for arena '" + am.selectedArena.configName() + "': " + buffy.toString()); + return true; + } + + /* + * Add a spawnpoint for the current arena. + */ + if (base.equals("addspawn")) + { + if (!op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1 == null || !arg1.matches("^[a-zA-Z][a-zA-Z0-9]*$")) + { + MAUtils.tellPlayer(sender, "Usage: /ma addspawn "); + return true; + } + + MAUtils.setArenaCoord(plugin.getConfig(), am.selectedArena, "spawnpoints." + arg1, p.getLocation()); + MAUtils.tellPlayer(sender, "Added spawnpoint " + arg1 + " for arena \"" + am.selectedArena.configName() + "\""); + return true; + } + + /* + * Delete a spawnpoint for the current arena. + */ + if (base.equals("delspawn")) + { + if (!(op || console)) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1 == null || !arg1.matches("^[a-zA-Z][a-zA-Z0-9]*$")) + { + MAUtils.tellPlayer(sender, "Usage: /ma delspawn "); + return true; + } + + if (MAUtils.delArenaCoord(plugin.getConfig(), am.selectedArena, "spawnpoints." + arg1)) + MAUtils.tellPlayer(sender, "Deleted spawnpoint " + arg1 + " for arena '" + am.selectedArena.configName() + "'"); + else + MAUtils.tellPlayer(sender, "Could not find the spawnpoint " + arg1 + "for the arena '" + am.selectedArena.configName() + "'"); + return true; + } + + if (base.equals("auto-generate")) + { + if (!op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1 == null || !arg1.matches("^[a-zA-Z][a-zA-Z0-9]*$")) + { + MAUtils.tellPlayer(sender, "Usage: /ma autogenerate "); + return true; + } + if (am.getArenaWithName(arg1) != null) + { + MAUtils.tellPlayer(sender, "An arena with that name already exists."); + return true; + } + + if (MAUtils.doooooItHippieMonster(p.getLocation(), 13, arg1, plugin)) + MAUtils.tellPlayer(sender, "Arena with name '" + arg1 + "' generated."); + else + MAUtils.tellPlayer(sender, "Could not auto-generate arena."); + return true; + } + + if (base.equals("auto-degenerate")) + { + if (!(op || console)) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1 == null || !arg1.matches("^[a-zA-Z][a-zA-Z0-9]*$")) + { + MAUtils.tellPlayer(sender, "Usage: /ma auto-degenerate "); + return true; + } + if (am.getArenaWithName(arg1) == null) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + return true; + } + + if (MAUtils.undoItHippieMonster(arg1, plugin, true)) + MAUtils.tellPlayer(sender, "Arena with name '" + arg1 + "' degenerated."); + else + MAUtils.tellPlayer(sender, "Could not degenerate arena."); + return true; + } + + MAUtils.tellPlayer(sender, "Command not found."); + return true; } } \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MAEntityListener.java b/src/com/garbagemule/MobArena/MAEntityListener.java new file mode 100644 index 0000000..d51e118 --- /dev/null +++ b/src/com/garbagemule/MobArena/MAEntityListener.java @@ -0,0 +1,65 @@ +package com.garbagemule.MobArena; + +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityCombustEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.EntityListener; +import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.event.entity.EntityTargetEvent; + +import com.garbagemule.MobArena.Arena; +import com.garbagemule.MobArena.ArenaMaster; + +public class MAEntityListener extends EntityListener +{ + private ArenaMaster am; + + public MAEntityListener(ArenaMaster am) + { + this.am = am; + } + + public void onEntityRegainHealth(EntityRegainHealthEvent event) + { + for (Arena arena : am.arenas) + arena.onEntityRegainHealth(event); + } + + public void onEntityDeath(EntityDeathEvent event) + { + for (Arena arena : am.arenas) + arena.onEntityDeath(event); + } + + public void onEntityDamage(EntityDamageEvent event) + { + for (Arena arena : am.arenas) + arena.onEntityDamage(event); + } + + public void onCreatureSpawn(CreatureSpawnEvent event) + { + for (Arena arena : am.arenas) + arena.onCreatureSpawn(event); + } + + public void onEntityExplode(EntityExplodeEvent event) + { + for (Arena arena : am.arenas) + arena.onEntityExplode(event); + } + + public void onEntityCombust(EntityCombustEvent event) + { + for (Arena arena : am.arenas) + arena.onEntityCombust(event); + } + + public void onEntityTarget(EntityTargetEvent event) + { + for (Arena arena : am.arenas) + arena.onEntityTarget(event); + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MAInventoryItem.java b/src/com/garbagemule/MobArena/MAInventoryItem.java new file mode 100644 index 0000000..ad33c45 --- /dev/null +++ b/src/com/garbagemule/MobArena/MAInventoryItem.java @@ -0,0 +1,22 @@ +package com.garbagemule.MobArena; + +import java.io.Serializable; + +public class MAInventoryItem implements Serializable +{ + private static final long serialVersionUID = 739709220350581510L; + private int typeId; + private int amount; + private short durability; + + public MAInventoryItem(int typeId, int amount, short durability) + { + this.typeId = typeId; + this.amount = amount; + this.durability = durability; + } + + public int getTypeId() { return typeId; } + public int getAmount() { return amount; } + public short getDurability() { return durability; } +} diff --git a/src/com/garbagemule/MobArena/MAMessages.java b/src/com/garbagemule/MobArena/MAMessages.java new file mode 100644 index 0000000..1ebb464 --- /dev/null +++ b/src/com/garbagemule/MobArena/MAMessages.java @@ -0,0 +1,232 @@ +package com.garbagemule.MobArena; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.util.HashMap; +import java.util.Map; + +public class MAMessages +{ + protected static Map msgMap; + private static Map defaults = new HashMap(); + protected static enum Msg + { + ARENA_START, + ARENA_END, + ARENA_DOES_NOT_EXIST, + JOIN_PLAYER_JOINED, + JOIN_NOT_ENABLED, + JOIN_IN_OTHER_ARENA, + JOIN_ARENA_NOT_ENABLED, + JOIN_ARENA_NOT_SETUP, + JOIN_ARENA_IS_RUNNING, + JOIN_ALREADY_PLAYING, + JOIN_ARG_NEEDED, + JOIN_EMPTY_INV, + JOIN_STORE_INV_FAIL, + LEAVE_PLAYER_LEFT, + LEAVE_NOT_PLAYING, + PLAYER_DIED, + SPEC_PLAYER_SPECTATE, + SPEC_NOT_RUNNING, + SPEC_ARG_NEEDED, + SPEC_EMPTY_INV, + SPEC_ALREADY_PLAYING, + NOT_READY_PLAYERS, + NOT_READY_RUNNING, + FORCE_START_STARTED, + FORCE_START_RUNNING, + FORCE_START_NOT_READY, + FORCE_END_ENDED, + FORCE_END_EMPTY, + FORCE_END_IDLE, + REWARDS_GIVE, + LOBBY_CLASS_PICKED, + LOBBY_PLAYER_READY, + LOBBY_DROP_ITEM, + LOBBY_PICK_CLASS, + LOBBY_RIGHT_CLICK, + WARP_TO_ARENA, + WARP_FROM_ARENA, + WAVE_DEFAULT, + WAVE_SPECIAL, + WAVE_REWARD, + // Misc + MISC_LIST_ARENAS, + MISC_LIST_PLAYERS, + MISC_COMMAND_NOT_ALLOWED, + MISC_NO_ACCESS, + MISC_NONE + } + + // Populate the defaults map. + static + { + defaults.put(Msg.ARENA_START, "Let the slaughter begin!"); + defaults.put(Msg.ARENA_END, "Arena finished."); + defaults.put(Msg.ARENA_DOES_NOT_EXIST, "That arena does not exist. Type /ma arenas for a list."); + defaults.put(Msg.JOIN_NOT_ENABLED, "MobArena is not enabled."); + defaults.put(Msg.JOIN_IN_OTHER_ARENA, "You are already in an arena! Leave that one first."); + defaults.put(Msg.JOIN_ARENA_NOT_ENABLED, "This arena is not enabled."); + defaults.put(Msg.JOIN_ARENA_NOT_SETUP, "This arena has not been set up yet."); + defaults.put(Msg.JOIN_ARENA_IS_RUNNING, "This arena is in already progress."); + defaults.put(Msg.JOIN_ALREADY_PLAYING, "You are already playing!"); + defaults.put(Msg.JOIN_ARG_NEEDED, "You must specify an arena. Type /ma arenas for a list."); + defaults.put(Msg.JOIN_EMPTY_INV, "You must empty your inventory to join the arena."); + defaults.put(Msg.JOIN_STORE_INV_FAIL, "Failed to store inventory. Try again."); + defaults.put(Msg.JOIN_PLAYER_JOINED, "You joined the arena. Have fun!"); + defaults.put(Msg.LEAVE_NOT_PLAYING, "You are not in the arena."); + defaults.put(Msg.LEAVE_PLAYER_LEFT, "You left the arena. Thanks for playing!"); + defaults.put(Msg.PLAYER_DIED, "% died!"); + defaults.put(Msg.SPEC_PLAYER_SPECTATE, "Enjoy the show!"); + defaults.put(Msg.SPEC_NOT_RUNNING, "This arena isn't running."); + defaults.put(Msg.SPEC_ARG_NEEDED, "You must specify an arena. Type /ma arenas for a list."); + defaults.put(Msg.SPEC_EMPTY_INV, "Empty your inventory first!"); + defaults.put(Msg.SPEC_ALREADY_PLAYING, "Can't spectate when in the arena!"); + defaults.put(Msg.NOT_READY_RUNNING, "The lobby is empty!"); + defaults.put(Msg.NOT_READY_PLAYERS, "Not ready: %"); + defaults.put(Msg.FORCE_START_RUNNING, "Arena has already started."); + defaults.put(Msg.FORCE_START_NOT_READY, "Can't force start, no players are ready."); + defaults.put(Msg.FORCE_START_STARTED, "Forced arena start."); + defaults.put(Msg.FORCE_END_EMPTY, "No one is in the arena."); + defaults.put(Msg.FORCE_END_ENDED, "Forced arena end."); + defaults.put(Msg.FORCE_END_IDLE, "You weren't quick enough!"); + defaults.put(Msg.REWARDS_GIVE, "Here are all of your rewards!"); + defaults.put(Msg.LOBBY_DROP_ITEM, "No sharing before the arena starts!"); + defaults.put(Msg.LOBBY_PLAYER_READY, "You have been flagged as ready!"); + defaults.put(Msg.LOBBY_PICK_CLASS, "You must first pick a class!"); + defaults.put(Msg.LOBBY_RIGHT_CLICK, "Punch the sign. Don't right-click."); + defaults.put(Msg.LOBBY_CLASS_PICKED, "You have chosen % as your class!"); + defaults.put(Msg.WARP_TO_ARENA, "Can't warp to the arena during battle!"); + defaults.put(Msg.WARP_FROM_ARENA, "Warping not allowed in the arena!"); + defaults.put(Msg.WAVE_DEFAULT, "Get ready for wave #%!"); + defaults.put(Msg.WAVE_SPECIAL, "Get ready for wave #%! [SPECIAL]"); + defaults.put(Msg.WAVE_REWARD, "You just earned a reward: %"); + defaults.put(Msg.MISC_LIST_PLAYERS, "Live players: %"); + defaults.put(Msg.MISC_LIST_ARENAS, "Available arenas: %"); + defaults.put(Msg.MISC_COMMAND_NOT_ALLOWED, "You can't use that command in the arena!"); + defaults.put(Msg.MISC_NO_ACCESS, "You don't have access to this comand."); + defaults.put(Msg.MISC_NONE, ""); + } + + /** + * Initializes the msgMap by reading from the announcements-file. + */ + public static void init(MobArena plugin, boolean update) + { + // Use defaults in case of any errors. + msgMap = defaults; + + // Grab the announcements-file. + File msgFile; + try + { + msgFile = new File(plugin.getDataFolder(), "announcements.properties"); + + // If it doesn't exist, create it. + if (!msgFile.exists()) + { + System.out.println("[MobArena] Announcements-file not found. Creating one..."); + msgFile.createNewFile(); + + FileWriter fw = new FileWriter(msgFile); + BufferedWriter bw = new BufferedWriter(fw); + + // Write default announcements to the file. + for (Msg m : Msg.values()) + { + bw.write(m.toString() + "=" + defaults.get(m)); + bw.newLine(); + } + bw.close(); + + return; + } + } + catch (Exception e) + { + System.out.println("[MobArena] ERROR: Couldn't initialize announcements-file. Using defaults."); + return; + } + + // If the file was found, populate the msgMap. + try + { + FileReader fr = new FileReader(msgFile); + BufferedReader br = new BufferedReader(fr); + + String s; + while ((s = br.readLine()) != null) + { + process(s); + } + br.close(); + } + catch (Exception e) + { + System.out.println("[MobArena] ERROR: Problem with announcements-file. Using defaults."); + return; + } + } + + public static void init(MobArena plugin) + { + init(plugin, false); + } + + /** + * Grabs the announcement from the msgMap, and in case of + * s not being null, replaces the % with s. + */ + public static String get(Msg msg, String s) + { + // If p is null, just return the announcement as is. + if (s == null) + return msgMap.get(msg); + + // Otherwise, replace the % with the input string. + return msgMap.get(msg).replace("%", s); + } + + /** + * Grabs the announcement from the msgMap. + */ + public static String get(Msg msg) + { + return get(msg, null); + } + + /** + * Helper-method for parsing the strings from the + * announcements-file. + */ + private static void process(String s) + { + // Split the string by the equals-sign. + String[] split = s.split("="); + if (split.length != 2) + { + System.out.println("[MobArena] ERROR: Couldn't parse \"" + s + "\"."); + return; + } + + // For simplicity... + String key = split[0]; + String val = split[1]; + Msg msg; + + try + { + msg = Msg.valueOf(key); + msgMap.put(msg, val); + } + catch (Exception e) + { + System.out.println("[MobArena] ERROR: " + key + " is not a valid key!"); + return; + } + } +} \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MAPlayerListener.java b/src/com/garbagemule/MobArena/MAPlayerListener.java new file mode 100644 index 0000000..9cb29dd --- /dev/null +++ b/src/com/garbagemule/MobArena/MAPlayerListener.java @@ -0,0 +1,87 @@ +package com.garbagemule.MobArena; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerKickEvent; +import org.bukkit.event.player.PlayerListener; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerTeleportEvent; + +public class MAPlayerListener extends PlayerListener +{ + private MobArena plugin; + private ArenaMaster am; + + public MAPlayerListener(MobArena plugin, ArenaMaster am) + { + this.plugin = plugin; + this.am = am; + } + + public void onPlayerInteract(PlayerInteractEvent event) + { + if (!am.enabled) return; + for (Arena arena : am.arenas) + arena.onPlayerInteract(event); + } + + public void onPlayerDropItem(PlayerDropItemEvent event) + { + if (!am.enabled) return; + for (Arena arena : am.arenas) + arena.onPlayerDropItem(event); + } + + public void onPlayerBucketEmpty(PlayerBucketEmptyEvent event) + { + if (!am.enabled) return; + for (Arena arena : am.arenas) + arena.onPlayerBucketEmpty(event); + } + + public void onPlayerTeleport(PlayerTeleportEvent event) + { + if (!am.enabled) return; + for (Arena arena : am.arenas) + arena.onPlayerTeleport(event); + } + + public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) + { + if (!am.enabled) return; + for (Arena arena : am.arenas) + arena.onPlayerCommandPreprocess(event); + } + + public void onPlayerQuit(PlayerQuitEvent event) + { + for (Arena arena : am.arenas) + arena.onPlayerQuit(event); + } + + public void onPlayerKick(PlayerKickEvent event) + { + for (Arena arena : am.arenas) + arena.onPlayerKick(event); + } + + public void onPlayerJoin(PlayerJoinEvent event) + { + if (!am.updateNotify || !event.getPlayer().isOp()) return; + + final Player p = event.getPlayer(); + Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, + new Runnable() + { + public void run() + { + MAUtils.checkForUpdates(plugin, p, false); + } + }, 100); + } +} diff --git a/src/com/garbagemule/MobArena/MASpawnThread.java b/src/com/garbagemule/MobArena/MASpawnThread.java index d0d25fb..bf5761c 100644 --- a/src/com/garbagemule/MobArena/MASpawnThread.java +++ b/src/com/garbagemule/MobArena/MASpawnThread.java @@ -1,6 +1,12 @@ package com.garbagemule.MobArena; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Random; +import java.util.Set; + import org.bukkit.Location; import org.bukkit.entity.Wolf; import org.bukkit.entity.Ghast; @@ -11,6 +17,9 @@ import org.bukkit.entity.Creature; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.CreatureType; +import org.bukkit.inventory.ItemStack; + +import com.garbagemule.MobArena.MAMessages.Msg; /** * Core class for handling wave spawning. @@ -20,92 +29,103 @@ import org.bukkit.entity.CreatureType; * 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 interval. -// TO-DO: Allow custom special wave monsters. -// TO-DO: Allow additional "default" waves. +// TODO: Allow custom special wave monsters. +// TODO: Allow additional "default" waves. public class MASpawnThread implements Runnable { - private int wave, ran, noOfSpawnPoints, noOfPlayers, modulo; + protected int wave, previousSize, taskId; + private int ran, noOfPlayers, modulo; private int dZombies, dSkeletons, dSpiders, dCreepers, dWolves; private int dPoweredCreepers, dPigZombies, dSlimes, dMonsters, dAngryWolves, dGiants, dGhasts; private Random random; - private String reward, currentRewards; + private Arena arena; + private final double MIN_DISTANCE = 256; - public MASpawnThread() + public MASpawnThread(Arena arena) { - modulo = ArenaManager.specialModulo; + this.arena = arena; + modulo = arena.specialModulo; if (modulo <= 0) modulo = -32768; - noOfPlayers = ArenaManager.playerSet.size(); - noOfSpawnPoints = ArenaManager.spawnpoints.size(); + taskId = -32768; + + noOfPlayers = arena.livePlayers.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; - dWolves = dCreepers + ArenaManager.dWolves; + dZombies = arena.distDefault.get("zombies"); + dSkeletons = dZombies + arena.distDefault.get("skeletons"); + dSpiders = dSkeletons + arena.distDefault.get("spiders"); + dCreepers = dSpiders + arena.distDefault.get("creepers"); + dWolves = dCreepers + arena.distDefault.get("wolves"); - dPoweredCreepers = ArenaManager.dPoweredCreepers; - dPigZombies = dPoweredCreepers + ArenaManager.dPigZombies; - dSlimes = dPigZombies + ArenaManager.dSlimes; - dMonsters = dSlimes + ArenaManager.dMonsters; - dAngryWolves = dMonsters + ArenaManager.dAngryWolves; - dGiants = dAngryWolves + ArenaManager.dGiants; - dGhasts = dGiants + ArenaManager.dGhasts; + dPoweredCreepers = arena.distSpecial.get("powered-creepers"); + dPigZombies = dPoweredCreepers + arena.distSpecial.get("zombie-pigmen"); + dSlimes = dPigZombies + arena.distSpecial.get("slimes"); + dMonsters = dSlimes + arena.distSpecial.get("humans"); + dAngryWolves = dMonsters + arena.distSpecial.get("angry-wolves"); + dGiants = dAngryWolves + arena.distSpecial.get("giants"); + dGhasts = dGiants + arena.distSpecial.get("ghasts"); } public void run() - { - // Check if we need to grant more rewards with the recurrent waves. - for (Integer i : ArenaManager.everyWaveMap.keySet()) + { + // Check if wave needs to be cleared first. If so, return! + if (arena.waveClear && wave > 1) { - 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); - } + if (!arena.monsters.isEmpty()) + return; } + // If maxIdleTime is defined, reset the timer. + //if (arena.maxIdleTime > 0) arena.resetIdleTimer(); + + // Check if we need to grant more rewards with the recurrent waves. + for (Map.Entry> entry : arena.everyWaveMap.entrySet()) + if (wave % entry.getKey() == 0) + addReward(entry.getValue()); + // 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); - } - } + if (arena.afterWaveMap.containsKey(wave)) + addReward(arena.afterWaveMap.get(wave)); // Check if this is a special wave. if (wave % modulo == 0) { - ArenaManager.tellAll("Get ready for wave #" + wave + "! [SPECIAL]"); - for (MobArenaListener m : ArenaManager.listeners) - m.onSpecialWave(wave, wave/modulo); + MAUtils.tellAll(arena, MAMessages.get(Msg.WAVE_SPECIAL, ""+wave)); + /*for (MobArenaListener m : ArenaManager.listeners) TODO: Get API back up to speed. + m.onSpecialWave(wave, wave/modulo);*/ + detonateCreepers(arena.detCreepers); + specialWave(); } else { - ArenaManager.tellAll("Get ready for wave #" + wave + "!"); - for (MobArenaListener m : ArenaManager.listeners) - m.onDefaultWave(wave); + MAUtils.tellAll(arena, MAMessages.get(Msg.WAVE_DEFAULT, ""+wave)); + /*for (MobArenaListener m : ArenaManager.listeners) TODO: More API + m.onDefaultWave(wave);*/ + detonateCreepers(arena.detCreepers); + defaultWave(); } - ArenaManager.wave = wave; wave++; + if (arena.maxIdleTime > 0) arena.resetIdleTimer(); + } + + /** + * Rewards all players with an item from the input String. + */ + private void addReward(List rewards) + { + for (Player p : arena.livePlayers) + { + ItemStack reward = MAUtils.getRandomReward(rewards); + arena.rewardMap.get(p).add(reward); + + MAUtils.tellPlayer(p, MAMessages.get(Msg.WAVE_REWARD, MAUtils.toCamelCase(reward.getType().toString()) + ":" + reward.getAmount())); + } } /** @@ -114,12 +134,15 @@ public class MASpawnThread implements Runnable private void defaultWave() { Location loc; - - for (int i = 0; i < wave + noOfPlayers; i++) + List spawnpoints = getValidSpawnpoints(); + int noOfSpawnpoints = spawnpoints.size(); + int count = wave + noOfPlayers; + CreatureType mob; + + for (int i = 0; i < count; i++) { - loc = ArenaManager.spawnpoints.get(i % noOfSpawnPoints); + loc = spawnpoints.get(i % noOfSpawnpoints); ran = random.nextInt(dWolves); - CreatureType mob; /* Because of the nature of the if-elseif-else statement, * we're able to evaluate the random number in this way. @@ -133,8 +156,8 @@ public class MASpawnThread implements Runnable else if (ran < dWolves) mob = CreatureType.WOLF; else continue; - LivingEntity e = ArenaManager.world.spawnCreature(loc,mob); - ArenaManager.monsterSet.add(e); + LivingEntity e = arena.world.spawnCreature(loc,mob); + arena.monsters.add(e); // Grab a random target. Creature c = (Creature) e; @@ -148,6 +171,8 @@ public class MASpawnThread implements Runnable private void specialWave() { Location loc; + List spawnpoints = getValidSpawnpoints(); + int noOfSpawnpoints = spawnpoints.size(); CreatureType mob; ran = random.nextInt(dGhasts); @@ -202,13 +227,10 @@ public class MASpawnThread implements Runnable // Spawn the hippie monsters. for (int i = 0; i < count; i++) { - loc = ArenaManager.spawnpoints.get(i % noOfSpawnPoints); + loc = spawnpoints.get(i % noOfSpawnpoints); - LivingEntity e = ArenaManager.world.spawnCreature(loc,mob); - if (!ArenaManager.monsterSet.contains(e)) - ArenaManager.monsterSet.add(e); - else - System.out.println("MASpawnThread - monsterSet contains this entity"); + LivingEntity e = arena.world.spawnCreature(loc,mob); + arena.monsters.add(e); if (slime) ((Slime)e).setSize(2); if (wolf) ((Wolf)e).setAngry(true); @@ -224,38 +246,78 @@ public class MASpawnThread implements Runnable c.setTarget(getClosestPlayer(e)); } - if (!ArenaManager.lightning) + if (!arena.lightning) return; // Lightning, just for effect ;) - for (Location spawn : ArenaManager.spawnpoints) + for (Location spawn : arena.spawnpoints.values()) + arena.world.strikeLightningEffect(spawn); + } + + /** + * "Detonates" all the Creepers in the monsterSet. + */ + public void detonateCreepers(boolean really) + { + if (!really) + return; + + Set tmp = new HashSet(); + for (Entity e : arena.monsters) { - ArenaManager.world.strikeLightningEffect(spawn); + if (!(e instanceof Creeper) || e.isDead()) + continue; + + tmp.add(e); + } + + Location loc; + for (Entity e : tmp) + { + arena.monsters.remove(e); + loc = e.getLocation().getBlock().getRelative(0,2,0).getLocation(); + arena.world.createExplosion(loc, 2); + e.remove(); } } /** - * Gets the player closest to the input entity. ArrayList implementation - * means a complexity of O(n). + * Get all the spawnpoints that have players nearby. */ - // TO-DO: Move this into MAUtils - public static Player getClosestPlayer(Entity e) + public List getValidSpawnpoints() { - // Grab the coordinates. - double x = e.getLocation().getX(); - double y = e.getLocation().getY(); - double z = e.getLocation().getZ(); + List result = new ArrayList(); + for (Location s : arena.spawnpoints.values()) + for (Player p : arena.livePlayers) + if (s.distanceSquared(p.getLocation()) < MIN_DISTANCE) + result.add(s); + + // If no players are in range, just use all the spawnpoints. + if (result.isEmpty()) + result.addAll(arena.spawnpoints.values()); + + return result; + } + + /** + * Get the player closest to the input entity. + */ + // TODO: Move this into MAUtils + public Player getClosestPlayer(Entity e) + { // Set up the comparison variable and the result. + double dist = 0; double current = Double.POSITIVE_INFINITY; Player result = null; /* Iterate through the ArrayList, and update current and result every * time a squared distance smaller than current is found. */ - for (Player p : ArenaManager.playerSet) + for (Player p : arena.livePlayers) { - double dist = distance(p.getLocation(), x, y, z); - if (dist < current) + dist = p.getLocation().distance(e.getLocation()); + //double dist = MAUtils.distance(p.getLocation(), e.getLocation()); + if (dist < current && dist < 256) { current = dist; result = p; @@ -263,17 +325,4 @@ public class MASpawnThread implements Runnable } return result; } - - /** - * Calculates the squared distance between locations. - */ - // TO-DO: Move this into MAUtils - private static double distance(Location loc, double d1, double d2, double d3) - { - double d4 = loc.getX() - d1; - double d5 = loc.getY() - d2; - double d6 = loc.getZ() - d3; - - return d4*d4 + d5*d5 + d6*d6; - } } \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MAUtils.java b/src/com/garbagemule/MobArena/MAUtils.java index cfdedc3..68f8183 100644 --- a/src/com/garbagemule/MobArena/MAUtils.java +++ b/src/com/garbagemule/MobArena/MAUtils.java @@ -7,36 +7,303 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.LinkedList; import java.util.Map; import java.util.HashMap; import java.util.Random; -import java.util.Iterator; +import java.util.Set; + +import net.minecraft.server.WorldServer; + import org.bukkit.block.Sign; +import org.bukkit.command.CommandSender; import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.World; import org.bukkit.Material; import org.bukkit.Location; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.bukkit.entity.Wolf; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.util.config.Configuration; public class MAUtils -{ - public static final List SWORDS_ID = new LinkedList(); - public static final List SWORDS_TYPE = new LinkedList(); +{ + public static final String sep = File.separator; + // Weapons + public static final List WEAPONS_TYPE = new LinkedList(); + public static final List SWORDS_TYPE = new LinkedList(); + public static final List AXES_TYPE = new LinkedList(); + public static final List PICKAXES_TYPE = new LinkedList(); + public static final List SPADES_TYPE = new LinkedList(); + public static final List HOES_TYPE = new LinkedList(); + // Armor + public static final List ARMORS_TYPE = new LinkedList(); + public static final List HELMETS_TYPE = new LinkedList(); + public static final List CHESTPLATES_TYPE = new LinkedList(); + public static final List LEGGINGS_TYPE = new LinkedList(); + public static final List BOOTS_TYPE = new LinkedList(); static { + // Weapons SWORDS_TYPE.add(Material.WOOD_SWORD); SWORDS_TYPE.add(Material.STONE_SWORD); SWORDS_TYPE.add(Material.GOLD_SWORD); SWORDS_TYPE.add(Material.IRON_SWORD); SWORDS_TYPE.add(Material.DIAMOND_SWORD); + + AXES_TYPE.add(Material.WOOD_AXE); + AXES_TYPE.add(Material.STONE_AXE); + AXES_TYPE.add(Material.GOLD_AXE); + AXES_TYPE.add(Material.IRON_AXE); + AXES_TYPE.add(Material.DIAMOND_AXE); + + PICKAXES_TYPE.add(Material.WOOD_PICKAXE); + PICKAXES_TYPE.add(Material.STONE_PICKAXE); + PICKAXES_TYPE.add(Material.GOLD_PICKAXE); + PICKAXES_TYPE.add(Material.IRON_PICKAXE); + PICKAXES_TYPE.add(Material.DIAMOND_PICKAXE); + + SPADES_TYPE.add(Material.WOOD_SPADE); + SPADES_TYPE.add(Material.STONE_SPADE); + SPADES_TYPE.add(Material.GOLD_SPADE); + SPADES_TYPE.add(Material.IRON_SPADE); + SPADES_TYPE.add(Material.DIAMOND_SPADE); + + HOES_TYPE.add(Material.WOOD_HOE); + HOES_TYPE.add(Material.STONE_HOE); + HOES_TYPE.add(Material.GOLD_HOE); + HOES_TYPE.add(Material.IRON_HOE); + HOES_TYPE.add(Material.DIAMOND_HOE); + + WEAPONS_TYPE.addAll(SWORDS_TYPE); + WEAPONS_TYPE.addAll(AXES_TYPE); + WEAPONS_TYPE.addAll(PICKAXES_TYPE); + WEAPONS_TYPE.addAll(SPADES_TYPE); + WEAPONS_TYPE.addAll(HOES_TYPE); + + // Armor + HELMETS_TYPE.add(Material.LEATHER_HELMET); + HELMETS_TYPE.add(Material.GOLD_HELMET); + HELMETS_TYPE.add(Material.CHAINMAIL_HELMET); + HELMETS_TYPE.add(Material.IRON_HELMET); + HELMETS_TYPE.add(Material.DIAMOND_HELMET); + + CHESTPLATES_TYPE.add(Material.LEATHER_CHESTPLATE); + CHESTPLATES_TYPE.add(Material.GOLD_CHESTPLATE); + CHESTPLATES_TYPE.add(Material.CHAINMAIL_CHESTPLATE); + CHESTPLATES_TYPE.add(Material.IRON_CHESTPLATE); + CHESTPLATES_TYPE.add(Material.DIAMOND_CHESTPLATE); + + LEGGINGS_TYPE.add(Material.LEATHER_LEGGINGS); + LEGGINGS_TYPE.add(Material.GOLD_LEGGINGS); + LEGGINGS_TYPE.add(Material.CHAINMAIL_LEGGINGS); + LEGGINGS_TYPE.add(Material.IRON_LEGGINGS); + LEGGINGS_TYPE.add(Material.DIAMOND_LEGGINGS); + + BOOTS_TYPE.add(Material.LEATHER_BOOTS); + BOOTS_TYPE.add(Material.GOLD_BOOTS); + BOOTS_TYPE.add(Material.CHAINMAIL_BOOTS); + BOOTS_TYPE.add(Material.IRON_BOOTS); + BOOTS_TYPE.add(Material.DIAMOND_BOOTS); + + ARMORS_TYPE.addAll(HELMETS_TYPE); + ARMORS_TYPE.addAll(CHESTPLATES_TYPE); + ARMORS_TYPE.addAll(LEGGINGS_TYPE); + ARMORS_TYPE.addAll(BOOTS_TYPE); + } + + + + /* ///////////////////////////////////////////////////////////////////// // + + INITIALIZATION METHODS + + // ///////////////////////////////////////////////////////////////////// */ + + /** + * Grab all the spawnpoints for a specific arena. + */ + public static Map getArenaSpawnpoints(Configuration config, World world, String arena) + { + Map spawnpoints = new HashMap(); + String arenaPath = "arenas." + arena + ".coords.spawnpoints"; + + if (config.getKeys(arenaPath) == null) + return spawnpoints; + + for (String point : config.getKeys(arenaPath)) + spawnpoints.put(point, makeLocation(world, config.getString(arenaPath + "." + point))); + + return spawnpoints; + } + + /** + * Returns a map of classnames mapped to lists of ItemStacks. + */ + public static Map> getClassItems(Configuration config, String type) + { + Map> result = new HashMap>(); + + for (String className : config.getKeys("classes")) + result.put(className, makeItemStackList(config.getString("classes." + className + "." + type))); + + return result; + } + + /** + * Takes a comma-separated list of items in the : format and + * returns a list of ItemStacks created from that data. + */ + public static List makeItemStackList(String string) + { + List result = new LinkedList(); + if (string == null) return result; + + string = string.trim(); + if (string.endsWith(",")) + string = string.substring(0, string.length()-1); + String[] items = string.split(","); + + for (String item : items) + { + item = item.trim(); + String[] parts = item.split(":"); + + // Grab the amount. + int amount = (parts.length == 2 && parts[1].matches("[0-9]+")) ? + Integer.parseInt(parts[1]) : + 1; + + // Make the ItemStack. + ItemStack stack = (parts[0].matches("[0-9]+")) ? + makeItemStack(Integer.parseInt(parts[0]), amount) : + makeItemStack(parts[0], amount); + + result.add(stack); + } + 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> getArenaRewardMap(Configuration config, String arena, String type) + { + String arenaPath = "arenas." + arena + ".rewards.waves."; + Map> result = new HashMap>(); + + if (config.getKeys(arenaPath + type) == null) + { + if (type.equals("every")) + { + config.setProperty(arenaPath + "every.3", "feather, bone, stick"); + config.setProperty(arenaPath + "every.5", "dirt:4, gravel:4, stone:4"); + config.setProperty(arenaPath + "every.10", "iron_ingot:10, gold_ingot:8"); + } + else if (type.equals("after")) + { + config.setProperty(arenaPath + "after.7", "minecart, storage_minecart, powered_minecart"); + config.setProperty(arenaPath + "after.13", "iron_sword, iron_pickaxe, iron_spade"); + config.setProperty(arenaPath + "after.16", "diamond_sword"); + } + } + + List waves = config.getKeys(arenaPath + type); + if (waves == null) return result; + + for (String n : waves) + { + if (!n.matches("[0-9]+")) + continue; + + int wave = Integer.parseInt(n); + String rewards = config.getString(arenaPath + type + "." + n); + + result.put(wave, makeItemStackList(rewards)); + } + return result; + } + + /** + * Grabs the distribution coefficients from the config-file. If + * no coefficients are found, defaults (10) are added. + */ + public static Map getArenaDistributions(Configuration config, String arena, String wave) + { + //config.load(); + String arenaPath = "arenas." + arena + ".waves." + wave; + Map result = new HashMap(); + List dists = config.getKeys(arenaPath); + + // If there are no distributions yet, add them. + if (dists == null) + { + if (wave.equals("default")) + { + config.setProperty(arenaPath + ".zombies", 10); + config.setProperty(arenaPath + ".skeletons", 10); + config.setProperty(arenaPath + ".spiders", 10); + config.setProperty(arenaPath + ".creepers", 10); + config.setProperty(arenaPath + ".wolves", 10); + } + else if (wave.equals("special")) + { + config.setProperty(arenaPath + ".powered-creepers", 10); + config.setProperty(arenaPath + ".zombie-pigmen", 10); + config.setProperty(arenaPath + ".slimes", 10); + config.setProperty(arenaPath + ".humans", 10); + config.setProperty(arenaPath + ".angry-wolves", 10); + config.setProperty(arenaPath + ".giants", 0); + config.setProperty(arenaPath + ".ghasts", 0); + } + //config.save(); + dists = config.getKeys(arenaPath); + } + + for (String monster : dists) + { + int value = config.getInt(arenaPath + "." + monster, -1); + + // If no distribution value was found, set one. + if (value == -1) + { + value = 10; + if (monster.equals("giant") || monster.equals("ghast")) + value = 0; + + config.setProperty(arenaPath + "." + monster, value); + //config.save(); + } + + result.put(monster, value); + } + return result; + } + + public static List getAllowedCommands(Configuration config) + { + String commands = config.getString("global-settings.allowed-commands"); + if (commands == null) + { + config.setProperty("global-settings.allowed-commands", "/list"); + config.save(); + commands = config.getString("global-settings.allowed-commands"); + } + + return stringToList(commands); } + /* ///////////////////////////////////////////////////////////////////// // INVENTORY AND REWARD METHODS @@ -55,11 +322,110 @@ public class MAUtils return inv; } - /* Checks if all inventory and armor slots are empty. */ - public static boolean hasEmptyInventory(Player player) + public static boolean storeInventory(Player p) { - ItemStack[] inventory = player.getInventory().getContents(); - ItemStack[] armor = player.getInventory().getArmorContents(); + // Grab the contents. + ItemStack[] armor = p.getInventory().getArmorContents(); + ItemStack[] items = p.getInventory().getContents(); + + String invPath = "plugins" + sep + "MobArena" + sep + "inventories"; + new File(invPath).mkdir(); + File backupFile = new File(invPath + sep + p.getName() + ".inv"); + + try + { + if (backupFile.exists()) + return false; + + backupFile.createNewFile(); + + MAInventoryItem[] inv = new MAInventoryItem[armor.length + items.length]; + for (int i = 0; i < armor.length; i++) + inv[i] = stackToItem(armor[i]); + for (int i = 0; i < items.length; i++) + inv[armor.length + i] = stackToItem(items[i]); + + FileOutputStream fos = new FileOutputStream(backupFile); + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(inv); + oos.close(); + } + catch (Exception e) + { + e.printStackTrace(); + System.out.println("[MobArena] ERROR! Could not create backup file for " + p.getName() + "."); + return false; + } + + clearInventory(p); + return true; + } + + public static boolean restoreInventory(Player p) + { + String invPath = "plugins" + sep + "MobArena" + sep + "inventories"; + File backupFile = new File(invPath + sep + p.getName() + ".inv"); + + try + { + // If the backup-file couldn't be found, return. + if (!backupFile.exists()) + return false; + + // Grab the MAInventoryItem array from the backup-file. + FileInputStream fis = new FileInputStream(backupFile); + ObjectInputStream ois = new ObjectInputStream(fis); + MAInventoryItem[] fromFile = (MAInventoryItem[]) ois.readObject(); + ois.close(); + + // Split that shit. + ItemStack[] armor = new ItemStack[4]; + ItemStack[] items = new ItemStack[fromFile.length-4]; + + for (int i = 0; i < 4; i++) + armor[i] = itemToStack(fromFile[i]); + for (int i = 4; i < fromFile.length; i++) + items[i - 4] = itemToStack(fromFile[i]); + + // Restore the inventory. + PlayerInventory inv = p.getInventory(); + inv.setArmorContents(armor); + for (ItemStack stack : items) + if (stack != null) + inv.addItem(stack); + + // Remove the backup-file. + backupFile.delete(); + } + catch (Exception e) + { + e.printStackTrace(); + System.out.println("[MobArena] ERROR! Could not restore inventory for " + p.getName()); + return false; + } + + return true; + } + + private static MAInventoryItem stackToItem(ItemStack stack) + { + if (stack == null) + return new MAInventoryItem(-1, -1, (short)0); + return new MAInventoryItem(stack.getTypeId(), stack.getAmount(), stack.getDurability()); + } + + private static ItemStack itemToStack(MAInventoryItem item) + { + if (item.getTypeId() == -1) + return null; + return new ItemStack(item.getTypeId(), item.getAmount(), item.getDurability()); + } + + /* Checks if all inventory and armor slots are empty. */ + public static boolean hasEmptyInventory(Player p) + { + ItemStack[] inventory = p.getInventory().getContents(); + ItemStack[] armor = p.getInventory().getArmorContents(); // For inventory, check for null for (ItemStack stack : inventory) @@ -72,540 +438,286 @@ public class MAUtils return true; } - /* Gives all the items in the input string(s) to the player */ - public static void giveItems(boolean reward, Player p, String... strings) + /** + * Gives the player all of the items in the list of ItemStacks. + */ + public static void giveItems(Player p, List stacks, boolean autoEquip, boolean rewards) { - // Variables used. - ItemStack stack; - int id, amount; + PlayerInventory inv = p.getInventory(); - PlayerInventory inv; - - if (reward) - inv = p.getInventory(); - else - inv = clearInventory(p); - - for (String s : strings) + for (ItemStack stack : stacks) { - /* 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) + // If these are rewards, don't tamper with them. + if (rewards) { - /* 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); - if (!reward && SWORDS_TYPE.contains(stack.getType())) - stack.setDurability((short)-3276); - } - else - { - stack = makeItemStack(item[0], amount); - if (stack == null) continue; - if (!reward && SWORDS_TYPE.contains(stack.getType())) - stack.setDurability((short)-3276); - } - inv.addItem(stack); + continue; } + + // If this is an armor piece, equip it and continue. + if (autoEquip && ARMORS_TYPE.contains(stack.getType())) + { + equipArmorPiece(stack, inv); + continue; + } + + // If this is a sword, set its durability to "unlimited". + //if (SWORDS_TYPE.contains(stack.getType())) + if (WEAPONS_TYPE.contains(stack.getType())) + stack.setDurability((short) -32768); + + inv.addItem(stack); } } - /* Used for giving items "normally". */ - public static void giveItems(Player p, String... strings) + public static void giveItems(Player p, List stacks, boolean autoEquip) { - giveItems(false, p, strings); + giveItems(p, stacks, autoEquip, false); } - /* Helper method for grabbing a random reward */ - public static String getRandomReward(String rewardlist) + public static void giveRewards(Player p, List stacks) { - Random ran = new Random(); + giveItems(p, stacks, false, true); + } + + public static int getPetAmount(Player p) + { + int result = 0; - String[] rewards = rewardlist.split(","); - String item = rewards[ran.nextInt(rewards.length)]; - return item.trim(); + for (ItemStack stack : p.getInventory().getContents()) + { + if (stack == null || stack.getTypeId() != 352) + continue; + + result += stack.getAmount(); + } + + return result; } - /* Helper method for making an ItemStack out of a string */ - private static ItemStack makeItemStack(String s, int amount) + /* Helper method for equipping armor pieces. */ + public static void equipArmorPiece(ItemStack stack, PlayerInventory inv) + { + Material type = stack.getType(); + + if (HELMETS_TYPE.contains(type)) + inv.setHelmet(stack); + else if (CHESTPLATES_TYPE.contains(type)) + inv.setChestplate(stack); + else if (LEGGINGS_TYPE.contains(type)) + inv.setLeggings(stack); + else if (BOOTS_TYPE.contains(type)) + inv.setBoots(stack); + } + + /* Helper methods for making ItemStacks out of strings and ints */ + private static ItemStack makeItemStack(String name, int amount) { - Material mat; try { - mat = Material.valueOf(s.toUpperCase()); - return new ItemStack(mat, amount); + Material material = Material.valueOf(name.toUpperCase()); + return new ItemStack(material, amount); } catch (Exception e) { - System.out.println("[MobArena] ERROR! Could not create item " + s + ". Check config.yml"); + System.out.println("[MobArena] ERROR! Could not create item \"" + name + "\". Check config.yml"); return null; } } - - - /* ///////////////////////////////////////////////////////////////////// // - - INITIALIZATION METHODS - - // ///////////////////////////////////////////////////////////////////// */ - - /** - * Creates a Configuration object from the config.yml file. - */ - public static Configuration getConfig() + private static ItemStack makeItemStack(int id, int amount) { - new File("plugins/MobArena").mkdir(); - File configFile = new File("plugins/MobArena/config.yml"); - try { - if(!configFile.exists()) - { - configFile.createNewFile(); - } + Material material = Material.getMaterial(id); + return new ItemStack(material, amount); } - catch(Exception e) + catch (Exception e) { - System.out.println("[MobArena] ERROR: Config file could not be created."); + System.out.println("[MobArena] ERROR! Could not create item with id " + id + ". Check config.yml"); return null; } - - return new Configuration(configFile); } - public static List getDisabledCommands() + /* Helper method for grabbing a random reward */ + public static ItemStack getRandomReward(List rewards) { - Configuration c = ArenaManager.config; - c.load(); - - String commands = c.getString("settings.disabledcommands", "/kill"); - c.setProperty("settings.disabledcommands", commands); - c.save(); - - List result = new LinkedList(); - for (String s : commands.split(",")) - result.add(s.trim()); - - return result; + Random ran = new Random(); + return rewards.get(ran.nextInt(rewards.size())); } - /** - * 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("settings.world", ArenaManager.server.getWorlds().get(0).getName()); - c.setProperty("settings.world", world); - - c.save(); - return ArenaManager.server.getWorld(world); - } - - /** - * Handles all spawn-monster bypassing. - * If toggle is true, swap the allowMonsters field if possible. Otherwise, just - * return the current value. - */ - public static boolean spawnBypass(boolean toggle) - { - // Cast the world to an nmsWorld. - net.minecraft.server.World nmsWorld = ((CraftWorld) ArenaManager.world).getHandle(); - - // If not toggling, just return the current variable. - if (!toggle) - { - ArenaManager.spawnMonstersInt = nmsWorld.spawnMonsters; - return nmsWorld.allowMonsters; - } - - // If arena is running, allow monsters, otherwise don't. - if (ArenaManager.isRunning) - { - nmsWorld.allowMonsters = true; - if (ArenaManager.spawnMonstersInt == 0) - nmsWorld.spawnMonsters = 1; - } - else - { - // If the server wasn't allowing monsters, set it back to false. - if (!ArenaManager.spawnMonsters) - nmsWorld.allowMonsters = false; - nmsWorld.spawnMonsters = ArenaManager.spawnMonstersInt; - } - - return true; - } - - /** - * 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:128, grilled_pork"); - c.setProperty("classes.Archer.armor", "298,299,300,301"); - c.setProperty("classes.Knight.items", "diamond_sword, grilled_pork:2"); - c.setProperty("classes.Knight.armor", "306,307,308,309"); - c.setProperty("classes.Tank.items", "iron_sword, grilled_pork:3, apple"); - c.setProperty("classes.Tank.armor", "310,311,312,313"); - c.setProperty("classes.Oddjob.items", "stone_sword, flint_and_steel, netherrack:2, wood_pickaxe, tnt:4, 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, set some. */ - if (c.getKeys("rewards.waves." + type) == null) - { - if (type.equals("every")) - { - c.setProperty("rewards.waves.every.3", "feather, bone, stick"); - c.setProperty("rewards.waves.every.5", "dirt:4, gravel:4, stone:4"); - c.setProperty("rewards.waves.every.10", "iron_ingot:10, gold_ingot:8"); - } - else if (type.equals("after")) - { - c.setProperty("rewards.waves.after.7", "minecart, storage_minecart, powered_minecart"); - c.setProperty("rewards.waves.after.13", "iron_sword, iron_pickaxe, iron_spade"); - c.setProperty("rewards.waves.after.16", "diamon_sword"); - } - - c.save(); - } - List waves = c.getKeys("rewards.waves." + type); - - // 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) - { - return getDistribution(monster, "default"); - } - - public static int getDistribution(String monster, String type) - { - Configuration c = ArenaManager.config; - c.load(); - - if (c.getInt("waves." + type + "." + monster, -1) == -1) - { - int dist = 10; - if (monster.equals("giants") || monster.equals("ghasts")) - dist = 0; - - c.setProperty("waves." + type + "." + monster, dist); - c.save(); - } - - return c.getInt("waves." + type + "." + monster, 0); - } - - /** - * Grabs an integer from the config-file. - */ - public static int getInt(String path, int def) - { - Configuration c = ArenaManager.config; - c.load(); - - int result = c.getInt(path, def); - c.setProperty(path, result); - - c.save(); - return result; - } - - /** - * Grabs a boolean from the config-file. - */ - public static boolean getBoolean(String path, boolean def) - { - Configuration c = ArenaManager.config; - c.load(); - - boolean result = c.getBoolean(path, def); - c.setProperty(path, result); - - c.save(); - return result; - } /* ///////////////////////////////////////////////////////////////////// // - REGION AND SETUP METHODS + PET CLASS METHODS // ///////////////////////////////////////////////////////////////////// */ /** - * Checks if the Location object is within the arena region. + * Makes all nearby wolves sit if their owner is the given player. */ - public static boolean inRegion(Location loc) + public static void sitPets(Player p) { - if (!loc.getWorld().getName().equals(ArenaManager.world.getName())) + List entities = p.getNearbyEntities(80, 40, 80); + for (Entity e : entities) + { + if (!(e instanceof Wolf)) + continue; + + Wolf w = (Wolf) e; + if (w.getOwner().equals(p)) + w.setSitting(true); + } + } + + /** + * Removes all the pets belonging to this player. + */ + public static void clearPets(Arena arena, Player p) + { + for (Wolf w : arena.pets) + { + if (w.getOwner().equals(p)) + w.remove(); + } + } + + + + /* ///////////////////////////////////////////////////////////////////// // + + REGION METHODS + + // ///////////////////////////////////////////////////////////////////// */ + + /** + * Create a Location object from the config-file. + */ + public static Location getArenaCoord(Configuration config, World world, String arena, String coord) + { + //config.load(); + String str = config.getString("arenas." + arena + ".coords." + coord); + if (str == null) + return null; + return makeLocation(world, str); + } + + /** + * Save an arena location to the Configuration. + */ + public static void setArenaCoord(Configuration config, Arena arena, String coord, Location loc) + { + config.setProperty("arenas." + arena.configName() + ".coords." + coord, makeCoord(loc)); + config.save(); + arena.load(config); + + if (coord.equals("p1") || coord.equals("p2")) + fixRegion(config, loc.getWorld(), arena); + } + + public static boolean delArenaCoord(Configuration config, Arena arena, String coord) + { + if (config.getString("arenas." + arena.configName() + ".coords." + coord) == null) return false; - if (!ArenaManager.isSetup) - return false; - - 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; - + config.removeProperty("arenas." + arena.configName() + ".coords." + coord); + config.save(); + arena.load(config); return true; } - /** - * Grabs coordinate information from the config-file. - */ - public static Location getCoords(String name) + private static void fixRegion(Configuration config, World world, Arena arena) { - 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); - } - - /** - * 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(); - } - - /** - * 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) + if (arena.p1 == null || arena.p2 == null) return; - - if (p1.getX() > p2.getX()) + + if (arena.p1.getX() > arena.p2.getX()) { - tmp = p1.getX(); - p1.setX(p2.getX()); - p2.setX(tmp); + double tmp = arena.p1.getX(); + arena.p1.setX(arena.p2.getX()); + arena.p2.setX(tmp); } - if (p1.getY() > p2.getY()) + if (arena.p1.getZ() > arena.p2.getZ()) { - tmp = p1.getY(); - p1.setY(p2.getY()); - p2.setY(tmp); + double tmp = arena.p1.getZ(); + arena.p1.setZ(arena.p2.getZ()); + arena.p2.setZ(tmp); } - if (p1.getZ() > p2.getZ()) + if (arena.p1.getY() > arena.p2.getY()) { - tmp = p1.getZ(); - p1.setZ(p2.getZ()); - p2.setZ(tmp); + double tmp = arena.p1.getY(); + arena.p1.setY(arena.p2.getY()); + arena.p2.setY(tmp); } - - setCoords("p1", p1); - setCoords("p2", p2); + arena.serializeConfig(); + arena.load(config); } /** - * Expands the arena region either upwards, downwards, or - * outwards (meaning on both the X and Z axes). + * Create a Location from the input String in the input World. */ - public static void expandRegion(String direction, int i) + public static Location makeLocation(World world, String str, boolean extras) { - Location p1 = ArenaManager.p1; - Location p2 = ArenaManager.p2; + String[] parts = str.split(","); - if (direction.equals("up")) - p2.setY(p2.getY() + i); - else if (direction.equals("down")) - p1.setY(p1.getY() - i); - else if (direction.equals("out")) + double x = Double.parseDouble(parts[0]); + double y = Double.parseDouble(parts[1]); + double z = Double.parseDouble(parts[2]); + + if (extras && parts.length == 5) { - p1.setX(p1.getX() - i); - p1.setZ(p1.getZ() - i); - p2.setX(p2.getX() + i); - p2.setZ(p2.getZ() + i); + float yaw = Float.parseFloat(parts[3]); + float pitch = Float.parseFloat(parts[4]); + return new Location(world, x, y, z, yaw, pitch); } - - setCoords("p1", p1); - setCoords("p2", p2); - fixCoords(); + + return new Location(world, x, y, z); } - public static String spawnList() + public static Location makeLocation(World world, String str) { - Configuration c = ArenaManager.config; - c.load(); + return makeLocation(world, str, true); + } + + /** + * Create a location String from the input Location. + */ + public static String makeCoord(Location loc, boolean extras) + { + int x = loc.getBlockX(); + int y = loc.getBlockY(); + int z = loc.getBlockZ(); - String result = ""; - if (c.getKeys("coords.spawnpoints") == null) - return result; + if (extras) + { + float yaw = loc.getYaw(); + float pitch = loc.getPitch(); + return x + "," + y + "," + z + "," + yaw + "," + pitch; + } - for (String s : c.getKeys("coords.spawnpoints")) - result += s + " "; + return x + "," + y + "," + z; + } + + public static String makeCoord(Location loc) + { + return makeCoord(loc, true); + } + + /** + * Check if a location is within any arena region. + */ + public static boolean inRegions(Location loc, Arena... arenas) + { + for (Arena arena : arenas) + { + if (arena.inRegion(loc)) + return true; + } - return result; + return false; } @@ -617,52 +729,173 @@ public class MAUtils // ///////////////////////////////////////////////////////////////////// */ /** - * 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. + * Sends a message to a player. */ - public static boolean verifyData() + public static boolean tellPlayer(CommandSender p, String msg) { - 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.dWolves != -1) && - (ArenaManager.classes.size() > 0) && - (ArenaManager.spawnpoints.size() > 0)); - } - - /** - * Notifies the player if MobArena is set up and ready to be used. - */ - public static void notifyIfSetup(Player p) - { - if (verifyData()) - { - ArenaManager.tellPlayer(p, "MobArena is set up and ready to roll!"); - } + if (p == null) + return false; + + p.sendMessage(ChatColor.GREEN + "[MobArena] " + ChatColor.WHITE + msg); + return true; } + /** + * Sends a message to all players in and around the arena. + */ + public static void tellAll(Arena arena, String msg) + { + Set tmp = new HashSet(); + tmp.addAll(arena.livePlayers); + tmp.addAll(arena.deadPlayers); + tmp.addAll(arena.specPlayers); + tmp.addAll(arena.readyPlayers); + for (Player p : tmp) + tellPlayer(p, msg); + } + + public static Player getClosestPlayer(Entity e, Arena arena) + { + // Set up the comparison variable and the result. + double current = Double.POSITIVE_INFINITY; + Player result = null; + + /* Iterate through the ArrayList, and update current and result every + * time a squared distance smaller than current is found. */ + for (Player p : arena.livePlayers) + { + double dist = p.getLocation().distanceSquared(e.getLocation()); //distance(p.getLocation(), e.getLocation()); + if (dist < current && dist < 256) + { + current = dist; + result = p; + } + } + return result; + } + + public static double distance(Location loc1, Location loc2) + { + double x = loc1.getX() - loc2.getX(); + double y = loc1.getY() - loc2.getY(); + double z = loc1.getZ() - loc2.getZ(); + + return x*x + y*y + z*z; + } + + /** + * Convert a proper arena name to a config-file name. + * All spaces are replaced by underscores, and the whole String is + * lowercased. + */ + public static String nameArenaToConfig(String name) + { + String tmp = name.replace(" ", "_"); + return tmp.toLowerCase(); + } + + /** + * Convert a config-name to a proper spaced and capsed arena name. + * The input String is split around all underscores, and every part + * of the String array is properly capsed. + */ + public static String nameConfigToArena(String name) + { + String[] parts = name.split("_"); + if (parts.length == 1) + return toCamelCase(parts[0]); + + String separator = " "; + StringBuffer buffy = new StringBuffer(name.length()); + for (String part : parts) + { + buffy.append(toCamelCase(part)); + buffy.append(separator); + } + buffy.replace(buffy.length()-1, buffy.length(), ""); + + return buffy.toString(); + } + + /** + * Returns the input String with a capital first letter, and all the + * other letters become lower case. + */ + public static String toCamelCase(String name) + { + return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase(); + } + + /** + * Turn a list into a space-separated list. + */ + public static String listToString(List list) + { + StringBuffer buffy = new StringBuffer(); + for (E e : list) + { + buffy.append(e.toString()); + buffy.append(" "); + } + return buffy.toString(); + } + + /** + * Returns a String-list version of a comma-separated list. + */ + public static List stringToList(String list) + { + List result = new LinkedList(); + if (list == null) return result; + + String[] parts = list.trim().split(","); + + for (String part : parts) + result.add(part.trim()); + + return result; + } + + /** + * Turns the current set of players into an array, and grabs a random + * element out of it. + */ + public static Player getRandomPlayer(Arena arena) + { + Random random = new Random(); + Player[] array = (Player[]) arena.livePlayers.toArray(); + return array[random.nextInt(array.length)]; + } + + /** + * Verifies that all important variables are declared. Returns true + * if, and only if, the warppoints, region, distribution coefficients, + * and spawnpoints are all set up. + */ + public static boolean verifyData(Arena arena) + { + return ((arena.arenaLoc != null) && + (arena.lobbyLoc != null) && + (arena.spectatorLoc != null) && + (arena.p1 != null) && + (arena.p2 != null) && + (arena.spawnpoints.size() > 0)); + } + /** * Checks if there is a new update of MobArena and notifies the * player if the boolean specified is true */ - public static void checkForUpdates(final Player p, boolean response) + public static void checkForUpdates(MobArena plugin, final Player p, boolean response) { String site = "http://forums.bukkit.org/threads/818.19144/"; try { - // Make a URL of the site address - //URL baseURL = new URL(site); - URI baseURL = new URI(site); + // Make a URI of the site address + URI baseURI = new URI(site); // Open the connection and don't redirect. - HttpURLConnection con = (HttpURLConnection) baseURL.toURL().openConnection(); + HttpURLConnection con = (HttpURLConnection) baseURI.toURL().openConnection(); con.setInstanceFollowRedirects(false); String header = con.getHeaderField("Location"); @@ -670,7 +903,7 @@ public class MAUtils // If something's wrong with the connection... if (header == null) { - ArenaManager.tellPlayer(p, "Couldn't connect to the MobArena thread."); + tellPlayer(p, "Couldn't connect to the MobArena thread."); return; } @@ -678,17 +911,17 @@ public class MAUtils String url = new URI(con.getHeaderField("Location")).toString(); // If the current version is the same as the thread version. - if (url.contains(ArenaManager.plugin.getDescription().getVersion().replace(".", "-"))) + if (url.contains(plugin.getDescription().getVersion().replace(".", "-"))) { if (!response) return; - ArenaManager.tellPlayer(p, "Your version of MobArena is up to date!"); + tellPlayer(p, "Your version of MobArena is up to date!"); return; } // Otherwise, notify the player that there is a new version. - ArenaManager.tellPlayer(p, "There is a new version of MobArena available!");; + tellPlayer(p, "There is a new version of MobArena available!");; } catch (Exception e) { @@ -696,22 +929,35 @@ public class MAUtils } } - /** - * Turns the current set of players into an array, and grabs a random - * element out of it. - */ - public static Player getRandomPlayer() + public static void setSpawnFlags(MobArena plugin, World world, int spawnMonsters, boolean allowMonsters, boolean allowAnimals) { - Random random = new Random(); - Object[] array = ArenaManager.playerSet.toArray(); - return (Player) array[random.nextInt(array.length)]; + for (Arena arena : plugin.getAM().getArenasInWorld(world)) + if (arena.running) + return; + + WorldServer ws = ((CraftWorld) world).getHandle(); + ws.spawnMonsters = spawnMonsters; + ws.allowMonsters = allowMonsters; + ws.allowAnimals = allowAnimals; } /** * Stand back, I'm going to try science! */ - public static void DoooooItHippieMonster(Location loc, int radius) + public static boolean doooooItHippieMonster(Location loc, int radius, String name, MobArena plugin) { + // Try to restore the old patch first. + undoItHippieMonster(name, plugin, false); + + // Grab the Configuration and ArenaMaster + ArenaMaster am = plugin.getAM(); + + // Create the arena node in the config-file. + World world = loc.getWorld(); + Arena arena = am.createArenaNode(name, world); + am.arenas.add(arena); + am.selectedArena = arena; + // Get the hippie bounds. int x1 = (int)loc.getX() - radius; int x2 = (int)loc.getX() + radius; @@ -721,9 +967,9 @@ public class MAUtils int z2 = (int)loc.getZ() + radius; int lx1 = x1; - int lx2 = x1 + ArenaManager.classes.size() + 3; - int ly1 = y1-5; - int ly2 = y1-1; + int lx2 = x1 + am.classes.size() + 3; + int ly1 = y1-6; + int ly2 = y1-2; int lz1 = z1; int lz2 = z1 + 6; @@ -737,24 +983,25 @@ public class MAUtils { for (int k = z1; k <= z2; k++) { - lo = ArenaManager.world.getBlockAt(i,j,k).getLocation(); - id = ArenaManager.world.getBlockAt(i,j,k).getTypeId(); + lo = world.getBlockAt(i,j,k).getLocation(); + id = world.getBlockAt(i,j,k).getTypeId(); preciousPatch.put(new EntityPosition(lo),id); } } } try { - FileOutputStream fos = new FileOutputStream("plugins/MobArena/precious.tmp"); + new File("plugins" + sep + "MobArena" + sep + "agbackup").mkdir(); + FileOutputStream fos = new FileOutputStream("plugins" + sep + "MobArena" + sep + "agbackup" + sep + name + ".tmp"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(preciousPatch); oos.close(); } catch (Exception e) { - System.out.println("Couldn't create backup file. Aborting..."); e.printStackTrace(); - return; + System.out.println("Couldn't create backup file. Aborting auto-generate..."); + return false; } // Build some monster walls. @@ -762,132 +1009,148 @@ public class MAUtils { for (int j = y1; j <= y2; j++) { - ArenaManager.world.getBlockAt(i,j,z1).setTypeId(24); - ArenaManager.world.getBlockAt(i,j,z2).setTypeId(24); + world.getBlockAt(i,j,z1).setTypeId(24); + world.getBlockAt(i,j,z2).setTypeId(24); } } for (int k = z1; k <= z2; k++) { for (int j = y1; j <= y2; j++) { - ArenaManager.world.getBlockAt(x1,j,k).setTypeId(24); - ArenaManager.world.getBlockAt(x2,j,k).setTypeId(24); + world.getBlockAt(x1,j,k).setTypeId(24); + world.getBlockAt(x2,j,k).setTypeId(24); } } // Add some hippie light. for (int i = x1; i <= x2; i++) { - ArenaManager.world.getBlockAt(i,y1+2,z1).setTypeId(89); - ArenaManager.world.getBlockAt(i,y1+2,z2).setTypeId(89); + world.getBlockAt(i,y1+2,z1).setTypeId(89); + world.getBlockAt(i,y1+2,z2).setTypeId(89); } for (int k = z1; k <= z2; k++) { - ArenaManager.world.getBlockAt(x1,y1+2,k).setTypeId(89); - ArenaManager.world.getBlockAt(x2,y1+2,k).setTypeId(89); + world.getBlockAt(x1,y1+2,k).setTypeId(89); + world.getBlockAt(x2,y1+2,k).setTypeId(89); } - // Build a monster floor. + // Build a monster floor, and some Obsidian foundation. for (int i = x1; i <= x2; i++) { for (int k = z1; k <= z2; k++) - ArenaManager.world.getBlockAt(i,y1,k).setTypeId(24); + { + world.getBlockAt(i,y1,k).setTypeId(24); + world.getBlockAt(i,y1-1,k).setTypeId(49); + } } // Make a hippie roof. for (int i = x1; i <= x2; i++) { for (int k = z1; k <= z2; k++) - ArenaManager.world.getBlockAt(i,y2,k).setTypeId(20); + world.getBlockAt(i,y2,k).setTypeId(20); } // Monster bulldoze for (int i = x1+1; i < x2; i++) for (int j = y1+1; j < y2; j++) for (int k = z1+1; k < z2; k++) - ArenaManager.world.getBlockAt(i,j,k).setTypeId(0); + world.getBlockAt(i,j,k).setTypeId(0); // Build a hippie lobby for (int i = lx1; i <= lx2; i++) // Walls { for (int j = ly1; j <= ly2; j++) { - ArenaManager.world.getBlockAt(i,j,lz1).setTypeId(24); - ArenaManager.world.getBlockAt(i,j,lz2).setTypeId(24); + world.getBlockAt(i,j,lz1).setTypeId(24); + world.getBlockAt(i,j,lz2).setTypeId(24); } } for (int k = lz1; k <= lz2; k++) // Walls { for (int j = ly1; j <= ly2; j++) { - ArenaManager.world.getBlockAt(lx1,j,k).setTypeId(24); - ArenaManager.world.getBlockAt(lx2,j,k).setTypeId(24); + world.getBlockAt(lx1,j,k).setTypeId(24); + world.getBlockAt(lx2,j,k).setTypeId(24); } } for (int k = lz1; k <= lz2; k++) // Lights { - ArenaManager.world.getBlockAt(lx1,ly1+2,k).setTypeId(89); - ArenaManager.world.getBlockAt(lx2,ly1+2,k).setTypeId(89); - ArenaManager.world.getBlockAt(lx1,ly1+3,k).setTypeId(89); - ArenaManager.world.getBlockAt(lx2,ly1+3,k).setTypeId(89); + world.getBlockAt(lx1,ly1+2,k).setTypeId(89); + world.getBlockAt(lx2,ly1+2,k).setTypeId(89); + world.getBlockAt(lx1,ly1+3,k).setTypeId(89); + world.getBlockAt(lx2,ly1+3,k).setTypeId(89); } for (int i = lx1; i <= lx2; i++) // Floor { for (int k = lz1; k <= lz2; k++) - ArenaManager.world.getBlockAt(i,ly1,k).setTypeId(24); + world.getBlockAt(i,ly1,k).setTypeId(24); } for (int i = x1+1; i < lx2; i++) // Bulldoze for (int j = ly1+1; j <= ly2; j++) for (int k = lz1+1; k < lz2; k++) - ArenaManager.world.getBlockAt(i,j,k).setTypeId(0); + world.getBlockAt(i,j,k).setTypeId(0); // Place the hippie signs - Iterator iterator = ArenaManager.classes.iterator(); + Iterator iterator = am.classes.iterator(); for (int i = lx1+2; i <= lx2-2; i++) // Signs { - ArenaManager.world.getBlockAt(i,ly1+1,lz2-1).setTypeIdAndData(63, (byte)0x8, false); - Sign sign = (Sign) ArenaManager.world.getBlockAt(i,ly1+1,lz2-1).getState(); + world.getBlockAt(i,ly1+1,lz2-1).setTypeIdAndData(63, (byte)0x8, false); + Sign sign = (Sign) world.getBlockAt(i,ly1+1,lz2-1).getState(); sign.setLine(0, (String)iterator.next()); } - ArenaManager.world.getBlockAt(lx2-2,ly1+1,lz1+2).setType(Material.IRON_BLOCK); + world.getBlockAt(lx2-2,ly1+1,lz1+2).setType(Material.IRON_BLOCK); - // Set up the monster points. - setCoords("arena", new Location(ArenaManager.world, loc.getX(), y1+1, loc.getZ())); - setCoords("lobby", new Location(ArenaManager.world, x1+2, y1-3, z1+2)); - setCoords("spectator", new Location(ArenaManager.world, loc.getX(), y2+1, loc.getZ())); - setCoords("p1", new Location(ArenaManager.world, x1, y1-4, z1)); - setCoords("p2", new Location(ArenaManager.world, x2, y2+1, z2)); - setCoords("spawnpoints.s1", new Location(ArenaManager.world, x1+3, y1+2, z1+3)); - setCoords("spawnpoints.s2", new Location(ArenaManager.world, x1+3, y1+2, z2-3)); - setCoords("spawnpoints.s3", new Location(ArenaManager.world, x2-3, y1+2, z1+3)); - setCoords("spawnpoints.s4", new Location(ArenaManager.world, x2-3, y1+2, z2-3)); + // Set up the monster points. + MAUtils.setArenaCoord(plugin.getConfig(), arena, "p1", new Location(world, x1, ly1, z1)); + MAUtils.setArenaCoord(plugin.getConfig(), arena, "p2", new Location(world, x2, y2+1, z2)); + MAUtils.setArenaCoord(plugin.getConfig(), arena, "arena", new Location(world, loc.getX(), y1+1, loc.getZ())); + MAUtils.setArenaCoord(plugin.getConfig(), arena, "lobby", new Location(world, x1+2, y1-3, z1+2)); + MAUtils.setArenaCoord(plugin.getConfig(), arena, "spectator", new Location(world, loc.getX(), y2+1, loc.getZ())); + MAUtils.setArenaCoord(plugin.getConfig(), arena, "spawnpoints.s1", new Location(world, x1+3, y1+2, z1+3)); + MAUtils.setArenaCoord(plugin.getConfig(), arena, "spawnpoints.s2", new Location(world, x1+3, y1+2, z2-3)); + MAUtils.setArenaCoord(plugin.getConfig(), arena, "spawnpoints.s3", new Location(world, x2-3, y1+2, z1+3)); + MAUtils.setArenaCoord(plugin.getConfig(), arena, "spawnpoints.s4", new Location(world, x2-3, y1+2, z2-3)); + + am.updateAll(); + return true; } /** * This fixes everything! */ @SuppressWarnings("unchecked") - public static void UnDoooooItHippieMonster() + public static boolean undoItHippieMonster(String name, MobArena plugin, boolean error) { + File file = new File("plugins" + sep + "MobArena" + sep + "agbackup" + sep + name + ".tmp"); HashMap preciousPatch; try { - FileInputStream fis = new FileInputStream("plugins/MobArena/precious.tmp"); + FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis); preciousPatch = (HashMap) ois.readObject(); ois.close(); } catch (Exception e) { - System.out.println("Couldn't find backup file..."); - return; + if (error) System.out.println("Couldn't find backup file for arena '" + name + "'"); + return false; } - for (EntityPosition ep : preciousPatch.keySet()) + World world = Bukkit.getServer().getWorld(preciousPatch.keySet().iterator().next().getWorld()); + + for (Map.Entry entry : preciousPatch.entrySet()) { - ArenaManager.world.getBlockAt(ep.getLocation(ArenaManager.world)).setTypeId(preciousPatch.get(ep)); + world.getBlockAt(entry.getKey().getLocation(world)).setTypeId(entry.getValue()); } - delCoords("coords"); + Configuration config = plugin.getConfig(); + config.removeProperty("arenas." + name); + config.save(); + + file.delete(); + + plugin.getAM().updateAll(); + return true; } } \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MobArena.java b/src/com/garbagemule/MobArena/MobArena.java index 768d859..81df684 100644 --- a/src/com/garbagemule/MobArena/MobArena.java +++ b/src/com/garbagemule/MobArena/MobArena.java @@ -1,6 +1,6 @@ package com.garbagemule.MobArena; -import java.util.List; +import java.io.File; import org.bukkit.event.Event; import org.bukkit.event.Event.Priority; @@ -10,21 +10,17 @@ import org.bukkit.event.entity.EntityListener; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.PluginManager; +import org.bukkit.util.config.Configuration; /** * MobArena - * * @author garbagemule */ public class MobArena extends JavaPlugin -{ - /* Array of commands used to determine if a command belongs to MobArena - * or Mean Admins. */ - public final String[] COMMANDS = {"join", "j", "leave", "l", "list", "who", "spectate", "spec", - "ready", "notready", "enabled", "force", "config", "setwarp", - "addspawn", "delspawn", "setregion", "expandregion", "protect", - "undo", "dooooo", "reset"}; - public List DISABLED_COMMANDS; +{ + /* Other useful variables. */ + private Configuration config; + private ArenaMaster am; public MobArena() { @@ -34,52 +30,204 @@ public class MobArena extends JavaPlugin { PluginDescriptionFile pdfFile = this.getDescription(); - // Initialize convenience variables in ArenaManager. - ArenaManager.init(this); - DISABLED_COMMANDS = MAUtils.getDisabledCommands(); + loadConfig(); + MAMessages.init(this); + am = new ArenaMaster(this); + am.initialize(); - // Bind the /ma and /marena commands to MACommands. - getCommand("ma").setExecutor(new MACommands()); - getCommand("marena").setExecutor(new MACommands()); - getCommand("mobarena").setExecutor(new MACommands()); + // Bind the /ma, /marena, and /mobarena commands to MACommands. + MACommands commandExecutor = new MACommands(this, am); + getCommand("ma").setExecutor(commandExecutor); + getCommand("marena").setExecutor(commandExecutor); + getCommand("mobarena").setExecutor(commandExecutor); - - // Create event listeners. PluginManager pm = getServer().getPluginManager(); - PlayerListener commandListener = new MADisabledCommands(this); - PlayerListener lobbyListener = new MALobbyListener(this); - PlayerListener teleportListener = new MATeleportListener(this); - PlayerListener discListener = new MADisconnectListener(this); - BlockListener blockListener = new MABlockListener(this); - EntityListener deathListener = new MADeathListener(this); - EntityListener monsterListener = new MAMonsterListener(this); - // TO-DO: PlayerListener to check for kills/deaths. + PlayerListener playerListener = new MAPlayerListener(this, am); + EntityListener entityListener = new MAEntityListener(am); + BlockListener blockListener = new MABlockListener(am); // Register events. - pm.registerEvent(Event.Type.PLAYER_COMMAND_PREPROCESS, commandListener, Priority.Monitor, this); - pm.registerEvent(Event.Type.PLAYER_INTERACT, lobbyListener, Priority.Normal, this); - pm.registerEvent(Event.Type.PLAYER_DROP_ITEM, lobbyListener, Priority.Normal, this); - pm.registerEvent(Event.Type.PLAYER_BUCKET_EMPTY, lobbyListener, Priority.Normal, this); - pm.registerEvent(Event.Type.PLAYER_TELEPORT, teleportListener, Priority.Normal, this); - pm.registerEvent(Event.Type.PLAYER_QUIT, discListener, Priority.Normal, this); - pm.registerEvent(Event.Type.PLAYER_KICK, discListener, Priority.Normal, this); - pm.registerEvent(Event.Type.PLAYER_JOIN, discListener, Priority.Normal, this); - pm.registerEvent(Event.Type.BLOCK_BREAK, blockListener, Priority.Normal, this); - pm.registerEvent(Event.Type.BLOCK_PLACE, blockListener, Priority.Normal, this); - pm.registerEvent(Event.Type.ENTITY_DEATH, deathListener, Priority.Lowest, this); // Lowest because of Tombstone - pm.registerEvent(Event.Type.ENTITY_EXPLODE, monsterListener, Priority.Normal, this); - pm.registerEvent(Event.Type.ENTITY_COMBUST, monsterListener, Priority.Normal, this); - pm.registerEvent(Event.Type.ENTITY_TARGET, monsterListener, Priority.Normal, this); - pm.registerEvent(Event.Type.CREATURE_SPAWN, monsterListener, Priority.Normal, this); + pm.registerEvent(Event.Type.PLAYER_INTERACT, playerListener, Priority.Normal, this); + pm.registerEvent(Event.Type.PLAYER_DROP_ITEM, playerListener, Priority.Normal, this); + pm.registerEvent(Event.Type.PLAYER_BUCKET_EMPTY, playerListener, Priority.Normal, this); + pm.registerEvent(Event.Type.PLAYER_TELEPORT, playerListener, Priority.Normal, this); + pm.registerEvent(Event.Type.PLAYER_QUIT, playerListener, Priority.Normal, this); + pm.registerEvent(Event.Type.PLAYER_KICK, playerListener, Priority.Normal, this); + pm.registerEvent(Event.Type.PLAYER_JOIN, playerListener, Priority.Normal, this); + pm.registerEvent(Event.Type.BLOCK_BREAK, blockListener, Priority.Highest, this); + pm.registerEvent(Event.Type.BLOCK_PLACE, blockListener, Priority.Highest, this); + pm.registerEvent(Event.Type.ENTITY_DAMAGE, entityListener, Priority.Highest, this); + pm.registerEvent(Event.Type.ENTITY_DEATH, entityListener, Priority.Lowest, this); // Lowest because of Tombstone + pm.registerEvent(Event.Type.ENTITY_REGAIN_HEALTH, entityListener, Priority.Normal, this); + pm.registerEvent(Event.Type.ENTITY_EXPLODE, entityListener, Priority.Highest, this); + pm.registerEvent(Event.Type.ENTITY_COMBUST, entityListener, Priority.Normal, this); + pm.registerEvent(Event.Type.ENTITY_TARGET, entityListener, Priority.Normal, this); + pm.registerEvent(Event.Type.CREATURE_SPAWN, entityListener, Priority.Highest, this); + pm.registerEvent(Event.Type.PLAYER_COMMAND_PREPROCESS, playerListener, Priority.Monitor, this); - System.out.println(pdfFile.getName() + " v" + pdfFile.getVersion() + " enabled." ); + System.out.println("[MobArena] v" + pdfFile.getVersion() + " enabled." ); } public void onDisable() - { - System.out.println("WAIT! WHAT ARE YOU DOING?!"); + { + for (Arena arena : am.arenas) + arena.forceEnd(); + am.arenaMap.clear(); - ArenaManager.forceEnd(null); + System.out.println("[MobArena] disabled."); + } + + /** + * Load the config-file and initialize the Configuration object. + */ + private void loadConfig() + { + File file = new File(this.getDataFolder(), "config.yml"); + if (!file.exists()) + { + try + { + this.getDataFolder().mkdir(); + file.createNewFile(); + } + catch (Exception e) + { + e.printStackTrace(); + return; + } + } + // TODO: Remove in v1.0 + else + { + Configuration tmp = new Configuration(file); + tmp.load(); + if (tmp.getKeys("global-settings") == null) + { + file.renameTo(new File(this.getDataFolder(), "config_OLD.yml")); + file = new File(this.getDataFolder(), "config.yml"); + try + { + this.getDataFolder().mkdir(); + file.createNewFile(); + } + catch (Exception e) + { + e.printStackTrace(); + return; + } + + config = new Configuration(file); + config.load(); + fixConfig(); + config.setHeader("# MobArena Configuration-file\r\n# Please go to https://github.com/garbagemule/MobArena/wiki/Installing-MobArena for more details."); + config.save(); + } + } + + config = new Configuration(file); + config.load(); + config.setHeader("# MobArena Configuration-file\r\n# Please go to https://github.com/garbagemule/MobArena/wiki/Installing-MobArena for more details."); + config.save(); + } + + public Configuration getConfig() { return config; } + public ArenaMaster getAM() { return am; } // More convenient. + public ArenaMaster getArenaMaster() { return am; } + + // TODO: Remove in v1.0 + private void fixConfig() + { + // If global-settings is sorted, don't do anything. + if (config.getKeys("global-settings") != null) + return; + + File oldFile = new File(this.getDataFolder(), "config_OLD.yml"); + if (!oldFile.exists()) + return; + + System.out.println("[MobArena] Config-file appears to be old. Trying to fix it..."); + + Configuration oldConfig = new Configuration(oldFile); + oldConfig.load(); + + config.setProperty("global-settings.enabled", true); + config.save(); + config.load(); + config.setProperty("global-settings.update-notification", true); + config.save(); + config.load(); + config.setProperty("global-settings.repair-delay", 5); + config.save(); + config.load(); + + // Copy classes + for (String s : oldConfig.getKeys("classes")) + { + config.setProperty("classes." + s + ".items", oldConfig.getString("classes." + s + ".items")); + config.setProperty("classes." + s + ".armor", oldConfig.getString("classes." + s + ".armor")); + } + config.save(); + + // Make the default arena node. + config.setProperty("arenas.default.settings.enabled", true); + config.save(); + config.load(); + config.setProperty("arenas.default.settings.world", oldConfig.getString("settings.world")); + config.save(); + config.load(); + + // Copy the waves and rewards to the new default arena. + for (String s : oldConfig.getKeys("waves.default")) + config.setProperty("arenas.default.waves.default." + s, oldConfig.getString("waves.default." + s)); + config.save(); + for (String s : oldConfig.getKeys("rewards.waves.every")) + config.setProperty("arenas.default.rewards.waves.every." + s, oldConfig.getString("rewards.waves.every." + s)); + for (String s : oldConfig.getKeys("rewards.waves.after")) + config.setProperty("arenas.default.rewards.waves.after." + s, oldConfig.getString("rewards.waves.after." + s)); + config.save(); + config.load(); + + // Copy the coords. + for (String s : oldConfig.getKeys("coords")) + { + if (s.equals("spawnpoints")) + continue; + + StringBuffer buffy = new StringBuffer(); + buffy.append(oldConfig.getString("coords." + s + ".x")); + buffy.append(","); + buffy.append(oldConfig.getString("coords." + s + ".y")); + buffy.append(","); + buffy.append(oldConfig.getString("coords." + s + ".z")); + buffy.append(","); + buffy.append(oldConfig.getString("coords." + s + ".yaw")); + buffy.append(","); + buffy.append(oldConfig.getString("coords." + s + ".pitch")); + + config.setProperty("arenas.default.coords." + s, buffy.toString()); + } + config.save(); + config.load(); + + for (String s : oldConfig.getKeys("coords.spawnpoints")) + { + StringBuffer buffy = new StringBuffer(); + buffy.append(oldConfig.getString("coords.spawnpoints." + s + ".x")); + buffy.append(","); + buffy.append(oldConfig.getString("coords.spawnpoints." + s + ".y")); + buffy.append(","); + buffy.append(oldConfig.getString("coords.spawnpoints." + s + ".z")); + buffy.append(","); + buffy.append(oldConfig.getString("coords.spawnpoints." + s + ".yaw")); + buffy.append(","); + buffy.append(oldConfig.getString("coords.spawnpoints." + s + ".pitch")); + + config.setProperty("arenas.default.coords.spawnpoints." + s, buffy.toString()); + } + config.save(); + config.load(); + + System.out.println("[MobArena] Updated the config-file!"); } } \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/MobArenaHandler.java b/src/com/garbagemule/MobArena/MobArenaHandler.java index e204808..cbdfb48 100644 --- a/src/com/garbagemule/MobArena/MobArenaHandler.java +++ b/src/com/garbagemule/MobArena/MobArenaHandler.java @@ -3,32 +3,121 @@ package com.garbagemule.MobArena; import java.util.List; import java.util.LinkedList; +import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; public class MobArenaHandler { + MobArena plugin; + public MobArenaHandler() { + plugin = (MobArena) Bukkit.getServer().getPluginManager().getPlugin("MobArena"); } // Check if there is an active arena session running. - public boolean isRunning() { return ArenaManager.isRunning; } + public boolean isRunning(String arenaName) + { + Arena arena = plugin.getAM().getArenaWithName(arenaName); + if (arena == null) + throw new NullPointerException("Arena with name '" + arenaName + "' does not exist!"); + + return arena.running; + } - // Check if the specified player is in the arena/lobby. - public boolean isPlaying(Player p) { return ArenaManager.playerSet.contains(p); } + // Check if the specified player is in an arena. + public boolean isPlaying(Player p) { return (plugin.getAM().getArenaWithPlayer(p) != null); } - // Get a list of all players currently in the arena. - public List getPlayers() { return new LinkedList(ArenaManager.playerSet); } + // Arena getters + public Arena getArenaWithName(String arenaName) { return plugin.getAM().getArenaWithName(arenaName); } + public Arena getArenaWithPlayer(Player p) { return plugin.getAM().getArenaWithPlayer(p); } + public Arena getArenaWithPlayer(String player) { return plugin.getAM().getArenaWithPlayer(player); } + public Arena getArenaWithPet(Entity wolf) { return plugin.getAM().getArenaWithPet(wolf); } + public Arena getArenaWithMonster(Entity monster) { return plugin.getAM().getArenaWithMonster(monster); } + public Arena getArenaInLocation(Location l) { return plugin.getAM().getArenaInLocation(l); } - // Get the warp locations. - public Location getArenaLocation() { return ArenaManager.arenaLoc; } - public Location getLobbyLocation() { return ArenaManager.lobbyLoc; } - public Location getSpectatorLocation() { return ArenaManager.spectatorLoc; } + // Player lists + public List getAllPlayers() { return new LinkedList(plugin.getAM().arenaMap.keySet()); } + public List getAllPlayers(Arena arena) { return arena.getAllPlayers(); } + public List getAllPlayers(String arenaName) { return getAllPlayers(plugin.getAM().getArenaWithName(arenaName)); } + + public List getLivingPlayers() + { + List result = new LinkedList(); + for (Arena arena : plugin.getAM().arenas) + result.addAll(arena.getLivingPlayers()); + return result; + } + + public List getLivingPlayers(String arenaName) + { + Arena arena = plugin.getAM().getArenaWithName(arenaName); + if (arena == null) + throw new NullPointerException("Arena with name '" + arenaName + "' does not exist!"); + + return new LinkedList(arena.livePlayers); + } + + // Warp locations. + public Location getArenaLocation(String arenaName) + { + Arena arena = plugin.getAM().getArenaWithName(arenaName); + if (arena == null) + throw new NullPointerException("Arena with name '" + arenaName + "' does not exist!"); + + return arena.arenaLoc; + } + public Location getLobbyLocation(String arenaName) + { + Arena arena = plugin.getAM().getArenaWithName(arenaName); + if (arena == null) + throw new NullPointerException("Arena with name '" + arenaName + "' does not exist!"); + + return arena.lobbyLoc; + } + public Location getSpectatorLocation(String arenaName) + { + Arena arena = plugin.getAM().getArenaWithName(arenaName); + if (arena == null) + throw new NullPointerException("Arena with name '" + arenaName + "' does not exist!"); + + return arena.spectatorLoc; + } // Get the current wave number. - public int getWave() { return ArenaManager.wave; } + public int getWave(Arena arena) { return arena.spawnThread.wave; } - // Check if a location is in the arena region - public boolean inRegion(Location l) { return MAUtils.inRegion(l); } + public int getWave(String arenaName) + { + Arena arena = plugin.getAM().getArenaWithName(arenaName); + if (arena == null) + throw new NullPointerException("Arena with name '" + arenaName + "' does not exist!"); + + if (arena.spawnThread == null) + throw new NullPointerException("Arena with name '" + arenaName + "' has not started!"); + + return arena.spawnThread.wave; + } + + // Check if a location is within any arena regions. + + public boolean inRegion(Location l, Arena arena) { return arena.inRegion(l); } + + public boolean inRegion(Location l, String arenaName) + { + Arena arena = plugin.getAM().getArenaWithName(arenaName); + if (arena == null) + throw new NullPointerException("Arena with name '" + arenaName + "' does not exist!"); + + return arena.inRegion(l); + } + public boolean inRegion(Location l) + { + for (Arena arena : plugin.getAM().arenas) + if (arena.inRegion(l)) + return true; + return false; + } } diff --git a/src/com/garbagemule/MobArena/MobArenaListener.java b/src/com/garbagemule/MobArena/MobArenaListener.java index a71e8bf..1613774 100644 --- a/src/com/garbagemule/MobArena/MobArenaListener.java +++ b/src/com/garbagemule/MobArena/MobArenaListener.java @@ -1,5 +1,6 @@ package com.garbagemule.MobArena; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; public class MobArenaListener @@ -7,9 +8,9 @@ public class MobArenaListener protected MobArena plugin; public MobArenaListener() - { - plugin = ArenaManager.plugin; - ArenaManager.listeners.add(this); + { + plugin = (MobArena) Bukkit.getServer().getPluginManager().getPlugin("MobArena"); + plugin.getAM().listeners.add(this); } public void onArenaStart() {}