diff --git a/src/main/java/de/diddiz/LogBlock/MaterialConverter.java b/src/main/java/de/diddiz/LogBlock/MaterialConverter.java index 57cef28..d392699 100644 --- a/src/main/java/de/diddiz/LogBlock/MaterialConverter.java +++ b/src/main/java/de/diddiz/LogBlock/MaterialConverter.java @@ -20,6 +20,16 @@ public class MaterialConverter { private static String[] idToBlockState = new String[10]; private static HashMap blockStateToID = new HashMap<>(); private static int nextBlockStateId; + + private static HashMap materialKeyToMaterial = new HashMap<>(); + + static { + for (Material m : Material.values()) { + if (!m.name().startsWith("LEGACY_") && m.getKey() != null) { + materialKeyToMaterial.put(m.getKey().toString(), m); + } + } + } public synchronized static int getOrAddMaterialId(NamespacedKey nameSpaceKey) { return getOrAddMaterialId(nameSpaceKey.toString()); @@ -80,7 +90,11 @@ public class MaterialConverter { } public static Material getMaterial(int materialId) { - return getBlockData(materialId, -1).getMaterial(); + String material = idToMaterial[materialId]; + if (materialId >= 0) { + material = material + idToBlockState[materialId]; + } + return materialKeyToMaterial.get(material); } public static void initializeMaterials(Connection connection) throws SQLException { diff --git a/src/main/java/de/diddiz/LogBlock/WorldEditor.java b/src/main/java/de/diddiz/LogBlock/WorldEditor.java index f4c736a..5cd9c03 100644 --- a/src/main/java/de/diddiz/LogBlock/WorldEditor.java +++ b/src/main/java/de/diddiz/LogBlock/WorldEditor.java @@ -1,288 +1,291 @@ -package de.diddiz.LogBlock; - -import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.Material; -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; -import org.bukkit.block.data.type.Bed.Part; -import org.bukkit.block.data.type.Door; -import org.bukkit.block.data.type.Piston; -import org.bukkit.block.data.type.PistonHead; -import org.bukkit.block.data.type.TechnicalPiston.Type; -import org.bukkit.command.CommandSender; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; - -import java.io.File; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.logging.Level; - -import static de.diddiz.LogBlock.config.Config.dontRollback; -import static de.diddiz.LogBlock.config.Config.replaceAnyway; -import static de.diddiz.util.BukkitUtils.*; -import static org.bukkit.Bukkit.getLogger; - -public class WorldEditor implements Runnable { - private final LogBlock logblock; - private final Queue edits = new LinkedBlockingQueue(); - private final World world; - - /** - * The player responsible for editing the world, used to report progress - */ - private CommandSender sender; - private int taskID; - private int successes = 0, blacklistCollisions = 0; - private long elapsedTime = 0; - public LookupCacheElement[] errors; - - public WorldEditor(LogBlock logblock, World world) { - this.logblock = logblock; - this.world = world; - } - - public int getSize() { - return edits.size(); - } - - public int getSuccesses() { - return successes; - } - - public int getErrors() { - return errors.length; - } - - public int getBlacklistCollisions() { - return blacklistCollisions; - } - - - public void setSender(CommandSender sender) { - 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 long getElapsedTime() { - return elapsedTime; - } - - synchronized public void start() throws Exception { - final long start = System.currentTimeMillis(); - taskID = logblock.getServer().getScheduler().scheduleSyncRepeatingTask(logblock, this, 0, 1); - if (taskID == -1) { - throw new Exception("Failed to schedule task"); - } - try { - this.wait(); - } catch (final InterruptedException ex) { - throw new Exception("Interrupted"); - } - elapsedTime = System.currentTimeMillis() - start; - } - - @Override - public synchronized void run() { - final List errorList = new ArrayList(); - int counter = 0; - float size = edits.size(); - while (!edits.isEmpty() && counter < 100) { - try { - switch (edits.poll().perform()) { - case SUCCESS: - successes++; - break; - case BLACKLISTED: - blacklistCollisions++; - break; - case NO_ACTION: - break; - } - } catch (final WorldEditorException ex) { - errorList.add(ex); - } catch (final Exception ex) { - getLogger().log(Level.WARNING, "[WorldEditor] Exeption: ", ex); - } - counter++; - if (sender != null) { - float percentage = ((size - edits.size()) / size) * 100.0F; - if (percentage % 20 == 0) { - sender.sendMessage(ChatColor.GOLD + "[LogBlock]" + ChatColor.YELLOW + " Rollback progress: " + percentage + "%" + - " Blocks edited: " + counter); - } - } - } - if (edits.isEmpty()) { - logblock.getServer().getScheduler().cancelTask(taskID); - if (errorList.size() > 0) { - try { - final File file = new File("plugins/LogBlock/error/WorldEditor-" + new SimpleDateFormat("yy-MM-dd-HH-mm-ss").format(System.currentTimeMillis()) + ".log"); - file.getParentFile().mkdirs(); - final PrintWriter writer = new PrintWriter(file); - for (final LookupCacheElement err : errorList) { - writer.println(err.getMessage()); - } - writer.close(); - } catch (final Exception ex) { - } - } - errors = errorList.toArray(new WorldEditorException[errorList.size()]); - notify(); - } - } - - private static enum PerformResult { - SUCCESS, BLACKLISTED, NO_ACTION - } - - 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); - } - - PerformResult perform() throws WorldEditorException { - BlockData replacedBlock = MaterialConverter.getBlockData(this.replacedMaterial, replacedData); - BlockData setBlock = MaterialConverter.getBlockData(this.typeMaterial, typeData); - // action: set to replaced - - if (dontRollback.contains(replacedBlock.getMaterial())) { - return PerformResult.BLACKLISTED; - } - final Block block = loc.getBlock(); - if (replacedBlock.getMaterial() == Material.AIR && block.getType() == Material.AIR) { - return PerformResult.NO_ACTION; - } - final BlockState state = block.getState(); - if (!world.isChunkLoaded(block.getChunk())) { - world.loadChunk(block.getChunk()); - } - if (setBlock.equals(replacedBlock)) { - if (setBlock.getMaterial() == Material.AIR) { - block.setType(Material.AIR); - } else if (ca != null) { - if (state instanceof InventoryHolder) { - int leftover; - try { - leftover = modifyContainer(state, new ItemStack(ca.itemStack), !ca.remove); - // Special-case blocks which might be double chests - if (leftover > 0 && (setBlock.getMaterial() == Material.CHEST || setBlock.getMaterial() == Material.TRAPPED_CHEST)) { - for (final BlockFace face : new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}) { - if (block.getRelative(face).getType() == setBlock.getMaterial()) { - ItemStack remaining = new ItemStack(ca.itemStack); - remaining.setAmount(leftover); - leftover = modifyContainer(block.getRelative(face).getState(), remaining, !ca.remove); - } - } - } - } catch (final Exception ex) { - throw new WorldEditorException(ex.getMessage(), block.getLocation()); - } - if (leftover > 0 && ca.remove) { - throw new WorldEditorException("Not enough space left in " + block.getType(), block.getLocation()); - } - } - } else if (!block.getBlockData().equals(replacedBlock)) { - block.setBlockData(replacedBlock); - } else { - return PerformResult.NO_ACTION; - } - return PerformResult.SUCCESS; - } - if (block.getType() != setBlock.getMaterial() && !replaceAnyway.contains(block.getType())) { - return PerformResult.NO_ACTION; - } - if (state instanceof InventoryHolder) { - ((InventoryHolder) state).getInventory().clear(); - state.update(); - } - block.setBlockData(replacedBlock); - BlockData newData = block.getBlockData(); - - 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) { - final Bed bed = (Bed) newData; - final Block secBlock = bed.getPart() == Part.HEAD ? block.getRelative(bed.getFacing().getOppositeFace()) : block.getRelative(bed.getFacing()); - if (secBlock.isEmpty()) { - Bed bed2 = (Bed) bed.clone(); - bed2.setPart(bed.getPart() == Part.HEAD ? Part.FOOT : Part.HEAD); - secBlock.setBlockData(bed2); - } - } else if (newData instanceof Door) { - final Door door = (Door) newData; - final Block secBlock = door.getHalf() == Half.TOP ? block.getRelative(BlockFace.DOWN) : block.getRelative(BlockFace.UP); - if (secBlock.isEmpty()) { - Door door2 = (Door) door.clone(); - door2.setHalf(door.getHalf() == Half.TOP ? Half.BOTTOM : Half.TOP); - secBlock.setBlockData(door2); - } - } else if ((curtype == Material.PISTON || curtype == Material.STICKY_PISTON)) { - Piston piston = (Piston) newData; - if (piston.isExtended()) { - final Block secBlock = block.getRelative(piston.getFacing()); - if (secBlock.isEmpty()) { - PistonHead head = (PistonHead) Material.PISTON_HEAD.createBlockData(); - head.setFacing(piston.getFacing()); - head.setType(curtype == Material.PISTON ? Type.NORMAL : Type.STICKY); - secBlock.setBlockData(head); - } - } - } else if (curtype == Material.PISTON_HEAD) { - PistonHead head = (PistonHead) newData; - final Block secBlock = block.getRelative(head.getFacing().getOppositeFace()); - if (secBlock.isEmpty()) { - Piston piston = (Piston) (head.getType() == Type.NORMAL ? Material.PISTON : Material.STICKY_PISTON).createBlockData(); - piston.setFacing(head.getFacing()); - piston.setExtended(true); - secBlock.setBlockData(piston); - } - } - return PerformResult.SUCCESS; - } - } - - @SuppressWarnings("serial") - public static class WorldEditorException extends Exception implements LookupCacheElement { - private final Location loc; - - public WorldEditorException(Material typeBefore, Material typeAfter, Location loc) { - this("Failed to replace " + typeBefore.name() + " with " + typeAfter.name(), loc); - } - - public WorldEditorException(String msg, Location loc) { - super(msg + " at " + loc.getWorld().getName() + ":" + loc.getBlockX() + ":" + loc.getBlockY() + ":" + loc.getBlockZ()); - this.loc = loc; - } - - @Override - public Location getLocation() { - return loc; - } - } -} +package de.diddiz.LogBlock; + +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +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; +import org.bukkit.block.data.type.Bed.Part; +import org.bukkit.block.data.type.Door; +import org.bukkit.block.data.type.Piston; +import org.bukkit.block.data.type.PistonHead; +import org.bukkit.block.data.type.TechnicalPiston.Type; +import org.bukkit.command.CommandSender; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; + +import java.io.File; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Level; + +import static de.diddiz.LogBlock.config.Config.dontRollback; +import static de.diddiz.LogBlock.config.Config.replaceAnyway; +import static de.diddiz.util.BukkitUtils.*; +import static org.bukkit.Bukkit.getLogger; + +public class WorldEditor implements Runnable { + private final LogBlock logblock; + private final Queue edits = new LinkedBlockingQueue(); + private final World world; + + /** + * The player responsible for editing the world, used to report progress + */ + private CommandSender sender; + private int taskID; + private int successes = 0, blacklistCollisions = 0; + private long elapsedTime = 0; + public LookupCacheElement[] errors; + + public WorldEditor(LogBlock logblock, World world) { + this.logblock = logblock; + this.world = world; + } + + public int getSize() { + return edits.size(); + } + + public int getSuccesses() { + return successes; + } + + public int getErrors() { + return errors.length; + } + + public int getBlacklistCollisions() { + return blacklistCollisions; + } + + + public void setSender(CommandSender sender) { + 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 long getElapsedTime() { + return elapsedTime; + } + + synchronized public void start() throws Exception { + final long start = System.currentTimeMillis(); + taskID = logblock.getServer().getScheduler().scheduleSyncRepeatingTask(logblock, this, 0, 1); + if (taskID == -1) { + throw new Exception("Failed to schedule task"); + } + try { + this.wait(); + } catch (final InterruptedException ex) { + throw new Exception("Interrupted"); + } + elapsedTime = System.currentTimeMillis() - start; + } + + @Override + public synchronized void run() { + final List errorList = new ArrayList(); + int counter = 0; + float size = edits.size(); + while (!edits.isEmpty() && counter < 100) { + try { + switch (edits.poll().perform()) { + case SUCCESS: + successes++; + break; + case BLACKLISTED: + blacklistCollisions++; + break; + case NO_ACTION: + break; + } + } catch (final WorldEditorException ex) { + errorList.add(ex); + } catch (final Exception ex) { + getLogger().log(Level.WARNING, "[WorldEditor] Exeption: ", ex); + } + counter++; + if (sender != null) { + float percentage = ((size - edits.size()) / size) * 100.0F; + if (percentage % 20 == 0) { + sender.sendMessage(ChatColor.GOLD + "[LogBlock]" + ChatColor.YELLOW + " Rollback progress: " + percentage + "%" + + " Blocks edited: " + counter); + } + } + } + if (edits.isEmpty()) { + logblock.getServer().getScheduler().cancelTask(taskID); + if (errorList.size() > 0) { + try { + final File file = new File("plugins/LogBlock/error/WorldEditor-" + new SimpleDateFormat("yy-MM-dd-HH-mm-ss").format(System.currentTimeMillis()) + ".log"); + file.getParentFile().mkdirs(); + final PrintWriter writer = new PrintWriter(file); + for (final LookupCacheElement err : errorList) { + writer.println(err.getMessage()); + } + writer.close(); + } catch (final Exception ex) { + } + } + errors = errorList.toArray(new WorldEditorException[errorList.size()]); + notify(); + } + } + + private static enum PerformResult { + SUCCESS, BLACKLISTED, NO_ACTION + } + + 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); + } + + PerformResult perform() throws WorldEditorException { + BlockData replacedBlock = MaterialConverter.getBlockData(this.replacedMaterial, replacedData); + BlockData setBlock = MaterialConverter.getBlockData(this.typeMaterial, typeData); + if (replacedBlock == null || setBlock == null) { + throw new WorldEditorException("Could not parse the material", loc.clone()); + } + // action: set to replaced + + if (dontRollback.contains(replacedBlock.getMaterial())) { + return PerformResult.BLACKLISTED; + } + final Block block = loc.getBlock(); + if (replacedBlock.getMaterial() == Material.AIR && block.getType() == Material.AIR) { + return PerformResult.NO_ACTION; + } + final BlockState state = block.getState(); + if (!world.isChunkLoaded(block.getChunk())) { + world.loadChunk(block.getChunk()); + } + if (setBlock.equals(replacedBlock)) { + if (setBlock.getMaterial() == Material.AIR) { + block.setType(Material.AIR); + } else if (ca != null) { + if (state instanceof InventoryHolder) { + int leftover; + try { + leftover = modifyContainer(state, new ItemStack(ca.itemStack), !ca.remove); + // Special-case blocks which might be double chests + if (leftover > 0 && (setBlock.getMaterial() == Material.CHEST || setBlock.getMaterial() == Material.TRAPPED_CHEST)) { + for (final BlockFace face : new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}) { + if (block.getRelative(face).getType() == setBlock.getMaterial()) { + ItemStack remaining = new ItemStack(ca.itemStack); + remaining.setAmount(leftover); + leftover = modifyContainer(block.getRelative(face).getState(), remaining, !ca.remove); + } + } + } + } catch (final Exception ex) { + throw new WorldEditorException(ex.getMessage(), block.getLocation()); + } + if (leftover > 0 && ca.remove) { + throw new WorldEditorException("Not enough space left in " + block.getType(), block.getLocation()); + } + } + } else if (!block.getBlockData().equals(replacedBlock)) { + block.setBlockData(replacedBlock); + } else { + return PerformResult.NO_ACTION; + } + return PerformResult.SUCCESS; + } + if (block.getType() != setBlock.getMaterial() && !replaceAnyway.contains(block.getType())) { + return PerformResult.NO_ACTION; + } + if (state instanceof InventoryHolder) { + ((InventoryHolder) state).getInventory().clear(); + state.update(); + } + block.setBlockData(replacedBlock); + BlockData newData = block.getBlockData(); + + 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) { + final Bed bed = (Bed) newData; + final Block secBlock = bed.getPart() == Part.HEAD ? block.getRelative(bed.getFacing().getOppositeFace()) : block.getRelative(bed.getFacing()); + if (secBlock.isEmpty()) { + Bed bed2 = (Bed) bed.clone(); + bed2.setPart(bed.getPart() == Part.HEAD ? Part.FOOT : Part.HEAD); + secBlock.setBlockData(bed2); + } + } else if (newData instanceof Door) { + final Door door = (Door) newData; + final Block secBlock = door.getHalf() == Half.TOP ? block.getRelative(BlockFace.DOWN) : block.getRelative(BlockFace.UP); + if (secBlock.isEmpty()) { + Door door2 = (Door) door.clone(); + door2.setHalf(door.getHalf() == Half.TOP ? Half.BOTTOM : Half.TOP); + secBlock.setBlockData(door2); + } + } else if ((curtype == Material.PISTON || curtype == Material.STICKY_PISTON)) { + Piston piston = (Piston) newData; + if (piston.isExtended()) { + final Block secBlock = block.getRelative(piston.getFacing()); + if (secBlock.isEmpty()) { + PistonHead head = (PistonHead) Material.PISTON_HEAD.createBlockData(); + head.setFacing(piston.getFacing()); + head.setType(curtype == Material.PISTON ? Type.NORMAL : Type.STICKY); + secBlock.setBlockData(head); + } + } + } else if (curtype == Material.PISTON_HEAD) { + PistonHead head = (PistonHead) newData; + final Block secBlock = block.getRelative(head.getFacing().getOppositeFace()); + if (secBlock.isEmpty()) { + Piston piston = (Piston) (head.getType() == Type.NORMAL ? Material.PISTON : Material.STICKY_PISTON).createBlockData(); + piston.setFacing(head.getFacing()); + piston.setExtended(true); + secBlock.setBlockData(piston); + } + } + return PerformResult.SUCCESS; + } + } + + @SuppressWarnings("serial") + public static class WorldEditorException extends Exception implements LookupCacheElement { + private final Location loc; + + public WorldEditorException(Material typeBefore, Material typeAfter, Location loc) { + this("Failed to replace " + typeBefore.name() + " with " + typeAfter.name(), loc); + } + + public WorldEditorException(String msg, Location loc) { + super(msg + " at " + loc.getWorld().getName() + ":" + loc.getBlockX() + ":" + loc.getBlockY() + ":" + loc.getBlockZ()); + this.loc = loc; + } + + @Override + public Location getLocation() { + return loc; + } + } +}