Add MySQL kill/death logging support. Closes gh-658

War now logs kills and deaths to a MySQL database. Records are created for
every player at the end of each round with the current date and amount of
times the player killed and was killed for the entire round. It has built
in support for automatic & configurable log clearing past a certain date
(default is 1 week).
This commit is contained in:
cmastudios 2013-08-09 23:30:46 -05:00
parent 0dc7ac837f
commit dbac21aa44
7 changed files with 262 additions and 5 deletions

View File

@ -30,6 +30,7 @@ import com.tommytony.war.config.FlagReturn;
import com.tommytony.war.config.InventoryBag;
import com.tommytony.war.config.ScoreboardType;
import com.tommytony.war.config.KillstreakReward;
import com.tommytony.war.config.MySQLConfig;
import com.tommytony.war.config.TeamConfig;
import com.tommytony.war.config.TeamConfigBag;
import com.tommytony.war.config.TeamKind;
@ -98,6 +99,7 @@ public class War extends JavaPlugin {
private final InventoryBag defaultInventories = new InventoryBag();
private KillstreakReward killstreakReward;
private MySQLConfig mysqlConfig;
private final WarConfigBag warConfig = new WarConfigBag();
private final WarzoneConfigBag warzoneDefaultConfig = new WarzoneConfigBag();
@ -240,6 +242,7 @@ public class War extends JavaPlugin {
this.getCommandWhitelist().add("who");
this.getZoneMakerNames().add("tommytony");
this.setKillstreakReward(new KillstreakReward());
this.setMysqlConfig(new MySQLConfig());
// Add constants
this.getDeadlyAdjectives().clear();
@ -273,6 +276,14 @@ public class War extends JavaPlugin {
SpoutFadeOutMessageJob fadeOutMessagesTask = new SpoutFadeOutMessageJob();
this.getServer().getScheduler().scheduleSyncRepeatingTask(this, fadeOutMessagesTask, 100, 100);
}
if (this.mysqlConfig.isEnabled()) {
try {
Class.forName("com.mysql.jdbc.Driver").newInstance();
} catch (Exception ex) {
this.log("MySQL driver not found!", Level.SEVERE);
this.getServer().getPluginManager().disablePlugin(this);
}
}
// Get own log file
try {
@ -286,6 +297,7 @@ public class War extends JavaPlugin {
Formatter formatter = new WarLogFormatter();
handler.setFormatter(formatter);
this.warLogger.addHandler(handler);
this.getLogger().addHandler(handler);
} catch (IOException e) {
this.getLogger().log(Level.WARNING, "Failed to create War log file");
}
@ -986,9 +998,9 @@ public class War extends JavaPlugin {
// Log to Bukkit console
this.getLogger().log(lvl, str);
if (this.warLogger != null) {
this.warLogger.log(lvl, str);
}
// if (this.warLogger != null) {
// this.warLogger.log(lvl, str);
// }
}
// the only way to find a zone that has only one corner
@ -1245,4 +1257,12 @@ public class War extends JavaPlugin {
public void setKillstreakReward(KillstreakReward killstreakReward) {
this.killstreakReward = killstreakReward;
}
public MySQLConfig getMysqlConfig() {
return mysqlConfig;
}
public void setMysqlConfig(MySQLConfig mysqlConfig) {
this.mysqlConfig = mysqlConfig;
}
}

View File

@ -32,6 +32,8 @@ import com.tommytony.war.config.WarzoneConfig;
import com.tommytony.war.config.WarzoneConfigBag;
import com.tommytony.war.job.InitZoneJob;
import com.tommytony.war.job.LoadoutResetJob;
import com.tommytony.war.job.LogKillsDeathsJob;
import com.tommytony.war.job.LogKillsDeathsJob.KillsDeathsRecord;
import com.tommytony.war.job.ScoreCapReachedJob;
import com.tommytony.war.mapper.LoadoutYmlMapper;
import com.tommytony.war.spout.SpoutDisplayer;
@ -56,6 +58,8 @@ import org.bukkit.OfflinePlayer;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Scoreboard;
import java.util.Map;
import org.bukkit.OfflinePlayer;
import org.bukkit.inventory.meta.LeatherArmorMeta;
/**
@ -88,6 +92,8 @@ public class Warzone {
private HashMap<String, Integer> killCount = new HashMap<String, Integer>();
private final List<Player> respawn = new ArrayList<Player>();
private final List<String> reallyDeadFighters = new ArrayList<String>();
private List<LogKillsDeathsJob.KillsDeathsRecord> killsDeathsTracker = new ArrayList();
private final WarzoneConfigBag warzoneConfig;
private final TeamConfigBag teamDefaultConfig;
@ -1592,4 +1598,21 @@ public class Warzone {
}
killCount.put(player, killCount.get(player) + amount);
}
public void addKillDeathRecord(OfflinePlayer player, int kills, int deaths) {
for (Iterator<KillsDeathsRecord> it = this.killsDeathsTracker.iterator(); it.hasNext();) {
LogKillsDeathsJob.KillsDeathsRecord kdr = it.next();
if (kdr.getPlayer().equals(player)) {
kills += kdr.getKills();
deaths += kdr.getDeaths();
it.remove();
}
}
LogKillsDeathsJob.KillsDeathsRecord kdr = new LogKillsDeathsJob.KillsDeathsRecord(player, kills, deaths);
this.killsDeathsTracker.add(kdr);
}
public List<LogKillsDeathsJob.KillsDeathsRecord> getKillsDeathsTracker() {
return killsDeathsTracker;
}
}

View File

@ -0,0 +1,102 @@
package com.tommytony.war.config;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemoryConfiguration;
/**
* Storage class for MySQL configuration settings.
*
* @author cmastudios
*/
public class MySQLConfig {
private ConfigurationSection section;
/**
* Load the values from the specified section into the MySQL config.
*
* @param section Section to load MySQL settings from.
*/
public MySQLConfig(ConfigurationSection section) {
this.section = section;
}
/**
* Create a new MySQL configuration section with default values.
*/
public MySQLConfig() {
this(new MemoryConfiguration());
section.set("enabled", false);
section.set("host", "localhost");
section.set("port", 3306);
section.set("database", "war");
section.set("username", "root");
section.set("password", "meow");
section.set("logging.enabled", false);
section.set("logging.autoclear",
"WHERE `date` < NOW() - INTERVAL 7 DAY");
}
/**
* Check if MySQL support is enabled.
*
* @return true if MySQL support is enabled, false otherwise.
*/
public boolean isEnabled() {
return section.getBoolean("enabled");
}
/**
* Check if kill-death logging is enabled.
*
* @return true if kill-death logging is enabled, false otherwise.
*/
public boolean isLoggingEnabled() {
return section.getBoolean("logging.enabled");
}
/**
* Get WHERE clause for automatic deletion from database table.
*
* @return deletion WHERE clause or empty string.
*/
public String getLoggingDeleteClause() {
return section.getString("logging.autoclear");
}
private String getJDBCUrl() {
return String.format("jdbc:mysql://%s:%d/%s?user=%s&password=%s",
section.getString("host"), section.getInt("port"),
section.getString("database"), section.getString("username"),
section.getString("password"));
}
/**
* Get a connection to the MySQL database represented by this configuration.
*
* @return connection to MySQL database.
* @throws SQLException Error occured connecting to database.
* @throws IllegalArgumentException MySQL support is not enabled.
*/
public Connection getConnection() throws SQLException {
Validate.isTrue(this.isEnabled(), "MySQL support is not enabled");
return DriverManager.getConnection(this.getJDBCUrl());
}
/**
* Copy represented configuration into another configuration section.
*
* @param section Mutable section to write values in.
*/
public void saveTo(ConfigurationSection section) {
Map<String, Object> values = this.section.getValues(true);
for (Map.Entry<String, Object> entry : values.entrySet()) {
section.set(entry.getKey(), entry.getValue());
}
}
}

View File

@ -182,6 +182,8 @@ public class WarEntityListener implements Listener {
defenderWarzone.handleDeath(d);
if (attacker.getEntityId() != defender.getEntityId()) {
defenderWarzone.addKillCount(a.getName(), 1);
defenderWarzone.addKillDeathRecord(a, 1, 0);
defenderWarzone.addKillDeathRecord(d, 0, 1);
if (attackerTeam.getTeamConfig().resolveBoolean(TeamConfig.XPKILLMETER)) {
a.setLevel(defenderWarzone.getKillCount(a.getName()));
}

View File

@ -0,0 +1,95 @@
package com.tommytony.war.job;
import com.google.common.collect.ImmutableList;
import com.tommytony.war.War;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import org.bukkit.OfflinePlayer;
import org.bukkit.scheduler.BukkitRunnable;
/**
* Job to insert kills and deaths information to MySQL database.
*
* @author cmastudios
*/
public class LogKillsDeathsJob extends BukkitRunnable {
private final ImmutableList<KillsDeathsRecord> records;
public LogKillsDeathsJob(final ImmutableList<KillsDeathsRecord> records) {
this.records = records;
}
@Override
/**
* Adds all #records to database at #databaseURL. Will attempt to open a
* connection to the database at #databaseURL. This method is thread safe.
*/
public void run() {
Connection conn = null;
try {
conn = War.war.getMysqlConfig().getConnection();
Statement createStmt = conn.createStatement();
createStmt.executeUpdate("CREATE TABLE IF NOT EXISTS `war_kills` (`date` datetime NOT NULL, `player` varchar(16) NOT NULL, `kills` int(11) NOT NULL, `deaths` int(11) NOT NULL, KEY `date` (`date`)) ENGINE=InnoDB DEFAULT CHARSET=latin1");
createStmt.close();
PreparedStatement stmt = conn.prepareStatement("INSERT INTO war_kills (date, player, kills, deaths) VALUES (NOW(), ?, ?, ?)");
conn.setAutoCommit(false);
for (KillsDeathsRecord kdr : records) {
stmt.setString(1, kdr.getPlayer().getName());
stmt.setInt(2, kdr.getKills());
stmt.setInt(3, kdr.getDeaths());
stmt.addBatch();
}
stmt.executeBatch();
conn.commit();
stmt.close();
final String deleteClause =
War.war.getMysqlConfig().getLoggingDeleteClause();
if (!deleteClause.isEmpty()) {
Statement deleteStmt = conn.createStatement();
deleteStmt.executeUpdate(
"DELETE FROM war_kills " + deleteClause);
deleteStmt.close();
conn.commit();
}
} catch (SQLException ex) {
War.war.getLogger().log(Level.SEVERE,
"Inserting kill-death logs into database", ex);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException ex) {
}
}
}
}
public static final class KillsDeathsRecord {
private final OfflinePlayer player;
private final int kills;
private final int deaths;
public KillsDeathsRecord(OfflinePlayer player, int kills, int deaths) {
this.player = player;
this.kills = kills;
this.deaths = deaths;
}
public OfflinePlayer getPlayer() {
return player;
}
public int getKills() {
return kills;
}
public int getDeaths() {
return deaths;
}
}
}

View File

@ -1,5 +1,6 @@
package com.tommytony.war.job;
import com.google.common.collect.ImmutableList;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
@ -63,5 +64,10 @@ public class ScoreCapReachedJob implements Runnable {
t.resetPoints();
t.getPlayers().clear(); // empty the team
}
if (War.war.getMysqlConfig().isEnabled() && War.war.getMysqlConfig().isLoggingEnabled()) {
LogKillsDeathsJob logKillsDeathsJob = new LogKillsDeathsJob(ImmutableList.copyOf(zone.getKillsDeathsTracker()));
War.war.getServer().getScheduler().runTaskAsynchronously(War.war, logKillsDeathsJob);
}
zone.getKillsDeathsTracker().clear();
}
}

View File

@ -15,6 +15,7 @@ import org.bukkit.inventory.ItemStack;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
import com.tommytony.war.config.KillstreakReward;
import com.tommytony.war.config.MySQLConfig;
import com.tommytony.war.job.RestoreYmlWarhubJob;
import com.tommytony.war.job.RestoreYmlWarzonesJob;
import com.tommytony.war.structure.WarHub;
@ -101,8 +102,13 @@ public class WarYmlMapper {
}
// Killstreak config
ConfigurationSection killstreakSection = warRootSection.getConfigurationSection("war.killstreak");
War.war.setKillstreakReward(new KillstreakReward(killstreakSection));
if (warRootSection.isConfigurationSection("war.killstreak")) {
War.war.setKillstreakReward(new KillstreakReward(warRootSection.getConfigurationSection("war.killstreak")));
}
if (warRootSection.isConfigurationSection("war.mysql")) {
War.war.setMysqlConfig(new MySQLConfig(warRootSection.getConfigurationSection("war.mysql")));
}
}
public static void save() {
@ -197,6 +203,9 @@ public class WarYmlMapper {
ConfigurationSection killstreakSection = warRootSection.createSection("war.killstreak");
War.war.getKillstreakReward().saveTo(killstreakSection);
ConfigurationSection mysqlSection = warRootSection.createSection("war.mysql");
War.war.getMysqlConfig().saveTo(mysqlSection);
// Save to disk
File warConfigFile = new File(War.war.getDataFolder().getPath() + "/war.yml");
try {