From 4c7d85193e6a752c5ff0714ce0b15134bf46f6d3 Mon Sep 17 00:00:00 2001 From: Andreas Troelsen Date: Sun, 31 Jul 2011 21:26:18 +0200 Subject: [PATCH] Preparations for customizable waves.. --- .classpath | 14 + .project | 17 + build.xml | 41 ++ resources/plugin.yml | 26 + resources/res/announcements.properties | 51 ++ resources/res/config.yml | 107 ++++ resources/res/totals.txt | 57 ++ resources/res/totals.yml | 57 ++ src/com/garbagemule/MobArena/ArenaPlayer.java | 42 ++ .../garbagemule/MobArena/util/FileUtils.java | 211 +++++++ .../garbagemule/MobArena/util/TextUtils.java | 42 ++ .../garbagemule/MobArena/util/WaveUtils.java | 534 ++++++++++++++++++ .../MobArena/util/data/PlainText.java | 153 +++++ .../MobArena/util/data/Totals.java | 159 ++++++ .../garbagemule/MobArena/util/data/XML.java | 6 + .../MobArena/waves/AbstractWave.java | 90 +++ .../garbagemule/MobArena/waves/BossWave.java | 35 ++ .../MobArena/waves/RecurrentWave.java | 47 ++ .../MobArena/waves/SingleWave.java | 37 ++ src/com/garbagemule/MobArena/waves/Wave.java | 119 ++++ 20 files changed, 1845 insertions(+) create mode 100644 .classpath create mode 100644 .project create mode 100644 build.xml create mode 100644 resources/plugin.yml create mode 100644 resources/res/announcements.properties create mode 100644 resources/res/config.yml create mode 100644 resources/res/totals.txt create mode 100644 resources/res/totals.yml create mode 100644 src/com/garbagemule/MobArena/ArenaPlayer.java create mode 100644 src/com/garbagemule/MobArena/util/FileUtils.java create mode 100644 src/com/garbagemule/MobArena/util/TextUtils.java create mode 100644 src/com/garbagemule/MobArena/util/WaveUtils.java create mode 100644 src/com/garbagemule/MobArena/util/data/PlainText.java create mode 100644 src/com/garbagemule/MobArena/util/data/Totals.java create mode 100644 src/com/garbagemule/MobArena/util/data/XML.java create mode 100644 src/com/garbagemule/MobArena/waves/AbstractWave.java create mode 100644 src/com/garbagemule/MobArena/waves/BossWave.java create mode 100644 src/com/garbagemule/MobArena/waves/RecurrentWave.java create mode 100644 src/com/garbagemule/MobArena/waves/SingleWave.java create mode 100644 src/com/garbagemule/MobArena/waves/Wave.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..2fc2280 --- /dev/null +++ b/.classpath @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..4492ba2 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + MobArena + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..318ff72 --- /dev/null +++ b/build.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/plugin.yml b/resources/plugin.yml new file mode 100644 index 0000000..1ab4980 --- /dev/null +++ b/resources/plugin.yml @@ -0,0 +1,26 @@ +name: MobArena +main: com.garbagemule.MobArena.MobArena +version: 0.93.4 +softdepend: [MultiVerse,XcraftGate,Towny] +commands: + ma: + description: Base command for MobArena + usage: | + /ma join - Join the arena. + /ma leave - Leave the arena. + /ma notready - List of players who aren't ready. + /ma spectate - Warp to the spectator area. + marena: + description: Base command for MobArena + usage: | + /marena join - Join the arena. + /marena leave - Leave the arena. + /marena notready - List of players who aren't ready. + /marena spectate - Warp to the spectator area. + mobarena: + description: Base command for MobArena + usage: | + /mobarena join - Join the arena. + /mobarena leave - Leave the arena. + /mobarena notready - List of players who aren't ready. + /mobarena spectate - Warp to the spectator area. \ No newline at end of file diff --git a/resources/res/announcements.properties b/resources/res/announcements.properties new file mode 100644 index 0000000..3f101af --- /dev/null +++ b/resources/res/announcements.properties @@ -0,0 +1,51 @@ +ARENA_START=Let the slaughter begin! +ARENA_END=Arena finished. +ARENA_DOES_NOT_EXIST=That arena does not exist. Type /ma arenas for a list. +JOIN_PLAYER_JOINED= +JOIN_NOT_ENABLED=MobArena is not enabled. +JOIN_IN_OTHER_ARENA=You are already in an arena! Leave that one first. +JOIN_ARENA_NOT_ENABLED=This arena is not enabled. +JOIN_ARENA_NOT_SETUP=This arena has not been set up yet. +JOIN_ARENA_PERMISSION=You don't have permission to join this arena. +JOIN_FEE_REQUIRED=Insufficient funds. Price: % +JOIN_FEE_PAID=Price to join was: % +JOIN_ARENA_IS_RUNNING=This arena is in already progress. +JOIN_ALREADY_PLAYING=You are already playing! +JOIN_ARG_NEEDED=You must specify an arena. Type /ma arenas for a list. +JOIN_TOO_FAR=You are too far away from the arena to join/spectate. +JOIN_EMPTY_INV=You must empty your inventory to join the arena. +JOIN_PLAYER_LIMIT_REACHED=The player limit of this arena has been reached. +JOIN_STORE_INV_FAIL=Failed to store inventory. Try again. +LEAVE_PLAYER_LEFT=You left the arena. Thanks for playing! +LEAVE_NOT_PLAYING=You are not in the arena. +PLAYER_DIED=% died! +SPEC_PLAYER_SPECTATE=Enjoy the show! +SPEC_NOT_RUNNING=This arena isn't running. +SPEC_ARG_NEEDED=You must specify an arena. Type /ma arenas for a list. +SPEC_EMPTY_INV=Empty your inventory first! +SPEC_ALREADY_PLAYING=Can't spectate when in the arena! +NOT_READY_PLAYERS=Not ready: % +FORCE_START_STARTED=Forced arena start. +FORCE_START_RUNNING=Arena has already started. +FORCE_START_NOT_READY=Can't force start, no players are ready. +FORCE_END_ENDED=Forced arena end. +FORCE_END_EMPTY=No one is in the arena. +FORCE_END_IDLE=You weren't quick enough! +REWARDS_GIVE=Here are all of your rewards! +LOBBY_CLASS_PICKED=You have chosen % as your class! +LOBBY_CLASS_RANDOM=You will get a random class on arena start. +LOBBY_CLASS_PERMISSION=You don't have permission to use this class! +LOBBY_PLAYER_READY=You have been flagged as ready! +LOBBY_DROP_ITEM=No sharing before the arena starts! +LOBBY_PICK_CLASS=You must first pick a class! +LOBBY_RIGHT_CLICK=Punch the sign. Don't right-click. +WARP_TO_ARENA=Can't warp to the arena during battle! +WARP_FROM_ARENA=Warping not allowed in the arena! +WAVE_DEFAULT=Get ready for wave #%! +WAVE_SPECIAL=Get ready for wave #%! [SPECIAL] +WAVE_REWARD=You just earned a reward: % +MISC_LIST_ARENAS=Available arenas: % +MISC_LIST_PLAYERS=Live players: % +MISC_COMMAND_NOT_ALLOWED=You can't use that command in the arena! +MISC_NO_ACCESS=You don't have access to this command. +MISC_NONE= diff --git a/resources/res/config.yml b/resources/res/config.yml new file mode 100644 index 0000000..56d07d6 --- /dev/null +++ b/resources/res/config.yml @@ -0,0 +1,107 @@ +### MobArena Configuration File +### Please visit the MobArena Wiki here: http://goo.gl/F5TTc for more details +### on how to set up this file. +### Note that you CAN'T use tabs in this file! Always use spaces! + +### GLOBAL SETTINGS +# These settings are arena-independent and count in all arenas. Note that if +# enabled: false in the global settings, MobArena is disabled globally, and +# arena-specific enabled-values do not matter. If true, however, the per-arena +# enabled-values count. +global-settings: + enabled: true + allowed-commands: /list, /pl + update-notification: true + +### CLASS SETUP +# Items can be written as either their data value (numbers) or their Material +# type (names); check the Wiki for links to lists of both. +# The notation is :. If no amount is given, 1 is assumed. Sub- +# types can be created +# Item SUBTYPES (wool/dye colors) are written as ::, +# but here, the amount is REQUIRED, even if it is just 1. +# Note: If you want to specify only one item, but apostrophes around that one +# item, e.g.: items: '278' +# Note: For every bone a class has, one PET WOLF will spawn upon arena start, +# which will assist the player in the arena session. +classes: + Knight: + items: diamond_sword, grilled_pork:2 + armor: 306,307,308,309 + Tank: + items: iron_sword, grilled_pork:3, apple + armor: 310,311,312,313 + Archer: + items: wood_sword, bow, arrow:64, arrow:64, grilled_pork, bone:2 + armor: 298,299,300,301 + Chef: + items: stone_sword, bread:6, grilled_pork:4, mushroom_soup, cake:5 + armor: 314,315,316,317 + Oddjob: + items: stone_sword, flint_and_steel, netherrack:2, tnt:4, pork:3 + armor: 298,299,300,301 + +### ARENA SETUP +# The arena setup is split into 4 different sections per arena: settings, +# waves, rewards and coords (not shown until coords are set up). +# Please refer to the Wiki for descriptions of all these settings. +# Note: The REWARDS use the same notation as the items in the CLASS SETUP +# section. However, only -one- item from the list is picked (at random) when +# the rewards are given. +arenas: + default: + settings: + world: '' + enabled: true + protect: true + entry-fee: '' + logging: true + clear-wave-before-next: false + detonate-creepers: false + detonate-damage: false + lightning: true + auto-equip-armor: true + force-restore: false + soft-restore: false + soft-restore-drops: false + require-empty-inv-join: false + require-empty-inv-spec: false + hellhounds: false + pvp-enabled: false + monster-infight: false + allow-teleporting: false + spectate-on-death: true + share-items-in-arena: true + min-players: 0 + max-players: 0 + max-join-distance: 0 + repair-delay: 5 + first-wave-delay: 5 + wave-interval: 20 + special-modulo: 4 + max-idle-time: 0 + waves: + default: + zombies: 10 + skeletons: 10 + spiders: 10 + creepers: 10 + wolves: 10 + special: + powered-creepers: 10 + zombie-pigmen: 10 + angry-wolves: 10 + slimes: 10 + humans: 10 + giants: 0 + ghasts: 0 + rewards: + waves: + every: + '3': feather, bone, stick + '5': dirt:4, gravel:4, stone:4 + '10': iron_ingot:10, gold_ingot:8 + after: + '7': minecart, storage_minecart, powered_minecart + '13': iron_sword, iron_pickaxe, iron_spade + '16': diamond_sword \ No newline at end of file diff --git a/resources/res/totals.txt b/resources/res/totals.txt new file mode 100644 index 0000000..bcbbbf4 --- /dev/null +++ b/resources/res/totals.txt @@ -0,0 +1,57 @@ +### General data +### Statistics for overall information about the arena, such as +### times played, durations, max players seen, total kills, etc. +general-info: + total-games-played: 0 + total-duration: 0:00:00 + longest-session-duration: 0:00:00 + most-players: 0 + highest-wave-reached: 0 + total-monsters-killed: 0 + +### Class specific data +### How many times has each class been played, and how many kills, how +### much damage, etc. does the class make/do/take. +classes: + overall-distribution: + # Percentages can be calculated with / total-count * 100. + total-count: 0 + +#classes: +# overall-distribution: +# total-count: 0 +# archer: +# kills: +# damage-done: +# damage-taken: +# accuracy: +# knight: +# kills: +# damage-done: +# damage-taken: +# accuracy: +# ... +### Reward data +### Totals for all rewards given, perhaps good for balancing? +rewards: + total-given: + # This might be useless information, I don't know. + # Might have to make it data values instead of Material names? + money: 0 + +### Player data +### Total games played, kills, damage, swings and hits (for accuracy), and how +### many times the player has played as each class. +players: {} +# garbagemule: +# games-played: 5 +# kills: 4 +# damage-done: 251 +# damage-taken: 102 +# swings: 1820 +# hits: 1214 +# classes-played: +# archer: 2 +# ... +# Agnate: +# ... \ No newline at end of file diff --git a/resources/res/totals.yml b/resources/res/totals.yml new file mode 100644 index 0000000..610f4a1 --- /dev/null +++ b/resources/res/totals.yml @@ -0,0 +1,57 @@ +### General data +### Statistics for overall information about the arena, such as +### times played, durations, max players seen, total kills, etc. +general-info: + total-games-played: 0 + total-duration: 0:00:00 + longest-session-duration: 0:00:00 + most-players: 0 + highest-wave-reached: 0 + total-monsters-killed: 0 + +### Class specific data +### How many times has each class been played, and how many kills, how +### much damage, etc. does the class make/do/take. +classes: + overall-distribution: + # Percentages can be calculated with / total-count * 100. + total-count: 0 + +#classes: +# overall-distribution: +# total-count: 0 +# archer: +# kills: +# damage-done: +# damage-taken: +# accuracy: +# knight: +# kills: +# damage-done: +# damage-taken: +# accuracy: +# ... +### Reward data +### Totals for all rewards given, perhaps good for balancing? +rewards: + total-given: + # This might be useless information, I don't know. + # Might have to make it data values instead of Material names? + money: 0 + +### Player data +### Total games played, kills, damage, swings and hits (for accuracy), and how +### many times the player has played as each class. +players: {} +# garbagemule: +# games-played: 5 +# kills: 4 +# damage-done: 251 +# damage-taken: 102 +# swings: 1820 +# hits: 1214 +# classes-played: +# archer: 2 +# ... +# Agnate: +# ... \ No newline at end of file diff --git a/src/com/garbagemule/MobArena/ArenaPlayer.java b/src/com/garbagemule/MobArena/ArenaPlayer.java new file mode 100644 index 0000000..c4efe41 --- /dev/null +++ b/src/com/garbagemule/MobArena/ArenaPlayer.java @@ -0,0 +1,42 @@ +package com.garbagemule.MobArena; + +import java.util.LinkedList; +import java.util.List; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class ArenaPlayer +{ + public MobArena plugin; + public Player player; + public String className; + public Arena arena; + public List rewards; + + protected boolean inArena, inLobby, inSpec, isReady; + + // Session fields. + public int kills, dmgDone, dmgTaken, swings, hits, deaths, lastWave; + public int flagCaps, flagAttempts, flagReturns; // BG: Capture the Pumpkin + public int baseCaps; // BG: Domination + + // All-time fields. + protected int totalKills, totalDmgDone, totalDmgTaken, totalSwings, totalHits, totalDeaths; + protected int totalFlagCaps, totalFlagAttempts, totalFlagReturns; // BG: Capture the Pumpkin + protected int totalBaseCaps; // BG: Domination + + public ArenaPlayer(Player player, Arena arena, MobArena plugin) + { + this.player = player; + this.arena = arena; + this.plugin = plugin; + + className = arena.classMap.get(player); + rewards = new LinkedList(); + } + + public Player getPlayer() { return player; } + public Arena getArena() { return arena; } + public String getClassName() { return className; } +} diff --git a/src/com/garbagemule/MobArena/util/FileUtils.java b/src/com/garbagemule/MobArena/util/FileUtils.java new file mode 100644 index 0000000..ef905df --- /dev/null +++ b/src/com/garbagemule/MobArena/util/FileUtils.java @@ -0,0 +1,211 @@ +package com.garbagemule.MobArena.util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.bukkit.util.config.Configuration; + +import com.garbagemule.MobArena.MobArena; + +public class FileUtils +{ + public static enum Libs + { + xml("jdom.jar", "http://mirrors.ibiblio.org/pub/mirrors/maven2/org/jdom/jdom/1.1/jdom-1.1.jar"); + + public String url, filename; + private Libs(String filename, String url) + { + this.filename = filename; + this.url = url; + } + + public static Libs getLib(String filename) + { + for (Libs l : Libs.values()) + if (l.filename.equals(filename)) + return l; + return null; + } + } + + /** + * Create default files from the res/ directory, if they exist. + * @param filenames Files to be created. + */ + public static void extractDefaults(String... filenames) + { + for (String filename : filenames) + extractFile(MobArena.dir, filename); + } + + public static void extractFile(File dir, String filename) + { + // Skip if file exists + File file = new File(dir, filename); + if (file.exists()) return; + + // Skip if there is no resource with that name + InputStream in = MobArena.class.getResourceAsStream("/res/" + filename); + if (in == null) return; + + try + { + // Set up an output stream + FileOutputStream out = new FileOutputStream(file); + byte[] buffer = new byte[8192]; + int length = 0; + + // Write the resource data to the file + while ((length = in.read(buffer)) > 0) + out.write(buffer, 0, length); + + if (in != null) in.close(); + if (out != null) out.close(); + } + catch (Exception e) + { + e.printStackTrace(); + System.out.println("[MobArena] ERROR! Problem creating file '" + filename + "'!"); + } + } + + /** + * Download all necessary libraries. + * @param config The MobArena config-file + */ + public static void fetchLibs(Configuration config) + { + // Get all arenas + List arenas = config.getKeys("arenas"); + if (arenas == null) return; + + // Add all the logging types + Set libs = new HashSet(); + for (String a : arenas) + { + String type = config.getString("arenas." + a + ".settings.logging", "").toLowerCase(); + if (type.equals("xml")) + libs.add(type); + } + + // Download all libraries + for (String lib : libs) + { + if (download(Libs.valueOf(lib))) + continue; + + // If a library couldn't be downloaded, default to false. + for (String a : arenas) + { + if (!config.getString("arenas." + a + ".settings.logging", "").equalsIgnoreCase(lib)) + continue; + + System.out.println("[MobArena] ERROR! Unrecognized format for arena '" + a + "': " + lib + ". Logging disabled."); + config.setProperty("arenas." + a + ".logging", "false"); + } + } + } + + private static synchronized boolean download(Libs lib) + { + if (lib == null) return false; + + InputStream in = null; + OutputStream out = null; + + try + { + URLConnection con = new URL(lib.url).openConnection(); + con.setUseCaches(false); + + // Library folder: plugin/MobArena/lib + File libdir = new File(MobArena.dir, "lib"); + libdir.mkdir(); + + // Create the file if it doesn't exist, if it does, return + File file = new File(libdir, lib.filename); + if (file.exists()) return true; + + long startTime = System.currentTimeMillis(); + System.out.println("[MobArena] Downloading library: " + lib.filename + "..."); + + // Set up the streams + in = con.getInputStream(); + out = new FileOutputStream(file); + if (in == null || out == null) return false; + + byte[] buffer = new byte[65536]; + int length = 0; + + // Write the library to disk + while ((length = in.read(buffer)) > 0) + out.write(buffer, 0, length); + + System.out.println("[MobArena] " + lib.filename + " downloaded in " + ((System.currentTimeMillis()-startTime)/1000.0) + " seconds."); + } + catch (Exception e) + { + e.printStackTrace(); + System.out.println("[MobArena] ERROR! Couldn't download library: " + lib.filename); + return false; + } + finally + { + try + { + if (in != null) in.close(); + if (out != null) out.close(); + } + catch (Exception e) { e.printStackTrace(); } + } + return true; + } + + public static File getMostRecent(String folder) + { + return getMostRecent(new File(folder)); + } + + public static File getMostRecent(File dir) + { + if (!dir.exists()) dir.mkdir(); + if (!dir.isDirectory()) return null; + + long mostRecent = 0; + File result = null; + + for (File file : dir.listFiles()) + { + if (file.isDirectory() || file.lastModified() > mostRecent) + continue; + + result = file; + mostRecent = file.lastModified(); + } + + return result; + } + + public static Configuration parseXML(File file) + { + return null; + } + + public static Configuration parseCSV(File file) + { + return null; + } + + public static Configuration parsePlainText(File file) + { + return null; + } +} diff --git a/src/com/garbagemule/MobArena/util/TextUtils.java b/src/com/garbagemule/MobArena/util/TextUtils.java new file mode 100644 index 0000000..85cda7d --- /dev/null +++ b/src/com/garbagemule/MobArena/util/TextUtils.java @@ -0,0 +1,42 @@ +package com.garbagemule.MobArena.util; + +public class TextUtils +{ + /** + * Add character padding on the right side of a String. + * @param s String to add padding to + * @param length Total amount of characters in the returned String + * @param pad The padding character + * @return A padded String with the input length + */ + public static String padRight(String s, int length, char pad) + { + StringBuffer buffy = new StringBuffer(); + buffy.append(s); + for (int i = s.length(); i < length; i++) + buffy.append(pad); + return buffy.toString(); + } + public static String padRight(String s, int length) { return padRight(s, length, ' '); } + public static String padRight(int s, int length) { return padRight(Integer.toString(s), length, ' '); } + public static String padRight(double s, int length) { return padRight(Double.toString(s), length, ' '); } + + /** + * Add character padding on the left side of a String. + * @param s String to add padding to + * @param length Total amount of characters in the returned String + * @param pad The padding character + * @return A padded String with the input length + */ + public static String padLeft(String s, int length, char pad) + { + StringBuffer buffy = new StringBuffer(); + for (int i = 0; i < length - s.length(); i++) + buffy.append(pad); + buffy.append(s); + return buffy.toString(); + } + public static String padLeft(String s, int length) { return padLeft(s, length, ' '); } + public static String padLeft(int s, int length) { return padLeft(Integer.toString(s), length, ' '); } + public static String padLeft(double s, int length) { return padLeft(Double.toString(s), length, ' '); } +} diff --git a/src/com/garbagemule/MobArena/util/WaveUtils.java b/src/com/garbagemule/MobArena/util/WaveUtils.java new file mode 100644 index 0000000..ef603a8 --- /dev/null +++ b/src/com/garbagemule/MobArena/util/WaveUtils.java @@ -0,0 +1,534 @@ +package com.garbagemule.MobArena.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.TreeSet; + +import org.bukkit.Location; +import org.bukkit.entity.CreatureType; +import org.bukkit.entity.Player; +import org.bukkit.util.config.Configuration; + +import com.garbagemule.MobArena.MAUtils; +import com.garbagemule.MobArena.MobArena; +import com.garbagemule.MobArena.waves.RecurrentWave; +import com.garbagemule.MobArena.waves.SingleWave; +import com.garbagemule.MobArena.waves.Wave; +import com.garbagemule.MobArena.waves.Wave.BossAbility; +import com.garbagemule.MobArena.waves.Wave.BossHealth; +import com.garbagemule.MobArena.waves.Wave.SwarmAmount; +import com.garbagemule.MobArena.waves.Wave.WaveBranch; +import com.garbagemule.MobArena.waves.Wave.WaveGrowth; +import com.garbagemule.MobArena.waves.Wave.WaveType; + +public class WaveUtils +{ + /** + * Get all the spawnpoints that have players nearby. + */ + public static List getValidSpawnpoints(Collection spawnpoints, Collection players) + { + List result = new ArrayList(); + + for (Location s : spawnpoints) + { + for (Player p : players) + { + // If the player somehow got out of the arena world, kick him. + if (!s.getWorld().getName().equals(p.getWorld().getName())) + { + System.out.println("[MobArena] Player '" + p.getName() + "' is not in the right world. Kicking..."); + p.kickPlayer("[MobArena] Cheater! (Warped out of the arena world.)"); + continue; + } + + if (s.distanceSquared(p.getLocation()) > MobArena.MIN_PLAYER_DISTANCE) + continue; + + result.add(s); + break; + } + } + + // If no players are in range, just use all the spawnpoints. + if (result.isEmpty()) + result.addAll(spawnpoints); + + // Else, return the valid spawnpoints. + return result; + } + + /** + * Grab and process all the waves in the config-file for the arena. + */ + public static TreeSet getWaves(Configuration config, String arena, WaveBranch branch) + { + // Determine the branch type of the wave, and grab the appropriate comparator + String b = branch.toString().toLowerCase(); + TreeSet result = new TreeSet(getComparator(branch)); + + // Grab the waves from the config-file + String path = "arenas." + arena + ".waves." + b; + List waves = config.getKeys(path); + + // If there are any waves, process them + if (waves != null) + { + Wave wave; + for (String w : waves) + { + // path argument becomes: "arenas..waves..." + wave = getWave(config, path + "." + w + ".", w, branch); + if (wave != null) result.add(wave); + } + } + + // If there are no waves and the type is 'recurrent', add a default wave. + if (branch == WaveBranch.RECURRENT && (result.isEmpty() || waves == null)) + { + RecurrentWave def = new RecurrentWave("DEF_WAVE_AUTO", 1, 1, 1); + def.setType(WaveType.DEFAULT); + def.setGrowth(WaveGrowth.MEDIUM); + def.setDefault(true); + + result.add(def); + } + + return result; + } + + /** + * Get a single wave based on the config-file, the path, and branch + * @return A Wave object if it is well defined, null otherwise. + */ + private static Wave getWave(Configuration config, String path, String name, WaveBranch branch) + { + // Grab the wave type, if null, return null + WaveType type = WaveType.fromString(config.getString(path + "type")); + if (type == null || !isWaveWellDefined(config, path, branch, type)) + return null; + + // TODO: Generate waves properly. These are place-holders! + Wave result; + if (branch == WaveBranch.RECURRENT) + { + int frequency = config.getInt(path + "frequency", 0); + int priority = config.getInt(path + "priority", 0); + int wave = config.getInt(path + "wave", frequency); + result = new RecurrentWave(name, wave, frequency, priority); + } + else + { + int wave = config.getInt(path + "wave", 0); + result = new SingleWave(name, wave); + } + return result; + } + /* + public static RecurrentWave getRecurrentWave(Configuration config, String arena, String w) + { + // Grab the path + String path = "arenas." + arena + ".waves.recurrent." + w + "."; + + // Ensure that frequency and priority exist, otherwise return null + int frequency = config.getInt(path + "frequency", 0); + int priority = config.getInt(path + "priority", 0); + if (frequency == 0 || priority == 0) return null; + + // Grab other variables + int wave = config.getInt(path + "wave", frequency); + WaveType type = WaveType.fromString(config.getString(path + "type", "default")); + WaveGrowth growth = WaveGrowth.fromString(config.getString(path + "growth", "medium")); + + // Grab monster distribution + //Map monsters = getWaveMonsters(config, arena, path, type); + + // Create the wave + RecurrentWave result = new RecurrentWave(w, wave, frequency, priority, type, growth); + + + return result; + } + + public static SingleWave getSingleWave(Configuration config, String arena, String w) + { + // Grab the path + String path = "arenas." + arena + ".waves.single." + w + "."; + + // Ensure that the wave number exists, otherwise return null + int wave = config.getInt(path + "wave", 0); + if (wave == 0) return null; + + // Grab other variables + + SingleWave result = new SingleWave(w, wave); + + return result; + } + + public static Map getWaveMonsters(Configuration config, String arena, String path, String type) + { + Map result = new HashMap(); + + List monsters = config.getKeys(path + "monsters"); + + // If no monsters specified, make sure to add some + if (monsters == null) + { + if (type.equals("default")) + { + result.put(CreatureType.ZOMBIE, 10); + result.put(CreatureType.SKELETON, 10); + result.put(CreatureType.SPIDER, 10); + result.put(CreatureType.CREEPER, 10); + result.put(CreatureType.WOLF, 10); + } + else if (type.equals("default")) + { + + } + } + + return result; + } + */ + + + /*//////////////////////////////////////////////////////////////////// + // + // Comparators + // + ////////////////////////////////////////////////////////////////////*/ + + /** + * Get a comparator based on the WaveBranch parameter. + */ + public static Comparator getComparator(WaveBranch branch) + { + if (branch == WaveBranch.SINGLE) + return getSingleComparator(); + else if (branch == WaveBranch.RECURRENT) + return getRecurrentComparator(); + else + return null; + } + + /** + * Get a Comparator that compares Wave objects by wave number. + * If the wave numbers are equal, the waves are equal. This is to + * DISALLOW "duplicates" in the SINGLE WAVES collection. + * @return Comparator whose compare()-method compares wave numbers. + */ + public static Comparator getSingleComparator() + { + return new Comparator() + { + public int compare(Wave w1, Wave w2) + { + if (w1.getWave() < w2.getWave()) + return -1; + else if (w1.getWave() > w2.getWave()) + return 1; + else return 0; + } + }; + } + + /** + * Get a Comparator that compares Wave objects by priority. + * If the priorities are equal, the names are compared. This is to + * ALLOW "duplicates" in the RECURRENT WAVES collection. + * @return Comparator whose compare()-method compares wave priorities. + */ + public static Comparator getRecurrentComparator() + { + return new Comparator() + { + public int compare(Wave w1, Wave w2) + { + if (w1.getPriority() < w2.getPriority()) + return -1; + else if (w1.getPriority() > w2.getPriority()) + return 1; + else return w1.getName().compareTo(w2.getName()); + } + }; + } + + /** + * Get all the single waves for the given arena. + */ + /*public static TreeSet getSingleWaves(Configuration config, String arena) + { + TreeSet result = new TreeSet(); + + List waves = config.getKeys("arenas." + arena + ".waves.single"); + if (waves != null) + { + int wave; + + for (String w : waves) + { + wave = config.getInt("arenas." + arena + ".waves.single." + w + ".wave", 0); + if (wave == 0) continue; + result.add(new SingleWave(w, wave)); + } + } + + return result; + }*/ + + /** + * Get all the recurrent waves for the given arena. + * If no waves are found, a default wave is added. This ensures that + * the arena always has monsters spawning, regardless of how badly the + * user messes up the config-file. + */ + /*public static TreeSet getRecurrentWaves(Configuration config, String arena) + { + TreeSet result = new TreeSet(); + + List waves = config.getKeys("arenas." + arena + ".waves.recurrent"); + if (waves != null) + { + int wave, frequency, priority; + + for (String w : waves) + { + frequency = config.getInt("arenas." + arena + ".waves.recurrent." + w + ".frequency", 0); + priority = config.getInt("arenas." + arena + ".waves.recurrent." + w + ".priority", 0); + if (frequency == 0 || priority == 0) continue; + + wave = config.getInt("arenas." + arena + ".waves.single." + w + ".wave", frequency); + result.add(new RecurrentWave(w, wave, frequency, priority)); + } + } + else + { + RecurrentWave def = new RecurrentWave("DEF_WAVE_AUTO", 1, 1, 1); + def.setType(WaveType.DEFAULT); + def.setGrowth(WaveGrowth.MEDIUM); + + RecurrentWave spec = new RecurrentWave("SPEC_WAVE_AUTO", 4, 4, 4); + spec.setType(WaveType.SPECIAL); + + result.add(def); + result.add(spec); + } + + return result; + }*/ + + + + /*//////////////////////////////////////////////////////////////////// + // + // Well definedness checks + // + ////////////////////////////////////////////////////////////////////*/ + + /** + * Check if a wave in the config-file is well-defined. + * The method first checks if the wave is well-defined according to + * the branch-specific requirements. Recurrent waves must have the + * two nodes 'priority' and 'frequency', and single waves must have + * the node 'wave'. + * Any other requirements are type-specific, and thus we check if the + * type is well-defined. + * @param config Config-file Configuration + * @param path The absolute path of the wave + * @param branch The branch of the wave + * @param type The wave type + * @return true, if the wave is well-defined, false otherwise + */ + private static boolean isWaveWellDefined(Configuration config, String path, WaveBranch branch, WaveType type) + { + if (branch == WaveBranch.RECURRENT) + { + // REQUIRED: Priority and frequency + int priority = config.getInt(path + "priority", 0); + int frequency = config.getInt(path + "frequency", 0); + if (priority == 0 || frequency == 0) + return false; + + // TODO: OPTIONAL: Wave growth, others? + } + else if (branch == WaveBranch.SINGLE) + { + // REQUIRED: Wave number + int wave = config.getInt(path + "wave", 0); + if (wave == 0) + return false; + } + else return false; + + // Passed branch-checks; check type + return isTypeWellDefined(config, path, type); + } + + /** + * Check if a wave type in the config-file is well-defined. + * The method calls the appropriate sub-method to check if the type + * is well-defined. + * @param config Config-file Configuration + * @param path The absolute path of the wave + * @param type The wave type + * @return true, if the wave type is well-defined, false otherwise + */ + private static boolean isTypeWellDefined(Configuration config, String path, WaveType type) + { + if (type == WaveType.DEFAULT || type == WaveType.SPECIAL) + return isNormalWaveWellDefined(config, path); + else if (type == WaveType.BOSS) + return isBossWaveWellDefined(config, path); + else if (type == WaveType.SWARM) + return isSwarmWaveWellDefined(config, path); + + return false; + } + + /** + * Check if a default or special wave is well-defined. + * There are no REQUIRED nodes for default or special wave types, besides + * the ones for the branch they belong to. + * The only OPTIONAL node is (currently) 'monsters' + * @param config Config-file Configuration + * @param path The absolute path of the wave + * @return true, if the wave type is well-defined, false otherwise + */ + private static boolean isNormalWaveWellDefined(Configuration config, String path) + { + // OPTIONAL: Monsters + List monsters = config.getKeys(path + "monsters"); + if (monsters == null) + return true; + + for (String monster : monsters) + { + if (getEnumFromString(CreatureType.class, monster) != null) + continue; + + MAUtils.error("Invalid monster type '" + monster + "' in " + path); + return false; + } + + return true; + } + + /** + * Check if a swarm wave is well defined + * @param config Config-file Configuration + * @param path The absolute path of the wave + * @return true, if the wave type is well-defined, false otherwise + */ + private static boolean isSwarmWaveWellDefined(Configuration config, String path) + { + // REQUIRED: Monster type + String monster = config.getString(path + "monster"); + if (monster == null) + { + MAUtils.error("Missing monster type in '" + path); + return false; + } + else if (getEnumFromString(CreatureType.class, monster) == null) + { + MAUtils.error("Invalid monster type '" + monster + "' in " + path); + return false; + } + + // OPTIONAL: Amount + String amount = config.getString(path + "amount"); + if (amount != null && SwarmAmount.fromString(amount) == null) + return false; + + return true; + } + + /** + * Check if a boss wave is well defined. + * @param config Config-file Configuration + * @param path The absolute path of the wave + * @return true, if the wave type is well-defined, false otherwise + */ + private static boolean isBossWaveWellDefined(Configuration config, String path) + { + // REQUIRED: Monster type + String monster = config.getString(path + "monster"); + if (monster == null) + { + MAUtils.error("Missing monster type in '" + path); + return false; + } + else if (getEnumFromString(CreatureType.class, monster) == null) + { + MAUtils.error("Invalid monster type '" + monster + "' in " + path); + return false; + } + + // OPTIONAL: Abilities + String abilities = config.getString(path + "abilities"); + if (abilities != null) + { + for (String ability : abilities.split(",")) + { + if (BossAbility.fromString(ability.trim().toUpperCase()) != null) + continue; + + MAUtils.error("Invalid boss ability '" + ability + "' in " + path); + return false; + } + } + + // TODO: OPTIONAL: Adds + // Unsure about config-file implementation... + + // OPTIONAL: Health + String health = config.getString(path + "health"); + if (health != null && BossHealth.fromString(health) == null) + return false; + + return true; + } + + + + /*//////////////////////////////////////////////////////////////////// + // + // Misc - Perhaps move into MAUtils? + // + ////////////////////////////////////////////////////////////////////*/ + + /** + * Get the num value of a string, def if it doesn't exist. + */ + public static > T getEnumFromString(Class c, String string, T def) + { + if (c != null && string != null) + { + try + { + return Enum.valueOf(c, string.trim().toUpperCase()); + } + catch (IllegalArgumentException ex) { } + } + return def; + } + + /** + * Get the enum value of a string, null if it doesn't exist. + */ + public static > T getEnumFromString(Class c, String string) + { + if(c != null && string != null) + { + try + { + return Enum.valueOf(c, string.trim().toUpperCase()); + } + catch(IllegalArgumentException ex) { } + } + return null; + } +} diff --git a/src/com/garbagemule/MobArena/util/data/PlainText.java b/src/com/garbagemule/MobArena/util/data/PlainText.java new file mode 100644 index 0000000..a4a34d8 --- /dev/null +++ b/src/com/garbagemule/MobArena/util/data/PlainText.java @@ -0,0 +1,153 @@ +package com.garbagemule.MobArena.util.data; + +import java.io.File; +import java.io.FileWriter; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.bukkit.entity.Player; + +import com.garbagemule.MobArena.ArenaLog; +import com.garbagemule.MobArena.ArenaPlayer; +import com.garbagemule.MobArena.MAUtils; +import com.garbagemule.MobArena.MobArena; +import com.garbagemule.MobArena.util.TextUtils; + +public class PlainText +{ + public static void saveSessionData(ArenaLog log) + { + File dir = new File(MobArena.arenaDir, log.getArena().configName()); + if (!dir.exists()) dir.mkdirs(); + + // General information + List toWrite = new LinkedList(); + toWrite.add("Start: " + log.getStartTime()); + toWrite.add("End: " + log.getEndTime()); + toWrite.add("Duration: " + log.getDuration()); + toWrite.add("Last wave: " + log.getLastWave()); + toWrite.add(" "); + + int classLength = longestClassName(log.distribution.keySet()); + int classCount = log.distribution.keySet().size(); + int playerCount = log.players.keySet().size(); + + // Class distribution + toWrite.add("Class Distribution: " + classCount + " classes"); + toWrite.addAll(getClassDistribution(log.distribution, playerCount, classLength)); + toWrite.add(" "); + + // Player data + toWrite.add("Player Data: " + playerCount + " players"); + toWrite.addAll(getPlayerData(log.players, classLength)); + + // Serialize! + try + { + File file = new File(dir, "lastsession.txt"); + if (!file.exists()) file.createNewFile(); + + FileWriter fw = new FileWriter(file); + for (String s : toWrite) + { + fw.write(s); + fw.write(System.getProperty("line.separator")); + } + fw.close(); + } + catch (Exception e) + { + e.printStackTrace(); + System.out.println("[MobArena] ERROR! Problem saving session data for arena '" + log.getArena().configName() + "'"); + } + } + + public static void updateArenaTotals(ArenaLog log) + { + // Grab the Configuration + //Configuration totals = Totals.getArenaTotals(log.arena); + + // Parse shit + + // Serialize + } + + /* public static void updateTotals(ArenaLog log, MobArena plugin) + { + File dir = new File(MobArena.arenaDir, log.getArena().configName()); + if (!dir.exists()) dir.mkdir(); + } */ + + private static List getClassDistribution(Map map, int playerCount, int classLength) + { + List result = new LinkedList(); + + // Add each entry - class: # (percentage%) + for (Map.Entry entry : map.entrySet()) + { + StringBuffer buffy = new StringBuffer(classLength + 15); + + buffy.append("- "); + buffy.append(TextUtils.padRight(entry.getKey() + ":", classLength + 3)); // : + buffy.append(TextUtils.padLeft(entry.getValue().toString(), 2)); // + buffy.append("(" + (entry.getValue()*100/playerCount) + "%)"); // (%) + + result.add(buffy.toString()); + } + + return result; + } + + private static List getPlayerData(Map map, int classLength) + { + List result = new LinkedList(); + String pad = " "; + + // Padding variables; +2 for extra padding + int NAME = 12, CLASS = classLength, WAVE = 4, KILLS = 5, DMGDONE = 7, DMGTAKEN = 8, ACCURACY = 8; + result.add(" " + TextUtils.padRight("NAME", NAME+2) + + TextUtils.padRight("CLASS", CLASS+2) + + TextUtils.padRight("WAVE", WAVE+2) + + TextUtils.padRight("KILLS", KILLS+2) + + TextUtils.padRight("DMGDONE", DMGDONE+2) + + TextUtils.padRight("DMGTAKEN", DMGTAKEN+2) + + TextUtils.padRight("ACCURACY", ACCURACY+2) + + "REWARDS"); + + // Add each player - name class wave dmgdone dmgtaken accuracy rewards + for (Map.Entry entry : map.entrySet()) + { + Player p = entry.getKey(); + ArenaPlayer ap = entry.getValue(); + StringBuffer buffy = new StringBuffer(80); + + String name = (p.getName().length() <= NAME) ? p.getName() : p.getName().substring(0, NAME+1); + buffy.append("- "); + buffy.append(TextUtils.padRight(name, NAME)); buffy.append(pad); + buffy.append(TextUtils.padRight(ap.getClassName(), CLASS)); buffy.append(pad); + buffy.append(TextUtils.padLeft(ap.lastWave, WAVE)); buffy.append(pad); + buffy.append(TextUtils.padLeft(ap.kills, KILLS)); buffy.append(pad); + buffy.append(TextUtils.padLeft(ap.dmgDone, DMGDONE)); buffy.append(pad); + buffy.append(TextUtils.padLeft(ap.dmgTaken, DMGTAKEN)); buffy.append(pad); + buffy.append(TextUtils.padLeft(((ap.swings != 0) ? ap.hits*100/ap.swings : 0), ACCURACY-1)); buffy.append("%"); buffy.append(pad); + buffy.append(MAUtils.listToString(ap.rewards)); + + result.add(buffy.toString()); + } + + return result; + } + + private static int longestClassName(Collection names) + { + int result = 0; + + for (String c : names) + if (c.length() > result) + result = c.length(); + + return result; + } +} diff --git a/src/com/garbagemule/MobArena/util/data/Totals.java b/src/com/garbagemule/MobArena/util/data/Totals.java new file mode 100644 index 0000000..98775f3 --- /dev/null +++ b/src/com/garbagemule/MobArena/util/data/Totals.java @@ -0,0 +1,159 @@ +package com.garbagemule.MobArena.util.data; + +import java.io.File; + +import org.bukkit.util.config.Configuration; + +import com.garbagemule.MobArena.Arena; +import com.garbagemule.MobArena.ArenaLog; +import com.garbagemule.MobArena.ArenaPlayer; +import com.garbagemule.MobArena.MAUtils; +import com.garbagemule.MobArena.MobArena; +import com.garbagemule.MobArena.util.FileUtils; + +public class Totals +{ + private static final String totals_yml = "totals.yml"; + + /** + * Update the totals-file with the session data from the ArenaLog object + * @param log ArenaLog to update with + */ + public static void updateArenaTotals(ArenaLog log) + { + // Grab the configuration + Configuration totals = getArenaTotals(log.arena); + totals.load(); + + // General data + updateInt(totals, "general-info.total-games-played", 1, true); + updateInt(totals, "general-info.most-players", log.players.keySet().size(), false); + updateInt(totals, "general-info.highest-wave-reached", log.lastWave, false); + updateInt(totals, "general-info.total-monsters-killed", countKills(log), true); + updateDuration(totals, "general-info.total-duration", log.getDurationLong(), true); + updateDuration(totals, "general-info.longest-session-duration", log.getDurationLong(), false); + + // Classes + updateInt(totals, "classes.overall-distribution.total-count", log.players.keySet().size(), true); + for (String c : log.arena.getClasses()) + { + // Array {kills, dmgDone, dmgTaken} + int[] a = getKillsAndDamageByClass(log, c); + updateInt(totals,"classes." + c + ".kills", a[0],true); + updateInt(totals,"classes." + c + ".damage-done", a[1],true); + updateInt(totals,"classes." + c + ".damage-taken",a[2],true); + } + + // Rewards + + // Players + for (ArenaPlayer ap : log.players.values()) + { + // Basic values + updateInt(totals,"players." + ap.player.getName() + ".games-played", 1, true); + updateInt(totals,"players." + ap.player.getName() + ".kills", ap.kills, true); + updateInt(totals,"players." + ap.player.getName() + ".damage-done", ap.dmgDone, true); + updateInt(totals,"players." + ap.player.getName() + ".damage-taken", ap.dmgTaken, true); + updateInt(totals,"players." + ap.player.getName() + ".swings", ap.swings, true); + updateInt(totals,"players." + ap.player.getName() + ".hits", ap.hits, true); + + // Class count + updateInt(totals,"players." + ap.player.getName() + ".classes." + ap.className,1,true); + } + + // Save everything + totals.save(); + } + + /** + * Get or create a Configuration from the totals-file + * @param arena Arena to get or create a Configuration for + * @return Configuration from the arena's totals-file + */ + public static Configuration getArenaTotals(Arena arena) + { + // Create the folder if it doesn't exist. + File dir = new File(MobArena.arenaDir, arena.configName()); + if (!dir.exists()) dir.mkdirs(); + + File file = new File(dir, totals_yml); + if (!file.exists()) FileUtils.extractFile(dir, totals_yml); + + // Grab the totals-file and return the Configuration + return new Configuration(file); + } + + /** + * Update an integer in a config-file + * @param totals Configuration to alter + * @param node Node to alter + * @param b Integer for comparison + * @param increment If true, the node will be incremented by b, otherwise overwritten if greater + */ + private static void updateInt(Configuration totals, String node, int b, boolean increment) + { + int a = totals.getInt(node, 0); + + if (increment) totals.setProperty(node, a+b); + else if (b > a) totals.setProperty(node, b); + } + + /** + * Update a duration in a config-file + * @param totals Configuration to alter + * @param node Node to alter + * @param b Duration for comparison. This is a java.sql.Timestamp.getTime() long + * @param increment If true, the node will be incremented by b, otherwise overwritten if greater + */ + private static void updateDuration(Configuration totals, String node, long b, boolean increment) + { + long a = MAUtils.parseDuration(totals.getString(node, "0:00:00")); + + if (increment) totals.setProperty(node, MAUtils.getDuration(a+b)); + else if (b > a) totals.setProperty(node, MAUtils.getDuration(b)); + } + + /** + * Get total kills from an arena session + * @param log The ArenaLog to count kills in + * @return Total kills + */ + private static int countKills(ArenaLog log) + { + int kills = 0; + for (ArenaPlayer ap : log.players.values()) + kills += ap.kills; + return kills; + } + + /* + private static int getKillsByClass(ArenaLog log, String className) + { + int kills = 0; + for (ArenaPlayer ap : log.players.values()) + if (ap.className.equals(className)) + kills += ap.kills; + return kills; + } + */ + + /** + * Get a (dirty) int-array in the form {kills, damage done, damage taken} for a class + * @param log ArenaLog to count kills, damage done/taken from + * @param className The class to count for + * @return Resulting kills, damage done and damage taken in an int-array + */ + private static int[] getKillsAndDamageByClass(ArenaLog log, String className) + { + int kills = 0, dmgDone = 0, dmgTaken = 0; + for (ArenaPlayer ap : log.players.values()) + { + if (!ap.className.equals(className)) + continue; + kills += ap.kills; + dmgDone += ap.dmgDone; + dmgTaken += ap.dmgTaken; + } + return new int[]{kills, dmgDone, dmgTaken}; + } +} diff --git a/src/com/garbagemule/MobArena/util/data/XML.java b/src/com/garbagemule/MobArena/util/data/XML.java new file mode 100644 index 0000000..cc85469 --- /dev/null +++ b/src/com/garbagemule/MobArena/util/data/XML.java @@ -0,0 +1,6 @@ +package com.garbagemule.MobArena.util.data; + +public class XML +{ + +} diff --git a/src/com/garbagemule/MobArena/waves/AbstractWave.java b/src/com/garbagemule/MobArena/waves/AbstractWave.java new file mode 100644 index 0000000..b34d548 --- /dev/null +++ b/src/com/garbagemule/MobArena/waves/AbstractWave.java @@ -0,0 +1,90 @@ +package com.garbagemule.MobArena.waves; + +public abstract class AbstractWave implements Wave +{ + private String name; + private int wave, frequency, priority; + private WaveType type; + private WaveGrowth growth; + + /** + * Basic wave constructor. + * Constructs a wave with an initial wave number, a wave frequency, and + * a wave priority. + * @param wave Initial wave number. This is the first wave number this wave can spawn at. + * @param frequency How often the wave can spawn. + * @param priority The priority of the wave. + */ + public AbstractWave(String name, int wave, int frequency, int priority) + { + this.name = name; + this.wave = wave; + this.frequency = frequency; + this.priority = priority; + } + + /** + * Default wave constructor. + * Constructs a basic wave with additional information in the type of + * wave and the wave growth. + * @param wave Initial wave number. This is the first wave number this wave can spawn at. + * @param frequency How often the wave can spawn. + * @param priority The priority of the wave. + * @param type The type of wave. + * @param growth The growth rate of the wave. + */ + public AbstractWave(String name, int wave, int frequency, int priority, WaveType type, WaveGrowth growth) + { + this(name, wave, frequency, priority); + this.type = type; + this.growth = growth; + } + + public WaveType getType() + { + return type; + } + + public void setType(WaveType type) + { + this.type = type; + } + + public WaveGrowth getGrowth() + { + return growth; + } + + public void setGrowth(WaveGrowth growth) + { + this.growth = growth; + } + + public int getWave() + { + return wave; + } + + public int getFrequency() + { + return frequency; + } + + public int getPriority() + { + return priority; + } + + public String getName() + { + return name; + } + + public String toString() + { + return "[name=" + name + + ", wave=" + wave + + ", frequency=" + frequency + + ", priority=" + priority + "]"; + } +} diff --git a/src/com/garbagemule/MobArena/waves/BossWave.java b/src/com/garbagemule/MobArena/waves/BossWave.java new file mode 100644 index 0000000..3552363 --- /dev/null +++ b/src/com/garbagemule/MobArena/waves/BossWave.java @@ -0,0 +1,35 @@ +package com.garbagemule.MobArena.waves; + +import java.util.Set; + +import org.bukkit.entity.Creature; + +import com.garbagemule.MobArena.waves.Wave.BossAbility; + +public class BossWave +{ + private Creature boss; + private Set abilities; + private Set adds; + private int health; + + public BossWave(Creature boss) + { + this.boss = boss; + } + + public void addAbility(BossAbility ability) + { + abilities.add(ability); + } + + public void addAdds(Creature creature) + { + adds.add(creature); + } + + public void setHealth(int health) + { + this.health = health; + } +} diff --git a/src/com/garbagemule/MobArena/waves/RecurrentWave.java b/src/com/garbagemule/MobArena/waves/RecurrentWave.java new file mode 100644 index 0000000..c0d7201 --- /dev/null +++ b/src/com/garbagemule/MobArena/waves/RecurrentWave.java @@ -0,0 +1,47 @@ +package com.garbagemule.MobArena.waves; + +import java.util.Collection; + +import org.bukkit.Location; + +public class RecurrentWave extends AbstractWave +{ + public RecurrentWave(String name, int wave, int frequency, int priority) + { + super(name, wave, frequency, priority); + } + + public RecurrentWave(String name, int wave, int frequency, int priority, WaveType type, WaveGrowth growth) + { + super(name, wave, frequency, priority, type, growth); + } + + public void spawn(int wave, Collection spawnpoints) + { + + } + + public void setDefault(boolean def) + { + + } + + public boolean matches(int wave) + { + return wave % getWave() + getFrequency() == 0; + } + + /** + * Recurrent waves are sorted by their priorities. + * If the priorities are equal, the names are compared. This is to + * ALLOW "duplicates" in the RECURRENT WAVES collection. + */ + public int compareTo(Wave w) + { + if (getPriority() < w.getPriority()) + return -1; + else if (getPriority() > w.getPriority()) + return 1; + else return getName().compareTo(w.getName()); + } +} diff --git a/src/com/garbagemule/MobArena/waves/SingleWave.java b/src/com/garbagemule/MobArena/waves/SingleWave.java new file mode 100644 index 0000000..057217c --- /dev/null +++ b/src/com/garbagemule/MobArena/waves/SingleWave.java @@ -0,0 +1,37 @@ +package com.garbagemule.MobArena.waves; + +import java.util.Collection; + +import org.bukkit.Location; + +public class SingleWave extends AbstractWave +{ + public SingleWave(String name, int wave) + { + super(name, wave, 0, 0); + } + + public void spawn(int wave, Collection spawnpoints) + { + + } + + public boolean matches(int wave) + { + return getWave() == wave; + } + + /** + * Single waves are compared by their wave numbers. + * If the wave numbers are equal, the waves are equal. This is to + * DISALLOW "duplicates" in the SINGLE WAVES collection. + */ + public int compareTo(Wave w) + { + if (this.getWave() < w.getWave()) + return -1; + else if (this.getWave() > w.getWave()) + return 1; + else return 0; + } +} diff --git a/src/com/garbagemule/MobArena/waves/Wave.java b/src/com/garbagemule/MobArena/waves/Wave.java new file mode 100644 index 0000000..29913f2 --- /dev/null +++ b/src/com/garbagemule/MobArena/waves/Wave.java @@ -0,0 +1,119 @@ +package com.garbagemule.MobArena.waves; + +import java.util.Collection; + +import org.bukkit.Location; + +import com.garbagemule.MobArena.util.WaveUtils; + +public interface Wave extends Comparable +{ + public enum WaveBranch + { + SINGLE, RECURRENT; + } + + public enum WaveType + { + DEFAULT, SPECIAL, SWARM, BOSS; + + public static WaveType fromString(String string) + { + return WaveUtils.getEnumFromString(WaveType.class, string, DEFAULT); + } + } + + public enum WaveGrowth + { + SLOW, MEDIUM, FAST; + + public static WaveGrowth fromString(String string) + { + return WaveUtils.getEnumFromString(WaveGrowth.class, string, MEDIUM); + } + } + + public enum BossAbility + { + ARROWS, FIREBALLS, RING_OF_FIRE; + + public static BossAbility fromString(String string) + { + return WaveUtils.getEnumFromString(BossAbility.class, string); + } + } + + public enum BossHealth + { + LOW, MEDIUM, HIGH; + + public static BossHealth fromString(String string) + { + return WaveUtils.getEnumFromString(BossHealth.class, string); + } + } + + public enum SwarmAmount + { + LOW, MEDIUM, HIGH, PSYCHO; + + public static SwarmAmount fromString(String string) + { + return WaveUtils.getEnumFromString(SwarmAmount.class, string); + } + } + + /** + * The spawn() method must spawn one or more monsters in + * the arena. The monster count, damage, health, etc. can + * be modified by the wave parameter. + * @param wave Wave number + */ + public void spawn(int wave, Collection spawnpoints); + + /** + * Get the type of wave. + * @return The WaveType of this Wave. + */ + public WaveType getType(); + + /** + * Get the growth rate of this wave. + * @return The growth rate + */ + public WaveGrowth getGrowth(); + + /** + * Get the first wave number for this wave + * @return The wave number + */ + public int getWave(); + + /** + * Get the wave's frequency, i.e. wave number "modulo" + * @return The wave's frequency + */ + public int getFrequency(); + + /** + * Get the wave's priority value. + * @return The priority + */ + public int getPriority(); + + /** + * Get the wave's name. + * @return The name + */ + public String getName(); + + /** + * Check if this wave matches the wave number. + * The SingleWave class does a simple check if its wave == the parameter. + * The RecurrentWave class is more complex in that it needs to do some + * calculations based on the initial wave and the frequency. + * @param wave The current wave number + * @return true, if the wave should spawn, false otherwise + */ + public boolean matches(int wave); +} \ No newline at end of file