From 53c70564fdb35c615848bd4286cee3c9fb6ae850 Mon Sep 17 00:00:00 2001 From: Garbage Mule Date: Mon, 4 Jul 2011 17:55:31 +0200 Subject: [PATCH] v0.92 - Added multiple arenas, pet classes, Permissions, etc. and rewrote most of the code --- MobArena.jar | Bin 45208 -> 75763 bytes bin/plugin.yml | 11 +- src/com/garbagemule/MobArena/Arena.java | 1193 ++++++++++++++ src/com/garbagemule/MobArena/ArenaMaster.java | 366 +++++ src/com/garbagemule/MobArena/Lobby.java | 26 + .../garbagemule/MobArena/MABlockListener.java | 62 +- src/com/garbagemule/MobArena/MACommands.java | 1098 ++++++++++--- .../MobArena/MAEntityListener.java | 65 + .../garbagemule/MobArena/MAInventoryItem.java | 22 + src/com/garbagemule/MobArena/MAMessages.java | 232 +++ .../MobArena/MAPlayerListener.java | 87 + .../garbagemule/MobArena/MASpawnThread.java | 244 +-- src/com/garbagemule/MobArena/MAUtils.java | 1395 ++++++++++------- src/com/garbagemule/MobArena/MobArena.java | 258 ++- .../garbagemule/MobArena/MobArenaHandler.java | 97 +- .../MobArena/MobArenaListener.java | 7 +- 16 files changed, 4142 insertions(+), 1021 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..7254bd6c37433adc8b8db94a60de112c826f0146 100644 GIT binary patch delta 73434 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 EFE_pz%m4rY delta 42657 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 S0s 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(plugin, this); + spawnTaskId = Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, spawnThread, waveDelay, waveInterval); + + readyPlayers.clear(); + MAUtils.tellAll(this, MAMessages.get(Msg.ARENA_START)); + + // Notify listeners. + for (MobArenaListener listener : plugin.getAM().listeners) + listener.onArenaStart(); + } + + /** + * 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 (!emptyInvJoin) + 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(); + 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)); + + // Notify listeners. + for (MobArenaListener listener : plugin.getAM().listeners) + listener.onArenaEnd(); + } + + /** + * 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); + + // Notify listeners. + for (MobArenaListener listener : plugin.getAM().listeners) + listener.onPlayerJoin(p); + } + + /** + * 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; + removePets(p); + + if (clear) MAUtils.clearInventory(p); + if (!emptyInvJoin) MAUtils.restoreInventory(p); + + if (running && livePlayers.isEmpty()) + endArena(); + else if (!readyPlayers.isEmpty() && readyPlayers.equals(livePlayers)) + startArena(); + + // Notify listeners. + for (MobArenaListener listener : plugin.getAM().listeners) + listener.onPlayerLeave(p); + } + + public void playerQuit(Player p) + { + p.teleport(locations.get(p)); + readyPlayers.remove(p); + livePlayers.remove(p); + deadPlayers.remove(p); + specPlayers.remove(p); + removePets(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); + removePets(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); + + // Notify listeners. + for (MobArenaListener listener : plugin.getAM().listeners) + listener.onPlayerDeath(p); + } + + 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 removePets(Player p) + { + for (Wolf w : pets) + if (w.getOwner().equals(p)) + 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 + "clear-wave-before-next", 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); + softRestoreDrops = config.getBoolean(arenaPath + "soft-restore-drops", false); + emptyInvJoin = config.getBoolean(arenaPath + "require-empty-inv-join", false); + emptyInvSpec = config.getBoolean(arenaPath + "require-empty-inv-spec", false); + hellhounds = config.getBoolean(arenaPath + "hellhounds", false); + pvp = config.getBoolean(arenaPath + "pvp-enabled", false); + monsterInfight = config.getBoolean(arenaPath + "monster-infight", false); + allowWarp = config.getBoolean(arenaPath + "allow-teleporting", false); + repairDelay = config.getInt(arenaPath + "repair-delay", 5); + 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) * 20; + + 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; + + // 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; + + if (softRestore) + { + int[] 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); + if (!softRestoreDrops) event.getBlock().setTypeId(0); + 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; + + if (!MobArena.hasDefTrue(p, "mobarena.classes." + className)) + { + MAUtils.tellPlayer(p, MAMessages.get(Msg.LOBBY_CLASS_PERMISSION)); + 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 || allowWarp) + 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 getNonreadyPlayers() + { + List result = new LinkedList(); + result.addAll(livePlayers); + result.removeAll(readyPlayers); + 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..cbfbd07 --- /dev/null +++ b/src/com/garbagemule/MobArena/ArenaMaster.java @@ -0,0 +1,366 @@ +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; + + // 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 List getAllPlayers() + { + List result = new LinkedList(); + for (Arena arena : arenas) + result.addAll(arena.getAllPlayers()); + return result; + } + + public List getAllPlayersInArena(String arenaName) + { + Arena arena = getArenaWithName(arenaName); + return (arena != null) ? arena.getLivingPlayers() : new LinkedList(); + } + + public List getAllLivingPlayers() + { + List result = new LinkedList(); + for (Arena arena : arenas) + result.addAll(arena.getLivingPlayers()); + return result; + } + + public List getLivingPlayersInArena(String arenaName) + { + Arena arena = getArenaWithName(arenaName); + return (arena != null) ? arena.getLivingPlayers() : new LinkedList(); + } + + public Arena getArenaWithPlayer(Player p) + { + return arenaMap.get(p); + } + + public Arena getArenaWithPlayer(String playerName) + { + return arenaMap.get(Bukkit.getServer().getPlayer(playerName)); + } + + public Arena getArenaWithSpectator(Player p) + { + for (Arena arena : arenas) + { + if (arena.specPlayers.contains(p)) + return arena; + } + 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); + } + + enabled = config.getBoolean("global-settings.enabled", true); + updateNotify = config.getBoolean("global-settings.update-notification", true); + } + + /** + * 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.clear-wave-before-next", 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.soft-restore-drops", false); + config.setProperty("arenas." + configName + ".settings.require-empty-inv-join", false); + config.setProperty("arenas." + configName + ".settings.require-empty-inv-spec", 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.setProperty("arenas." + configName + ".settings.allow-teleporting", false); + config.save(); + config.load(); + config.setProperty("arenas." + configName + ".settings.repair-delay", 5); + 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.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..6a6439a 100644 --- a/src/com/garbagemule/MobArena/MACommands.java +++ b/src/com/garbagemule/MobArena/MACommands.java @@ -1,320 +1,922 @@ 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 > 1) ? args[1].toLowerCase() : ""; + String arg2 = (args.length > 2) ? args[2].toLowerCase() : ""; - 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 || !MobArena.has(p, "mobarena.use.join")) { - 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, MAMessages.get(Msg.MISC_NO_ACCESS)); return true; } - // End the arena. - if (arg.equals("end")) + boolean error; + + if (!arg1.isEmpty()) { - 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.emptyInvJoin && !MAUtils.hasEmptyInventory(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_EMPTY_INV)); + else if (!arena.emptyInvJoin && !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; + } + else + { + 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.emptyInvJoin && !MAUtils.hasEmptyInventory(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.JOIN_EMPTY_INV)); + else if (!arena.emptyInvJoin && !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 || !MobArena.has(p, "mobarena.use.leave")) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); return true; } - ArenaManager.tellPlayer(p, "/ma force [start|end]"); + if (!am.arenaMap.containsKey(p)) + { + Arena arena = am.getArenaWithSpectator(p); + if (arena != null) + { + arena.playerLeave(p); + MAUtils.tellPlayer(p, MAMessages.get(Msg.LEAVE_PLAYER_LEFT)); + return true; + } + + 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 || !MobArena.has(p, "mobarena.use.spectate")) { - ArenaManager.tellPlayer(p, "/ma config reload"); + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); 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.isEmpty()) + { + 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.emptyInvSpec && !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.emptyInvSpec && !MAUtils.hasEmptyInventory(p)) + error = MAUtils.tellPlayer(p, MAMessages.get(Msg.SPEC_EMPTY_INV)); + else error = false; + + if (error) + return true; + } + + arena.playerSpec(p, p.getLocation()); + MAUtils.tellPlayer(p, MAMessages.get(Msg.SPEC_PLAYER_SPECTATE)); return true; } - // ma addspawn - if (cmd.equals("addspawn")) - { - // The name must start with a letter, followed by any letter(s) or number(s). - if (!arg.matches("[a-z]+([[0-9][a-z]])*")) - { - ArenaManager.tellPlayer(p, "Name must consist of only letters a-z and numbers 0-9"); - return true; - } - - // Write the coordinate data to the config-file. - MAUtils.setCoords("spawnpoints." + arg, p.getLocation().getBlock().getRelative(0,1,0).getLocation()); - - ArenaManager.tellPlayer(p, "Spawn point with name \"" + arg + "\" added."); - MAUtils.notifyIfSetup(p); + /* + * Prints a list of all arenas. + */ + if (base.equals("arenas")) + { + String list = MAUtils.listToString(am.arenas); + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_LIST_ARENAS, list)); return true; } - // ma delspawn - if (cmd.equals("delspawn")) + /* + * Prints a list of all live players in all arenas, or live players in a specific arena. + */ + if (base.equals("players") || base.equals("list")) { - // 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.isEmpty()) { - ArenaManager.tellPlayer(p, "Name must consist of only letters a-z and numbers 0-9"); - return true; + 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)); } - - // If the spawnpoint does not exist, notify the player. - if (MAUtils.getCoords("spawnpoints." + arg) == null) + else { - ArenaManager.tellPlayer(p, "Couldn't find spawnpoint \"" + arg + "\"."); - ArenaManager.tellPlayer(p, "Spawnpoints: " + MAUtils.spawnList()); - return true; + StringBuffer buffy = new StringBuffer(); + for (Arena arena : am.arenas) + buffy.append(MAUtils.listToString(arena.getLivingPlayers(), false)); + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_LIST_PLAYERS, buffy.toString())); } - - 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")) + /* + * Prints a list of all non-ready players in current arena, or non-ready players in a specific arena. + */ + if (base.equals("notready")) { - if (!arg.equals("p1") && !arg.equals("p2")) + Arena arena; + if (!arg1.isEmpty()) { - ArenaManager.tellPlayer(p, "/ma setregion [p1|p2]"); + arena = am.getArenaWithName(arg1); + if (arena == null) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + return true; + } + } + else if (player) + { + arena = am.getArenaWithPlayer(p); + if (arena == null) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.LEAVE_NOT_PLAYING)); + return true; + } + } + else + { + MAUtils.tellPlayer(sender, "Usage: /ma notready "); return true; } - MAUtils.setCoords(arg, p.getLocation()); - MAUtils.fixCoords(); - - ArenaManager.tellPlayer(p, "Region point \"" + arg + "\" set."); - MAUtils.notifyIfSetup(p); + String list = MAUtils.listToString(arena.getNonreadyPlayers()); + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_LIST_PLAYERS, list)); 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!"); - 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 (!console && !(player && MobArena.has(p, "mobarena.admin.enable")) && !op) { - MAUtils.UnDoooooItHippieMonster(); - ArenaManager.tellPlayer(p, "Restored your precious little patch >_>"); + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + + if (!arg1.isEmpty()) + { + 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 (!console && !(player && MobArena.has(p, "mobarena.admin.protect")) && !op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1.isEmpty() || !arg1.matches("^[a-zA-Z][a-zA-Z0-9_]*$") || !(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; + } + + /* + * Restore a player's inventory. + */ + if (base.equals("restore")) + { + if (!console && !(player && MobArena.has(p, "mobarena.admin.restore")) && !op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + + if (!arg1.isEmpty()) + { + 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; } } - return false; + /* + * Force start/end arenas. + */ + if (base.equals("force") && arg1.equals("end")) + { + if (arg1.equals("end")) + { + if (!console && !(player && MobArena.has(p, "mobarena.admin.force.end")) && !op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + + if (arg2.isEmpty()) + { + for (Arena arena : am.arenas) + arena.forceEnd(); + + am.arenaMap.clear(); + return true; + } + + Arena arena = am.getArenaWithName(arg2); + if (arena == null) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.ARENA_DOES_NOT_EXIST)); + return true; + } + + 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; + } + else if (arg1.equals("start")) + { + if (!console && !(player && MobArena.has(p, "mobarena.admin.force.start")) && !op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + + if (arg2.isEmpty()) + { + 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; + } + + 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; + } + else + { + MAUtils.tellPlayer(sender, "Usage: /ma force [start|end] ()"); + return true; + } + } + + /* + * Reload the config-file. + */ + if (base.equals("config")) + { + if (!console && !(player && MobArena.has(p, "mobarena.admin.config.reload")) && !op) + { + 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 (!console && !(player && MobArena.has(p, "mobarena.setup.arena")) && !op) + { + 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 (!console && !(player && MobArena.has(p, "mobarena.setup.setarena")) && !op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1.isEmpty()) + { + MAUtils.tellPlayer(sender, "Usage: /ma setarena "); + return true; + } + + Arena arena = am.getArenaWithName(arg1); + if (arena != null) + { + am.selectedArena = arena; + MAUtils.tellPlayer(sender, "Currently selected arena: " + arena.configName()); + } + 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 (!(player && MobArena.has(p, "mobarena.setup.addarena")) && !op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1.isEmpty()) + { + 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 (!console && !(player && MobArena.has(p, "mobarena.setup.delarena")) && !op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1.isEmpty()) + { + MAUtils.tellPlayer(sender, "Usage: /ma delarena "); + return true; + } + if (am.arenas.size() < 2) + { + 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 (!console && !(player && MobArena.has(p, "mobarena.setup.editarena")) && !op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1.isEmpty() || !(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 (!(player && MobArena.has(p, "mobarena.setup.setregion")) && !op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + + if (!(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 (!console && !(player && MobArena.has(p, "mobarena.setup.expandregion")) && !op) + { + 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 (!(player && MobArena.has(p, "mobarena.setup.setwarp")) && !op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (!(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 (!console && !(player && MobArena.has(p, "mobarena.setup.spawnpoints")) && !op) + { + 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 (!(player && MobArena.has(p, "mobarena.setup.addspawn")) && !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 (!console && !(player && MobArena.has(p, "mobarena.setup.delspawn")) && !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 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 (!(player && MobArena.has(p, "mobarena.setup.autogenerate")) && !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 (!console && !(player && MobArena.has(p, "mobarena.setup.autodegenerate")) && !op) + { + MAUtils.tellPlayer(sender, MAMessages.get(Msg.MISC_NO_ACCESS)); + return true; + } + if (arg1.isEmpty()) + { + MAUtils.tellPlayer(sender, "Usage: /ma auto-degenerate "); + return true; + } + if (am.arenas.size() < 2) + { + MAUtils.tellPlayer(sender, "At least one arena must exist!"); + 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..0a735b2 --- /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, + 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_CLASS_PERMISSION, + 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_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.LOBBY_CLASS_PERMISSION, "You don't have permission to use this 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 + "\". Check announcements-file."); + 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. Check announcements-file."); + 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..23c6a64 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,107 @@ 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 MobArena plugin; + private Arena arena; + private final double MIN_DISTANCE = 256; - public MASpawnThread() + public MASpawnThread(MobArena plugin, Arena arena) { - modulo = ArenaManager.specialModulo; + this.plugin = plugin; + 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)); + detonateCreepers(arena.detCreepers); specialWave(); + + // Notify listeners. + for (MobArenaListener listener : plugin.getAM().listeners) + listener.onSpecialWave(wave, wave/modulo); } 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)); + detonateCreepers(arena.detCreepers); defaultWave(); + + // Notify listeners. + for (MobArenaListener listener : plugin.getAM().listeners) + listener.onDefaultWave(wave); } - 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 +138,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 +160,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 +175,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 +231,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 +250,85 @@ 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) + continue; + + result.add(s); + break; + } + } + + // 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 +336,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..04b3a41 100644 --- a/src/com/garbagemule/MobArena/MAUtils.java +++ b/src/com/garbagemule/MobArena/MAUtils.java @@ -7,36 +7,305 @@ 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; +import com.garbagemule.MobArena.MAMessages.Msg; + 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, /pl"); + config.save(); + commands = config.getString("global-settings.allowed-commands"); + } + + return stringToList(commands); } + /* ///////////////////////////////////////////////////////////////////// // INVENTORY AND REWARD METHODS @@ -55,11 +324,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 +440,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 +731,181 @@ 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 string-representation of the list. + */ + public static String listToString(List list) + { + return listToString(list, true); + } + + public static String listToString(List list, boolean none) + { + if (none && list.isEmpty()) + return MAMessages.get(Msg.MISC_NONE); + + 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 +913,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 +921,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 +939,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 +977,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 +993,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 +1019,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..1d705e7 100644 --- a/src/com/garbagemule/MobArena/MobArena.java +++ b/src/com/garbagemule/MobArena/MobArena.java @@ -1,30 +1,33 @@ package com.garbagemule.MobArena; -import java.util.List; +import java.io.File; +import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.Event.Priority; import org.bukkit.event.block.BlockListener; import org.bukkit.event.player.PlayerListener; import org.bukkit.event.entity.EntityListener; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.PluginManager; +import org.bukkit.util.config.Configuration; + +import com.nijiko.permissions.PermissionHandler; +import com.nijikokun.bukkit.Permissions.Permissions; /** * 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; + private Configuration config; + private ArenaMaster am; + + // Permissions stuff + protected static PermissionHandler permissionHandler; public MobArena() { @@ -34,52 +37,217 @@ public class MobArena extends JavaPlugin { PluginDescriptionFile pdfFile = this.getDescription(); - // Initialize convenience variables in ArenaManager. - ArenaManager.init(this); - DISABLED_COMMANDS = MAUtils.getDisabledCommands(); + // Config, messages and ArenaMaster initialization + 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()); - - + // Permissions + setupPermissions(); + // 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(); + } + + // Permissions stuff + public static boolean has(Player p, String s) + { + //return (permissionHandler != null && permissionHandler.has(p, s)); + return (permissionHandler == null || permissionHandler.has(p, s)); + } + + public static boolean hasDefTrue(Player p, String s) + { + return (permissionHandler == null || permissionHandler.has(p, s)); + } + + private void setupPermissions() + { + if (permissionHandler != null) + return; + + Plugin permissionsPlugin = this.getServer().getPluginManager().getPlugin("Permissions"); + if (permissionsPlugin == null) return; + + permissionHandler = ((Permissions) permissionsPlugin).getHandler(); + } + + 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(); + + // 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 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..80f4b3b 100644 --- a/src/com/garbagemule/MobArena/MobArenaHandler.java +++ b/src/com/garbagemule/MobArena/MobArenaHandler.java @@ -1,34 +1,105 @@ 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 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 plugin.getAM().getAllPlayers(); } + public List getAllLivingPlayers() { return plugin.getAM().getAllLivingPlayers(); } + public List getAllPlayersInArena(String arenaName) { return plugin.getAM().getAllPlayersInArena(arenaName); } + public List getLivingPlayersInArena(String arenaName) { return plugin.getAM().getLivingPlayersInArena(arenaName); } + + // 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() {}