diff --git a/src/com/garbagemule/MobArena/log/ArenaLog.java b/src/com/garbagemule/MobArena/log/ArenaLog.java new file mode 100644 index 0000000..71f72cb --- /dev/null +++ b/src/com/garbagemule/MobArena/log/ArenaLog.java @@ -0,0 +1,63 @@ +package com.garbagemule.MobArena.log; + +import java.util.HashMap; +import java.util.Map; + +import com.garbagemule.MobArena.ArenaPlayer; +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.util.MutableInt; + +public class ArenaLog +{ + private Arena arena; + private LogSessionBuilder sessionBuilder; + private LogTotalsBuilder totalsBuilder; + + public ArenaLog(Arena arena, LogSessionBuilder sessionBuilder, LogTotalsBuilder totalsBuilder) { + this.arena = arena; + this.sessionBuilder = sessionBuilder; + this.totalsBuilder = totalsBuilder; + } + + public void start() { + // Log number of players. + sessionBuilder.buildNumberOfPlayers(arena.getPlayersInArena().size()); + + // Log the distribution of classes. + Map classDistribution = new HashMap(); + for (String classname : arena.getClasses().keySet()) { + classDistribution.put(classname, new MutableInt()); + } + for (ArenaPlayer ap : arena.getArenaPlayerSet()) { + classDistribution.get(ap.getArenaClass().getName()).inc(); + } + sessionBuilder.buildClassDistribution(classDistribution); + totalsBuilder.updateClassDistribution(classDistribution); + + // Log the current time and set it as the start time. + sessionBuilder.buildStartTime(); + totalsBuilder.recordStartTime(); + } + + public void end() { + // Log the end time and duration + sessionBuilder.buildEndTime(); + sessionBuilder.buildDuration(); + + totalsBuilder.updateTimePlayed(); + totalsBuilder.updateSessionsPlayed(); + + // Update the last wave. + totalsBuilder.updateLastWave(arena.getWaveManager().getWaveNumber()); + + // Finalize the session. + sessionBuilder.finalize(); + totalsBuilder.finish(); + } + + public void playerLeave(ArenaPlayer ap) { + ArenaLogPlayerEntry entry = ArenaLogPlayerEntry.create(ap); + sessionBuilder.buildPlayerEntry(entry, arena.getRewardManager().getRewards(ap.getPlayer())); + totalsBuilder.updatePlayerEntry(entry); + } +} diff --git a/src/com/garbagemule/MobArena/log/ArenaLogPlayerEntry.java b/src/com/garbagemule/MobArena/log/ArenaLogPlayerEntry.java new file mode 100644 index 0000000..1958acf --- /dev/null +++ b/src/com/garbagemule/MobArena/log/ArenaLogPlayerEntry.java @@ -0,0 +1,53 @@ +package com.garbagemule.MobArena.log; + +import java.util.Date; +import java.util.Map; + +import org.bukkit.Material; + +import com.garbagemule.MobArena.ArenaPlayer; +import com.garbagemule.MobArena.ArenaPlayerStatistics; + +public class ArenaLogPlayerEntry +{ + /** + * Player name is used as a unique ID. + */ + protected String playername, classname; + + /** + * All recordable statistics. + */ + protected int kills, dmgDone, dmgTaken, swings, hits, lastWave; + + /** + * The time at which the player left or died. + */ + protected long leaveTime; + + /** + * Total amounts of each item rewarded. + */ + protected Map rewards; + + private ArenaLogPlayerEntry() {} + + public static ArenaLogPlayerEntry create(ArenaPlayer ap) { + ArenaLogPlayerEntry entry = new ArenaLogPlayerEntry(); + + entry.playername = ap.getPlayer().getName(); + entry.classname = ap.getArenaClass().getName(); + + ArenaPlayerStatistics stats = ap.getStats(); + entry.kills = stats.getInt("kills"); + entry.dmgDone = stats.getInt("dmgDone"); + entry.dmgTaken = stats.getInt("dmgTaken"); + entry.swings = stats.getInt("swings"); + entry.hits = stats.getInt("hits"); + entry.lastWave = stats.getInt("lastWave"); + + entry.leaveTime = (new Date()).getTime(); + + return entry; + } +} diff --git a/src/com/garbagemule/MobArena/log/LogSessionBuilder.java b/src/com/garbagemule/MobArena/log/LogSessionBuilder.java new file mode 100644 index 0000000..cb23ad9 --- /dev/null +++ b/src/com/garbagemule/MobArena/log/LogSessionBuilder.java @@ -0,0 +1,66 @@ +package com.garbagemule.MobArena.log; + +import java.util.List; +import java.util.Map; + +import org.bukkit.inventory.ItemStack; + +import com.garbagemule.MobArena.util.MutableInt; + +public interface LogSessionBuilder +{ + /** + * Builds the start time of this session. + * The method should be called as soon as the session starts, or as soon + * as possible right after for the most precise results. + */ + public void buildStartTime(); + + /** + * Builds the start time of this session. + * The method should be called as soon as the session ends, or as soon as + * possible right after for the most precise results. + * @param end the start time + */ + public void buildEndTime(); + + /** + * Builds the duration of this session by creating a duration string on + * the form HH:MM:SS using the provided start and end times. + * PRECONDITION: buildStartTime() and buildEndTime() must be called before + * calling this method. + */ + public void buildDuration(); + + /** + * Builds the last reached wave of this session. + * @param lastWave the wave number of the last reached wave + */ + public void buildLastWave(int lastWave); + + /** + * Builds the number of players that participated in this session. + * @param amount the amount of players + */ + public void buildNumberOfPlayers(int amount); + + /** + * Builds the distribution of players over all classes. + * @param classDistribution class names mapped to number of players per class + */ + public void buildClassDistribution(Map classDistribution); + + /** + * Builds a player entry when a player has left/died. + * @param entry the log entry to build + * @param rewards a list of rewards given to the player in question + */ + public void buildPlayerEntry(ArenaLogPlayerEntry entry, List rewards); + + /** + * Finalizes the session log, possibly writing it to disk or updating a database. + * The method should only be called when no more changes are expected. + * Behavior is undefined for the other methods after calling this method. + */ + public void finalize(); +} diff --git a/src/com/garbagemule/MobArena/log/LogTotalsBuilder.java b/src/com/garbagemule/MobArena/log/LogTotalsBuilder.java new file mode 100644 index 0000000..09806df --- /dev/null +++ b/src/com/garbagemule/MobArena/log/LogTotalsBuilder.java @@ -0,0 +1,51 @@ +package com.garbagemule.MobArena.log; + +import java.util.Map; + +import com.garbagemule.MobArena.util.MutableInt; + +public interface LogTotalsBuilder +{ + /** + * Store the start time of the current session. + * Used to add to the total time played per player. + */ + public void recordStartTime(); + + /** + * Update the total time played in this arena. + * PRECONDITION: recordStartTime() must have been called first. + */ + public void updateTimePlayed(); + + /** + * Increment the total number of sessions played. + * The method should be called once per session and no more. + */ + public void updateSessionsPlayed(); + + /** + * Update the last wave recorded to be the maximum of the current last wave + * and the last wave of the last session. + * @param wave last wave of the last session + */ + public void updateLastWave(int lastWave); + + /** + * Update the total distribution of players over all classes. + * @param classDistribution class names mapped to number of players per class + */ + public void updateClassDistribution(Map classDistribution); + + /** + * Update the totals for a specific player given a log entry. + * @param entry a log entry + */ + public void updatePlayerEntry(ArenaLogPlayerEntry entry); + + /** + * Finish updating the totals. Calling this method makes all changes permanent + * and resets the builder, making it ready for updates for the next session. + */ + public void finish(); +} diff --git a/src/com/garbagemule/MobArena/log/YMLSessionBuilder.java b/src/com/garbagemule/MobArena/log/YMLSessionBuilder.java new file mode 100644 index 0000000..e77a2b6 --- /dev/null +++ b/src/com/garbagemule/MobArena/log/YMLSessionBuilder.java @@ -0,0 +1,111 @@ +package com.garbagemule.MobArena.log; + +import java.io.File; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.inventory.ItemStack; + +import com.garbagemule.MobArena.MobArena; +import com.garbagemule.MobArena.util.MutableInt; +import com.garbagemule.MobArena.util.TimeUtils; +import com.garbagemule.MobArena.util.config.Config; + +public class YMLSessionBuilder implements LogSessionBuilder +{ + private final String GENERAL = "general-info."; + private final String PLAYERS = "players."; + private final String CLASSES = "class-distribution."; + + private Config config; + private long start, end; + + public YMLSessionBuilder(File file) { + config = new Config(file); + reset(); + } + + @Override + public void buildStartTime() { + start = new Date().getTime(); + config.set(GENERAL + "start-time", TimeUtils.toDateTime(start)); + } + + @Override + public void buildEndTime() { + end = new Date().getTime(); + config.set(GENERAL + "end-time", TimeUtils.toDateTime(end)); + } + + @Override + public void buildDuration() { + String duration = TimeUtils.toTime(end - start); + config.set(GENERAL + "duration", duration); + } + + @Override + public void buildLastWave(int lastWave) { + config.set(GENERAL + "last-wave", lastWave); + } + + @Override + public void buildNumberOfPlayers(int amount) { + config.set(GENERAL + "number-of-players", amount); + } + + @Override + public void buildClassDistribution(Map classDistribution) { + for (Entry entry : classDistribution.entrySet()) { + int amount = entry.getValue().value(); + config.set(CLASSES + entry.getKey(), amount); + } + } + + @Override + public void buildPlayerEntry(ArenaLogPlayerEntry entry, List rewards) { + String path = PLAYERS + entry.playername + "."; + + // Name and class + config.set(path + "name", entry.playername); + config.set(path + "class", entry.classname); + + // Stats + config.set(path + "kills", entry.kills); + config.set(path + "damage-done", entry.dmgDone); + config.set(path + "damage-taken", entry.dmgTaken); + config.set(path + "swings", entry.swings); + config.set(path + "hits", entry.hits); + config.set(path + "last-wave", entry.lastWave); + config.set(path + "time-played", TimeUtils.toTime(entry.leaveTime - start)); + + // Rewards + Map summed = new HashMap(); + + for (ItemStack stack : rewards) { + String type = (stack.getTypeId() == MobArena.ECONOMY_MONEY_ID ? "money" : stack.getType().toString().toLowerCase()); + if (!summed.containsKey(type)) { + summed.put(type, new MutableInt()); + } + summed.get(type).add(stack.getAmount()); + } + + for (Entry e : summed.entrySet()) { + config.set(path + "rewards." + e.getKey(), e.getValue().value()); + } + } + + @Override + public void finalize() { + config.save(); + reset(); + } + + private void reset() { + config.set(GENERAL, null); + config.set(CLASSES, null); + config.set(PLAYERS, null); + } +} diff --git a/src/com/garbagemule/MobArena/log/YMLTotalsBuilder.java b/src/com/garbagemule/MobArena/log/YMLTotalsBuilder.java new file mode 100644 index 0000000..22af6ff --- /dev/null +++ b/src/com/garbagemule/MobArena/log/YMLTotalsBuilder.java @@ -0,0 +1,109 @@ +package com.garbagemule.MobArena.log; + +import java.io.File; +import java.util.Date; +import java.util.Map; +import java.util.Map.Entry; + +import com.garbagemule.MobArena.util.MutableInt; +import com.garbagemule.MobArena.util.TimeUtils; +import com.garbagemule.MobArena.util.config.Config; + +public class YMLTotalsBuilder implements LogTotalsBuilder +{ + private final String GENERAL = "general-info."; + private final String PLAYERS = "players."; + private final String CLASSES = "class-distribution."; + + private Config config; + private long start; + + public YMLTotalsBuilder(File file) { + config = new Config(file); + if (file.exists()) { + config.load(); + } + } + + @Override + public void recordStartTime() { + start = new Date().getTime(); + } + + @Override + public void updateSessionsPlayed() { + String path = GENERAL + "sessions-played"; + int games = config.getInt(path, 0); + config.set(path, games + 1); + } + + @Override + public void updateTimePlayed() { + long end = new Date().getTime(); + String duration = TimeUtils.toTime(end - start); + + String path = GENERAL + "time-played"; + String old = config.getString(path, "00:00:00"); + config.set(path, TimeUtils.addTimes(old, duration)); + } + + @Override + public void updateLastWave(int lastWave) { + String path = GENERAL + "last-wave"; + int old = config.getInt(path, 0); + config.set(path, Math.max(old, lastWave)); + } + + @Override + public void updateClassDistribution(Map classDistribution) { + for (Entry entry : classDistribution.entrySet()) { + String path = CLASSES + entry.getKey(); + int old = config.getInt(path, 0); + int amount = entry.getValue().value(); + config.set(path, old + amount); + } + } + + @Override + public void updatePlayerEntry(ArenaLogPlayerEntry entry) { + String path = PLAYERS + entry.playername + "."; + + // Name and class + config.set(path + "name", entry.playername); + int old = config.getInt(path + "classes." + entry.classname, 0); + config.set(path + "classes." + entry.classname, old + 1); + + // All stats + old = config.getInt(path + "kills", 0); + config.set(path + "kills", old + entry.kills); + + old = config.getInt(path + "damage-done", 0); + config.set(path + "damage-done", old + entry.dmgDone); + + old = config.getInt(path + "damage-taken", 0); + config.set(path + "damage-taken", old + entry.dmgTaken); + + old = config.getInt(path + "swings", 0); + config.set(path + "swings", old + entry.swings); + + old = config.getInt(path + "hits", 0); + config.set(path + "hits", old + entry.hits); + + old = config.getInt(path + "games-played", 0); + config.set(path + "games-played", old + 1); + + old = config.getInt(path + "last-wave", 0); + config.set(path + "last-wave", Math.max(old, entry.lastWave)); + + // Time played + long end = new Date().getTime(); + String session = TimeUtils.toTime(end - start); + String played = config.getString(path + "time-played", "00:00:00"); + config.set(path + "time-played", TimeUtils.addTimes(played, session)); + } + + @Override + public void finish() { + config.save(); + } +} diff --git a/src/com/garbagemule/MobArena/util/TimeUtils.java b/src/com/garbagemule/MobArena/util/TimeUtils.java new file mode 100644 index 0000000..cfe5c65 --- /dev/null +++ b/src/com/garbagemule/MobArena/util/TimeUtils.java @@ -0,0 +1,94 @@ +package com.garbagemule.MobArena.util; + +import java.util.Date; + +public class TimeUtils +{ + /** + * Turn the input long into a string on the form (D:)HH:MM:SS, where the + * day-part is only added if the number of days is greater than or equal + * to 1, i.e. a long value of 86,399,999. + * @param ms time in milliseconds + * @return string-representation of the input long + */ + public static String toTime(long ms) { + long total = ms / 1000; + long secs = total % 60; + long mins = total % 3600 / 60; + long hours = total / 3600 % 24; + long days = total / 3600 / 24; + String time = (days > 0 ? days + ":" : "") + + (hours < 10 ? "0" + hours : hours) + ":" + + (mins < 10 ? "0" + mins : mins) + ":" + + (secs < 10 ? "0" + secs : secs); + return time; + } + + /** + * Makes a new java.util.Date with the input long and toString()s it. + * @param ms time in milliseconds + * @return java.util.Date toString() of the input long + */ + public static String toDateTime(long ms) { + return new Date(ms).toString(); + } + + /** + * Adds two string-representations of time and returns the resulting time. + * @param t1 a time-string + * @param t2 another time-string + * @return the sum of the time-strings + */ + public static String addTimes(String t1, String t2) { + String[] parts1 = t1.split(":"); + String[] parts2 = t2.split(":"); + + long secs1 = extractSeconds(parts1); + long secs2 = extractSeconds(parts2); + + long mins1 = extractMinutes(parts1); + long mins2 = extractMinutes(parts2); + + long hours1 = extractHours(parts1); + long hours2 = extractHours(parts2); + + long days1 = extractDays(parts1); + long days2 = extractDays(parts2); + + long time = (secs1 + secs2 + mins1 + mins2 + hours1 + hours2 + days1 + days2) * 1000; + + return toTime(time); + } + + private static long extractSeconds(String[] parts) { + int length = parts.length; + if (length < 1) { + return 0L; + } + return Long.parseLong(parts[length - 1]); + } + + private static long extractMinutes(String[] parts) { + int length = parts.length; + if (length < 2) { + return 0L; + } + return Long.parseLong(parts[length - 2]) * 60; + } + + private static long extractHours(String[] parts) { + int length = parts.length; + if (length < 3) { + return 0L; + } + return Long.parseLong(parts[length - 3]) * 3600; + } + + private static long extractDays(String[] parts) { + int length = parts.length; + if (length < 4) { + return 0L; + } + return Long.parseLong(parts[length - 4]) * 24 * 3600; + } +}