From 9cf4f6c9f0ca6224dced04b543159c6ac6117849 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Mon, 18 Apr 2016 04:54:27 +1000 Subject: [PATCH] More optimizations! Schematics now load in a fraction of a time. (took me 5.13 seconds to load a 102,572,228 block schematic) Also made some minor optimizations here and there. --- build.gradle | 2 +- bukkit/build/resources/main/plugin.yml | 2 +- bukkit/src/main/resources/plugin.yml | 2 +- .../main/java/com/boydti/fawe/FaweCache.java | 4 + .../main/java/com/boydti/fawe/config/BBC.java | 1 + .../object/changeset/DiskStorageHistory.java | 8 +- .../changeset/MemoryOptimizedHistory.java | 46 ++- .../java/com/sk89q/worldedit/EditSession.java | 10 +- .../extent/clipboard/BlockArrayClipboard.java | 318 +++++++++--------- .../extent/clipboard/io/SchematicReader.java | 287 ++++++++++++++++ .../transform/BlockTransformExtent.java | 2 +- .../com/boydti/fawe/forge/SpongeMain.java | 2 +- pom.xml | 2 +- 13 files changed, 503 insertions(+), 183 deletions(-) create mode 100644 core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java diff --git a/build.gradle b/build.gradle index 3e11f4cf..f949e617 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { } group = 'com.boydti.fawe' -version = '3.3.13' +version = '3.3.14' description = """FastAsyncWorldEdit""" subprojects { diff --git a/bukkit/build/resources/main/plugin.yml b/bukkit/build/resources/main/plugin.yml index c14d5c9e..3a39fe0e 100644 --- a/bukkit/build/resources/main/plugin.yml +++ b/bukkit/build/resources/main/plugin.yml @@ -1,6 +1,6 @@ name: FastAsyncWorldEdit main: com.boydti.fawe.bukkit.FaweBukkit -version: 3.3.13 +version: 3.3.14 description: Fast Async WorldEdit plugin authors: [Empire92] loadbefore: [WorldEdit] diff --git a/bukkit/src/main/resources/plugin.yml b/bukkit/src/main/resources/plugin.yml index c14d5c9e..3a39fe0e 100644 --- a/bukkit/src/main/resources/plugin.yml +++ b/bukkit/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: FastAsyncWorldEdit main: com.boydti.fawe.bukkit.FaweBukkit -version: 3.3.13 +version: 3.3.14 description: Fast Async WorldEdit plugin authors: [Empire92] loadbefore: [WorldEdit] diff --git a/core/src/main/java/com/boydti/fawe/FaweCache.java b/core/src/main/java/com/boydti/fawe/FaweCache.java index 939a35f8..b97f99ad 100644 --- a/core/src/main/java/com/boydti/fawe/FaweCache.java +++ b/core/src/main/java/com/boydti/fawe/FaweCache.java @@ -20,6 +20,10 @@ public class FaweCache { // Faster than java random (since the game just needs to look random) public final static PseudoRandom RANDOM = new PseudoRandom(); + public static BaseBlock getBlock(int id, int data) { + return CACHE_BLOCK[(id << 4) + data]; + } + static { for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { diff --git a/core/src/main/java/com/boydti/fawe/config/BBC.java b/core/src/main/java/com/boydti/fawe/config/BBC.java index 50070b41..89fd07f8 100644 --- a/core/src/main/java/com/boydti/fawe/config/BBC.java +++ b/core/src/main/java/com/boydti/fawe/config/BBC.java @@ -42,6 +42,7 @@ public enum BBC { WORLDEDIT_OOM("&cYour WorldEdit action was cancelled due to low memory.", "Info"), WORLDEDIT_OOM_ADMIN("&cPossible options:\n&8 - &7//fast\n&8 - &7Do smaller edits\n&8 - &7Allocate more memory\n&8 - &7Disable this safeguard", "Info"), NOT_PLAYER("&cYou must be a player to perform this action!", "Error"), + COMPRESSED("History compressed. Saved ~ %s0b (%s1x smaller)", "Info"), OOM( "&8[&cCritical&8] &cDetected low memory i.e. < 1%. FAWE will take the following actions:\n&8 - &7Terminate WE block placement\n&8 - &7Clear WE history\n&8 - &7Unload non essential chunks\n&8 - &7Kill entities\n&8 - &7Garbage collect\n&cIgnore this if trying to crash server.\n&7Note: Low memory is likely (but not necessarily) caused by WE", "Error"); diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java b/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java index 6fd5575c..74b5805c 100644 --- a/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java +++ b/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java @@ -379,20 +379,22 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet { int y = gis.read() & 0xff; int from1 = gis.read(); int from2 = gis.read(); - BaseBlock from = new BaseBlock(((from2 << 4) + (from1 >> 4)), (from1 & 0xf)); + BaseBlock from = FaweCache.getBlock(((from2 << 4) + (from1 >> 4)), (from1 & 0xf)); if (lastFrom != null && FaweCache.hasNBT(from.getId())) { Map t = lastFrom.getValue(); if (((IntTag) t.get("x")).getValue() == x && ((IntTag) t.get("z")).getValue() == z && ((IntTag) t.get("y")).getValue() == y) { + from = new BaseBlock(from.getId(), from.getData()); from.setNbtData(lastFrom); lastFrom = read(nbtf); } } int to1 = gis.read(); int to2 = gis.read(); - BaseBlock to = new BaseBlock(((to2 << 4) + (to1 >> 4)), (to1 & 0xf)); + BaseBlock to = FaweCache.getBlock(((to2 << 4) + (to1 >> 4)), (to1 & 0xf)); if (lastTo != null && FaweCache.hasNBT(to.getId())) { Map t = lastTo.getValue(); if (((IntTag) t.get("x")).getValue() == x && ((IntTag) t.get("z")).getValue() == z && ((IntTag) t.get("y")).getValue() == y) { + to = new BaseBlock(to.getId(), to.getData()); to.setNbtData(lastTo); lastTo = read(nbtt); } @@ -432,7 +434,7 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet { @Override public void remove() { - throw new IllegalArgumentException("CANNOT REMIVE"); + throw new IllegalArgumentException("CANNOT REMOVE"); } }; } diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java b/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java index d26a07f4..5055f291 100644 --- a/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java +++ b/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java @@ -13,6 +13,7 @@ import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.BlockVector; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.history.change.BlockChange; import com.sk89q.worldedit.history.change.Change; import com.sk89q.worldedit.history.changeset.ChangeSet; @@ -36,7 +37,8 @@ import net.jpountz.lz4.LZ4OutputStream; * - Low memory usage */ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { - + + private final Actor actor; private ArrayDeque fromTags; private ArrayDeque toTags; @@ -54,8 +56,8 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { private int size; - public MemoryOptimizedHistory() { - + public MemoryOptimizedHistory(Actor actor) { + this.actor = actor; } @@ -204,20 +206,22 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { int y = gis.read() & 0xff; int from1 = gis.read(); int from2 = gis.read(); - BaseBlock from = new BaseBlock(((from2 << 4) + (from1 >> 4)), (from1 & 0xf)); + BaseBlock from = FaweCache.getBlock(((from2 << 4) + (from1 >> 4)), (from1 & 0xf)); if (lastFrom != null && FaweCache.hasNBT(from.getId())) { Map t = lastFrom.getValue(); if (((IntTag) t.get("x")).getValue() == x && ((IntTag) t.get("z")).getValue() == z && ((IntTag) t.get("y")).getValue() == y) { + from = new BaseBlock(from.getId(), from.getData()); from.setNbtData(lastFrom); lastFrom = read(lastFromIter); } } int to1 = gis.read(); int to2 = gis.read(); - BaseBlock to = new BaseBlock(((to2 << 4) + (to1 >> 4)), (to1 & 0xf)); + BaseBlock to = FaweCache.getBlock(((to2 << 4) + (to1 >> 4)), (to1 & 0xf)); if (lastTo != null && FaweCache.hasNBT(to.getId())) { Map t = lastTo.getValue(); if (((IntTag) t.get("x")).getValue() == x && ((IntTag) t.get("z")).getValue() == z && ((IntTag) t.get("y")).getValue() == y) { + to = new BaseBlock(to.getId(), to.getData()); to.setNbtData(lastTo); lastTo = read(lastToIter); } @@ -250,7 +254,7 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { @Override public void remove() { - throw new IllegalArgumentException("CANNOT REMIVE"); + throw new IllegalArgumentException("CANNOT REMOVE"); } }; } @@ -284,13 +288,29 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { idsStreamZip.flush(); idsStreamZip.close(); ids = idsStream.toByteArray(); - // Estimate - int total = 0x18 * size; - int ratio = total / ids.length; - int saved = total - ids.length; - if (ratio > 3) { - // TODO remove this debug message - Fawe.debug(BBC.PREFIX.s() + "History compressed. Saved ~ " + saved + "b (" + ratio + "x smaller)"); + /* + * BlockVector + * - reference to the object --> 8 bytes + * - object header (java internals) --> 8 bytes + * - double x, y, z --> 24 bytes + * + * BaseBlock + * - reference to the object --> 8 bytes + * - object header (java internals) --> 8 bytes + * - short id, data --> 4 bytes + * - NBTCompound (assuming null) --> 4 bytes + * + * There are usually two lists for the block changes: + * 2 * BlockVector + 2 * BaseBlock = 128b + * + * This compares FAWE's usage to standard WE. + */ + int total = 128 * size; + int current = ids.length + 16; + int ratio = total / current; + int saved = total - current; + if (ratio > 3 && Thread.currentThread() != Fawe.get().getMainThread() && actor != null && actor.isPlayer() && actor.getSessionKey().isActive() && BBC.COMPRESSED.s().length() > 0) { + actor.print(BBC.PREFIX.s() + " " + BBC.COMPRESSED.format(saved, ratio)); } idsStream = null; idsStreamZip = null; diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java index 7db428dc..f8cb4a96 100644 --- a/core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -248,7 +248,7 @@ public class EditSession implements Extent { this.changeSet = new NullChangeSet(); return; } - this.changeSet = Settings.STORE_HISTORY_ON_DISK ? new DiskStorageHistory(world, actor.getUniqueId()) : new MemoryOptimizedHistory(); + this.changeSet = Settings.STORE_HISTORY_ON_DISK ? new DiskStorageHistory(world, actor.getUniqueId()) : new MemoryOptimizedHistory(actor); Extent extent; final String name = actor.getName(); final FawePlayer fp = FawePlayer.wrap(name); @@ -2089,7 +2089,7 @@ public class EditSession implements Extent { treeGenerator.generate(EditSession.this, new Vector(vector.getX(), y + 1, vector.getZ())); break; } else if (t == BlockID.SNOW) { - setBlock(vector, new BaseBlock(BlockID.AIR)); + setBlock(vector, nullBlock); } else if (t != BlockID.AIR) { // Trees won't grow on this! break; } @@ -2192,7 +2192,7 @@ public class EditSession implements Extent { for (int z = minZ; z <= maxZ; ++z) { final Vector pt = new Vector(x, y, z); - final BaseBlock blk = new BaseBlock(this.getBlockType(pt), this.getBlockData(pt)); + final BaseBlock blk = FaweCache.getBlock(this.getBlockType(pt), this.getBlockData(pt)); if (map.containsKey(blk)) { map.get(blk).increment(); @@ -2206,7 +2206,7 @@ public class EditSession implements Extent { } } else { for (final Vector pt : region) { - final BaseBlock blk = new BaseBlock(this.getBlockType(pt), this.getBlockData(pt)); + final BaseBlock blk = FaweCache.getBlock(this.getBlockType(pt), this.getBlockData(pt)); if (map.containsKey(blk)) { map.get(blk).increment(); @@ -2246,7 +2246,7 @@ public class EditSession implements Extent { return null; } - return new BaseBlock((int) typeVariable.getValue(), (int) dataVariable.getValue()); + return FaweCache.getBlock((int) typeVariable.getValue(), (int) dataVariable.getValue()); } catch (final Exception e) { EditSession.this.log.log(Level.WARNING, "Failed to create shape", e); return null; diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java index 2f6cdc61..d47da08a 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java @@ -22,11 +22,11 @@ package com.sk89q.worldedit.extent.clipboard; import com.boydti.fawe.FaweCache; import com.boydti.fawe.object.IntegerTrio; import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.Vector2D; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseBlock; -import com.sk89q.worldedit.blocks.BlockID; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.extent.Extent; @@ -176,7 +176,7 @@ public class BlockArrayClipboard implements Clipboard { return block; } - return new BaseBlock(BlockID.AIR); + return EditSession.nullBlock; } @Override @@ -187,160 +187,70 @@ public class BlockArrayClipboard implements Clipboard { @Override public boolean setBlock(Vector location, BaseBlock block) throws WorldEditException { if (region.contains(location)) { - final int id = block.getId(); - final int x = location.getBlockX() - mx; - final int y = location.getBlockY() - my; - final int z = location.getBlockZ() - mz; - switch (id) { - case 0: - return true; - case 54: - case 130: - case 142: - case 27: - case 137: - case 52: - case 154: - case 84: - case 25: - case 144: - case 138: - case 176: - case 177: - case 63: - case 119: - case 68: - case 323: - case 117: - case 116: - case 28: - case 66: - case 157: - case 61: - case 62: - case 140: - case 146: - case 149: - case 150: - case 158: - case 23: - case 123: - case 124: - case 29: - case 33: - case 151: - case 178: { - if (block.hasNbtData()) { - nbtMap.put(new IntegerTrio(x, y, z), block.getNbtData()); - } - int i = x + z * dx + (y >> 4) * dxz; - int y2 = y & 0xF; - byte[] idArray = ids[i]; - if (idArray == null) { - idArray = new byte[16]; - ids[i] = idArray; - } - idArray[y2] = (byte) id; - if (FaweCache.hasData(id)) { - int data = block.getData(); - if (data == 0) { - return true; - } - if (datas == null) { - datas = new byte[dx * size.getBlockZ() * ((size.getBlockY() + 15) >> 4)][]; - } - byte[] dataArray = datas[i]; - if (dataArray == null) { - dataArray = datas[i] = new byte[16]; - } - dataArray[y2] = (byte) data; - } - return true; + final int x = location.getBlockX(); + final int y = location.getBlockY(); + final int z = location.getBlockZ(); + return setBlock(x, y, z, block); + } + return false; + } + + public boolean setBlock(int x, int y, int z, BaseBlock block) throws WorldEditException { + x -= mx; + y -= my; + z -= mz; + final int id = block.getId(); + switch (id) { + case 0: + return true; + case 54: + case 130: + case 142: + case 27: + case 137: + case 52: + case 154: + case 84: + case 25: + case 144: + case 138: + case 176: + case 177: + case 63: + case 119: + case 68: + case 323: + case 117: + case 116: + case 28: + case 66: + case 157: + case 61: + case 62: + case 140: + case 146: + case 149: + case 150: + case 158: + case 23: + case 123: + case 124: + case 29: + case 33: + case 151: + case 178: { + if (block.hasNbtData()) { + nbtMap.put(new IntegerTrio(x, y, z), block.getNbtData()); } - case 2: - case 4: - case 13: - case 14: - case 15: - case 20: - case 21: - case 22: - case 30: - case 32: - case 37: - case 39: - case 40: - case 41: - case 42: - case 45: - case 46: - case 47: - case 48: - case 49: - case 51: - case 56: - case 57: - case 58: - case 60: - case 7: - case 8: - case 9: - case 10: - case 11: - case 73: - case 74: - case 78: - case 79: - case 80: - case 81: - case 82: - case 83: - case 85: - case 87: - case 88: - case 101: - case 102: - case 103: - case 110: - case 112: - case 113: - case 121: - case 122: - case 129: - case 133: - case 165: - case 166: - case 169: - case 170: - case 172: - case 173: - case 174: - case 181: - case 182: - case 188: - case 189: - case 190: - case 191: - case 192: { - int i = x + z * dx + (y >> 4) * dxz; - int y2 = y & 0xF; - byte[] idArray = ids[i]; - if (idArray == null) { - idArray = new byte[16]; - ids[i] = idArray; - } - idArray[y2] = (byte) id; - return true; + int i = x + z * dx + (y >> 4) * dxz; + int y2 = y & 0xF; + byte[] idArray = ids[i]; + if (idArray == null) { + idArray = new byte[16]; + ids[i] = idArray; } - default: { - int i = x + z * dx + (y >> 4) * dxz; - int y2 = y & 0xF; - byte[] idArray = ids[i]; - if (idArray == null) { - idArray = new byte[16]; - ids[i] = idArray; - } - idArray[y2] = (byte) id; + idArray[y2] = (byte) id; + if (FaweCache.hasData(id)) { int data = block.getData(); if (data == 0) { return true; @@ -353,11 +263,107 @@ public class BlockArrayClipboard implements Clipboard { dataArray = datas[i] = new byte[16]; } dataArray[y2] = (byte) data; + } + return true; + } + case 2: + case 4: + case 13: + case 14: + case 15: + case 20: + case 21: + case 22: + case 30: + case 32: + case 37: + case 39: + case 40: + case 41: + case 42: + case 45: + case 46: + case 47: + case 48: + case 49: + case 51: + case 56: + case 57: + case 58: + case 60: + case 7: + case 8: + case 9: + case 10: + case 11: + case 73: + case 74: + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + case 85: + case 87: + case 88: + case 101: + case 102: + case 103: + case 110: + case 112: + case 113: + case 121: + case 122: + case 129: + case 133: + case 165: + case 166: + case 169: + case 170: + case 172: + case 173: + case 174: + case 181: + case 182: + case 188: + case 189: + case 190: + case 191: + case 192: { + int i = x + z * dx + (y >> 4) * dxz; + int y2 = y & 0xF; + byte[] idArray = ids[i]; + if (idArray == null) { + idArray = new byte[16]; + ids[i] = idArray; + } + idArray[y2] = (byte) id; + return true; + } + default: { + int i = x + z * dx + (y >> 4) * dxz; + int y2 = y & 0xF; + byte[] idArray = ids[i]; + if (idArray == null) { + idArray = new byte[16]; + ids[i] = idArray; + } + idArray[y2] = (byte) id; + int data = block.getData(); + if (data == 0) { return true; } + if (datas == null) { + datas = new byte[dx * size.getBlockZ() * ((size.getBlockY() + 15) >> 4)][]; + } + byte[] dataArray = datas[i]; + if (dataArray == null) { + dataArray = datas[i] = new byte[16]; + } + dataArray[y2] = (byte) data; + return true; } - } else { - return false; } } diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java new file mode 100644 index 00000000..ed4a7014 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java @@ -0,0 +1,287 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.extent.clipboard.io; + +import com.boydti.fawe.FaweCache; +import com.sk89q.jnbt.ByteArrayTag; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.IntTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.NBTInputStream; +import com.sk89q.jnbt.NamedTag; +import com.sk89q.jnbt.ShortTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.registry.WorldData; +import com.sk89q.worldedit.world.storage.NBTConversions; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Reads schematic files based that are compatible with MCEdit and other editors. + */ +public class SchematicReader implements ClipboardReader { + + private static final Logger log = Logger.getLogger(SchematicReader.class.getCanonicalName()); + private final NBTInputStream inputStream; + + /** + * Create a new instance. + * + * @param inputStream the input stream to read from + */ + public SchematicReader(NBTInputStream inputStream) { + checkNotNull(inputStream); + this.inputStream = inputStream; + } + + @Override + public Clipboard read(WorldData data) throws IOException { + // Schematic tag + NamedTag rootTag = inputStream.readNamedTag(); + if (!rootTag.getName().equals("Schematic")) { + throw new IOException("Tag 'Schematic' does not exist or is not first"); + } + CompoundTag schematicTag = (CompoundTag) rootTag.getTag(); + + // Check + Map schematic = schematicTag.getValue(); + if (!schematic.containsKey("Blocks")) { + throw new IOException("Schematic file is missing a 'Blocks' tag"); + } + + // Check type of Schematic + String materials = requireTag(schematic, "Materials", StringTag.class).getValue(); + if (!materials.equals("Alpha")) { + throw new IOException("Schematic file is not an Alpha schematic"); + } + + // ==================================================================== + // Metadata + // ==================================================================== + + Vector origin; + Region region; + + // Get information + short width = requireTag(schematic, "Width", ShortTag.class).getValue(); + short height = requireTag(schematic, "Height", ShortTag.class).getValue(); + short length = requireTag(schematic, "Length", ShortTag.class).getValue(); + + try { + int originX = requireTag(schematic, "WEOriginX", IntTag.class).getValue(); + int originY = requireTag(schematic, "WEOriginY", IntTag.class).getValue(); + int originZ = requireTag(schematic, "WEOriginZ", IntTag.class).getValue(); + Vector min = new Vector(originX, originY, originZ); + + int offsetX = requireTag(schematic, "WEOffsetX", IntTag.class).getValue(); + int offsetY = requireTag(schematic, "WEOffsetY", IntTag.class).getValue(); + int offsetZ = requireTag(schematic, "WEOffsetZ", IntTag.class).getValue(); + Vector offset = new Vector(offsetX, offsetY, offsetZ); + + origin = min.subtract(offset); + region = new CuboidRegion(min, min.add(width, height, length).subtract(Vector.ONE)); + } catch (IOException ignored) { + origin = new Vector(0, 0, 0); + region = new CuboidRegion(origin, origin.add(width, height, length).subtract(Vector.ONE)); + } + + // ==================================================================== + // Blocks + // ==================================================================== + + // Get blocks + byte[] blockId = requireTag(schematic, "Blocks", ByteArrayTag.class).getValue(); + byte[] blockData = requireTag(schematic, "Data", ByteArrayTag.class).getValue(); + byte[] addId = null; + + // We support 4096 block IDs using the same method as vanilla Minecraft, where + // the highest 4 bits are stored in a separate byte array. + if (schematic.containsKey("AddBlocks")) { + addId = requireTag(schematic, "AddBlocks", ByteArrayTag.class).getValue(); + } + + // Need to pull out tile entities + List tileEntities = requireTag(schematic, "TileEntities", ListTag.class).getValue(); + Map> tileEntitiesMap = new HashMap>(); + + for (Tag tag : tileEntities) { + if (!(tag instanceof CompoundTag)) continue; + CompoundTag t = (CompoundTag) tag; + + int x = 0; + int y = 0; + int z = 0; + + Map values = new HashMap(); + + for (Map.Entry entry : t.getValue().entrySet()) { + if (entry.getKey().equals("x")) { + if (entry.getValue() instanceof IntTag) { + x = ((IntTag) entry.getValue()).getValue(); + } + } else if (entry.getKey().equals("y")) { + if (entry.getValue() instanceof IntTag) { + y = ((IntTag) entry.getValue()).getValue(); + } + } else if (entry.getKey().equals("z")) { + if (entry.getValue() instanceof IntTag) { + z = ((IntTag) entry.getValue()).getValue(); + } + } + + values.put(entry.getKey(), entry.getValue()); + } + + BlockVector vec = new BlockVector(x, y, z); + tileEntitiesMap.put(vec, values); + } + + BlockArrayClipboard clipboard = new BlockArrayClipboard(region); + clipboard.setOrigin(origin); + + // Don't log a torrent of errors + int failedBlockSets = 0; + + Vector min = region.getMinimumPoint(); + int mx = min.getBlockX(); + int my = min.getBlockY(); + int mz = min.getBlockZ(); + + BlockVector pt = new BlockVector(0, 0, 0); + + int i; + for (int y = 0; y < height; y++) { + int yy = y + my; + final int i1 = y * width * length; + for (int z = 0; z < length; z++) { + int zz = z + mz; + final int i2 = (z * width) + i1; + for (int x = 0; x < width; x++) { + i = i2 + x; + int xx = x + mx; + int id = blockId[i] & 0xFF; + int db = blockData[i]; + if (addId != null) { + if ((i & 1) == 0) { + id += ((addId[i >> 1] & 0x0F) << 8); + } else { + id += ((addId[i >> 1] & 0xF0) << 4); + } + } + BaseBlock block = FaweCache.getBlock(id, db); + if (FaweCache.hasNBT(id)) { + pt.x = x; + pt.y = y; + pt.z = z; + if (tileEntitiesMap.containsKey(pt)) { + block = new BaseBlock(block.getId(), block.getData()); + block.setNbtData(new CompoundTag(tileEntitiesMap.get(pt))); + } + } + try { + clipboard.setBlock(xx, yy, zz, block); + } catch (Exception e) { + switch (failedBlockSets) { + case 0: + log.log(Level.WARNING, "Failed to set block on a Clipboard", e); + break; + case 1: + log.log(Level.WARNING, "Failed to set block on a Clipboard (again) -- no more messages will be logged", e); + break; + default: + } + failedBlockSets++; + } + } + } + } + + // ==================================================================== + // Entities + // ==================================================================== + + try { + List entityTags = requireTag(schematic, "Entities", ListTag.class).getValue(); + + for (Tag tag : entityTags) { + if (tag instanceof CompoundTag) { + CompoundTag compound = (CompoundTag) tag; + String id = compound.getString("id"); + Location location = NBTConversions.toLocation(clipboard, compound.getListTag("Pos"), compound.getListTag("Rotation")); + if (!id.isEmpty()) { + BaseEntity state = new BaseEntity(id, compound); + clipboard.createEntity(location, state); + } + } + } + } catch (IOException ignored) { // No entities? No problem + } + + return clipboard; + } + + private static T requireTag(Map items, String key, Class expected) throws IOException { + if (!items.containsKey(key)) { + throw new IOException("Schematic file is missing a \"" + key + "\" tag"); + } + + Tag tag = items.get(key); + if (!expected.isInstance(tag)) { + throw new IOException(key + " tag is not of tag type " + expected.getName()); + } + + return expected.cast(tag); + } + + @Nullable + private static T getTag(CompoundTag tag, Class expected, String key) { + Map items = tag.getValue(); + + if (!items.containsKey(key)) { + return null; + } + + Tag test = items.get(key); + if (!expected.isInstance(test)) { + return null; + } + + return expected.cast(test); + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java b/core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java index 26732c85..e0eceb85 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java @@ -91,7 +91,7 @@ public class BlockTransformExtent extends AbstractDelegateExtent { @Override public boolean setBlock(Vector location, BaseBlock block) throws WorldEditException { - return super.setBlock(location, transformBlock(new BaseBlock(block), true)); + return super.setBlock(location, transformBlock(block, true)); } diff --git a/forge/src/main/java/com/boydti/fawe/forge/SpongeMain.java b/forge/src/main/java/com/boydti/fawe/forge/SpongeMain.java index 7124b77e..34683378 100644 --- a/forge/src/main/java/com/boydti/fawe/forge/SpongeMain.java +++ b/forge/src/main/java/com/boydti/fawe/forge/SpongeMain.java @@ -13,7 +13,7 @@ import org.spongepowered.api.plugin.Plugin; import org.spongepowered.api.plugin.PluginContainer; import org.spongepowered.api.profile.GameProfileManager; -@Plugin(id = "com.boydti.fawe", name = "FastAsyncWorldEdit", description = "Lagless WorldEdit, Area restrictions, Memory mangement, Block logging", url = "https://github.com/boy0001/FastAsyncWorldedit", version = "3.3.13") +@Plugin(id = "com.boydti.fawe", name = "FastAsyncWorldEdit", description = "Lagless WorldEdit, Area restrictions, Memory mangement, Block logging", url = "https://github.com/boy0001/FastAsyncWorldedit", version = "3.3.14") public class SpongeMain { public PluginContainer plugin; diff --git a/pom.xml b/pom.xml index 4dea2a42..40768473 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ UTF-8 FastAsyncWorldEdit - 3.3.13 + 3.3.14 FastAsyncWorldEdit jar