forked from LogBlock/LogBlock
Improve entity logging/rollbacks
- Allow UUID changes on respawn - Log Spawn - Implement rollback and redo for entities
This commit is contained in:
@@ -753,6 +753,18 @@ public class Consumer extends Thread {
|
||||
addQueueLast(new EntityRow(loc, actor, entityType, entityId, changeType, Utils.serializeYamlConfiguration(data)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the UUID that is stored for an entity in the database. This is needed when an entity is respawned
|
||||
* and now has a different UUID.
|
||||
*
|
||||
* @param world the world that contains the entity
|
||||
* @param entityId the database id of the entity
|
||||
* @param entityUUID the new UUID of the entity
|
||||
*/
|
||||
public void queueEntityUUIDChange(World world, int entityId, UUID entityUUID) {
|
||||
addQueueLast(new EntityUUIDChange(world, entityId, entityUUID));
|
||||
}
|
||||
|
||||
private String playerID(Actor actor) {
|
||||
if (actor == null) {
|
||||
return "NULL";
|
||||
@@ -1037,9 +1049,9 @@ public class Consumer extends Thread {
|
||||
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";
|
||||
inserts[0] = "INSERT IGNORE INTO `" + table + "-entityids` (entityuuid) SELECT '" + mysqlTextEscape(entityUUID.toString()) + "' FROM `" + table + "-entityids` WHERE NOT EXISTS (SELECT NULL FROM `" + table + "-entityids` WHERE entityuuid = '" + mysqlTextEscape(entityUUID.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()) + "')"
|
||||
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(entityUUID.toString()) + "')"
|
||||
+ ", " + entityTypeId + ", '" + loc.getBlockX() + "', " + safeY(loc) + ", '" + loc.getBlockZ() + "', " + changeType.ordinal() + ", " + Utils.mysqlPrepareBytesForInsertAllowNull(data) + ");";
|
||||
return inserts;
|
||||
}
|
||||
@@ -1069,10 +1081,10 @@ public class Consumer extends Thread {
|
||||
rs.close();
|
||||
}
|
||||
}
|
||||
PreparedStatement smt = batchHelper.getOrPrepareStatement(conn, statementString, Statement.RETURN_GENERATED_KEYS);
|
||||
PreparedStatement smt = batchHelper.getOrPrepareStatement(conn, statementString, Statement.NO_GENERATED_KEYS);
|
||||
smt.setLong(1, date);
|
||||
smt.setInt(2, sourceActor);
|
||||
smt.setInt(3, getEntityUUID(conn, loc.getWorld(), entityid));
|
||||
smt.setInt(3, getEntityUUID(conn, loc.getWorld(), entityUUID));
|
||||
smt.setInt(4, EntityTypeConverter.getOrAddEntityTypeId(type));
|
||||
smt.setInt(5, loc.getBlockX());
|
||||
smt.setInt(6, safeY(loc));
|
||||
@@ -1083,6 +1095,42 @@ public class Consumer extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
private class EntityUUIDChange implements Row {
|
||||
private final World world;
|
||||
private final int entityId;
|
||||
private final UUID entityUUID;
|
||||
final String updateEntityUUIDString;
|
||||
|
||||
public EntityUUIDChange(World world, int entityId, UUID entityUUID) {
|
||||
this.world = world;
|
||||
this.entityId = entityId;
|
||||
this.entityUUID = entityUUID;
|
||||
updateEntityUUIDString = getWorldConfig(world).updateEntityUUIDString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getInserts() {
|
||||
final String table = getWorldConfig(world).table;
|
||||
final String[] inserts = new String[1];
|
||||
|
||||
inserts[0] = "UPDATE `" + table + "-entityids` SET entityuuid = '" + mysqlTextEscape(entityUUID.toString()) + "' WHERE entityid = " + entityId;
|
||||
return inserts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Actor[] getActors() {
|
||||
return new Actor[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Connection conn, BatchHelper batchHelper) throws SQLException {
|
||||
PreparedStatement smt = batchHelper.getOrPrepareStatement(conn, updateEntityUUIDString, Statement.NO_GENERATED_KEYS);
|
||||
smt.setString(1, entityUUID.toString());
|
||||
smt.setInt(2, entityId);
|
||||
smt.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private int safeY(Location loc) {
|
||||
int safeY = loc.getBlockY();
|
||||
if (safeY < 0)
|
||||
|
@@ -30,7 +30,8 @@ public class EntityChange implements LookupCacheElement {
|
||||
public final Location loc;
|
||||
public final Actor actor;
|
||||
public final EntityType type;
|
||||
public final UUID entityid;
|
||||
public final int entityId;
|
||||
public final UUID entityUUID;
|
||||
public final EntityChangeType changeType;
|
||||
public final byte[] data;
|
||||
|
||||
@@ -40,7 +41,8 @@ public class EntityChange implements LookupCacheElement {
|
||||
this.loc = loc;
|
||||
this.actor = actor;
|
||||
this.type = type;
|
||||
this.entityid = entityid;
|
||||
this.entityId = -1;
|
||||
this.entityUUID = entityid;
|
||||
this.changeType = changeType;
|
||||
this.data = data;
|
||||
}
|
||||
@@ -51,7 +53,8 @@ public class EntityChange implements LookupCacheElement {
|
||||
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.needData ? UUID.fromString(rs.getString("entityuuid")) : null;
|
||||
entityId = p.needData ? rs.getInt("entityid") : 0;
|
||||
entityUUID = p.needData ? UUID.fromString(rs.getString("entityuuid")) : null;
|
||||
changeType = p.needType ? EntityChangeType.valueOf(rs.getInt("action")) : null;
|
||||
data = p.needData ? rs.getBytes("data") : null;
|
||||
}
|
||||
|
@@ -270,7 +270,7 @@ public final class QueryParams implements Cloneable {
|
||||
select += "entitytypeid, action, ";
|
||||
}
|
||||
if(needData) {
|
||||
select += "entityuuid, data, ";
|
||||
select += "entityid, entityuuid, data, ";
|
||||
}
|
||||
}
|
||||
select = select.substring(0, select.length() - 2) + " ";
|
||||
|
@@ -33,8 +33,10 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -56,6 +58,7 @@ public class WorldEditor implements Runnable {
|
||||
private long elapsedTime = 0;
|
||||
public LookupCacheElement[] errors;
|
||||
private boolean forceReplace;
|
||||
private HashMap<Integer, UUID> uuidReplacements = new HashMap<>();
|
||||
|
||||
public WorldEditor(LogBlock logblock, World world) {
|
||||
this(logblock, world, false);
|
||||
@@ -164,6 +167,11 @@ public class WorldEditor implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
protected UUID getReplacedUUID(int entityid, UUID unreplaced) {
|
||||
UUID replaced = uuidReplacements.get(entityid);
|
||||
return replaced != null ? replaced : unreplaced;
|
||||
}
|
||||
|
||||
public static enum PerformResult {
|
||||
SUCCESS, BLACKLISTED, NO_ACTION
|
||||
}
|
||||
@@ -182,7 +190,10 @@ public class WorldEditor implements Runnable {
|
||||
|
||||
@Override
|
||||
public PerformResult perform() throws WorldEditorException {
|
||||
if (changeType == EntityChangeType.KILL && rollback) {
|
||||
if (changeType == (rollback ? EntityChangeType.KILL : EntityChangeType.CREATE)) {
|
||||
// spawn entity
|
||||
UUID uuid = getReplacedUUID(entityId, entityUUID);
|
||||
Entity result = null;
|
||||
YamlConfiguration deserialized = Utils.deserializeYamlConfiguration(data);
|
||||
double x = deserialized.getDouble("x");
|
||||
double y = deserialized.getDouble("y");
|
||||
@@ -190,14 +201,39 @@ public class WorldEditor implements Runnable {
|
||||
float yaw = (float) deserialized.getDouble("yaw");
|
||||
float pitch = (float) deserialized.getDouble("pitch");
|
||||
Location location = new Location(world, x, y, z, yaw, pitch);
|
||||
Entity existing = Utils.loadChunksForEntity(location.getChunk(), uuid);
|
||||
if (existing != null) {
|
||||
return PerformResult.NO_ACTION;
|
||||
}
|
||||
byte[] serializedWorldEditEntity = (byte[]) deserialized.get("worldedit");
|
||||
if (serializedWorldEditEntity != null) {
|
||||
Entity result = WorldEditHelper.restoreEntity(location, type, serializedWorldEditEntity);
|
||||
if (result == null) {
|
||||
throw new WorldEditorException("Could not restore " + type, location);
|
||||
result = WorldEditHelper.restoreEntity(location, type, serializedWorldEditEntity);
|
||||
}
|
||||
if (result == null) {
|
||||
throw new WorldEditorException("Could not restore " + type, location);
|
||||
} else {
|
||||
if (!result.getUniqueId().equals(uuid)) {
|
||||
logblock.getConsumer().queueEntityUUIDChange(world, entityId, result.getUniqueId());
|
||||
uuidReplacements.put(entityId, result.getUniqueId());
|
||||
}
|
||||
}
|
||||
return PerformResult.SUCCESS;
|
||||
} else if (changeType == (rollback ? EntityChangeType.CREATE : EntityChangeType.KILL)) {
|
||||
// kill entity
|
||||
UUID uuid = getReplacedUUID(entityId, entityUUID);
|
||||
YamlConfiguration deserialized = Utils.deserializeYamlConfiguration(data);
|
||||
double x = deserialized.getDouble("x");
|
||||
double y = deserialized.getDouble("y");
|
||||
double z = deserialized.getDouble("z");
|
||||
float yaw = (float) deserialized.getDouble("yaw");
|
||||
float pitch = (float) deserialized.getDouble("pitch");
|
||||
Location location = new Location(world, x, y, z, yaw, pitch);
|
||||
Entity existing = Utils.loadChunksForEntity(location.getChunk(), uuid);
|
||||
if (existing != null) {
|
||||
existing.remove();
|
||||
return PerformResult.SUCCESS;
|
||||
}
|
||||
return PerformResult.NO_ACTION; // the entity is not there, so we cannot do anything
|
||||
}
|
||||
return PerformResult.NO_ACTION;
|
||||
}
|
||||
|
38
src/main/java/de/diddiz/LogBlock/WorldEditorEditFactory.java
Normal file
38
src/main/java/de/diddiz/LogBlock/WorldEditorEditFactory.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package de.diddiz.LogBlock;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import de.diddiz.LogBlock.QueryParams.BlockChangeType;
|
||||
import de.diddiz.util.Utils;
|
||||
|
||||
public class WorldEditorEditFactory {
|
||||
private final WorldEditor editor;
|
||||
private final boolean rollback;
|
||||
private final QueryParams params;
|
||||
|
||||
public WorldEditorEditFactory(WorldEditor editor, QueryParams params, boolean rollback) {
|
||||
this.editor = editor;
|
||||
this.params = params;
|
||||
this.rollback = rollback;
|
||||
}
|
||||
|
||||
public void processRow(ResultSet rs) throws SQLException {
|
||||
if (params.bct == BlockChangeType.ENTITIES) {
|
||||
editor.queueEntityEdit(rs, params, rollback);
|
||||
return;
|
||||
}
|
||||
ChestAccess chestaccess = null;
|
||||
ItemStack stack = Utils.loadItemStack(rs.getBytes("item"));
|
||||
if (stack != null) {
|
||||
chestaccess = new ChestAccess(stack, rs.getBoolean("itemremove") == rollback, rs.getInt("itemtype"));
|
||||
}
|
||||
if (rollback) {
|
||||
editor.queueBlockEdit(rs.getInt("x"), rs.getInt("y"), rs.getInt("z"), rs.getInt("replaced"), rs.getInt("replacedData"), rs.getBytes("replacedState"), rs.getInt("type"), rs.getInt("typeData"), rs.getBytes("typeState"), chestaccess);
|
||||
} else {
|
||||
editor.queueBlockEdit(rs.getInt("x"), rs.getInt("y"), rs.getInt("z"), rs.getInt("type"), rs.getInt("typeData"), rs.getBytes("typeState"), rs.getInt("replaced"), rs.getInt("replacedData"), rs.getBytes("replacedState"), chestaccess);
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,6 +17,7 @@ public class WorldConfig extends LoggingEnabledMapping {
|
||||
public final String insertBlockStateStatementString;
|
||||
public final String insertBlockChestDataStatementString;
|
||||
public final String insertEntityStatementString;
|
||||
public final String updateEntityUUIDString;
|
||||
|
||||
public WorldConfig(String world, File file) throws IOException {
|
||||
this.world = world;
|
||||
@@ -44,5 +45,6 @@ public class WorldConfig extends LoggingEnabledMapping {
|
||||
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(?), ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
updateEntityUUIDString = "UPDATE `" + table + "-entityids` SET entityuuid = ? WHERE entityid = ?";
|
||||
}
|
||||
}
|
||||
|
@@ -11,14 +11,18 @@ import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import java.util.zip.ZipException;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import de.diddiz.LogBlock.LogBlock;
|
||||
@@ -284,4 +288,28 @@ public class Utils {
|
||||
public static String serializeForSQL(YamlConfiguration conf) {
|
||||
return mysqlPrepareBytesForInsertAllowNull(serializeYamlConfiguration(conf));
|
||||
}
|
||||
|
||||
public static Entity loadChunksForEntity(Chunk chunk, UUID uuid) {
|
||||
Entity e = Bukkit.getEntity(uuid);
|
||||
if (e != null) {
|
||||
return e;
|
||||
}
|
||||
chunk.load();
|
||||
e = Bukkit.getEntity(uuid);
|
||||
if (e != null) {
|
||||
return e;
|
||||
}
|
||||
int chunkx = chunk.getX();
|
||||
int chunkz = chunk.getZ();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int x = i < 3 ? chunkx - 1 : (i < 5 ? chunkx : chunkx + 1);
|
||||
int z = i == 0 || i == 3 || i == 5 ? chunkz - 1 : (i == 1 || i == 6 ? chunkz : chunkz + 1);
|
||||
chunk.getWorld().loadChunk(x, z);
|
||||
e = Bukkit.getEntity(uuid);
|
||||
if (e != null) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -7,9 +7,12 @@ 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.CreatureSpawnEvent;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
import org.bukkit.event.entity.EntitySpawnEvent;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
|
||||
|
||||
import de.diddiz.LogBlock.Actor;
|
||||
import de.diddiz.LogBlock.EntityChange;
|
||||
@@ -28,12 +31,12 @@ public class AdvancedKillLogging extends LoggingListener {
|
||||
if (!(entity instanceof Animals) && !(entity instanceof Villager)) {
|
||||
return;
|
||||
}
|
||||
Actor killer;
|
||||
Actor actor;
|
||||
EntityDamageEvent lastDamage = entity.getLastDamageCause();
|
||||
if (lastDamage instanceof EntityDamageByEntityEvent) {
|
||||
killer = Actor.actorFromEntity(((EntityDamageByEntityEvent) lastDamage).getDamager());
|
||||
actor = Actor.actorFromEntity(((EntityDamageByEntityEvent) lastDamage).getDamager());
|
||||
} else {
|
||||
killer = new Actor(lastDamage.getCause().toString());
|
||||
actor = new Actor(lastDamage.getCause().toString());
|
||||
}
|
||||
Location location = entity.getLocation();
|
||||
YamlConfiguration data = new YamlConfiguration();
|
||||
@@ -44,7 +47,30 @@ public class AdvancedKillLogging extends LoggingListener {
|
||||
data.set("pitch", location.getPitch());
|
||||
|
||||
data.set("worldedit", WorldEditHelper.serializeEntity(entity));
|
||||
|
||||
consumer.queueEntityModification(killer, entity.getUniqueId(), entity.getType(), location, EntityChange.EntityChangeType.KILL, data);
|
||||
|
||||
consumer.queueEntityModification(actor, entity.getUniqueId(), entity.getType(), location, EntityChange.EntityChangeType.KILL, data);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onEntitySpawn(CreatureSpawnEvent event) {
|
||||
if (event.getSpawnReason() == SpawnReason.CUSTOM) {
|
||||
return;
|
||||
}
|
||||
LivingEntity entity = event.getEntity();
|
||||
if (!(entity instanceof Animals) && !(entity instanceof Villager)) {
|
||||
return;
|
||||
}
|
||||
Actor actor = new Actor(event.getSpawnReason().toString());
|
||||
Location location = entity.getLocation();
|
||||
YamlConfiguration data = new YamlConfiguration();
|
||||
data.set("x", location.getX());
|
||||
data.set("y", location.getY());
|
||||
data.set("z", location.getZ());
|
||||
data.set("yaw", location.getYaw());
|
||||
data.set("pitch", location.getPitch());
|
||||
|
||||
data.set("worldedit", WorldEditHelper.serializeEntity(entity));
|
||||
|
||||
consumer.queueEntityModification(actor, entity.getUniqueId(), entity.getType(), location, EntityChange.EntityChangeType.CREATE, data);
|
||||
}
|
||||
}
|
||||
|
@@ -68,14 +68,10 @@ public class WorldEditHelper {
|
||||
if (namedTag.getName().equals("entity") && namedTag.getTag() instanceof CompoundTag) {
|
||||
CompoundTag serializedState = (CompoundTag) namedTag.getTag();
|
||||
BaseEntity state = new BaseEntity(weType, serializedState);
|
||||
CompoundTag oldNbt = state.getNbtData();
|
||||
UUID oldUUID = new UUID(oldNbt.getLong("UUIDMost"), oldNbt.getLong("UUIDLeast"));
|
||||
com.sk89q.worldedit.entity.Entity weEntity = weLocation.getExtent().createEntity(weLocation, state);
|
||||
if (weEntity != null) {
|
||||
CompoundTag newNbt = weEntity.getState().getNbtData();
|
||||
newUUID = new UUID(newNbt.getLong("UUIDMost"), newNbt.getLong("UUIDLeast"));
|
||||
System.out.println("Old UUID: " + oldUUID);
|
||||
System.out.println("New UUID: " + newUUID);
|
||||
}
|
||||
}
|
||||
return newUUID == null ? null : Bukkit.getEntity(newUUID);
|
||||
|
Reference in New Issue
Block a user