PageRenderTime 28ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java

https://gitlab.com/Skull3x/WorldEdit
Java | 278 lines | 187 code | 46 blank | 45 comment | 33 complexity | 19cccf0296e96e51ff72eaba06dbac6e MD5 | raw file
  1. /*
  2. * WorldEdit, a Minecraft world manipulation toolkit
  3. * Copyright (C) sk89q <http://www.sk89q.com>
  4. * Copyright (C) WorldEdit team and contributors
  5. *
  6. * This program is free software: you can redistribute it and/or modify it
  7. * under the terms of the GNU Lesser General Public License as published by the
  8. * Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but WITHOUT
  12. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13. * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
  14. * for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.sk89q.worldedit.extent.clipboard.io;
  20. import com.sk89q.jnbt.ByteArrayTag;
  21. import com.sk89q.jnbt.CompoundTag;
  22. import com.sk89q.jnbt.IntTag;
  23. import com.sk89q.jnbt.ListTag;
  24. import com.sk89q.jnbt.NBTInputStream;
  25. import com.sk89q.jnbt.NamedTag;
  26. import com.sk89q.jnbt.ShortTag;
  27. import com.sk89q.jnbt.StringTag;
  28. import com.sk89q.jnbt.Tag;
  29. import com.sk89q.worldedit.BlockVector;
  30. import com.sk89q.worldedit.Vector;
  31. import com.sk89q.worldedit.WorldEditException;
  32. import com.sk89q.worldedit.blocks.BaseBlock;
  33. import com.sk89q.worldedit.entity.BaseEntity;
  34. import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
  35. import com.sk89q.worldedit.extent.clipboard.Clipboard;
  36. import com.sk89q.worldedit.regions.CuboidRegion;
  37. import com.sk89q.worldedit.regions.Region;
  38. import com.sk89q.worldedit.util.Location;
  39. import com.sk89q.worldedit.world.registry.WorldData;
  40. import com.sk89q.worldedit.world.storage.NBTConversions;
  41. import javax.annotation.Nullable;
  42. import java.io.IOException;
  43. import java.util.HashMap;
  44. import java.util.List;
  45. import java.util.Map;
  46. import java.util.logging.Level;
  47. import java.util.logging.Logger;
  48. import static com.google.common.base.Preconditions.checkNotNull;
  49. /**
  50. * Reads schematic files based that are compatible with MCEdit and other editors.
  51. */
  52. public class SchematicReader implements ClipboardReader {
  53. private static final Logger log = Logger.getLogger(SchematicReader.class.getCanonicalName());
  54. private final NBTInputStream inputStream;
  55. /**
  56. * Create a new instance.
  57. *
  58. * @param inputStream the input stream to read from
  59. */
  60. public SchematicReader(NBTInputStream inputStream) {
  61. checkNotNull(inputStream);
  62. this.inputStream = inputStream;
  63. }
  64. @Override
  65. public Clipboard read(WorldData data) throws IOException {
  66. // Schematic tag
  67. NamedTag rootTag = inputStream.readNamedTag();
  68. if (!rootTag.getName().equals("Schematic")) {
  69. throw new IOException("Tag 'Schematic' does not exist or is not first");
  70. }
  71. CompoundTag schematicTag = (CompoundTag) rootTag.getTag();
  72. // Check
  73. Map<String, Tag> schematic = schematicTag.getValue();
  74. if (!schematic.containsKey("Blocks")) {
  75. throw new IOException("Schematic file is missing a 'Blocks' tag");
  76. }
  77. // Check type of Schematic
  78. String materials = requireTag(schematic, "Materials", StringTag.class).getValue();
  79. if (!materials.equals("Alpha")) {
  80. throw new IOException("Schematic file is not an Alpha schematic");
  81. }
  82. // ====================================================================
  83. // Metadata
  84. // ====================================================================
  85. Vector origin;
  86. Region region;
  87. // Get information
  88. short width = requireTag(schematic, "Width", ShortTag.class).getValue();
  89. short height = requireTag(schematic, "Height", ShortTag.class).getValue();
  90. short length = requireTag(schematic, "Length", ShortTag.class).getValue();
  91. try {
  92. int originX = requireTag(schematic, "WEOriginX", IntTag.class).getValue();
  93. int originY = requireTag(schematic, "WEOriginY", IntTag.class).getValue();
  94. int originZ = requireTag(schematic, "WEOriginZ", IntTag.class).getValue();
  95. Vector min = new Vector(originX, originY, originZ);
  96. int offsetX = requireTag(schematic, "WEOffsetX", IntTag.class).getValue();
  97. int offsetY = requireTag(schematic, "WEOffsetY", IntTag.class).getValue();
  98. int offsetZ = requireTag(schematic, "WEOffsetZ", IntTag.class).getValue();
  99. Vector offset = new Vector(offsetX, offsetY, offsetZ);
  100. origin = min.subtract(offset);
  101. region = new CuboidRegion(min, min.add(width, height, length).subtract(Vector.ONE));
  102. } catch (IOException ignored) {
  103. origin = new Vector(0, 0, 0);
  104. region = new CuboidRegion(origin, origin.add(width, height, length).subtract(Vector.ONE));
  105. }
  106. // ====================================================================
  107. // Blocks
  108. // ====================================================================
  109. // Get blocks
  110. byte[] blockId = requireTag(schematic, "Blocks", ByteArrayTag.class).getValue();
  111. byte[] blockData = requireTag(schematic, "Data", ByteArrayTag.class).getValue();
  112. byte[] addId = new byte[0];
  113. short[] blocks = new short[blockId.length]; // Have to later combine IDs
  114. // We support 4096 block IDs using the same method as vanilla Minecraft, where
  115. // the highest 4 bits are stored in a separate byte array.
  116. if (schematic.containsKey("AddBlocks")) {
  117. addId = requireTag(schematic, "AddBlocks", ByteArrayTag.class).getValue();
  118. }
  119. // Combine the AddBlocks data with the first 8-bit block ID
  120. for (int index = 0; index < blockId.length; index++) {
  121. if ((index >> 1) >= addId.length) { // No corresponding AddBlocks index
  122. blocks[index] = (short) (blockId[index] & 0xFF);
  123. } else {
  124. if ((index & 1) == 0) {
  125. blocks[index] = (short) (((addId[index >> 1] & 0x0F) << 8) + (blockId[index] & 0xFF));
  126. } else {
  127. blocks[index] = (short) (((addId[index >> 1] & 0xF0) << 4) + (blockId[index] & 0xFF));
  128. }
  129. }
  130. }
  131. // Need to pull out tile entities
  132. List<Tag> tileEntities = requireTag(schematic, "TileEntities", ListTag.class).getValue();
  133. Map<BlockVector, Map<String, Tag>> tileEntitiesMap = new HashMap<BlockVector, Map<String, Tag>>();
  134. for (Tag tag : tileEntities) {
  135. if (!(tag instanceof CompoundTag)) continue;
  136. CompoundTag t = (CompoundTag) tag;
  137. int x = 0;
  138. int y = 0;
  139. int z = 0;
  140. Map<String, Tag> values = new HashMap<String, Tag>();
  141. for (Map.Entry<String, Tag> entry : t.getValue().entrySet()) {
  142. if (entry.getKey().equals("x")) {
  143. if (entry.getValue() instanceof IntTag) {
  144. x = ((IntTag) entry.getValue()).getValue();
  145. }
  146. } else if (entry.getKey().equals("y")) {
  147. if (entry.getValue() instanceof IntTag) {
  148. y = ((IntTag) entry.getValue()).getValue();
  149. }
  150. } else if (entry.getKey().equals("z")) {
  151. if (entry.getValue() instanceof IntTag) {
  152. z = ((IntTag) entry.getValue()).getValue();
  153. }
  154. }
  155. values.put(entry.getKey(), entry.getValue());
  156. }
  157. BlockVector vec = new BlockVector(x, y, z);
  158. tileEntitiesMap.put(vec, values);
  159. }
  160. BlockArrayClipboard clipboard = new BlockArrayClipboard(region);
  161. clipboard.setOrigin(origin);
  162. // Don't log a torrent of errors
  163. int failedBlockSets = 0;
  164. for (int x = 0; x < width; ++x) {
  165. for (int y = 0; y < height; ++y) {
  166. for (int z = 0; z < length; ++z) {
  167. int index = y * width * length + z * width + x;
  168. BlockVector pt = new BlockVector(x, y, z);
  169. BaseBlock block = new BaseBlock(blocks[index], blockData[index]);
  170. if (tileEntitiesMap.containsKey(pt)) {
  171. block.setNbtData(new CompoundTag(tileEntitiesMap.get(pt)));
  172. }
  173. try {
  174. clipboard.setBlock(region.getMinimumPoint().add(pt), block);
  175. } catch (WorldEditException e) {
  176. switch (failedBlockSets) {
  177. case 0:
  178. log.log(Level.WARNING, "Failed to set block on a Clipboard", e);
  179. break;
  180. case 1:
  181. log.log(Level.WARNING, "Failed to set block on a Clipboard (again) -- no more messages will be logged", e);
  182. break;
  183. default:
  184. }
  185. failedBlockSets++;
  186. }
  187. }
  188. }
  189. }
  190. // ====================================================================
  191. // Entities
  192. // ====================================================================
  193. try {
  194. List<Tag> entityTags = requireTag(schematic, "Entities", ListTag.class).getValue();
  195. for (Tag tag : entityTags) {
  196. if (tag instanceof CompoundTag) {
  197. CompoundTag compound = (CompoundTag) tag;
  198. String id = compound.getString("id");
  199. Location location = NBTConversions.toLocation(clipboard, compound.getListTag("Pos"), compound.getListTag("Rotation"));
  200. if (!id.isEmpty()) {
  201. BaseEntity state = new BaseEntity(id, compound);
  202. clipboard.createEntity(location, state);
  203. }
  204. }
  205. }
  206. } catch (IOException ignored) { // No entities? No problem
  207. }
  208. return clipboard;
  209. }
  210. private static <T extends Tag> T requireTag(Map<String, Tag> items, String key, Class<T> expected) throws IOException {
  211. if (!items.containsKey(key)) {
  212. throw new IOException("Schematic file is missing a \"" + key + "\" tag");
  213. }
  214. Tag tag = items.get(key);
  215. if (!expected.isInstance(tag)) {
  216. throw new IOException(key + " tag is not of tag type " + expected.getName());
  217. }
  218. return expected.cast(tag);
  219. }
  220. @Nullable
  221. private static <T extends Tag> T getTag(CompoundTag tag, Class<T> expected, String key) {
  222. Map<String, Tag> items = tag.getValue();
  223. if (!items.containsKey(key)) {
  224. return null;
  225. }
  226. Tag test = items.get(key);
  227. if (!expected.isInstance(test)) {
  228. return null;
  229. }
  230. return expected.cast(test);
  231. }
  232. }