From 58e5cdf890301951b401394bd92a82b02cd26ef1 Mon Sep 17 00:00:00 2001 From: Brokkonaut Date: Tue, 31 Jul 2018 02:19:40 +0200 Subject: [PATCH] Serialize BlockStates Banner, spawner and player heads can now be rolled back --- pom.xml | 2 +- .../java/de/diddiz/LogBlock/BlockChange.java | 56 ++++++++---- .../de/diddiz/LogBlock/CommandsHandler.java | 27 ++---- .../java/de/diddiz/LogBlock/Consumer.java | 59 +++++++------ .../java/de/diddiz/LogBlock/QueryParams.java | 10 +-- src/main/java/de/diddiz/LogBlock/Updater.java | 86 ++++++++++++++++++- .../java/de/diddiz/LogBlock/WorldEditor.java | 35 ++++---- .../LogBlock/blockstate/BlockStateCodec.java | 15 ++++ .../blockstate/BlockStateCodecBanner.java | 70 +++++++++++++++ .../blockstate/BlockStateCodecSign.java | 76 ++++++++++++++++ .../blockstate/BlockStateCodecSkull.java | 54 ++++++++++++ .../blockstate/BlockStateCodecSpawner.java | 60 +++++++++++++ .../LogBlock/blockstate/BlockStateCodecs.java | 80 +++++++++++++++++ .../worldedit/WorldEditLoggingHook.java | 8 +- 14 files changed, 539 insertions(+), 99 deletions(-) create mode 100644 src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodec.java create mode 100644 src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecBanner.java create mode 100644 src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecSign.java create mode 100644 src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecSkull.java create mode 100644 src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecSpawner.java create mode 100644 src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecs.java diff --git a/pom.xml b/pom.xml index 6207091..31ddf18 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ de.diddiz logblock - 1.13-SNAPSHOT + 1.13.1-SNAPSHOT jar LogBlock diff --git a/src/main/java/de/diddiz/LogBlock/BlockChange.java b/src/main/java/de/diddiz/LogBlock/BlockChange.java index 30af6f7..75351ab 100644 --- a/src/main/java/de/diddiz/LogBlock/BlockChange.java +++ b/src/main/java/de/diddiz/LogBlock/BlockChange.java @@ -1,5 +1,6 @@ package de.diddiz.LogBlock; +import de.diddiz.LogBlock.blockstate.BlockStateCodecs; import de.diddiz.LogBlock.config.Config; import de.diddiz.util.BukkitUtils; import de.diddiz.util.Utils; @@ -14,29 +15,28 @@ import org.bukkit.inventory.ItemStack; import java.sql.ResultSet; import java.sql.SQLException; - -import static de.diddiz.util.LoggingUtil.checkText; +import java.util.logging.Level; public class BlockChange implements LookupCacheElement { public final long id, date; public final Location loc; public final Actor actor; public final String playerName; - // public final BlockData replaced, type; public final int replacedMaterial, replacedData, typeMaterial, typeData; - public final String signtext; + public final byte[] replacedState, typeState; public final ChestAccess ca; - public BlockChange(long date, Location loc, Actor actor, int replaced, int replacedData, int type, int typeData, String signtext, ChestAccess ca) { + public BlockChange(long date, Location loc, Actor actor, int replaced, int replacedData, byte[] replacedState, int type, int typeData, byte[] typeState, ChestAccess ca) { id = 0; this.date = date; this.loc = loc; this.actor = actor; this.replacedMaterial = replaced; this.replacedData = replacedData; + this.replacedState = replacedState; this.typeMaterial = type; this.typeData = typeData; - this.signtext = checkText(signtext); + this.typeState = typeState; this.ca = ca; this.playerName = actor == null ? null : actor.getName(); } @@ -51,7 +51,8 @@ public class BlockChange implements LookupCacheElement { replacedData = p.needType ? rs.getInt("replacedData") : -1; typeMaterial = p.needType ? rs.getInt("type") : 0; typeData = p.needType ? rs.getInt("typeData") : -1; - signtext = p.needSignText ? rs.getString("signtext") : null; + replacedState = p.needType ? rs.getBytes("replacedState") : null; + typeState = p.needType ? rs.getBytes("typeState") : null; ChestAccess catemp = null; if (p.needChestAccess) { ItemStack stack = Utils.loadItemStack(rs.getBytes("item")); @@ -66,6 +67,32 @@ public class BlockChange implements LookupCacheElement { public String toString() { BlockData type = MaterialConverter.getBlockData(typeMaterial, typeData); BlockData replaced = MaterialConverter.getBlockData(replacedMaterial, replacedData); + String typeDetails = null; + if (BlockStateCodecs.hasCodec(type.getMaterial())) { + try { + typeDetails = BlockStateCodecs.toString(type.getMaterial(), typeState); + } catch (Exception e) { + LogBlock.getInstance().getLogger().log(Level.SEVERE, "Could not parse BlockState for " + type.getMaterial(), e); + } + } + if (typeDetails == null) { + typeDetails = ""; + } else { + typeDetails = " " + typeDetails; + } + String replacedDetails = null; + if (BlockStateCodecs.hasCodec(replaced.getMaterial())) { + try { + replacedDetails = BlockStateCodecs.toString(replaced.getMaterial(), replacedState); + } catch (Exception e) { + LogBlock.getInstance().getLogger().log(Level.SEVERE, "Could not parse BlockState for " + replaced.getMaterial(), e); + } + } + if (replacedDetails == null) { + replacedDetails = ""; + } else { + replacedDetails = " " + replacedDetails; + } final StringBuilder msg = new StringBuilder(); if (date > 0) { msg.append(Config.formatter.format(date)).append(" "); @@ -73,14 +100,7 @@ public class BlockChange implements LookupCacheElement { if (actor != null) { msg.append(actor.getName()).append(" "); } - if (signtext != null) { - final String action = BukkitUtils.isEmpty(type.getMaterial()) ? "destroyed " : "created "; - if (!signtext.contains("\0")) { - msg.append(action).append(signtext); - } else { - msg.append(action).append((!BukkitUtils.isEmpty(type.getMaterial()) ? type : replaced).getMaterial().name()).append(" [").append(signtext.replace("\0", "] [")).append("]"); - } - } else if (type.equals(replaced)) { + if (type.equals(replaced)) { if (BukkitUtils.isEmpty(type.getMaterial())) { msg.append("did an unspecified action"); } else if (ca != null) { @@ -110,11 +130,11 @@ public class BlockChange implements LookupCacheElement { msg.append("ran into ").append(type.getMaterial().name()); } } else if (BukkitUtils.isEmpty(type.getMaterial())) { - msg.append("destroyed ").append(replaced.getMaterial().name()); + msg.append("destroyed ").append(replaced.getMaterial().name()).append(replacedDetails); } else if (BukkitUtils.isEmpty(replaced.getMaterial())) { - msg.append("created ").append(type.getMaterial().name()); + msg.append("created ").append(type.getMaterial().name()).append(typeDetails); } else { - msg.append("replaced ").append(replaced.getMaterial().name()).append(" with ").append(type.getMaterial().name()); + msg.append("replaced ").append(replaced.getMaterial().name()).append(replacedDetails).append(" with ").append(type.getMaterial().name()).append(typeDetails); } if (loc != null) { msg.append(" at ").append(loc.getBlockX()).append(":").append(loc.getBlockY()).append(":").append(loc.getBlockZ()); diff --git a/src/main/java/de/diddiz/LogBlock/CommandsHandler.java b/src/main/java/de/diddiz/LogBlock/CommandsHandler.java index 6ae53cf..fe74da1 100755 --- a/src/main/java/de/diddiz/LogBlock/CommandsHandler.java +++ b/src/main/java/de/diddiz/LogBlock/CommandsHandler.java @@ -10,7 +10,6 @@ import de.diddiz.util.Utils; import org.bukkit.ChatColor; import org.bukkit.Location; -import org.bukkit.Material; import org.bukkit.World; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -456,9 +455,6 @@ public class CommandsHandler implements CommandExecutor { params.needType = true; params.needData = true; params.needPlayer = true; - if (params.types.isEmpty() || params.types.contains(Material.SIGN) || params.types.contains(Material.WALL_SIGN)) { - params.needSignText = true; - } if (params.bct == BlockChangeType.CHESTACCESS || params.bct == BlockChangeType.ALL) { params.needChestAccess = true; } @@ -521,9 +517,6 @@ public class CommandsHandler implements CommandExecutor { params.needType = true; params.needData = true; params.needPlayer = true; - if (params.types.isEmpty() || params.types.contains(Material.SIGN) || params.types.contains(Material.WALL_SIGN)) { - params.needSignText = true; - } if (params.bct == BlockChangeType.CHESTACCESS || params.bct == BlockChangeType.ALL) { params.needChestAccess = true; } @@ -646,7 +639,6 @@ public class CommandsHandler implements CommandExecutor { params.needCoords = true; params.needType = true; params.needData = true; - params.needSignText = true; params.needChestAccess = true; params.order = Order.DESC; params.sum = SummarizationMode.NONE; @@ -674,7 +666,7 @@ public class CommandsHandler implements CommandExecutor { if (stack != null) { chestaccess = new ChestAccess(stack, rs.getBoolean("itemremove")); } - editor.queueEdit(rs.getInt("x"), rs.getInt("y"), rs.getInt("z"), rs.getInt("replaced"), rs.getInt("replacedData"), rs.getInt("type"), rs.getInt("typeData"), rs.getString("signtext"), chestaccess); + editor.queueEdit(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); } final int changes = editor.getSize(); if (changes > 10000) { @@ -725,7 +717,6 @@ public class CommandsHandler implements CommandExecutor { params.needCoords = true; params.needType = true; params.needData = true; - params.needSignText = true; params.needChestAccess = true; params.order = Order.ASC; params.sum = SummarizationMode.NONE; @@ -749,7 +740,7 @@ public class CommandsHandler implements CommandExecutor { if (stack != null) { chestaccess = new ChestAccess(stack, !rs.getBoolean("itemremove")); } - editor.queueEdit(rs.getInt("x"), rs.getInt("y"), rs.getInt("z"), rs.getInt("type"), rs.getInt("typeData"), rs.getInt("replaced"), rs.getInt("replacedData"), rs.getString("signtext"), chestaccess); + editor.queueEdit(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); } final int changes = editor.getSize(); if (!params.silent) { @@ -824,23 +815,23 @@ public class CommandsHandler implements CommandExecutor { state.execute("DELETE `" + table + "` FROM `" + table + "-blocks` " + join + params.getWhere()); sender.sendMessage(ChatColor.GREEN + "Cleared out table " + table + ". Deleted " + deleted + " entries."); } - rs = state.executeQuery("SELECT COUNT(*) FROM `" + table + "-sign` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL"); + rs = state.executeQuery("SELECT COUNT(*) FROM `" + table + "-state` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL"); rs.next(); if ((deleted = rs.getInt(1)) > 0) { if (dumpDeletedLog) { - state.execute("SELECT id, signtext FROM `" + table + "-sign` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL INTO OUTFILE '" + new File(dumpFolder, time + " " + table + "-sign " + params.getTitle() + ".csv").getAbsolutePath().replace("\\", "\\\\") + "' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\n'"); + state.execute("SELECT id, replacedState, typeState FROM `" + table + "-state` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL INTO OUTFILE '" + new File(dumpFolder, time + " " + table + "-state " + params.getTitle() + ".csv").getAbsolutePath().replace("\\", "\\\\") + "' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\n'"); } - state.execute("DELETE `" + table + "-sign` FROM `" + table + "-sign` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL;"); - sender.sendMessage(ChatColor.GREEN + "Cleared out table " + table + "-sign. Deleted " + deleted + " entries."); + state.execute("DELETE `" + table + "-state` FROM `" + table + "-state` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL;"); + sender.sendMessage(ChatColor.GREEN + "Cleared out table " + table + "-state. Deleted " + deleted + " entries."); } rs = state.executeQuery("SELECT COUNT(*) FROM `" + table + "-chestdata` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL"); rs.next(); if ((deleted = rs.getInt(1)) > 0) { if (dumpDeletedLog) { - state.execute("SELECT id, item, itemremove FROM `" + table + "-chest` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL INTO OUTFILE '" + new File(dumpFolder, time + " " + table + "-chest " + params.getTitle() + ".csv").getAbsolutePath().replace("\\", "\\\\") + "' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\n'"); + state.execute("SELECT id, item, itemremove FROM `" + table + "-chestdata` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL INTO OUTFILE '" + new File(dumpFolder, time + " " + table + "-chest " + params.getTitle() + ".csv").getAbsolutePath().replace("\\", "\\\\") + "' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\n'"); } - state.execute("DELETE `" + table + "-chest` FROM `" + table + "-chest` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL;"); - sender.sendMessage(ChatColor.GREEN + "Cleared out table " + table + "-chest. Deleted " + deleted + " entries."); + state.execute("DELETE `" + table + "-chestdata` FROM `" + table + "-chestdata` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL;"); + sender.sendMessage(ChatColor.GREEN + "Cleared out table " + table + "-chestdata. Deleted " + deleted + " entries."); } } catch (final Exception ex) { sender.sendMessage(ChatColor.RED + "Exception, check error log"); diff --git a/src/main/java/de/diddiz/LogBlock/Consumer.java b/src/main/java/de/diddiz/LogBlock/Consumer.java index 20bdb12..c01371c 100644 --- a/src/main/java/de/diddiz/LogBlock/Consumer.java +++ b/src/main/java/de/diddiz/LogBlock/Consumer.java @@ -1,5 +1,7 @@ package de.diddiz.LogBlock; +import de.diddiz.LogBlock.blockstate.BlockStateCodecSign; +import de.diddiz.LogBlock.blockstate.BlockStateCodecs; import de.diddiz.LogBlock.config.Config; import de.diddiz.LogBlock.events.BlockChangePreLogEvent; import de.diddiz.util.Utils; @@ -63,7 +65,7 @@ public class Consumer extends TimerTask { * Data of the block after the change */ public void queueBlock(Actor actor, Location loc, BlockData typeBefore, BlockData typeAfter) { - queueBlock(actor, loc, typeBefore, typeAfter, null, null); + queueBlock(actor, loc, typeBefore, typeAfter, null, null, null); } /** @@ -75,7 +77,7 @@ public class Consumer extends TimerTask { * Blockstate of the block before actually being destroyed. */ public void queueBlockBreak(Actor actor, BlockState before) { - queueBlockBreak(actor, new Location(before.getWorld(), before.getX(), before.getY(), before.getZ()), before.getBlockData()); + queueBlock(actor, new Location(before.getWorld(), before.getX(), before.getY(), before.getZ()), before.getBlockData(), null, BlockStateCodecs.serialize(before), null, null); } /** @@ -103,7 +105,7 @@ public class Consumer extends TimerTask { * Blockstate of the block after actually being placed. */ public void queueBlockPlace(Actor actor, BlockState after) { - queueBlockPlace(actor, new Location(after.getWorld(), after.getX(), after.getY(), after.getZ()), after.getBlockData()); + queueBlock(actor, new Location(after.getWorld(), after.getX(), after.getY(), after.getZ()), null, after.getBlockData(), null, BlockStateCodecs.serialize(after), null); } /** @@ -133,7 +135,7 @@ public class Consumer extends TimerTask { * Blockstate of the block after actually being placed. */ public void queueBlockReplace(Actor actor, BlockState before, BlockState after) { - queueBlockReplace(actor, new Location(before.getWorld(), before.getX(), before.getY(), before.getZ()), before.getBlockData(), after.getBlockData()); + queueBlock(actor, new Location(before.getWorld(), before.getX(), before.getY(), before.getZ()), before.getBlockData(), after.getBlockData(), BlockStateCodecs.serialize(before), BlockStateCodecs.serialize(after), null); } /** @@ -149,7 +151,7 @@ public class Consumer extends TimerTask { * Data of the block after being replaced */ public void queueBlockReplace(Actor actor, BlockState before, BlockData typeAfter) { - queueBlockReplace(actor, new Location(before.getWorld(), before.getX(), before.getY(), before.getZ()), before.getBlockData(), typeAfter); + queueBlock(actor, new Location(before.getWorld(), before.getX(), before.getY(), before.getZ()), before.getBlockData(), typeAfter, BlockStateCodecs.serialize(before), null, null); } /** @@ -165,12 +167,11 @@ public class Consumer extends TimerTask { * Blockstate of the block after actually being placed. */ public void queueBlockReplace(Actor actor, BlockData typeBefore, BlockState after) { - queueBlockReplace(actor, new Location(after.getWorld(), after.getX(), after.getY(), after.getZ()), typeBefore, after.getBlockData()); + queueBlock(actor, new Location(after.getWorld(), after.getX(), after.getY(), after.getZ()), typeBefore, after.getBlockData(), null, BlockStateCodecs.serialize(after), null); } public void queueBlockReplace(Actor actor, Location loc, BlockData typeBefore, BlockData typeAfter) { - queueBlockBreak(actor, loc, typeBefore); - queueBlockPlace(actor, loc, typeAfter); + queueBlock(actor, loc, typeBefore, typeAfter, null, null, null); } /** @@ -211,7 +212,7 @@ public class Consumer extends TimerTask { * Data of the item taken/stored */ public void queueChestAccess(Actor actor, Location loc, BlockData type, ItemStack itemStack, boolean remove) { - queueBlock(actor, loc, type, type, null, new ChestAccess(itemStack, remove)); + queueBlock(actor, loc, type, type, null, null, new ChestAccess(itemStack, remove)); } /** @@ -326,11 +327,8 @@ public class Consumer extends TimerTask { * @param lines * The four lines on the sign. */ - public void queueSignBreak(Actor actor, Location loc, BlockData type, String[] lines) { - if ((type.getMaterial() != Material.SIGN && type.getMaterial() != Material.WALL_SIGN) || lines == null || lines.length != 4) { - return; - } - queueBlock(actor, loc, type, null, lines[0] + "\0" + lines[1] + "\0" + lines[2] + "\0" + lines[3], null); + public void queueSignBreak(Actor actor, Location loc, BlockData type, byte[] typeState) { + queueBlock(actor, loc, type, null, typeState, null, null); } /** @@ -342,7 +340,7 @@ public class Consumer extends TimerTask { * The sign being broken */ public void queueSignBreak(Actor actor, Sign sign) { - queueSignBreak(actor, new Location(sign.getWorld(), sign.getX(), sign.getY(), sign.getZ()), sign.getBlockData(), sign.getLines()); + queueSignBreak(actor, new Location(sign.getWorld(), sign.getX(), sign.getY(), sign.getZ()), sign.getBlockData(), BlockStateCodecs.serialize(sign)); } /** @@ -363,7 +361,7 @@ public class Consumer extends TimerTask { if ((type.getMaterial() != Material.SIGN && type.getMaterial() != Material.WALL_SIGN) || lines == null || lines.length != 4) { return; } - queueBlock(actor, loc, null, type, lines[0] + "\0" + lines[1] + "\0" + lines[2] + "\0" + lines[3], null); + queueBlock(actor, loc, null, type, null, Utils.serializeYamlConfiguration(BlockStateCodecSign.serialize(lines)), null); } /** @@ -585,7 +583,7 @@ public class Consumer extends TimerTask { return playerIds.containsKey(actor); } - private void queueBlock(Actor actor, Location loc, BlockData typeBefore, BlockData typeAfter, String signtext, ChestAccess ca) { + private void queueBlock(Actor actor, Location loc, BlockData typeBefore, BlockData typeAfter, byte[] stateBefore, byte[] stateAfter, ChestAccess ca) { if (typeBefore == null) { typeBefore = Bukkit.createBlockData(Material.AIR); } @@ -594,7 +592,7 @@ public class Consumer extends TimerTask { } if (Config.fireCustomEvents) { // Create and call the event - BlockChangePreLogEvent event = new BlockChangePreLogEvent(actor, loc, typeBefore, typeAfter, signtext, ca); + BlockChangePreLogEvent event = new BlockChangePreLogEvent(actor, loc, typeBefore, typeAfter, null, ca); logblock.getServer().getPluginManager().callEvent(event); if (event.isCancelled()) { return; @@ -605,7 +603,7 @@ public class Consumer extends TimerTask { loc = event.getLocation(); typeBefore = event.getTypeBefore(); typeAfter = event.getTypeAfter(); - signtext = event.getSignText(); + // signtext = event.getSignText(); ca = event.getChestAccess(); } // Do this last so LogBlock still has final say in what is being added @@ -620,7 +618,7 @@ public class Consumer extends TimerTask { int typeMaterialId = MaterialConverter.getOrAddMaterialId(typeString); int typeStateId = MaterialConverter.getOrAddBlockStateId(typeString); - queue.add(new BlockRow(loc, actor, replacedMaterialId, replacedStateId, typeMaterialId, typeStateId, signtext, ca)); + queue.add(new BlockRow(loc, actor, replacedMaterialId, replacedStateId, stateBefore, typeMaterialId, typeStateId, stateAfter, ca)); } private String playerID(Actor actor) { @@ -670,19 +668,19 @@ public class Consumer extends TimerTask { private class BlockRow extends BlockChange implements MergeableRow { private Connection connection; - public BlockRow(Location loc, Actor actor, int replaced, int replacedData, int type, int typeData, String signtext, ChestAccess ca) { - super(System.currentTimeMillis() / 1000, loc, actor, replaced, replacedData, type, typeData, signtext, ca); + public BlockRow(Location loc, Actor actor, int replaced, int replacedData, byte[] replacedState, int type, int typeData, byte[] typeState, ChestAccess ca) { + super(System.currentTimeMillis() / 1000, loc, actor, replaced, replacedData, replacedState, type, typeData, typeState, ca); } @Override public String[] getInserts() { final String table = getWorldConfig(loc.getWorld()).table; - final String[] inserts = new String[ca != null || signtext != null ? 2 : 1]; + final String[] inserts = new String[ca != null || replacedState != null || typeState != null ? 2 : 1]; inserts[0] = "INSERT INTO `" + table + "-blocks` (date, playerid, replaced, replaceddata, type, typedata, x, y, z) VALUES (FROM_UNIXTIME(" + date + "), " + playerID(actor) + ", " + replacedMaterial + ", " + replacedData + ", " + typeMaterial + ", " + typeData + ", '" + loc.getBlockX() + "', " + safeY(loc) + ", '" + loc.getBlockZ() + "');"; - if (signtext != null) { - inserts[1] = "INSERT INTO `" + table + "-sign` (id, signtext) values (LAST_INSERT_ID(), '" + mysqlTextEscape(signtext) + "');"; + if (replacedState != null || typeState != null) { + inserts[1] = "INSERT INTO `" + table + "-state` (replacedState, typeState, id) VALUES('" + Utils.mysqlEscapeBytes(replacedState) + "', '" + Utils.mysqlEscapeBytes(typeState) + "', LAST_INSERT_ID());"; } else if (ca != null) { inserts[1] = "INSERT INTO `" + table + "-chestdata` (id, item, itemremoved) values (LAST_INSERT_ID(), '" + Utils.mysqlEscapeBytes(Utils.saveItemStack(ca.itemStack)) + "', " + (ca.remove ? 1 : 0) + ");"; } @@ -727,10 +725,11 @@ public class Consumer extends TimerTask { rs.next(); id = rs.getInt(1); - if (signtext != null) { - ps = connection.prepareStatement("INSERT INTO `" + table + "-sign` (signtext, id) VALUES(?, ?)"); - ps.setString(1, signtext); - ps.setInt(2, id); + if (typeState != null || replacedState != null) { + ps = connection.prepareStatement("INSERT INTO `" + table + "-state` (replacedState, typeState, id) VALUES(?, ?, ?)"); + ps.setBytes(1, replacedState); + ps.setBytes(2, typeState); + ps.setInt(3, id); ps.executeUpdate(); } else if (ca != null) { ps = connection.prepareStatement("INSERT INTO `" + table + "-chestdata` (item, itemremove, id) values (?, ?, ?)"); @@ -772,7 +771,7 @@ public class Consumer extends TimerTask { @Override public boolean isUnique() { - return !(signtext == null && ca == null && playerIds.containsKey(actor)); + return !(typeState == null && replacedState == null && ca == null && playerIds.containsKey(actor)); } @Override diff --git a/src/main/java/de/diddiz/LogBlock/QueryParams.java b/src/main/java/de/diddiz/LogBlock/QueryParams.java index 3d0a151..b97ee03 100644 --- a/src/main/java/de/diddiz/LogBlock/QueryParams.java +++ b/src/main/java/de/diddiz/LogBlock/QueryParams.java @@ -34,7 +34,7 @@ public final class QueryParams implements Cloneable { public List typeIds = new ArrayList(); public World world = null; public String match = null; - public boolean needCount = false, needId = false, needDate = false, needType = false, needData = false, needPlayer = false, needCoords = false, needSignText = false, needChestAccess = false, needMessage = false, needKiller = false, needVictim = false, needWeapon = false; + public boolean needCount = false, needId = false, needDate = false, needType = false, needData = false, needPlayer = false, needCoords = false, needChestAccess = false, needMessage = false, needKiller = false, needVictim = false, needWeapon = false; private final LogBlock logblock; public QueryParams(LogBlock logblock) { @@ -145,8 +145,8 @@ public final class QueryParams implements Cloneable { if (needCoords) { select += "x, y, z, "; } - if (needSignText) { - select += "signtext, "; + if (needData) { + select += "replacedState, typeState, "; } if (needChestAccess) { select += "item, itemremove, "; @@ -157,8 +157,8 @@ public final class QueryParams implements Cloneable { if (needPlayer || players.size() > 0) { from += "INNER JOIN `lb-players` USING (playerid) "; } - if (needSignText) { - from += "LEFT JOIN `" + getTable() + "-sign` USING (id) "; + if (!needCount && needData) { + from += "LEFT JOIN `" + getTable() + "-state` USING (id) "; } if (needChestAccess) // If BlockChangeType is CHESTACCESS, we can use more efficient query diff --git a/src/main/java/de/diddiz/LogBlock/Updater.java b/src/main/java/de/diddiz/LogBlock/Updater.java index dde3cc3..123be33 100644 --- a/src/main/java/de/diddiz/LogBlock/Updater.java +++ b/src/main/java/de/diddiz/LogBlock/Updater.java @@ -1,5 +1,6 @@ package de.diddiz.LogBlock; +import de.diddiz.LogBlock.blockstate.BlockStateCodecSign; import de.diddiz.LogBlock.config.Config; import de.diddiz.LogBlock.config.WorldConfig; import de.diddiz.util.UUIDFetcher; @@ -386,7 +387,7 @@ class Updater { } for (final WorldConfig wcfg : getLoggedWorlds()) { if (wcfg.isLogging(Logging.SIGNTEXT)) { - checkCharset(wcfg.table + "-sign","signtext",st); + // checkCharset(wcfg.table + "-sign","signtext",st); } } st.close(); @@ -565,7 +566,7 @@ class Updater { } rs.close(); - PreparedStatement updateWeaponStatement = conn.prepareStatement("UPDATE `" + wcfg.table + "`-kills SET weapon = ? WHERE id = ?"); + PreparedStatement updateWeaponStatement = conn.prepareStatement("UPDATE `" + wcfg.table + "-kills` SET weapon = ? WHERE id = ?"); for (int start = 0;; start += 10000) { rs = st.executeQuery("SELECT id, weapon FROM `" + wcfg.table + "-kills` ORDER BY id ASC LIMIT " + start + ",10000"); boolean anyUpdate = false; @@ -622,7 +623,85 @@ class Updater { } config.set("version", "1.13.0"); } + + if (configVersion.compareTo(new ComparableVersion("1.13.1")) < 0) { + getLogger().info("Updating tables to 1.13.1 ..."); + try { + final Connection conn = logblock.getConnection(); + conn.setAutoCommit(false); + final Statement st = conn.createStatement(); + for (final WorldConfig wcfg : getLoggedWorlds()) { + getLogger().info("Processing world " + wcfg.world + "..."); + if (wcfg.isLogging(Logging.SIGNTEXT)) { + int rowsToConvert = 0; + int done = 0; + try { + ResultSet rs = st.executeQuery("SELECT count(*) as rowcount FROM `" + wcfg.table + "-sign`"); + if (rs.next()) { + rowsToConvert = rs.getInt(1); + getLogger().info("Converting " + rowsToConvert + " entries in " + wcfg.table + "-sign"); + } + rs.close(); + PreparedStatement insertSignState = conn.prepareStatement("INSERT INTO `" + wcfg.table + "-state` (id, replacedState, typeState) VALUES (?, ?, ?)"); + PreparedStatement deleteSign = conn.prepareStatement("DELETE FROM `" + wcfg.table + "-sign` WHERE id = ?"); + while (true) { + rs = st.executeQuery("SELECT id, signtext, replaced, type FROM `" + wcfg.table + "-sign` LEFT JOIN `" + wcfg.table + "-blocks` USING (id) ORDER BY id ASC LIMIT 10000"); + boolean anyRow = false; + while (rs.next()) { + anyRow = true; + int id = rs.getInt("id"); + String signText = rs.getString("signtext"); + int replaced = rs.getInt("replaced"); + boolean nullBlock = rs.wasNull(); + int type = rs.getInt("type"); + + if (!nullBlock && signText != null) { + String[] lines = signText.split("\0", 4); + byte[] bytes = Utils.serializeYamlConfiguration(BlockStateCodecSign.serialize(lines)); + + Material replacedMaterial = MaterialConverter.getBlockData(replaced, -1).getMaterial(); + Material typeMaterial = MaterialConverter.getBlockData(type, -1).getMaterial(); + boolean wasSign = replacedMaterial == Material.SIGN || replacedMaterial == Material.WALL_SIGN; + boolean isSign = typeMaterial == Material.SIGN || typeMaterial == Material.WALL_SIGN; + + insertSignState.setInt(1, id); + insertSignState.setBytes(2, wasSign ? bytes : null); + insertSignState.setBytes(3, isSign ? bytes : null); + insertSignState.addBatch(); + } + + deleteSign.setInt(1, id); + deleteSign.addBatch(); + done++; + } + rs.close(); + if (!anyRow) { + break; + } + insertSignState.executeBatch(); + deleteSign.executeBatch(); + conn.commit(); + getLogger().info("Done: " + done + "/" + rowsToConvert + " (" + (rowsToConvert > 0 ? (done * 100 / rowsToConvert) : 100) + "%)"); + } + insertSignState.close(); + deleteSign.close(); + } catch (SQLException e) { + getLogger().info("Could not convert " + wcfg.table + "-sign: " + e.getMessage()); + } + } + } + + st.close(); + conn.close(); + } catch (final SQLException ex) { + Bukkit.getLogger().log(Level.SEVERE, "[Updater] Error: ", ex); + return false; + } + + config.set("version", "1.13.1"); + } + logblock.saveConfig(); return true; } @@ -667,8 +746,9 @@ class Updater { 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 + "-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, 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))"); } diff --git a/src/main/java/de/diddiz/LogBlock/WorldEditor.java b/src/main/java/de/diddiz/LogBlock/WorldEditor.java index 813493a..a83cf7b 100644 --- a/src/main/java/de/diddiz/LogBlock/WorldEditor.java +++ b/src/main/java/de/diddiz/LogBlock/WorldEditor.java @@ -7,7 +7,6 @@ import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; import org.bukkit.block.data.Bisected.Half; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Bed; @@ -20,6 +19,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; +import de.diddiz.LogBlock.blockstate.BlockStateCodecs; import de.diddiz.util.BukkitUtils; import java.io.File; @@ -76,8 +76,8 @@ public class WorldEditor implements Runnable { this.sender = sender; } - public void queueEdit(int x, int y, int z, int replaced, int replaceData, int type, int typeData, String signtext, ChestAccess item) { - edits.add(new Edit(0, new Location(world, x, y, z), null, replaced, replaceData, type, typeData, signtext, item)); + public void queueEdit(int x, int y, int z, int replaced, int replaceData, byte[] replacedState, int type, int typeData, byte[] typeState, ChestAccess item) { + edits.add(new Edit(0, new Location(world, x, y, z), null, replaced, replaceData, replacedState, type, typeData, typeState, item)); } public long getElapsedTime() { @@ -153,8 +153,8 @@ public class WorldEditor implements Runnable { } private class Edit extends BlockChange { - public Edit(long time, Location loc, Actor actor, int replaced, int replaceData, int type, int typeData, String signtext, ChestAccess ca) { - super(time, loc, actor, replaced, replaceData, type, typeData, signtext, ca); + public Edit(long time, Location loc, Actor actor, int replaced, int replaceData, byte[] replacedState, int type, int typeData, byte[] typeState, ChestAccess ca) { + super(time, loc, actor, replaced, replaceData,replacedState , type, typeData, typeState, ca); } PerformResult perform() throws WorldEditorException { @@ -172,7 +172,7 @@ public class WorldEditor implements Runnable { if (BukkitUtils.isEmpty(replacedBlock.getMaterial()) && BukkitUtils.isEmpty(block.getType())) { return PerformResult.NO_ACTION; } - final BlockState state = block.getState(); + BlockState state = block.getState(); if (!world.isChunkLoaded(block.getChunk())) { world.loadChunk(block.getChunk()); } @@ -217,21 +217,18 @@ public class WorldEditor implements Runnable { } block.setBlockData(replacedBlock); BlockData newData = block.getBlockData(); + if (BlockStateCodecs.hasCodec(replacedBlock.getMaterial())) { + state = block.getState(); + try { + BlockStateCodecs.deserialize(state, replacedState); + state.update(); + } catch (Exception e) { + throw new WorldEditorException("Failed to restore blockstate of " + block.getType() + ": " + e, block.getLocation()); + } + } final Material curtype = block.getType(); - if (signtext != null && (curtype == Material.SIGN || curtype == Material.WALL_SIGN)) { - final Sign sign = (Sign) block.getState(); - final String[] lines = signtext.split("\0", 4); - if (lines.length < 4) { - return PerformResult.NO_ACTION; - } - for (int i = 0; i < 4; i++) { - sign.setLine(i, lines[i]); - } - if (!sign.update()) { - throw new WorldEditorException("Failed to update signtext of " + block.getType(), block.getLocation()); - } - } else if (newData instanceof Bed) { + if (newData instanceof Bed) { final Bed bed = (Bed) newData; final Block secBlock = bed.getPart() == Part.HEAD ? block.getRelative(bed.getFacing().getOppositeFace()) : block.getRelative(bed.getFacing()); if (secBlock.isEmpty()) { diff --git a/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodec.java b/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodec.java new file mode 100644 index 0000000..a3e5b0b --- /dev/null +++ b/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodec.java @@ -0,0 +1,15 @@ +package de.diddiz.LogBlock.blockstate; + +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.configuration.file.YamlConfiguration; + +public interface BlockStateCodec { + Material[] getApplicableMaterials(); + + YamlConfiguration serialize(BlockState state); + + void deserialize(BlockState state, YamlConfiguration conf); + + String toString(YamlConfiguration conf); +} diff --git a/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecBanner.java b/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecBanner.java new file mode 100644 index 0000000..65359eb --- /dev/null +++ b/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecBanner.java @@ -0,0 +1,70 @@ +package de.diddiz.LogBlock.blockstate; + +import java.util.List; + +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.block.Banner; +import org.bukkit.block.BlockState; +import org.bukkit.block.banner.Pattern; +import org.bukkit.block.banner.PatternType; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +public class BlockStateCodecBanner implements BlockStateCodec { + @Override + public Material[] getApplicableMaterials() { + return new Material[] { Material.WHITE_BANNER, Material.ORANGE_BANNER, Material.MAGENTA_BANNER, Material.LIGHT_BLUE_BANNER, Material.YELLOW_BANNER, Material.LIME_BANNER, Material.PINK_BANNER, Material.GRAY_BANNER, Material.LIGHT_GRAY_BANNER, Material.CYAN_BANNER, Material.PURPLE_BANNER, + Material.BLUE_BANNER, Material.BROWN_BANNER, Material.GREEN_BANNER, Material.RED_BANNER, Material.BLACK_BANNER, Material.WHITE_WALL_BANNER, Material.ORANGE_WALL_BANNER, Material.MAGENTA_WALL_BANNER, Material.LIGHT_BLUE_WALL_BANNER, Material.YELLOW_WALL_BANNER, + Material.LIME_WALL_BANNER, Material.PINK_WALL_BANNER, Material.GRAY_WALL_BANNER, Material.LIGHT_GRAY_WALL_BANNER, Material.CYAN_WALL_BANNER, Material.PURPLE_WALL_BANNER, Material.BLUE_WALL_BANNER, Material.BROWN_WALL_BANNER, Material.GREEN_WALL_BANNER, Material.RED_WALL_BANNER, + Material.BLACK_WALL_BANNER }; + } + + @Override + public YamlConfiguration serialize(BlockState state) { + if (state instanceof Banner) { + Banner banner = (Banner) state; + int nr = 0; + List patterns = banner.getPatterns(); + if (!patterns.isEmpty()) { + YamlConfiguration conf = new YamlConfiguration(); + ConfigurationSection patternsSection = conf.createSection("patterns"); + for (Pattern pattern : patterns) { + ConfigurationSection section = patternsSection.createSection(Integer.toString(nr)); + section.set("color", pattern.getColor().name()); + section.set("pattern", pattern.getPattern().name()); + nr++; + } + return conf; + } + } + return null; + } + + @Override + public void deserialize(BlockState state, YamlConfiguration conf) { + if (state instanceof Banner) { + Banner banner = (Banner) state; + int oldPatterns = banner.getPatterns().size(); + for (int i = 0; i < oldPatterns; i++) { + banner.removePattern(0); + } + ConfigurationSection patternsSection = conf == null ? null : conf.getConfigurationSection("patterns"); + if (patternsSection != null) { + for (String key : patternsSection.getKeys(false)) { + ConfigurationSection section = patternsSection.getConfigurationSection(key); + if (section != null) { + DyeColor color = DyeColor.valueOf(section.getString("color")); + PatternType type = PatternType.valueOf(section.getString("pattern")); + banner.addPattern(new Pattern(color, type)); + } + } + } + } + } + + @Override + public String toString(YamlConfiguration conf) { + return null; + } +} diff --git a/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecSign.java b/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecSign.java new file mode 100644 index 0000000..04e6e9d --- /dev/null +++ b/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecSign.java @@ -0,0 +1,76 @@ +package de.diddiz.LogBlock.blockstate; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; +import org.bukkit.configuration.file.YamlConfiguration; + +public class BlockStateCodecSign implements BlockStateCodec { + @Override + public Material[] getApplicableMaterials() { + return new Material[] { Material.WALL_SIGN, Material.SIGN }; + } + + @Override + public YamlConfiguration serialize(BlockState state) { + if (state instanceof Sign) { + Sign sign = (Sign) state; + String[] lines = sign.getLines(); + boolean hasText = false; + for (int i = 0; i < lines.length; i++) { + if (lines[i] != null && lines[i].length() > 0) { + hasText = true; + break; + } + } + if (hasText) { + YamlConfiguration conf = new YamlConfiguration(); + conf.set("lines", Arrays.asList(lines)); + return conf; + } + } + return null; + } + + /** + * This is required for the SignChangeEvent, because we have no BlockState there. + */ + public static YamlConfiguration serialize(String[] lines) { + YamlConfiguration conf = new YamlConfiguration(); + conf.set("lines", Arrays.asList(lines)); + return conf; + } + + @Override + public void deserialize(BlockState state, YamlConfiguration conf) { + if (state instanceof Sign) { + Sign sign = (Sign) state; + List lines = Collections.emptyList(); + if (conf != null) { + lines = conf.getStringList("lines"); + } + for (int i = 0; i < 4; i++) { + String line = lines.size() > i && lines.get(i) != null ? lines.get(i) : ""; + sign.setLine(i, line); + } + } + } + + @Override + public String toString(YamlConfiguration conf) { + if (conf != null) { + StringBuilder sb = new StringBuilder(); + for (String line : conf.getStringList("lines")) { + if (sb.length() > 0) + sb.append(" "); + sb.append("[").append(line).append("]"); + } + return sb.toString(); + } + return null; + } +} diff --git a/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecSkull.java b/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecSkull.java new file mode 100644 index 0000000..dbeeaf8 --- /dev/null +++ b/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecSkull.java @@ -0,0 +1,54 @@ +package de.diddiz.LogBlock.blockstate; + +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.block.BlockState; +import org.bukkit.block.Skull; +import org.bukkit.configuration.file.YamlConfiguration; + +public class BlockStateCodecSkull implements BlockStateCodec { + @Override + public Material[] getApplicableMaterials() { + return new Material[] { Material.PLAYER_WALL_HEAD, Material.PLAYER_HEAD }; + } + + @Override + public YamlConfiguration serialize(BlockState state) { + if (state instanceof Skull) { + Skull skull = (Skull) state; + OfflinePlayer owner = skull.hasOwner() ? skull.getOwningPlayer() : null; + if (owner != null) { + YamlConfiguration conf = new YamlConfiguration(); + conf.set("owner", owner.getUniqueId().toString()); + return conf; + } + } + return null; + } + + @Override + public void deserialize(BlockState state, YamlConfiguration conf) { + if (state instanceof Skull) { + Skull skull = (Skull) state; + UUID ownerId = conf == null ? null : UUID.fromString(conf.getString("owner")); + if (ownerId == null) { + skull.setOwningPlayer(null); + } else { + skull.setOwningPlayer(Bukkit.getOfflinePlayer(ownerId)); + } + } + } + + @Override + public String toString(YamlConfiguration conf) { + UUID ownerId = conf == null ? null : UUID.fromString(conf.getString("owner")); + if (ownerId != null) { + OfflinePlayer owner = Bukkit.getOfflinePlayer(ownerId); + return "[" + (owner.getName() != null ? owner.getName() : owner.getUniqueId().toString()) + "]"; + } + return null; + } +} diff --git a/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecSpawner.java b/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecSpawner.java new file mode 100644 index 0000000..3269684 --- /dev/null +++ b/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecSpawner.java @@ -0,0 +1,60 @@ +package de.diddiz.LogBlock.blockstate; + +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.EntityType; + +public class BlockStateCodecSpawner implements BlockStateCodec { + @Override + public Material[] getApplicableMaterials() { + return new Material[] { Material.SPAWNER }; + } + + @Override + public YamlConfiguration serialize(BlockState state) { + if (state instanceof CreatureSpawner) { + CreatureSpawner spawner = (CreatureSpawner) state; + YamlConfiguration conf = new YamlConfiguration(); + conf.set("delay", spawner.getDelay()); + conf.set("maxNearbyEntities", spawner.getMaxNearbyEntities()); + conf.set("maxSpawnDelay", spawner.getMaxSpawnDelay()); + conf.set("minSpawnDelay", spawner.getMinSpawnDelay()); + conf.set("requiredPlayerRange", spawner.getRequiredPlayerRange()); + conf.set("spawnCount", spawner.getSpawnCount()); + conf.set("spawnedType", spawner.getSpawnedType().name()); + conf.set("spawnRange", spawner.getSpawnRange()); + return conf; + } + return null; + } + + @Override + public void deserialize(BlockState state, YamlConfiguration conf) { + if (state instanceof CreatureSpawner) { + CreatureSpawner spawner = (CreatureSpawner) state; + if (conf != null) { + spawner.setDelay(conf.getInt("delay")); + spawner.setMaxNearbyEntities(conf.getInt("maxNearbyEntities")); + spawner.setMaxSpawnDelay(conf.getInt("maxSpawnDelay")); + spawner.setMinSpawnDelay(conf.getInt("minSpawnDelay")); + spawner.setRequiredPlayerRange(conf.getInt("requiredPlayerRange")); + spawner.setSpawnCount(conf.getInt("spawnCount")); + spawner.setSpawnedType(EntityType.valueOf(conf.getString("spawnedType"))); + spawner.setSpawnRange(conf.getInt("spawnRange")); + } + } + } + + @Override + public String toString(YamlConfiguration conf) { + if (conf != null) { + EntityType entity = EntityType.valueOf(conf.getString("spawnedType")); + if (entity != null) { + return "[" + entity + "]"; + } + } + return null; + } +} diff --git a/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecs.java b/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecs.java new file mode 100644 index 0000000..8cac08a --- /dev/null +++ b/src/main/java/de/diddiz/LogBlock/blockstate/BlockStateCodecs.java @@ -0,0 +1,80 @@ +package de.diddiz.LogBlock.blockstate; + +import java.util.EnumMap; +import java.util.Map; +import java.util.logging.Level; + +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; + +import de.diddiz.LogBlock.LogBlock; +import de.diddiz.util.Utils; + +public class BlockStateCodecs { + private static Map codecs = new EnumMap<>(Material.class); + + public static void registerCodec(BlockStateCodec codec) { + Material[] materials = codec.getApplicableMaterials(); + for (Material material : materials) { + if (codecs.containsKey(material)) { + throw new IllegalArgumentException("BlockStateCodec for " + material + " already registered!"); + } + codecs.put(material, codec); + } + } + + static { + registerCodec(new BlockStateCodecSign()); + registerCodec(new BlockStateCodecSkull()); + registerCodec(new BlockStateCodecBanner()); + registerCodec(new BlockStateCodecSpawner()); + } + + public static boolean hasCodec(Material material) { + return codecs.containsKey(material); + } + + public static byte[] serialize(BlockState state) { + BlockStateCodec codec = codecs.get(state.getType()); + if (codec != null) { + YamlConfiguration serialized = codec.serialize(state); + if (serialized != null && !serialized.getKeys(false).isEmpty()) { + return Utils.serializeYamlConfiguration(serialized); + } + } + return null; + } + + public static void deserialize(BlockState block, byte[] state) { + BlockStateCodec codec = codecs.get(block.getType()); + if (codec != null) { + YamlConfiguration conf = null; + try { + if (state != null) { + conf = Utils.deserializeYamlConfiguration(state); + } + } catch (InvalidConfigurationException e) { + LogBlock.getInstance().getLogger().log(Level.SEVERE, "Exception while deserializing BlockState", e); + } + codec.deserialize(block, conf); + } + } + + public static String toString(Material material, byte[] state) { + BlockStateCodec codec = codecs.get(material); + if (codec != null) { + YamlConfiguration conf = null; + try { + if (state != null) { + conf = Utils.deserializeYamlConfiguration(state); + } + } catch (InvalidConfigurationException e) { + LogBlock.getInstance().getLogger().log(Level.SEVERE, "Exception while deserializing BlockState", e); + } + return codec.toString(conf); + } + return null; + } +} diff --git a/src/main/java/de/diddiz/worldedit/WorldEditLoggingHook.java b/src/main/java/de/diddiz/worldedit/WorldEditLoggingHook.java index 64b2a8f..783c9dd 100644 --- a/src/main/java/de/diddiz/worldedit/WorldEditLoggingHook.java +++ b/src/main/java/de/diddiz/worldedit/WorldEditLoggingHook.java @@ -14,6 +14,7 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; import de.diddiz.LogBlock.LogBlock; import de.diddiz.LogBlock.Logging; +import de.diddiz.LogBlock.blockstate.BlockStateCodecs; import de.diddiz.LogBlock.config.Config; import de.diddiz.util.BukkitUtils; @@ -22,8 +23,6 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.Sign; import org.bukkit.block.data.BlockData; import java.util.logging.Level; @@ -104,9 +103,8 @@ public class WorldEditLoggingHook { Material typeBefore = origin.getType(); // Check to see if we've broken a sign - if (Config.isLogging(location.getWorld().getName(), Logging.SIGNTEXT) && (typeBefore == Material.SIGN || typeBefore == Material.WALL_SIGN)) { - BlockState stateBefore = origin.getState(); - plugin.getConsumer().queueSignBreak(lbActor, (Sign) stateBefore); + if (BlockStateCodecs.hasCodec(typeBefore)) { + plugin.getConsumer().queueBlockBreak(lbActor, origin.getState()); } else if (!origin.isEmpty()) { plugin.getConsumer().queueBlockBreak(lbActor, location, origin.getBlockData()); }