diff --git a/src/main/java/de/diddiz/LogBlock/Actor.java b/src/main/java/de/diddiz/LogBlock/Actor.java
index 0307870..1fde072 100644
--- a/src/main/java/de/diddiz/LogBlock/Actor.java
+++ b/src/main/java/de/diddiz/LogBlock/Actor.java
@@ -3,6 +3,7 @@ package de.diddiz.LogBlock;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
+import org.bukkit.entity.Projectile;
import org.bukkit.projectiles.BlockProjectileSource;
import org.bukkit.projectiles.ProjectileSource;
@@ -86,9 +87,14 @@ public class Actor {
public static Actor actorFromEntity(Entity entity) {
if (entity instanceof Player) {
return new Actor(entityName(entity), entity.getUniqueId());
- } else {
- return new Actor(entityName(entity));
}
+ if (entity instanceof Projectile) {
+ ProjectileSource shooter = ((Projectile) entity).getShooter();
+ if (shooter != null) {
+ return actorFromProjectileSource(shooter);
+ }
+ }
+ return new Actor(entityName(entity));
}
public static Actor actorFromEntity(EntityType entity) {
@@ -109,17 +115,19 @@ public class Actor {
/**
* Generate an Actor object from a String name, trying to guess if it's an online player
- * and if so, setting the UUID accordingly. This only checks against currently online
+ * and if so, setting the UUID accordingly. This only checks against currently online
* players and is a "best effort" attempt for use with the pre-UUID API
*
* If you know something is an entity (player or otherwise) use the {@link #actorFromEntity(org.bukkit.entity.Entity) }
* or {@link #actorFromEntity(org.bukkit.entity.EntityType) } methods
*
* If you know something is a server effect (like gravity) use {@link #Actor(java.lang.String)}
+ *
* @deprecated Only use this if you have a String of unknown origin
*
- * @param actorName String of unknown origin
- * @return
+ * @param actorName
+ * String of unknown origin
+ * @return
*/
public static Actor actorFromString(String actorName) {
Collection extends Player> players = Bukkit.getServer().getOnlinePlayers();
diff --git a/src/main/java/de/diddiz/LogBlock/Consumer.java b/src/main/java/de/diddiz/LogBlock/Consumer.java
index b89b300..d96daa1 100644
--- a/src/main/java/de/diddiz/LogBlock/Consumer.java
+++ b/src/main/java/de/diddiz/LogBlock/Consumer.java
@@ -26,16 +26,19 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
+import org.bukkit.World;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.block.data.BlockData;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.inventory.Inventory;
@@ -43,6 +46,7 @@ import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.projectiles.ProjectileSource;
+import de.diddiz.LogBlock.EntityChange.EntityChangeType;
import de.diddiz.LogBlock.blockstate.BlockStateCodecSign;
import de.diddiz.LogBlock.blockstate.BlockStateCodecs;
import de.diddiz.LogBlock.config.Config;
@@ -52,8 +56,10 @@ import de.diddiz.util.Utils;
public class Consumer extends Thread {
private final Deque queue = new ArrayDeque();
private final LogBlock logblock;
- private final Map playerIds = new HashMap();
- private final Map uncommitedPlayerIds = new HashMap();
+ private final Map playerIds = new HashMap<>();
+ private final Map uncommitedPlayerIds = new HashMap<>();
+ private final Map> uncommitedEntityIds = new HashMap<>();
+
private long addEntryCounter;
private long nextWarnCounter;
@@ -495,6 +501,7 @@ public class Consumer extends Thread {
currentRows.clear();
playerIds.putAll(uncommitedPlayerIds);
uncommitedPlayerIds.clear();
+ uncommitedEntityIds.clear();
lastCommitsFailed = 0;
}
} catch (Exception e) {
@@ -520,6 +527,7 @@ public class Consumer extends Thread {
currentRows.clear();
batchHelper.reset();
uncommitedPlayerIds.clear();
+ uncommitedEntityIds.clear();
if (conn != null) {
try {
conn.close();
@@ -657,6 +665,48 @@ public class Consumer extends Thread {
return uncommitedPlayerIds.containsKey(actor);
}
+ private int getEntityUUID(Connection conn, World world, UUID uuid) throws SQLException {
+ Map uncommitedEntityIdsHere = uncommitedEntityIds.get(world);
+ if (uncommitedEntityIdsHere == null) {
+ uncommitedEntityIdsHere = new HashMap<>();
+ uncommitedEntityIds.put(world, uncommitedEntityIdsHere);
+ }
+ Integer existing = uncommitedEntityIdsHere.get(uuid);
+ if (existing != null) {
+ return existing;
+ }
+
+ // Odd query contruction is to work around innodb auto increment behaviour - bug #492
+ final String table = getWorldConfig(world).table;
+ String uuidString = uuid.toString();
+ Statement state = conn.createStatement();
+ String q1 = "INSERT IGNORE INTO `" + table + "-entityids` (entityuuid) SELECT '" + mysqlTextEscape(uuidString) + "' FROM `" + table + "-entityids` WHERE NOT EXISTS (SELECT NULL FROM `" + table + "-entityids` WHERE entityuuid = '" + mysqlTextEscape(uuidString) + "') LIMIT 1";
+ String q2 = "SELECT entityid FROM `" + table + "-entityids` WHERE entityuuid = '" + mysqlTextEscape(uuidString) + "'";
+ int q1Result = state.executeUpdate(q1);
+ ResultSet rs = state.executeQuery(q2);
+ if (rs.next()) {
+ uncommitedEntityIdsHere.put(uuid, rs.getInt(1));
+ }
+ rs.close();
+ // if there was not any row in the table the query above does not work, so we need to try this one
+ if (!uncommitedEntityIdsHere.containsKey(uuid)) {
+ state.executeUpdate("INSERT IGNORE INTO `" + table + "-entityids` (entityuuid) VALUES ('" + mysqlTextEscape(uuidString) + "')");
+ rs = state.executeQuery(q2);
+ if (rs.next()) {
+ uncommitedEntityIdsHere.put(uuid, rs.getInt(1));
+ } else {
+ logblock.getLogger().warning("[Consumer] Failed to add entity uuid " + uuidString.toString());
+ logblock.getLogger().warning("[Consumer-Debug] World: " + world.getName());
+ logblock.getLogger().warning("[Consumer-Debug] Query 1: " + q1);
+ logblock.getLogger().warning("[Consumer-Debug] Query 1 - Result: " + q1Result);
+ logblock.getLogger().warning("[Consumer-Debug] Query 2: " + q2);
+ }
+ rs.close();
+ }
+ state.close();
+ return uncommitedEntityIdsHere.get(uuid);
+ }
+
private void queueBlock(Actor actor, Location loc, BlockData typeBefore, BlockData typeAfter, YamlConfiguration stateBefore, YamlConfiguration stateAfter, ChestAccess ca) {
if (typeBefore == null || typeBefore.getMaterial() == Material.CAVE_AIR || typeBefore.getMaterial() == Material.VOID_AIR) {
typeBefore = Bukkit.createBlockData(Material.AIR);
@@ -696,6 +746,13 @@ public class Consumer extends Thread {
addQueueLast(new BlockRow(loc, actor, replacedMaterialId, replacedStateId, Utils.serializeYamlConfiguration(stateBefore), typeMaterialId, typeStateId, Utils.serializeYamlConfiguration(stateAfter), ca));
}
+ public void queueEntityModification(Actor actor, UUID entityId, EntityType entityType, Location loc, EntityChangeType changeType, YamlConfiguration data) {
+ if (actor == null || loc == null || changeType == null || entityId == null || entityType == null || hiddenPlayers.contains(actor.getName().toLowerCase()) || !isLogged(loc.getWorld())) {
+ return;
+ }
+ addQueueLast(new EntityRow(loc, actor, entityType, entityId, changeType, Utils.serializeYamlConfiguration(data)));
+ }
+
private String playerID(Actor actor) {
if (actor == null) {
return "NULL";
@@ -965,6 +1022,67 @@ public class Consumer extends Thread {
}
}
+ private class EntityRow extends EntityChange implements Row {
+ final String statementString;
+ final String selectActorIdStatementString;
+
+ public EntityRow(Location loc, Actor actor, EntityType type, UUID entityid, EntityChangeType changeType, byte[] data) {
+ super(System.currentTimeMillis() / 1000, loc, actor, type, entityid, changeType, data);
+ statementString = getWorldConfig(loc.getWorld()).insertEntityStatementString;
+ selectActorIdStatementString = getWorldConfig(loc.getWorld()).selectBlockActorIdStatementString;
+ }
+
+ @Override
+ public String[] getInserts() {
+ final String table = getWorldConfig(loc.getWorld()).table;
+ final String[] inserts = new String[2];
+
+ inserts[0] = "INSERT IGNORE INTO `" + table + "-entityids` (entityuuid) SELECT '" + mysqlTextEscape(entityid.toString()) + "' FROM `" + table + "-entityids` WHERE NOT EXISTS (SELECT NULL FROM `" + table + "-entityids` WHERE entityuuid = '" + mysqlTextEscape(entityid.toString()) + "') LIMIT 1";
+ int entityTypeId = EntityTypeConverter.getOrAddEntityTypeId(type);
+ inserts[1] = "INSERT INTO `" + table + "-entities` (date, playerid, entityid, entitytypeid, x, y, z, action, data) VALUES (FROM_UNIXTIME(" + date + "), " + playerID(actor) + ", " + "(SELECT entityid FROM `" + table + "-entityids` WHERE entityuuid = '" + mysqlTextEscape(entityid.toString()) + "')"
+ + ", " + entityTypeId + ", '" + loc.getBlockX() + "', " + safeY(loc) + ", '" + loc.getBlockZ() + "', " + changeType.ordinal() + ", " + Utils.mysqlPrepareBytesForInsertAllowNull(data) + ");";
+ return inserts;
+ }
+
+ @Override
+ public Actor[] getActors() {
+ return new Actor[] { actor };
+ }
+
+ @Override
+ public void process(Connection conn, BatchHelper batchHelper) throws SQLException {
+ int sourceActor = playerIDAsIntIncludeUncommited(actor);
+ Location actorBlockLocation = actor.getBlockLocation();
+ if (actorBlockLocation != null) {
+ Integer tempSourceActor = batchHelper.getUncommitedBlockActor(actorBlockLocation);
+ if (tempSourceActor != null) {
+ sourceActor = tempSourceActor;
+ } else {
+ PreparedStatement smt = batchHelper.getOrPrepareStatement(conn, selectActorIdStatementString, Statement.NO_GENERATED_KEYS);
+ smt.setInt(1, actorBlockLocation.getBlockX());
+ smt.setInt(2, safeY(actorBlockLocation));
+ smt.setInt(3, actorBlockLocation.getBlockZ());
+ ResultSet rs = smt.executeQuery();
+ if (rs.next()) {
+ sourceActor = rs.getInt(1);
+ }
+ rs.close();
+ }
+ }
+ PreparedStatement smt = batchHelper.getOrPrepareStatement(conn, statementString, Statement.RETURN_GENERATED_KEYS);
+ smt.setLong(1, date);
+ smt.setInt(2, sourceActor);
+ smt.setInt(3, getEntityUUID(conn, loc.getWorld(), entityid));
+ smt.setInt(4, EntityTypeConverter.getOrAddEntityTypeId(type));
+ smt.setInt(5, loc.getBlockX());
+ smt.setInt(6, safeY(loc));
+ smt.setInt(7, loc.getBlockZ());
+ smt.setInt(8, changeType.ordinal());
+ smt.setBytes(9, data);
+ batchHelper.addBatch(smt, null);
+ }
+ }
+
private int safeY(Location loc) {
int safeY = loc.getBlockY();
if (safeY < 0)
diff --git a/src/main/java/de/diddiz/LogBlock/EntityChange.java b/src/main/java/de/diddiz/LogBlock/EntityChange.java
new file mode 100644
index 0000000..878464d
--- /dev/null
+++ b/src/main/java/de/diddiz/LogBlock/EntityChange.java
@@ -0,0 +1,98 @@
+package de.diddiz.LogBlock;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.bukkit.Location;
+import org.bukkit.entity.ArmorStand;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.LivingEntity;
+
+import de.diddiz.LogBlock.config.Config;
+
+public class EntityChange implements LookupCacheElement {
+ public static enum EntityChangeType {
+ CREATE,
+ KILL,
+ MODIFY,
+ ADDEQUIP,
+ REMOVEEQUIP;
+
+ private static EntityChangeType[] values = values();
+
+ public static EntityChangeType valueOf(int ordinal) {
+ return values[ordinal];
+ }
+ }
+
+ public final long id, date;
+ public final Location loc;
+ public final Actor actor;
+ public final EntityType type;
+ public final UUID entityid;
+ public final EntityChangeType changeType;
+ public final byte[] data;
+
+ public EntityChange(long date, Location loc, Actor actor, EntityType type, UUID entityid, EntityChangeType changeType, byte[] data) {
+ id = 0;
+ this.date = date;
+ this.loc = loc;
+ this.actor = actor;
+ this.type = type;
+ this.entityid = entityid;
+ this.changeType = changeType;
+ this.data = data;
+ }
+
+ public EntityChange(ResultSet rs, QueryParams p) throws SQLException {
+ id = p.needId ? rs.getInt("id") : 0;
+ date = p.needDate ? rs.getTimestamp("date").getTime() : 0;
+ loc = p.needCoords ? new Location(p.world, rs.getInt("x"), rs.getInt("y"), rs.getInt("z")) : null;
+ actor = p.needPlayer ? new Actor(rs) : null;
+ type = p.needType ? EntityTypeConverter.getEntityType(rs.getInt("entitytypeid")) : null;
+ entityid = p.needType ? UUID.fromString(rs.getString("entityuuid")) : null;
+ changeType = p.needType ? EntityChangeType.valueOf(rs.getInt("action")) : null;
+ data = p.needType ? rs.getBytes("data") : null;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder msg = new StringBuilder();
+ if (date > 0) {
+ msg.append(Config.formatter.format(date)).append(" ");
+ }
+ if (actor != null) {
+ msg.append(actor.getName()).append(" ");
+ }
+ if (type != null) {
+ boolean living = LivingEntity.class.isAssignableFrom(type.getEntityClass()) && !ArmorStand.class.isAssignableFrom(type.getDeclaringClass());
+ if (changeType == EntityChangeType.CREATE) {
+ msg.append("created ");
+ } else if (changeType == EntityChangeType.KILL) {
+ msg.append(living ? "killed " : "destroyed ");
+ } else if (changeType == EntityChangeType.ADDEQUIP) {
+ msg.append("added an item to ");
+ } else if (changeType == EntityChangeType.REMOVEEQUIP) {
+ msg.append("removed an item from ");
+ } else if (changeType == EntityChangeType.MODIFY) {
+ msg.append("modified ");
+ }
+ msg.append(type.name());
+ }
+ if (loc != null) {
+ msg.append(" at ").append(loc.getBlockX()).append(":").append(loc.getBlockY()).append(":").append(loc.getBlockZ());
+ }
+ return msg.toString();
+ }
+
+ @Override
+ public Location getLocation() {
+ return loc;
+ }
+
+ @Override
+ public String getMessage() {
+ return toString();
+ }
+}
diff --git a/src/main/java/de/diddiz/LogBlock/EntityTypeConverter.java b/src/main/java/de/diddiz/LogBlock/EntityTypeConverter.java
new file mode 100644
index 0000000..83e8c1b
--- /dev/null
+++ b/src/main/java/de/diddiz/LogBlock/EntityTypeConverter.java
@@ -0,0 +1,82 @@
+package de.diddiz.LogBlock;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.logging.Level;
+
+import org.bukkit.entity.EntityType;
+
+public class EntityTypeConverter {
+ private static EntityType[] idToEntityType = new EntityType[10];
+ private static HashMap entityTypeToId = new HashMap<>();
+ private static int nextEntityTypeId;
+
+ public static int getOrAddEntityTypeId(EntityType entityType) {
+ Integer key = entityTypeToId.get(entityType);
+ while (key == null) {
+ key = nextEntityTypeId;
+ Connection conn = LogBlock.getInstance().getConnection();
+ try {
+ conn.setAutoCommit(false);
+ PreparedStatement smt = conn.prepareStatement("INSERT IGNORE INTO `lb-entitytypes` (id, name) VALUES (?, ?)");
+ smt.setInt(1, key);
+ smt.setString(2, entityType.name());
+ boolean couldAdd = smt.executeUpdate() > 0;
+ conn.commit();
+ smt.close();
+ if (couldAdd) {
+ internalAddEntityType(key, entityType);
+ } else {
+ initializeEntityTypes(conn);
+ }
+ } catch (SQLException e) {
+ LogBlock.getInstance().getLogger().log(Level.SEVERE, "Could not update lb-entitytypes", e);
+ } finally {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ // ignored
+ }
+ }
+ key = entityTypeToId.get(entityType);
+ }
+ return key.intValue();
+ }
+
+ public static EntityType getEntityType(int entityTypeId) {
+ return idToEntityType[entityTypeId];
+ }
+
+ public static void initializeEntityTypes(Connection connection) throws SQLException {
+ Statement smt = connection.createStatement();
+ ResultSet rs = smt.executeQuery("SELECT id, name FROM `lb-entitytypes`");
+ while (rs.next()) {
+ int key = rs.getInt(1);
+ EntityType entityType = EntityType.valueOf(rs.getString(2));
+ internalAddEntityType(key, entityType);
+ }
+ rs.close();
+ smt.close();
+ connection.close();
+ }
+
+ private synchronized static void internalAddEntityType(int key, EntityType entityType) {
+ entityTypeToId.put(entityType, key);
+ int length = idToEntityType.length;
+ while (length <= key) {
+ length = (length * 3 / 2) + 5;
+ }
+ if (length > idToEntityType.length) {
+ idToEntityType = Arrays.copyOf(idToEntityType, length);
+ }
+ idToEntityType[key] = entityType;
+ if (nextEntityTypeId <= key) {
+ nextEntityTypeId = key + 1;
+ }
+ }
+}
diff --git a/src/main/java/de/diddiz/LogBlock/LogBlock.java b/src/main/java/de/diddiz/LogBlock/LogBlock.java
index e3e35e8..65bf474 100644
--- a/src/main/java/de/diddiz/LogBlock/LogBlock.java
+++ b/src/main/java/de/diddiz/LogBlock/LogBlock.java
@@ -5,6 +5,8 @@ import de.diddiz.LogBlock.listeners.*;
import de.diddiz.LogBlock.questioner.Questioner;
import de.diddiz.util.BukkitUtils;
import de.diddiz.util.MySQLConnectionPool;
+import de.diddiz.worldedit.AdvancedKillLogging;
+import de.diddiz.worldedit.WorldEditHelper;
import de.diddiz.worldedit.WorldEditLoggingHook;
import org.bukkit.ChatColor;
import org.bukkit.Material;
@@ -89,6 +91,7 @@ public class LogBlock extends JavaPlugin {
updater.checkTables();
MaterialConverter.initializeMaterials(getConnection());
MaterialConverter.getOrAddMaterialId(Material.AIR.getKey()); // AIR must be the first entry
+ EntityTypeConverter.initializeEntityTypes(getConnection());
if (updater.update()) {
load(this);
}
@@ -100,7 +103,7 @@ public class LogBlock extends JavaPlugin {
return;
}
- if (pm.getPlugin("WorldEdit") != null) {
+ if (WorldEditHelper.hasWorldEdit()) {
new WorldEditLoggingHook(this).hook();
}
commandsHandler = new CommandsHandler(this);
@@ -186,6 +189,7 @@ public class LogBlock extends JavaPlugin {
if (isLogging(Logging.DRAGONEGGTELEPORT)) {
pm.registerEvents(new DragonEggLogging(this), this);
}
+ pm.registerEvents(new AdvancedKillLogging(this), this);
}
@Override
diff --git a/src/main/java/de/diddiz/LogBlock/QueryParams.java b/src/main/java/de/diddiz/LogBlock/QueryParams.java
index 958ebeb..506dd57 100644
--- a/src/main/java/de/diddiz/LogBlock/QueryParams.java
+++ b/src/main/java/de/diddiz/LogBlock/QueryParams.java
@@ -3,12 +3,13 @@ package de.diddiz.LogBlock;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.util.Utils;
import de.diddiz.worldedit.CuboidRegion;
+import de.diddiz.worldedit.WorldEditHelper;
+
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
-import org.bukkit.plugin.Plugin;
import java.util.*;
@@ -120,6 +121,17 @@ public final class QueryParams implements Cloneable {
}
return from;
}
+ if (bct == BlockChangeType.ENTITIES) {
+ String from = "FROM `" + getTable() + "-entities` ";
+
+ if (needPlayer || players.size() > 0) {
+ from += "INNER JOIN `lb-players` USING (playerid) ";
+ }
+ if (!needCount && needType) {
+ from += "LEFT JOIN `" + getTable() + "-entityids` USING (entityid) ";
+ }
+ return from;
+ }
String from = "FROM `" + getTable() + "-blocks` ";
@@ -262,6 +274,9 @@ public final class QueryParams implements Cloneable {
}
throw new IllegalStateException("Invalid summarization for kills");
}
+ if (bct == BlockChangeType.ENTITIES) {
+ throw new IllegalStateException("Not implemented yet");
+ }
if (sum == SummarizationMode.TYPES) {
return "SELECT type, SUM(created) AS created, SUM(destroyed) AS destroyed FROM ((SELECT type, count(*) AS created, 0 AS destroyed FROM `" + getTable() + "-blocks` INNER JOIN `lb-players` USING (playerid) " + getWhere(BlockChangeType.CREATED) + "GROUP BY type) UNION (SELECT replaced AS type, 0 AS created, count(*) AS destroyed FROM `" + getTable() + "-blocks` INNER JOIN `lb-players` USING (playerid) " + getWhere(BlockChangeType.DESTROYED) + "GROUP BY replaced)) AS t GROUP BY type ORDER BY SUM(created) + SUM(destroyed) " + order + " " + getLimit();
} else {
@@ -281,6 +296,8 @@ public final class QueryParams implements Cloneable {
title.append("chat messages ");
} else if (bct == BlockChangeType.KILLS) {
title.append("kills ");
+ } else if (bct == BlockChangeType.ENTITIES) {
+ title.append("entity changes ");
} else {
if (!types.isEmpty()) {
if (excludeBlocksMode) {
@@ -745,9 +762,8 @@ public final class QueryParams implements Cloneable {
if (player == null) {
throw new IllegalArgumentException("You have to be a player to use selection");
}
- final Plugin we = player.getServer().getPluginManager().getPlugin("WorldEdit");
- if (we != null) {
- setSelection(CuboidRegion.fromPlayerSelection(player, we));
+ if (WorldEditHelper.hasWorldEdit()) {
+ setSelection(CuboidRegion.fromPlayerSelection(player));
} else {
throw new IllegalArgumentException("WorldEdit not found!");
}
@@ -786,6 +802,8 @@ public final class QueryParams implements Cloneable {
bct = BlockChangeType.CHAT;
} else if (param.equals("kills")) {
bct = BlockChangeType.KILLS;
+ } else if (param.equals("entities")) {
+ bct = BlockChangeType.ENTITIES;
} else if (param.equals("all")) {
bct = BlockChangeType.ALL;
} else if (param.equals("limit")) {
@@ -963,7 +981,7 @@ public final class QueryParams implements Cloneable {
}
public static enum BlockChangeType {
- ALL, BOTH, CHESTACCESS, CREATED, DESTROYED, CHAT, KILLS
+ ALL, BOTH, CHESTACCESS, CREATED, DESTROYED, CHAT, KILLS, ENTITIES
}
public static enum Order {
diff --git a/src/main/java/de/diddiz/LogBlock/Updater.java b/src/main/java/de/diddiz/LogBlock/Updater.java
index 6322b22..d164e09 100644
--- a/src/main/java/de/diddiz/LogBlock/Updater.java
+++ b/src/main/java/de/diddiz/LogBlock/Updater.java
@@ -779,15 +779,17 @@ class Updater {
}
createTable(dbm, state, "lb-materials", "(id INT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET " + charset);
createTable(dbm, state, "lb-blockstates", "(id INT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET " + charset);
+ createTable(dbm, state, "lb-entitytypes", "(id INT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET " + charset);
for (final WorldConfig wcfg : getLoggedWorlds()) {
createTable(dbm, state, wcfg.table + "-blocks", "(id INT UNSIGNED NOT NULL AUTO_INCREMENT, date DATETIME NOT NULL, playerid INT UNSIGNED NOT NULL, replaced SMALLINT UNSIGNED NOT NULL, replacedData SMALLINT NOT NULL, type SMALLINT UNSIGNED NOT NULL, typeData SMALLINT NOT NULL, x MEDIUMINT NOT NULL, y SMALLINT UNSIGNED NOT NULL, z MEDIUMINT NOT NULL, PRIMARY KEY (id), KEY coords (x, z, y), KEY date (date), KEY playerid (playerid))");
- // createTable(dbm, state, wcfg.table + "-sign", "(id INT UNSIGNED NOT NULL, signtext VARCHAR(255) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET " + charset);
createTable(dbm, state, wcfg.table + "-chestdata", "(id INT UNSIGNED NOT NULL, item MEDIUMBLOB, itemremove TINYINT, itemtype SMALLINT NOT NULL DEFAULT '0', PRIMARY KEY (id))");
createTable(dbm, state, wcfg.table + "-state", "(id INT UNSIGNED NOT NULL, replacedState MEDIUMBLOB NULL, typeState MEDIUMBLOB NULL, PRIMARY KEY (id))");
if (wcfg.isLogging(Logging.KILL)) {
createTable(dbm, state, wcfg.table + "-kills", "(id INT UNSIGNED NOT NULL AUTO_INCREMENT, date DATETIME NOT NULL, killer INT UNSIGNED, victim INT UNSIGNED NOT NULL, weapon SMALLINT UNSIGNED NOT NULL, x MEDIUMINT NOT NULL, y SMALLINT NOT NULL, z MEDIUMINT NOT NULL, PRIMARY KEY (id))");
}
+ createTable(dbm, state, wcfg.table + "-entityids", "(entityid INT UNSIGNED NOT NULL AUTO_INCREMENT, entityuuid VARCHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, PRIMARY KEY (entityid), UNIQUE KEY (entityuuid))");
+ createTable(dbm, state, wcfg.table + "-entities", "(id INT UNSIGNED NOT NULL AUTO_INCREMENT, date DATETIME NOT NULL, playerid INT UNSIGNED NOT NULL, entityid INT UNSIGNED NOT NULL, entitytypeid INT UNSIGNED NOT NULL, x MEDIUMINT NOT NULL, y SMALLINT NOT NULL, z MEDIUMINT NOT NULL, action TINYINT UNSIGNED NOT NULL, data MEDIUMBLOB NULL, PRIMARY KEY (id), KEY coords (x, z, y), KEY date (date), KEY playerid (playerid))");
}
state.close();
conn.close();
diff --git a/src/main/java/de/diddiz/LogBlock/config/WorldConfig.java b/src/main/java/de/diddiz/LogBlock/config/WorldConfig.java
index 3fe0d99..8b647d3 100644
--- a/src/main/java/de/diddiz/LogBlock/config/WorldConfig.java
+++ b/src/main/java/de/diddiz/LogBlock/config/WorldConfig.java
@@ -16,6 +16,7 @@ public class WorldConfig extends LoggingEnabledMapping {
public final String selectBlockActorIdStatementString;
public final String insertBlockStateStatementString;
public final String insertBlockChestDataStatementString;
+ public final String insertEntityStatementString;
public WorldConfig(String world, File file) throws IOException {
this.world = world;
@@ -42,5 +43,6 @@ public class WorldConfig extends LoggingEnabledMapping {
selectBlockActorIdStatementString = "SELECT playerid FROM `" + table + "-blocks` WHERE x = ? AND y = ? AND z = ? ORDER BY date DESC LIMIT 1";
insertBlockStateStatementString = "INSERT INTO `" + table + "-state` (replacedState, typeState, id) VALUES(?, ?, ?)";
insertBlockChestDataStatementString = "INSERT INTO `" + table + "-chestdata` (item, itemremove, id, itemtype) values (?, ?, ?, ?)";
+ insertEntityStatementString = "INSERT INTO `" + table + "-entities` (date, playerid, entityid, entitytypeid, x, y, z, action, data) VALUES (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?)";
}
}
diff --git a/src/main/java/de/diddiz/worldedit/AdvancedKillLogging.java b/src/main/java/de/diddiz/worldedit/AdvancedKillLogging.java
new file mode 100644
index 0000000..2c85122
--- /dev/null
+++ b/src/main/java/de/diddiz/worldedit/AdvancedKillLogging.java
@@ -0,0 +1,50 @@
+package de.diddiz.worldedit;
+
+import org.bukkit.Location;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.entity.Animals;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Villager;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.event.entity.EntityDeathEvent;
+
+import de.diddiz.LogBlock.Actor;
+import de.diddiz.LogBlock.EntityChange;
+import de.diddiz.LogBlock.LogBlock;
+import de.diddiz.LogBlock.listeners.LoggingListener;
+
+public class AdvancedKillLogging extends LoggingListener {
+
+ public AdvancedKillLogging(LogBlock lb) {
+ super(lb);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onEntityDeath(EntityDeathEvent event) {
+ LivingEntity entity = event.getEntity();
+ if (!(entity instanceof Animals) && !(entity instanceof Villager)) {
+ return;
+ }
+ Actor killer;
+ EntityDamageEvent lastDamage = entity.getLastDamageCause();
+ if (lastDamage instanceof EntityDamageByEntityEvent) {
+ killer = Actor.actorFromEntity(((EntityDamageByEntityEvent) lastDamage).getDamager());
+ } else {
+ killer = new Actor(lastDamage.getCause().toString());
+ }
+ Location location = entity.getLocation();
+ YamlConfiguration data = new YamlConfiguration();
+ data.set("x", location.getX());
+ data.set("y", location.getX());
+ data.set("z", location.getX());
+ data.set("yaw", location.getYaw());
+ data.set("pitch", location.getPitch());
+
+ data.set("worldedit", WorldEditHelper.serializeEntity(entity));
+
+ consumer.queueEntityModification(killer, entity.getUniqueId(), entity.getType(), location, EntityChange.EntityChangeType.KILL, data);
+ }
+}
diff --git a/src/main/java/de/diddiz/worldedit/CuboidRegion.java b/src/main/java/de/diddiz/worldedit/CuboidRegion.java
index 3755a06..7b09429 100644
--- a/src/main/java/de/diddiz/worldedit/CuboidRegion.java
+++ b/src/main/java/de/diddiz/worldedit/CuboidRegion.java
@@ -29,7 +29,8 @@ public class CuboidRegion implements Cloneable {
this.max.setZ(Math.max(first.getBlockZ(),second.getBlockZ()));
}
- public static CuboidRegion fromPlayerSelection(Player player, Plugin worldEditPlugin) {
+ public static CuboidRegion fromPlayerSelection(Player player) {
+ Plugin worldEditPlugin = player.getServer().getPluginManager().getPlugin("WorldEdit");
LocalSession session = ((WorldEditPlugin) worldEditPlugin).getSession(player);
World world = player.getWorld();
com.sk89q.worldedit.world.World weWorld = BukkitAdapter.adapt(world);
diff --git a/src/main/java/de/diddiz/worldedit/WorldEditHelper.java b/src/main/java/de/diddiz/worldedit/WorldEditHelper.java
new file mode 100644
index 0000000..08441b3
--- /dev/null
+++ b/src/main/java/de/diddiz/worldedit/WorldEditHelper.java
@@ -0,0 +1,60 @@
+package de.diddiz.worldedit;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+import org.bukkit.plugin.Plugin;
+
+import com.sk89q.jnbt.NBTOutputStream;
+import com.sk89q.worldedit.bukkit.BukkitAdapter;
+import com.sk89q.worldedit.entity.BaseEntity;
+
+public class WorldEditHelper {
+ private static boolean checkedForWorldEdit;
+ private static boolean hasWorldEdit;
+
+ public static boolean hasWorldEdit() {
+ if (!checkedForWorldEdit) {
+ checkedForWorldEdit = true;
+ Plugin worldEdit = Bukkit.getPluginManager().getPlugin("WorldEdit");
+ hasWorldEdit = worldEdit != null;
+ Internal.setWorldEdit(worldEdit);
+ }
+ return hasWorldEdit;
+ }
+
+ public static byte[] serializeEntity(Entity entity) {
+ if (!hasWorldEdit()) {
+ return null;
+ }
+ return Internal.serializeEntity(entity);
+ }
+
+ private static class Internal {
+ // private static WorldEditPlugin worldEdit;
+
+ public static void setWorldEdit(Plugin worldEdit) {
+ // Internal.worldEdit = (WorldEditPlugin) worldEdit;
+ }
+
+ public static byte[] serializeEntity(Entity entity) {
+ com.sk89q.worldedit.entity.Entity weEntity = BukkitAdapter.adapt(entity);
+ BaseEntity state = weEntity.getState();
+ if (state != null) {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ NBTOutputStream nbtos = new NBTOutputStream(baos);
+ nbtos.writeNamedTag("entity", state.getNbtData());
+ nbtos.close();
+ return baos.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException("This IOException should be impossible", e);
+ }
+ }
+ return null;
+ }
+
+ }
+}