diff --git a/MobArena.jar b/MobArena.jar index 1ec76a8..0312f7b 100644 Binary files a/MobArena.jar and b/MobArena.jar differ diff --git a/abilities/ChainLightning.java b/abilities/ChainLightning.java new file mode 100644 index 0000000..4f3884b --- /dev/null +++ b/abilities/ChainLightning.java @@ -0,0 +1,60 @@ +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Chain Lightning", + aliases = {"chainlightning"} +) +public class ChainLightning implements Ability +{ + /** + * How many blocks the chain lightning can spread over. + * Must be greater than 0. + */ + private final int RADIUS = 4; + + /** + * How many server ticks between each lightning strike. + * Must be greater than 0. + */ + private final int TICKS = 10; + + @Override + public void execute(Arena arena, MABoss boss) { + final LivingEntity target = AbilityUtils.getTarget(arena, boss.getEntity(), true); + if (target == null || !(target instanceof Player)) + return; + + strikeLightning(arena, (Player) target, new ArrayList()); + } + + private void strikeLightning(final Arena arena, final Player p, final List done) { + arena.scheduleTask(new Runnable() { + public void run() { + if (!arena.isRunning() || !arena.inArena(p)) + return; + + // Smite the target + arena.getWorld().strikeLightning(p.getLocation()); + done.add(p); + + // Grab all nearby players + List nearby = AbilityUtils.getNearbyPlayers(arena, p, RADIUS); + + // Remove all that are "done", and return if empty + nearby.removeAll(done); + if (nearby.isEmpty()) return; + + // Otherwise, smite the next target! + strikeLightning(arena, nearby.get(0), done); + } + }, TICKS); + } +} diff --git a/abilities/DisorientDistant.java b/abilities/DisorientDistant.java new file mode 100644 index 0000000..3def274 --- /dev/null +++ b/abilities/DisorientDistant.java @@ -0,0 +1,27 @@ +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Disorient Distant", + aliases = {"disorientdistant"} +) +public class DisorientDistant implements Ability +{ + /** + * How far away players must be to be affected by the ability. + */ + private final int RADIUS = 8; + + @Override + public void execute(Arena arena, MABoss boss) { + for (Player p : AbilityUtils.getDistantPlayers(arena, boss.getEntity(), RADIUS)) { + Location loc = p.getLocation(); + loc.setYaw(loc.getYaw() + 45 + AbilityUtils.random.nextInt(270)); + p.teleport(loc); + } + } +} diff --git a/abilities/DisorientNearby.java b/abilities/DisorientNearby.java new file mode 100644 index 0000000..9167c92 --- /dev/null +++ b/abilities/DisorientNearby.java @@ -0,0 +1,31 @@ +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Disorient Nearby", + aliases = {"disorientnearby"} +) +public class DisorientNearby implements Ability +{ + /** + * How close players must be to be affected by the ability. + */ + private final int RADIUS = 5; + + @Override + public void execute(Arena arena, MABoss boss) { + LivingEntity target = AbilityUtils.getTarget(arena, boss.getEntity(), false); + if (target == null) return; + + for (Player p : AbilityUtils.getNearbyPlayers(arena, boss.getEntity(), RADIUS)) { + Location loc = p.getLocation(); + loc.setYaw(loc.getYaw() + 45 + AbilityUtils.random.nextInt(270)); + p.teleport(loc); + } + } +} diff --git a/abilities/DisorientTarget.java b/abilities/DisorientTarget.java new file mode 100644 index 0000000..582687e --- /dev/null +++ b/abilities/DisorientTarget.java @@ -0,0 +1,28 @@ +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Disorient Target", + aliases = {"disorienttarget"} +) +public class DisorientTarget implements Ability +{ + /** + * If the boss has no target, should a random player be selected? + */ + private final boolean RANDOM = false; + + @Override + public void execute(Arena arena, MABoss boss) { + LivingEntity target = AbilityUtils.getTarget(arena, boss.getEntity(), RANDOM); + if (target == null) return; + + Location loc = target.getLocation(); + loc.setYaw(loc.getYaw() + 45 + AbilityUtils.random.nextInt(270)); + target.teleport(loc); + } +} diff --git a/abilities/FetchDistant.java b/abilities/FetchDistant.java new file mode 100644 index 0000000..8e0a750 --- /dev/null +++ b/abilities/FetchDistant.java @@ -0,0 +1,27 @@ +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Fetch Distant", + aliases = {"fetchdistant"} +) +public class FetchDistant implements Ability +{ + /** + * How far away players must be to be affected by the ability. + */ + private final int RADIUS = 8; + + @Override + public void execute(Arena arena, MABoss boss) { + Location bLoc = boss.getEntity().getLocation(); + + for (Player p : AbilityUtils.getDistantPlayers(arena, boss.getEntity(), RADIUS)) { + p.teleport(bLoc); + } + } +} diff --git a/abilities/FetchNearby.java b/abilities/FetchNearby.java new file mode 100644 index 0000000..a5367c0 --- /dev/null +++ b/abilities/FetchNearby.java @@ -0,0 +1,27 @@ +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Fetch Nearby", + aliases = {"fetchnearby"} +) +public class FetchNearby implements Ability +{ + /** + * How close players must be to be affected by the ability. + */ + private final int RADIUS = 5; + + @Override + public void execute(Arena arena, MABoss boss) { + Location bLoc = boss.getEntity().getLocation(); + + for (Player p : AbilityUtils.getNearbyPlayers(arena, boss.getEntity(), RADIUS)) { + p.teleport(bLoc); + } + } +} diff --git a/abilities/FetchTarget.java b/abilities/FetchTarget.java new file mode 100644 index 0000000..03aec40 --- /dev/null +++ b/abilities/FetchTarget.java @@ -0,0 +1,25 @@ +import org.bukkit.entity.LivingEntity; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Fetch Target", + aliases = {"fetchtarget"} +) +public class FetchTarget implements Ability +{ + /** + * If the boss has no target, should a random player be selected? + */ + private final boolean RANDOM = true; + + @Override + public void execute(Arena arena, MABoss boss) { + LivingEntity target = AbilityUtils.getTarget(arena, boss.getEntity(), RANDOM); + if (target == null) return; + + target.teleport(boss.getEntity()); + } +} diff --git a/abilities/FireAura.java b/abilities/FireAura.java new file mode 100644 index 0000000..8a90f21 --- /dev/null +++ b/abilities/FireAura.java @@ -0,0 +1,28 @@ +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Fire Aura", + aliases = {"fireaura","auraoffire"} +) +public class FireAura implements Ability +{ + /** + * How close players must be to be affected by the ability. + */ + private final int RADIUS = 5; + + /** + * How many ticks the players should be on fire for. + */ + private final int TICKS = 20; + + @Override + public void execute(Arena arena, MABoss boss) { + for (Player p : AbilityUtils.getNearbyPlayers(arena, boss.getEntity(), RADIUS)) + p.setFireTicks(TICKS); + } +} diff --git a/abilities/Flood.java b/abilities/Flood.java new file mode 100644 index 0000000..d026ef8 --- /dev/null +++ b/abilities/Flood.java @@ -0,0 +1,24 @@ +import org.bukkit.block.Block; +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Flood", + aliases = {"flood"} +) +public class Flood implements Ability +{ + @Override + public void execute(Arena arena, MABoss boss) { + Player p = AbilityUtils.getRandomPlayer(arena); + Block block = p.getLocation().getBlock(); + + if (block.getTypeId() == 0) { + block.setTypeId(8); + arena.addBlock(block); + } + } +} diff --git a/abilities/LightningAura.java b/abilities/LightningAura.java new file mode 100644 index 0000000..5c6c29a --- /dev/null +++ b/abilities/LightningAura.java @@ -0,0 +1,26 @@ +import org.bukkit.World; +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Lightning Aura", + aliases = {"lightningaura","auraoflightning"} +) +public class LightningAura implements Ability +{ + /** + * How close players must be to be affected by the ability. + */ + private final int RADIUS = 5; + + @Override + public void execute(Arena arena, MABoss boss) { + World world = arena.getWorld(); + for (Player p : AbilityUtils.getNearbyPlayers(arena, boss.getEntity(), RADIUS)) { + world.strikeLightning(p.getLocation()); + } + } +} diff --git a/abilities/LivingBomb.java b/abilities/LivingBomb.java new file mode 100644 index 0000000..fe51b85 --- /dev/null +++ b/abilities/LivingBomb.java @@ -0,0 +1,59 @@ +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Living Bomb", + aliases = {"livingbomb"} +) +public class LivingBomb implements Ability +{ + /** + * How many ticks before the bomb goes off. + */ + private final int FUSE = 60; + + /** + * How close players must be to be affected by the bomb. + */ + private final int RADIUS = 3; + + /** + * How many ticks players affected by the bomb should burn. + */ + private final int AFTERBURN = 40; + + @Override + public void execute(final Arena arena, MABoss boss) { + // Grab the target, or a random player. + LivingEntity target = AbilityUtils.getTarget(arena, boss.getEntity(), true); + + // We only want players. + if (target == null || !(target instanceof Player)) + return; + + final Player p = (Player) target; + p.setFireTicks(FUSE + 5); + + // Create an explosion after 4 seconds + arena.scheduleTask(new Runnable() { + public void run() { + // If the player died, or if they put out the fire. + if (!arena.isRunning() || !arena.inArena(p) || p.getFireTicks() <= 0) { + return; + } + + // Explode! + arena.getWorld().createExplosion(p.getLocation(), 1F); + + // And set every nearby player on fire! + for (Player nearby : AbilityUtils.getNearbyPlayers(arena, p, RADIUS)) { + nearby.setFireTicks(AFTERBURN); + } + } + }, FUSE); + } +} diff --git a/abilities/ObsidianBomb.java b/abilities/ObsidianBomb.java new file mode 100644 index 0000000..167c8b7 --- /dev/null +++ b/abilities/ObsidianBomb.java @@ -0,0 +1,44 @@ +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.LivingEntity; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Obsidian Bomb", + aliases = {"obsidianbomb"} +) +public class ObsidianBomb implements Ability +{ + /** + * How many ticks before the bomb goes off. + */ + private final int FUSE = 80; + + @Override + public void execute(final Arena arena, MABoss boss) { + // Grab the target, or a random player. + LivingEntity target = AbilityUtils.getTarget(arena, boss.getEntity(), true); + + final World world = arena.getWorld(); + final Location loc = target.getLocation(); + + Block b = world.getBlockAt(target.getLocation()); + b.setType(Material.OBSIDIAN); + arena.addBlock(b); + + arena.scheduleTask(new Runnable() { + public void run() { + if (!arena.isRunning()) + return; + + world.getBlockAt(loc).breakNaturally(); + world.createExplosion(loc, 3F); + } + }, FUSE); + } +} diff --git a/abilities/PullDistant.java b/abilities/PullDistant.java new file mode 100644 index 0000000..fb6ce03 --- /dev/null +++ b/abilities/PullDistant.java @@ -0,0 +1,35 @@ +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Pull Distant", + aliases = {"pulldistant"} +) +public class PullDistant implements Ability +{ + /** + * How far away players must be to be affected by the ability. + */ + private final int RADIUS = 8; + + @Override + public void execute(Arena arena, MABoss boss) { + Location bLoc = boss.getEntity().getLocation(); + + for (Player p : AbilityUtils.getDistantPlayers(arena, boss.getEntity(), RADIUS)) { + Location loc = p.getLocation(); + Vector v = new Vector(bLoc.getX() - loc.getX(), 0, bLoc.getZ() - loc.getZ()); + + double a = Math.abs(bLoc.getX() - loc.getX()); + double b = Math.abs(bLoc.getZ() - loc.getZ()); + double c = Math.sqrt((a*a + b*b)); + + p.setVelocity(v.normalize().multiply(c*0.3).setY(0.8)); + } + } +} diff --git a/abilities/PullNearby.java b/abilities/PullNearby.java new file mode 100644 index 0000000..e35be4f --- /dev/null +++ b/abilities/PullNearby.java @@ -0,0 +1,35 @@ +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Pull Nearby", + aliases = {"pullnearby"} +) +public class PullNearby implements Ability +{ + /** + * How close players must be to be affected by the ability. + */ + private final int RADIUS = 5; + + @Override + public void execute(Arena arena, MABoss boss) { + Location bLoc = boss.getEntity().getLocation(); + + for (Player p : AbilityUtils.getNearbyPlayers(arena, boss.getEntity(), RADIUS)) { + Location loc = p.getLocation(); + Vector v = new Vector(bLoc.getX() - loc.getX(), 0, bLoc.getZ() - loc.getZ()); + + double a = Math.abs(bLoc.getX() - loc.getX()); + double b = Math.abs(bLoc.getZ() - loc.getZ()); + double c = Math.sqrt((a*a + b*b)); + + p.setVelocity(v.normalize().multiply(c*0.3).setY(0.8)); + } + } +} diff --git a/abilities/PullTarget.java b/abilities/PullTarget.java new file mode 100644 index 0000000..5105428 --- /dev/null +++ b/abilities/PullTarget.java @@ -0,0 +1,35 @@ +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.util.Vector; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Pull Target", + aliases = {"pulltarget"} +) +public class PullTarget implements Ability +{ + /** + * If the boss has no target, should a random player be selected? + */ + private final boolean RANDOM = false; + + @Override + public void execute(Arena arena, MABoss boss) { + LivingEntity target = AbilityUtils.getTarget(arena, boss.getEntity(), RANDOM); + if (target == null) return; + + Location loc = target.getLocation(); + Location bLoc = boss.getEntity().getLocation(); + Vector v = new Vector(bLoc.getX() - loc.getX(), 0, bLoc.getZ() - loc.getZ()); + + double a = Math.abs(bLoc.getX() - loc.getX()); + double b = Math.abs(bLoc.getZ() - loc.getZ()); + double c = Math.sqrt((a*a + b*b)); + + target.setVelocity(v.normalize().multiply(c*0.3).setY(0.8)); + } +} diff --git a/abilities/RootTarget.java b/abilities/RootTarget.java new file mode 100644 index 0000000..0c42d32 --- /dev/null +++ b/abilities/RootTarget.java @@ -0,0 +1,73 @@ +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +/** + * DISCLAIMER: The Ability source code is provided as-is, and the creator(s) of + * MobArena WILL NOT be held responsible for any damage that may + * result from altering the files. + * + * WARNING: Unless you know exactly what you are doing, i.e. you have a lot + * of experience with Java and Bukkit, you should never change any + * other values than those of the variables in CAPITAL LETTERS. + * + * + * Root Target + * Freezes the boss' target in place for ~3 seconds (default), by warping the + * player to the same spot [ITERATIONS] times, with [TICKS] server ticks + * between each iteration. + * + * @author garbagemule + */ +@AbilityInfo( + name = "Root Target", + aliases = {"roottarget", "freezetarget"} +) +public class RootTarget implements Ability +{ + /** + * How many times the player will be warped back to his original position. + * Must be greater than 0. + */ + private final int ITERATIONS = 5; + + /** + * How many server ticks between each iteration of + * Must be greater than 0. + */ + private final int TICKS = 5; + + @Override + public void execute(Arena arena, MABoss boss) { + final LivingEntity target = AbilityUtils.getTarget(arena, boss.getEntity(), true); + if (target == null || !(target instanceof Player)) + return; + + Player p = (Player) target; + Location loc = p.getLocation(); + + rootTarget(arena, p, loc, ITERATIONS); + } + + private void rootTarget(final Arena arena, final Player p, final Location loc, final int counter) { + // If the counter is 0, we're done. + if (counter <= 0) { + return; + } + + arena.scheduleTask(new Runnable() { + public void run() { + if (!arena.isRunning() || !arena.inArena(p)) { + return; + } + + p.teleport(loc); + rootTarget(arena, p, loc, counter - 1); + } + }, TICKS); + } +} diff --git a/abilities/ShootArrow.java b/abilities/ShootArrow.java new file mode 100644 index 0000000..b1ba5ab --- /dev/null +++ b/abilities/ShootArrow.java @@ -0,0 +1,18 @@ +import org.bukkit.entity.Arrow; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Shoot Arrow", + aliases = {"arrow","arrows"} +) +public class ShootArrow implements Ability +{ + @Override + public void execute(Arena arena, MABoss boss) { + //boss.getEntity().launchProjectile(Arrow.class); + boss.getEntity().shootArrow(); + } +} diff --git a/abilities/ShootFireball.java b/abilities/ShootFireball.java new file mode 100644 index 0000000..8f2562f --- /dev/null +++ b/abilities/ShootFireball.java @@ -0,0 +1,18 @@ +import org.bukkit.entity.Fireball; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Shoot Fireball", + aliases = {"fireball","fireballs"} +) +public class ShootFireball implements Ability +{ + @Override + public void execute(Arena arena, MABoss boss) { + //boss.getEntity().launchProjectile(Fireball.class); + boss.getEntity().throwEgg(); + } +} diff --git a/abilities/ShufflePositions.java b/abilities/ShufflePositions.java new file mode 100644 index 0000000..c46ce89 --- /dev/null +++ b/abilities/ShufflePositions.java @@ -0,0 +1,41 @@ +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Shuffle Positions", + aliases = {"shuffle","shufflepositions"} +) +public class ShufflePositions implements Ability +{ + @Override + public void execute(Arena arena, MABoss boss) { + // Grab the players and add the boss + List entities = new ArrayList(arena.getPlayersInArena()); + entities.add(boss.getEntity()); + + // Grab the locations + List locations = new LinkedList(); + for (LivingEntity e : entities) { + locations.add(e.getLocation()); + } + + // Shuffle the entities list. + Collections.shuffle(entities); + + /* The entities are shuffled, but the locations are not, so if + * we remove the first element of each list, chances are they + * will not match, i.e. shuffle achieved! */ + while (!entities.isEmpty() && !locations.isEmpty()) { + entities.remove(0).teleport(locations.remove(0)); + } + } +} diff --git a/abilities/ThrowDistant.java b/abilities/ThrowDistant.java new file mode 100644 index 0000000..f919436 --- /dev/null +++ b/abilities/ThrowDistant.java @@ -0,0 +1,30 @@ +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Throw Distant", + aliases = {"throwdistant"} +) +public class ThrowDistant implements Ability +{ + /** + * How far away players must be to be affected by the ability. + */ + private final int RADIUS = 8; + + @Override + public void execute(Arena arena, MABoss boss) { + Location bLoc = boss.getEntity().getLocation(); + + for (Player p : AbilityUtils.getDistantPlayers(arena, boss.getEntity(), RADIUS)) { + Location loc = p.getLocation(); + Vector v = new Vector(loc.getX() - bLoc.getX(), 0, loc.getZ() - bLoc.getZ()); + p.setVelocity(v.normalize().setY(0.8)); + } + } +} diff --git a/abilities/ThrowNearby.java b/abilities/ThrowNearby.java new file mode 100644 index 0000000..00dc2bf --- /dev/null +++ b/abilities/ThrowNearby.java @@ -0,0 +1,30 @@ +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Throw Nearby", + aliases = {"thrownearby"} +) +public class ThrowNearby implements Ability +{ + /** + * How close players must be to be affected by the ability. + */ + private final int RADIUS = 5; + + @Override + public void execute(Arena arena, MABoss boss) { + Location bLoc = boss.getEntity().getLocation(); + + for (Player p : AbilityUtils.getNearbyPlayers(arena, boss.getEntity(), RADIUS)) { + Location loc = p.getLocation(); + Vector v = new Vector(loc.getX() - bLoc.getX(), 0, loc.getZ() - bLoc.getZ()); + p.setVelocity(v.normalize().setY(0.8)); + } + } +} diff --git a/abilities/ThrowTarget.java b/abilities/ThrowTarget.java new file mode 100644 index 0000000..bc08234 --- /dev/null +++ b/abilities/ThrowTarget.java @@ -0,0 +1,31 @@ +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.util.Vector; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Throw Target", + aliases = {"throwtarget"} +) +public class ThrowTarget implements Ability +{ + /** + * If the boss has no target, should a random player be selected? + */ + private final boolean RANDOM = false; + + @Override + public void execute(Arena arena, MABoss boss) { + LivingEntity target = AbilityUtils.getTarget(arena, boss.getEntity(), RANDOM); + if (target == null) return; + + Location bLoc = boss.getEntity().getLocation(); + Location loc = target.getLocation(); + Vector v = new Vector(loc.getX() - bLoc.getX(), 0, loc.getZ() - bLoc.getZ()); + + target.setVelocity(v.normalize().setY(0.8)); + } +} diff --git a/abilities/WarpToPlayer.java b/abilities/WarpToPlayer.java new file mode 100644 index 0000000..7c555d6 --- /dev/null +++ b/abilities/WarpToPlayer.java @@ -0,0 +1,18 @@ +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; +import com.garbagemule.MobArena.waves.ability.*; + +@AbilityInfo( + name = "Warp", + aliases = {"warp","warptoplayer"} +) +public class WarpToPlayer implements Ability +{ + @Override + public void execute(Arena arena, MABoss boss) { + Player p = AbilityUtils.getRandomPlayer(arena); + boss.getEntity().teleport(p); + } +} diff --git a/src/com/garbagemule/MobArena/ArenaListener.java b/src/com/garbagemule/MobArena/ArenaListener.java new file mode 100644 index 0000000..d232714 --- /dev/null +++ b/src/com/garbagemule/MobArena/ArenaListener.java @@ -0,0 +1,799 @@ +package com.garbagemule.MobArena; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.ContainerBlock; +import org.bukkit.block.Sign; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Slime; +import org.bukkit.entity.Wolf; +import org.bukkit.event.Event.Result; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockBurnEvent; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.event.block.BlockFormEvent; +import org.bukkit.event.block.BlockIgniteEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.event.entity.EntityCombustEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; +import org.bukkit.event.entity.EntityTargetEvent.TargetReason; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerAnimationEvent; +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.PlayerKickEvent; +import org.bukkit.event.player.PlayerPreLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.Attachable; +import org.bukkit.material.Bed; +import org.bukkit.material.Door; +import org.bukkit.material.Redstone; + +import com.garbagemule.MobArena.MAUtils; +import com.garbagemule.MobArena.MobArena; +import com.garbagemule.MobArena.Msg; +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.leaderboards.Leaderboard; +import com.garbagemule.MobArena.listeners.MAGlobalListener.TeleportResponse; +import com.garbagemule.MobArena.region.ArenaRegion; +import com.garbagemule.MobArena.region.RegionPoint; +import com.garbagemule.MobArena.repairable.*; +import com.garbagemule.MobArena.util.TextUtils; +import com.garbagemule.MobArena.util.config.ConfigSection; +import com.garbagemule.MobArena.waves.MABoss; + +public class ArenaListener +{ + private MobArena plugin; + private Arena arena; + private ArenaRegion region; + private MonsterManager monsters; + + private boolean softRestore, + softRestoreDrops, + protect; + private boolean monsterExp, + monsterInfight, + pvpEnabled, + foodRegen, + lockFoodLevel; + private boolean allowTeleport, + canShare, + allowMonsters; + + private Set banned; + + public ArenaListener(Arena arena, MobArena plugin) { + this.plugin = plugin; + this.arena = arena; + this.region = arena.getRegion(); + this.monsters = arena.getMonsterManager(); + + /* + * TODO: Figure out if this is really a good idea + It saves needing all + * those methods in Arena.java + It is relatively simple + It would be + * fairly easy to implement an observer pattern - More private fields - + * Uglier code + */ + ConfigSection s = arena.getSettings(); + this.softRestore = s.getBoolean("soft-restore", false); + this.softRestoreDrops = s.getBoolean("soft-restore-drops", false); + this.protect = s.getBoolean("protect", true); + this.monsterExp = s.getBoolean("monster-exp", false); + this.monsterInfight = s.getBoolean("monster-infight", false); + this.pvpEnabled = s.getBoolean("pvp-enabled", false); + this.foodRegen = s.getBoolean("food-regen", false); + this.lockFoodLevel = s.getBoolean("lock-food-level", true); + this.allowTeleport = s.getBoolean("allow-teleporting", false); + this.canShare = s.getBoolean("share-items-in-arena", true); + + this.allowMonsters = arena.getWorld().getAllowMonsters(); + + this.banned = new HashSet(); + } + + public void onBlockBreak(BlockBreakEvent event) { + if (onBlockDestroy(event)) + return; + + event.setCancelled(true); + } + + public void onBlockBurn(BlockBurnEvent event) { + if (onBlockDestroy(event)) + return; + + event.setCancelled(true); + } + + private boolean onBlockDestroy(BlockEvent event) { + if (!arena.getRegion().contains(event.getBlock().getLocation()) || arena.inEditMode() || (!arena.isProtected() && arena.isRunning())) + return true; + + Block b = event.getBlock(); + if (arena.removeBlock(b) || b.getType() == Material.TNT) + return true; + + if (softRestore && arena.isRunning()) { + BlockState state = b.getState(); + + Repairable r = null; + if (state instanceof ContainerBlock) + r = new RepairableContainer(state); + else if (state instanceof Sign) + r = new RepairableSign(state); + else if (state.getData() instanceof Attachable) + r = new RepairableAttachable(state); + else + r = new RepairableBlock(state); + + arena.addRepairable(r); + + if (!softRestoreDrops) + b.setTypeId(0); + + return true; + } + + return false; + } + + public void onBlockPlace(BlockPlaceEvent event) { + Block b = event.getBlock(); + + // If the event didn't happen in the region, or if in edit mode, ignore + if (!arena.getRegion().contains(b.getLocation()) || arena.inEditMode()) { + return; + } + + // If the arena isn't running, or if the player isn't in the arena, cancel. + if (!arena.isRunning() || !arena.inArena(event.getPlayer())) { + event.setCancelled(true); + return; + } + + // Otherwise, block was placed during a session. + arena.addBlock(b); + + switch (b.getType()){ + // For doors, add the block just above (so we get both halves) + case WOODEN_DOOR: + case IRON_DOOR_BLOCK: + arena.addBlock(b.getRelative(0, 1, 0)); + break; + } + } + + public void onBlockForm(BlockFormEvent event) { + if (!arena.getRegion().contains(event.getBlock().getLocation())) + return; + + // If a snowman forms some snow on its path, add the block + if (event.getNewState().getType() == Material.SNOW) + arena.addBlock(event.getBlock()); + } + + public void onBlockIgnite(BlockIgniteEvent event) { + if (!arena.getRegion().contains(event.getBlock().getLocation())) + return; + + switch (event.getCause()){ + case LIGHTNING: + case SPREAD: + event.setCancelled(true); + break; + case FLINT_AND_STEEL: + if (arena.isRunning()) + arena.addBlock(event.getBlock().getRelative(BlockFace.UP)); + else + event.setCancelled(true); + break; + default: + break; + } + } + + public void onSignChange(SignChangeEvent event) { + arena.setLeaderboard(new Leaderboard(plugin, arena, event.getBlock().getLocation())); + arena.getRegion().set(RegionPoint.LEADERBOARD, event.getBlock().getLocation()); + + Messenger.tellPlayer(event.getPlayer(), "Leaderboard made. Now set up the stat signs!"); + } + + public void onCreatureSpawn(CreatureSpawnEvent event) { + if (!arena.getRegion().contains(event.getLocation())) { + if (!event.isCancelled()) { + event.setCancelled(!allowMonsters); + } + return; + } + + if (event.getSpawnReason() != SpawnReason.CUSTOM) { + event.setCancelled(true); + return; + } + + LivingEntity entity = (LivingEntity) event.getEntity(); + if (arena.isRunning() && entity instanceof Slime) + monsters.addMonster(entity); + + // If running == true, setCancelled(false), and vice versa. + event.setCancelled(!arena.isRunning()); + } + + public void onEntityExplode(EntityExplodeEvent event) { + if (!monsters.getMonsters().contains(event.getEntity()) && !arena.getRegion().contains(event.getLocation(), 10)) + return; + + monsters.removeMonster(event.getEntity()); + + // Cancel if the arena isn't running + if (!arena.isRunning()) { + event.setCancelled(true); + return; + } + + // Uncancel, just in case. + event.setCancelled(false); + + // If the arena isn't destructible, just clear the blocklist. + if (!softRestore && protect) { + List blocks = new LinkedList(arena.getBlocks()); + event.blockList().retainAll(blocks); + return; + } + + if (!softRestoreDrops) + event.setYield(0); + + // Handle all the blocks in the block list. + for (Block b : event.blockList()) { + BlockState state = b.getState(); + + if (state.getData() instanceof Door && ((Door) state.getData()).isTopHalf()) { + state = b.getRelative(BlockFace.DOWN).getState(); + } + else if (state.getData() instanceof Bed && ((Bed) state.getData()).isHeadOfBed()) { + state = b.getRelative(((Bed) state.getData()).getFacing().getOppositeFace()).getState(); + } + + // Create a Repairable from the block. + Repairable r = null; + if (state instanceof ContainerBlock) + r = new RepairableContainer(state); + else if (state instanceof Sign) + r = new RepairableSign(state); + else if (state.getData() instanceof Bed) + r = new RepairableBed(state); + else if (state.getData() instanceof Door) + r = new RepairableDoor(state); + else if (state.getData() instanceof Attachable || state.getData() instanceof Redstone) + r = new RepairableAttachable(state); + else + r = new RepairableBlock(state); + + // Cakes and liquids should just get removed. If player-placed block, drop as item. + Material mat = state.getType(); + if (mat == Material.CAKE_BLOCK || mat == Material.WATER || mat == Material.LAVA) + arena.removeBlock(b); + else if (arena.removeBlock(b)) + arena.getWorld().dropItemNaturally(b.getLocation(), new ItemStack(state.getTypeId(), 1)); + else if (softRestore) + arena.addRepairable(r); + else + arena.queueRepairable(r); + } + } + + /****************************************************** + * + * DEATH LISTENERS + * + ******************************************************/ + + public void onEntityDeath(EntityDeathEvent event) { + if (event instanceof PlayerDeathEvent) { + onPlayerDeath((PlayerDeathEvent) event, (Player) event.getEntity()); + } + else if (monsters.removeMonster(event.getEntity())) { + onMonsterDeath(event); + } + } + + private void onPlayerDeath(PlayerDeathEvent event, Player player) { + if (arena.inArena(player) || arena.inLobby(player)) { + event.getDrops().clear(); + event.setDroppedExp(0); + event.setKeepLevel(true); + arena.playerDeath(player); + } + } + + public boolean onPlayerRespawn(PlayerRespawnEvent event) { + Player p = event.getPlayer(); + if (!arena.isDead(p)) { + return false; + } + + Location loc = arena.getRespawnLocation(p); + event.setRespawnLocation(loc); + + arena.playerRespawn(p); + return true; + } + + private void onMonsterDeath(EntityDeathEvent event) { + EntityDamageEvent e1 = event.getEntity().getLastDamageCause(); + EntityDamageByEntityEvent e2 = (e1 instanceof EntityDamageByEntityEvent) ? (EntityDamageByEntityEvent) e1 : null; + Entity damager = (e2 != null) ? e2.getDamager() : null; + + // Make sure to grab the owner of a projectile/pet + if (damager instanceof Projectile) { + damager = ((Projectile) damager).getShooter(); + } + else if (damager instanceof Wolf && arena.hasPet(damager)) { + damager = (Player) ((Wolf) damager).getOwner(); + } + + // If the damager was a player, add to kills. + if (damager instanceof Player) { + ArenaPlayer ap = arena.getArenaPlayer((Player) damager); + if (ap != null) { + ArenaPlayerStatistics stats = ap.getStats(); + if (stats != null) { + ap.getStats().inc("kills"); + } + } + } + + if (!monsterExp) + event.setDroppedExp(0); + + event.getDrops().clear(); + + List loot = monsters.getLoot(event.getEntity()); + if (loot != null && !loot.isEmpty()) { + event.getDrops().add(getRandomItem(loot)); + } + + return; + } + + private ItemStack getRandomItem(List stacks) { + return stacks.get((new Random()).nextInt(stacks.size())); + } + + /****************************************************** + * + * DAMAGE LISTENERS + * + ******************************************************/ + + public void onEntityDamage(EntityDamageEvent event) { + Entity damagee = event.getEntity(); + if (!arena.isRunning() || !arena.getRegion().contains(damagee.getLocation())) { + return; + } + + EntityDamageByEntityEvent edbe = (event instanceof EntityDamageByEntityEvent) ? (EntityDamageByEntityEvent) event : null; + Entity damager = null; + + if (edbe != null) { + damager = edbe.getDamager(); + + if (damager instanceof Projectile) { + damager = ((Projectile) damager).getShooter(); + } + else if (damager instanceof Wolf && arena.hasPet(damager)) { + damager = (Player) ((Wolf) damager).getOwner(); + } + } + + // Pet wolf + if (damagee instanceof Wolf && arena.hasPet(damagee)) { + onPetDamage(event, (Wolf) damagee, damager); + } + // Player + else if (damagee instanceof Player) { + onPlayerDamage(event, (Player) damagee, damager); + } + // Boss + else if (monsters.getBossMonsters().contains(damagee)) { + onBossDamage(event, (LivingEntity) damagee, damager); + } + // Regular monster + else if (monsters.getMonsters().contains(damagee)) { + onMonsterDamage(event, damagee, damager); + } + } + + private void onPlayerDamage(EntityDamageEvent event, Player player, Entity damager) { + // Cancel all damage in the lobby and spec area + if (arena.inLobby(player) || arena.inSpec(player)) { + event.setCancelled(true); + return; + } + // If PvP is disabled and damager is a player, cancel damage + else if (arena.inArena(player)) { + if (!pvpEnabled && damager instanceof Player) { + event.setCancelled(true); + return; + } + event.setCancelled(false); + arena.getArenaPlayer(player).getStats().add("dmgTaken", event.getDamage()); + } + } + + private void onPetDamage(EntityDamageEvent event, Wolf pet, Entity damager) { + event.setCancelled(true); + } + + private void onMonsterDamage(EntityDamageEvent event, Entity monster, Entity damager) { + if (damager instanceof Player) { + Player p = (Player) damager; + if (!arena.inArena(p)) { + event.setCancelled(true); + return; + } + + ArenaPlayerStatistics aps = arena.getArenaPlayer(p).getStats(); + aps.add("dmgDone", event.getDamage()); + aps.inc("hits"); + } + else if (damager instanceof Wolf && arena.hasPet(damager)) { + event.setDamage(1); + Player p = (Player) ((Wolf) damager).getOwner(); + ArenaPlayerStatistics aps = arena.getArenaPlayer(p).getStats(); + aps.add("dmgDone", event.getDamage()); + // arena.getArenaPlayer(p).getStats().dmgDone += event.getDamage(); + } + else if (damager instanceof LivingEntity) { + if (!monsterInfight) + event.setCancelled(true); + } + } + + private void onBossDamage(EntityDamageEvent event, LivingEntity monster, Entity damager) { + // Health the boss back up. + monster.setHealth(monster.getMaxHealth()); + + // Damage the underlying MABoss. + MABoss boss = monsters.getBoss(monster); + boss.damage(event.getDamage()); + + // If it died, remove it from the arena. + if (boss.isDead()) { + monsters.removeBoss(monster); + monster.damage(10000); + } + + // And "cancel out" the damage. + event.setDamage(1); + } + + public void onEntityCombust(EntityCombustEvent event) { + if (monsters.getMonsters().contains(event.getEntity())) + event.setCancelled(true); + } + + public void onEntityTarget(EntityTargetEvent event) { + if (!arena.isRunning() || event.isCancelled()) + return; + + if (arena.hasPet(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); + } + + else if (monsters.getMonsters().contains(event.getEntity())) { + if (event.getReason() == TargetReason.FORGOT_TARGET) + event.setTarget(MAUtils.getClosestPlayer(plugin, event.getEntity(), arena)); + + else if (event.getReason() == TargetReason.TARGET_DIED) + event.setTarget(MAUtils.getClosestPlayer(plugin, event.getEntity(), arena)); + + else if (event.getReason() == TargetReason.TARGET_ATTACKED_ENTITY) + if (arena.hasPet(event.getTarget())) + event.setCancelled(true); + + else if (event.getReason() == TargetReason.CLOSEST_PLAYER) + if (!arena.inArena((Player) event.getTarget())) + event.setCancelled(true); + } + } + + public void onEntityChangeBlock(EntityChangeBlockEvent event) { + if (arena.getRegion().contains(event.getBlock().getLocation())) + event.setCancelled(true); + } + + public void onEntityRegainHealth(EntityRegainHealthEvent event) { + if (!arena.isRunning()) + return; + + if (!(event.getEntity() instanceof Player) || !arena.inArena((Player) event.getEntity())) + return; + + if (!foodRegen && event.getRegainReason() == RegainReason.SATIATED) { + event.setCancelled(true); + } + } + + public void onFoodLevelChange(FoodLevelChangeEvent event) { + if (!arena.isRunning()) + return; + + if (!(event.getEntity() instanceof Player) || !arena.inArena((Player) event.getEntity())) + return; + + // If the food level is locked, cancel all changes. + if (lockFoodLevel) + event.setCancelled(true); + } + + public void onPlayerAnimation(PlayerAnimationEvent event) { + if (!arena.isRunning() || !arena.inArena(event.getPlayer())) + return; + + arena.getArenaPlayer(event.getPlayer()).getStats().inc("swings"); + } + + public void onPlayerDropItem(PlayerDropItemEvent event) { + Player p = event.getPlayer(); + + // If the player is active in the arena, only cancel if sharing is not + // allowed + if (arena.inArena(p)) { + if (!canShare) { + Messenger.tellPlayer(p, Msg.LOBBY_DROP_ITEM); + event.setCancelled(true); + } + } + + // Else, if the player is in the lobby or a spectator, just cancel + else if (arena.inLobby(p) || arena.inSpec(p)) { + Messenger.tellPlayer(p, Msg.LOBBY_DROP_ITEM); + event.setCancelled(true); + } + + /* + * If the player is not in the arena in any way (as arena player, lobby + * player or a spectator), but they -are- in the region, it must mean + * they are trying to drop items when not allowed + */ + else if (region.contains(p.getLocation())) { + Messenger.tellPlayer(p, Msg.LOBBY_DROP_ITEM); + event.setCancelled(true); + } + + /* + * If the player is in the banned set, it means they got kicked or + * disconnected during a session, meaning they are more than likely + * trying to steal items, if a PlayerDropItemEvent is fired. + */ + else if (banned.contains(p)) { + Messenger.warning("Player " + p.getName() + " tried to steal class items!"); + event.setCancelled(true); + } + } + + public void onPlayerBucketEmpty(PlayerBucketEmptyEvent event) { + if (!arena.getReadyPlayersInLobby().contains(event.getPlayer()) && !arena.inArena(event.getPlayer())) + return; + + if (!arena.isRunning()) { + event.getBlockClicked().getRelative(event.getBlockFace()).setTypeId(0); + event.setCancelled(true); + return; + } + + Block liquid = event.getBlockClicked().getRelative(event.getBlockFace()); + arena.addBlock(liquid); + } + + public void onPlayerInteract(PlayerInteractEvent event) { + Player p = event.getPlayer(); + if (arena.inArena(p) || !arena.inLobby(p)) + return; + + // Player is in the lobby, so disallow using items. + Action a = event.getAction(); + if (a == Action.RIGHT_CLICK_AIR || a == Action.RIGHT_CLICK_BLOCK) { + event.setUseItemInHand(Result.DENY); + event.setCancelled(true); + } + + // If there's no block involved, just return. + if (!event.hasBlock()) + return; + + // Iron block + if (event.getClickedBlock().getTypeId() == 42) { + handleReadyBlock(p); + } + // Sign + else if (event.getClickedBlock().getState() instanceof Sign) { + Sign sign = (Sign) event.getClickedBlock().getState(); + handleSign(sign, p); + } + } + + private void handleReadyBlock(Player p) { + if (arena.getArenaPlayer(p).getArenaClass() != null) { + Messenger.tellPlayer(p, Msg.LOBBY_PLAYER_READY); + arena.playerReady(p); + } + else { + Messenger.tellPlayer(p, Msg.LOBBY_PICK_CLASS); + } + } + + private void handleSign(Sign sign, Player p) { + // Check if the first line is a class name. + String className = ChatColor.stripColor(sign.getLine(0)).toLowerCase(); + + if (!arena.getClasses().containsKey(className) && !className.equals("random")) + return; + + // Check for permission. + if (!plugin.has(p, "mobarena.classes." + className) && !className.equals("random")) { + Messenger.tellPlayer(p, Msg.LOBBY_CLASS_PERMISSION); + return; + } + + // Delay the inventory stuff to ensure that right-clicking works. + delayAssignClass(p, className); + } + + private void delayAssignClass(final Player p, final String className) { + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin,new Runnable() { + public void run() { + arena.assignClass(p, className); + + if (!className.equalsIgnoreCase("random")) + Messenger.tellPlayer(p, Msg.LOBBY_CLASS_PICKED, TextUtils.camelCase(className), arena.getClassLogo(className)); + else + Messenger.tellPlayer(p, Msg.LOBBY_CLASS_RANDOM); + } + }); + } + + public void onPlayerQuit(PlayerQuitEvent event) { + Player p = event.getPlayer(); + if (!arena.isEnabled() || (!arena.inArena(p) && !arena.inLobby(p))) + return; + + arena.playerLeave(p); + banned.add(p); + scheduleUnban(p, 20); + } + + public void onPlayerKick(PlayerKickEvent event) { + Player p = event.getPlayer(); + if (!arena.isEnabled() || (!arena.inArena(p) && !arena.inLobby(p))) { + return; + } + + arena.playerLeave(p); + banned.add(p); + scheduleUnban(p, 20); + } + + private void scheduleUnban(final Player p, int ticks) { + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { + public void run() { + banned.remove(p); + } + }, ticks); + } + + public TeleportResponse onPlayerTeleport(PlayerTeleportEvent event) { + if (!arena.isEnabled() || !region.isSetup() || arena.inEditMode() || allowTeleport) { + return TeleportResponse.IDGAF; + } + + Location to = event.getTo(); + Location from = event.getFrom(); + Player p = event.getPlayer(); + + if (region.contains(from)) { + // Players not in the arena are free to warp out. + if (!arena.inArena(p) && !arena.inLobby(p) && !arena.inSpec(p)) { + return TeleportResponse.ALLOW; + } + + // Covers the case in which both locations are in the arena. + if (region.contains(to) || region.isWarp(to) || to.equals(arena.getPlayerEntry(p))) { + return TeleportResponse.ALLOW; + } + + Messenger.tellPlayer(p, Msg.WARP_FROM_ARENA); + return TeleportResponse.REJECT; + } + else if (region.contains(to)) { + if (region.isWarp(from) || region.isWarp(to) || to.equals(arena.getPlayerEntry(p))) { + return TeleportResponse.ALLOW; + } + + Messenger.tellPlayer(p, Msg.WARP_TO_ARENA); + return TeleportResponse.REJECT; + } + + return TeleportResponse.IDGAF; + } + + public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { + Player p = event.getPlayer(); + + if (event.isCancelled() || (!arena.inArena(p) && !arena.inLobby(p))) { + return; + } + + // This is safe, because commands will always have at least one element. + String base = event.getMessage().split(" ")[0]; + + // Check if the entire base command is allowed. + if (plugin.getArenaMaster().isAllowed(base)) { + return; + } + + // If not, check if the specific command is allowed. + String noslash = event.getMessage().substring(1); + if (plugin.getArenaMaster().isAllowed(noslash)) { + return; + } + + // This is dirty, but it ensures that commands are indeed blocked. + event.setMessage("/"); + + // Cancel the event regardless. + event.setCancelled(true); + Messenger.tellPlayer(p, Msg.MISC_COMMAND_NOT_ALLOWED); + } + + public void onPlayerPreLogin(PlayerPreLoginEvent event) { + Player p = plugin.getServer().getPlayer(event.getName()); + if (p == null || !p.isOnline()) return; + + Arena arena = plugin.getArenaMaster().getArenaWithPlayer(p); + if (arena == null) return; + + arena.playerLeave(p); + } +} diff --git a/src/com/garbagemule/MobArena/waves/ability/Ability.java b/src/com/garbagemule/MobArena/waves/ability/Ability.java new file mode 100644 index 0000000..6bcc407 --- /dev/null +++ b/src/com/garbagemule/MobArena/waves/ability/Ability.java @@ -0,0 +1,18 @@ +package com.garbagemule.MobArena.waves.ability; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.MABoss; + +public interface Ability +{ + /** + * Execute the boss ability. + * The ability should be concise and not start any unnecessary threading, + * or schedule any unnecessary tasks. + * The MABoss object can be used to get the LivingEntity that is the boss, + * and to damage, heal or just set the boss health directly. + * @param arena the Arena of the boss + * @param boss the MABoss object + */ + public void execute(Arena arena, MABoss boss); +} diff --git a/src/com/garbagemule/MobArena/waves/ability/AbilityInfo.java b/src/com/garbagemule/MobArena/waves/ability/AbilityInfo.java new file mode 100644 index 0000000..0c0ef6e --- /dev/null +++ b/src/com/garbagemule/MobArena/waves/ability/AbilityInfo.java @@ -0,0 +1,20 @@ +package com.garbagemule.MobArena.waves.ability; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy;; + +@Retention(RetentionPolicy.RUNTIME) +public @interface AbilityInfo +{ + /** + * The "pretty print" name of the ability. + * This value is printed when a boss executes the ability in the arena. + */ + public String name(); + + /** + * The config aliases for the ability. + * This is used by MobArena to parse ability names from the config-file. + */ + public String[] aliases(); +} diff --git a/src/com/garbagemule/MobArena/waves/ability/AbilityManager.java b/src/com/garbagemule/MobArena/waves/ability/AbilityManager.java new file mode 100644 index 0000000..9af7903 --- /dev/null +++ b/src/com/garbagemule/MobArena/waves/ability/AbilityManager.java @@ -0,0 +1,279 @@ +package com.garbagemule.MobArena.waves.ability; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; + +import com.garbagemule.MobArena.Messenger; +import com.garbagemule.MobArena.util.FileUtils; + +public class AbilityManager +{ + private static final String jarpath = "." + File.separator + "plugins" + File.separator + "MobArena.jar"; + private static final String classpath = jarpath + ";" + System.getProperty("java.class.path"); + + private static Map abilities; + + /** + * Get an Ability by one of its aliases. + * @param name an Ability alias + * @return an Ability, if one exists with the given alias, false otherwise + */ + public static Ability fromString(String name) { + return abilities.get(name.toLowerCase().replaceAll("[-_.]", "")); + } + + /** + * Load the known abilities as well as all custom abilities from + * the specified directory. + * @param dir a directory of .class (and/or .java) files + */ + public static void loadAbilities(File classDir) { + abilities = new HashMap(); + + // Extract all of the source files. + File javaDir = new File(classDir, "src"); + extractSourceFiles(javaDir); + + // The custom abilities to compile will be in the 'src'-dir + compileAbilities(javaDir, classDir); + + // Load all the custom abilities. + loadClasses(classDir); + } + + private static void extractSourceFiles(File javaDir) { + // Only extract the files if the folder doesn't exist. + if (javaDir.exists()) return; + + String path = "abilities/"; + + List files = new ArrayList(); + + files.add(path + "ChainLightning.java"); + files.add(path + "LivingBomb.java"); + files.add(path + "ObsidianBomb.java"); + files.add(path + "Flood.java"); + files.add(path + "WarpToPlayer.java"); + files.add(path + "RootTarget.java"); + files.add(path + "ShufflePositions.java"); + + files.add(path + "ShootArrow.java"); + files.add(path + "ShootFireball.java"); + + files.add(path + "FireAura.java"); + files.add(path + "LightningAura.java"); + + files.add(path + "DisorientDistant.java"); + files.add(path + "DisorientNearby.java"); + files.add(path + "DisorientTarget.java"); + + files.add(path + "PullDistant.java"); + files.add(path + "PullNearby.java"); + files.add(path + "PullTarget.java"); + + files.add(path + "ThrowDistant.java"); + files.add(path + "ThrowNearby.java"); + files.add(path + "ThrowTarget.java"); + + FileUtils.extractResources(javaDir, files.toArray(new String[0])); + } + + private static void compileAbilities(File javaDir, File classDir) { + if (!javaDir.exists()) return; + + // Make ready a new list of files to compile. + List toCompile = getSourceFilesToCompile(javaDir, classDir); + + // No files to compile? + if (toCompile.isEmpty()) { + return; + } + + // Get the compiler and the file manager + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); + + // Generate some JavaFileObjects + Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(toCompile); + + // Include the MobArena.jar on the classpath, and set the destination folder. + List options = Arrays.asList("-classpath", classpath, "-d", classDir.getPath()); + + // Set up the compilation task. + JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options, null, compilationUnits); + + // Call the task. + task.call(); + + // And close the file manager. + try { + fileManager.close(); + } + catch (Exception e) {} + } + + private static List getSourceFilesToCompile(File javaDir, File classDir) { + List result = new ArrayList(); + + if (javaDir == null || !javaDir.exists()) { + return result; + } + + // Grab the array of compiled files. + File[] classFiles = classDir.listFiles(); + + // Go through each source file. + for (File javaFile : javaDir.listFiles()) { + // Skip if it's not a .java file. + if (!javaFile.getName().endsWith(".java")) { + Messenger.info("Found invalid ability file: " + javaFile.getName()); + continue; + } + + // Find the associated .class file. + File classFile = findClassFile(javaFile, classFiles); + + // If the .class file is newer, we don't need to compile. + if (isClassFileNewer(javaFile, classFile)) { + continue; + } + result.add(javaFile); + } + + return result; + } + + private static File findClassFile(File javaFile, File[] classFiles) { + String javaFileName = javaFile.getName(); + String classFileName = javaFileName.substring(0, javaFileName.lastIndexOf(".")) + ".class"; + + for (File classFile : classFiles) { + if (classFile.getName().equals(classFileName)) { + return classFile; + } + } + return null; + } + + private static boolean isClassFileNewer(File javaFile, File classFile) { + if (classFile == null) return false; + + return (classFile.lastModified() > javaFile.lastModified()); + } + + /** + * (Compiles and) loads all custom abilities in the given directory. + * @param dir a directory + */ + private static void loadClasses(File classDir) { + // Grab the class loader + ClassLoader loader = getLoader(classDir); + if (loader == null) return; + + StringBuffer buffy = new StringBuffer(); + + for (File file : classDir.listFiles()) { + String filename = file.getName(); + + // Only load .class files. + int dot = filename.lastIndexOf(".class"); + if (dot < 0) continue; + + // Trim off the .class extension + String name = filename.substring(0, file.getName().lastIndexOf(".")); + + // And make an Ability. + Ability ability = makeAbility(loader, name); + if (ability == null) continue; + + // Then load the ability into the map. + String abilityName = loadAbility(ability); + + if (abilityName != null) { + buffy.append(", " + abilityName); + } + } + } + + /** + * Loads an Ability into the abilities map by all of its aliases. + * @param ability an Ability + * @return the first alias of + */ + private static String loadAbility(Ability ability) { + // Grab the annotation. + AbilityInfo info = ability.getClass().getAnnotation(AbilityInfo.class); + if (info == null) return null; + + // Put the command in the map with each of its aliases. + for (String name : info.aliases()) { + abilities.put(name, ability); + } + + return info.aliases()[0]; + } + + /** + * Ask the given ClassLoader to load the ability with the given name. + * @param loader a ClassLoader + * @param name a class name + * @return an Ability, if the ClassLoader found one with the given name, null otherwise + */ + private static Ability makeAbility(ClassLoader loader, String name) { + try { + // Load the class. + Class c = loader.loadClass(name); + + // Create an instance of it. + Object o = c.newInstance(); + + // If it's an ability, return it. + if (o instanceof Ability) { + return (Ability) o; + } + } + catch (Exception e) {} + + // Otherwise, return null. + return null; + } + + /** + * Get a ClassLoader for the given directory. + * @param dir a directory + * @return a ClassLoader, or null + */ + private static ClassLoader getLoader(File dir) { + try { + ClassLoader loader = new URLClassLoader(new URL[] { dir.toURI().toURL() }, Ability.class.getClassLoader()); + return loader; + } + catch (Exception e) {} + + return null; + } + + /** + * Turn an array of aliases into a comma-separated string of aliases. + * @param aliases an array of Strings + * @return a comma-separated String + */ + /*private static String aliasString(String[] aliases) { + StringBuffer buffy = new StringBuffer(); + for (String a : aliases) { + buffy.append(a + ", "); + } + return buffy.substring(0, buffy.length() - 2); + }*/ +} diff --git a/src/com/garbagemule/MobArena/waves/ability/AbilityUtils.java b/src/com/garbagemule/MobArena/waves/ability/AbilityUtils.java new file mode 100644 index 0000000..112eaa0 --- /dev/null +++ b/src/com/garbagemule/MobArena/waves/ability/AbilityUtils.java @@ -0,0 +1,86 @@ +package com.garbagemule.MobArena.waves.ability; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.bukkit.entity.Creature; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.MAUtils; +import com.garbagemule.MobArena.framework.Arena; + +public class AbilityUtils +{ + public static Random random = new Random(); + + /** + * Get the target player of the LivingEntity if possible. + * @param the arena + * @param entity The entity whose target to get + * @param random Grab a random player if no target was found + * @return The target player, or null + */ + public static LivingEntity getTarget(Arena arena, LivingEntity entity, boolean random) { + if (entity instanceof Creature) { + LivingEntity target = ((Creature) entity).getTarget(); + + if (target instanceof Player) { + return target; + } + } + + if (random) { + return getRandomPlayer(arena); + } + return null; + } + + /** + * Get a random arena player. + * @param arena the arena + * @return a random arena player, or null if none were found + */ + public static Player getRandomPlayer(Arena arena) { + List list = new ArrayList(arena.getPlayersInArena()); + if (list.isEmpty()) return null; + + return list.get(random.nextInt(list.size())); + } + + /** + * Get a list of nearby players + * @param arena the arena + * @param boss the boss + * @param x the 'radius' in which to grab players + * @return a list of nearby players + */ + public static List getNearbyPlayers(Arena arena, Entity boss, int x) { + List result = new ArrayList(); + for (Entity e : boss.getNearbyEntities(x, x, x)) { + if (arena.getPlayersInArena().contains(e)) { + result.add((Player) e); + } + } + return result; + } + + /** + * Get a list of distant players + * @param arena the arena + * @param boss the boss + * @param x the 'radius' in which to exclude players + * @return a list of distant players + */ + public static List getDistantPlayers(Arena arena, Entity boss, int x) { + List result = new ArrayList(); + for (Player p : arena.getPlayersInArena()) { + if (MAUtils.distanceSquared(arena.getPlugin(), p, boss.getLocation()) > (double) (x*x)) { + result.add(p); + } + } + return result; + } +}