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 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; + } + + } +}