/* * _____ _ _ _____ _ * | __ \| | | | / ____| | | * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| * | | * |_| * PlotSquared plot management system for Minecraft * Copyright (C) 2020 IntellectualSites * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.plotsquared.bukkit.schematic; import com.plotsquared.bukkit.util.BukkitUtil; import com.plotsquared.core.location.Location; import com.plotsquared.core.util.task.RunnableVal; import com.plotsquared.core.util.MainUtil; import com.plotsquared.core.util.SchematicHandler; import com.plotsquared.core.util.task.TaskManager; import com.plotsquared.core.queue.LocalBlockQueue; import com.sk89q.jnbt.*; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import java.io.ByteArrayOutputStream; import java.util.*; import java.util.stream.IntStream; /** * Schematic Handler. */ public class BukkitSchematicHandler extends SchematicHandler { @Override public void getCompoundTag(final String world, final Set regions, final RunnableVal whenDone) { // async TaskManager.runTaskAsync(new Runnable() { @Override public void run() { // Main positions Location[] corners = MainUtil.getCorners(world, regions); final Location bot = corners[0]; final Location top = corners[1]; CuboidRegion cuboidRegion = new CuboidRegion(BukkitUtil.IMP.getWeWorld(world), bot.getBlockVector3(), top.getBlockVector3()); final int width = cuboidRegion.getWidth(); int height = cuboidRegion.getHeight(); final int length = cuboidRegion.getLength(); Map schematic = new HashMap<>(); schematic.put("Version", new IntTag(2)); schematic.put("DataVersion", new IntTag(WorldEdit.getInstance().getPlatformManager() .queryCapability(Capability.WORLD_EDITING).getDataVersion())); Map metadata = new HashMap<>(); metadata.put("WEOffsetX", new IntTag(0)); metadata.put("WEOffsetY", new IntTag(0)); metadata.put("WEOffsetZ", new IntTag(0)); schematic.put("Metadata", new CompoundTag(metadata)); schematic.put("Width", new ShortTag((short) width)); schematic.put("Height", new ShortTag((short) height)); schematic.put("Length", new ShortTag((short) length)); // The Sponge format Offset refers to the 'min' points location in the world. That's our 'Origin' schematic.put("Offset", new IntArrayTag(new int[] {0, 0, 0,})); Map palette = new HashMap<>(); Map biomePalette = new HashMap<>(); List tileEntities = new ArrayList<>(); ByteArrayOutputStream buffer = new ByteArrayOutputStream(width * height * length); ByteArrayOutputStream biomeBuffer = new ByteArrayOutputStream(width * length); // Queue final ArrayDeque queue = new ArrayDeque<>(regions); TaskManager.runTask(new Runnable() { @Override public void run() { if (queue.isEmpty()) { TaskManager.runTaskAsync(() -> { schematic.put("PaletteMax", new IntTag(palette.size())); Map paletteTag = new HashMap<>(); palette.forEach( (key, value) -> paletteTag.put(key, new IntTag(value))); schematic.put("Palette", new CompoundTag(paletteTag)); schematic.put("BlockData", new ByteArrayTag(buffer.toByteArray())); schematic.put("TileEntities", new ListTag(CompoundTag.class, tileEntities)); schematic.put("BiomePaletteMax", new IntTag(biomePalette.size())); Map biomePaletteTag = new HashMap<>(); biomePalette.forEach((key, value) -> biomePaletteTag.put(key, new IntTag(value))); schematic.put("BiomePalette", new CompoundTag(biomePaletteTag)); schematic.put("BiomeData", new ByteArrayTag(biomeBuffer.toByteArray())); whenDone.value = new CompoundTag(schematic); TaskManager.runTask(whenDone); }); return; } final Runnable regionTask = this; CuboidRegion region = queue.poll(); Location pos1 = new Location(world, region.getMinimumPoint().getX(), region.getMinimumPoint().getY(), region.getMinimumPoint().getZ()); Location pos2 = new Location(world, region.getMaximumPoint().getX(), region.getMaximumPoint().getY(), region.getMaximumPoint().getZ()); final int p1x = pos1.getX(); final int sy = pos1.getY(); final int p1z = pos1.getZ(); final int p2x = pos2.getX(); final int p2z = pos2.getZ(); final int ey = pos2.getY(); Iterator yiter = IntStream.range(sy, ey + 1).iterator(); final Runnable yTask = new Runnable() { @Override public void run() { long ystart = System.currentTimeMillis(); while (yiter.hasNext() && System.currentTimeMillis() - ystart < 20) { final int y = yiter.next(); Iterator ziter = IntStream.range(p1z, p2z + 1).iterator(); final Runnable zTask = new Runnable() { @Override public void run() { long zstart = System.currentTimeMillis(); while (ziter.hasNext() && System.currentTimeMillis() - zstart < 20) { final int z = ziter.next(); Iterator xiter = IntStream.range(p1x, p2x + 1).iterator(); final Runnable xTask = new Runnable() { @Override public void run() { long xstart = System.currentTimeMillis(); final int ry = y - sy; final int rz = z - p1z; while (xiter.hasNext() && System.currentTimeMillis() - xstart < 20) { final int x = xiter.next(); final int rx = x - p1x; BlockVector3 point = BlockVector3.at(x, y, z); BaseBlock block = cuboidRegion.getWorld() .getFullBlock(point); if (block.getNbtData() != null) { Map values = new HashMap<>(); for (Map.Entry entry : block .getNbtData().getValue() .entrySet()) { values.put(entry.getKey(), entry.getValue()); } // Remove 'id' if it exists. We want 'Id' values.remove("id"); // Positions are kept in NBT, we don't want that. values.remove("x"); values.remove("y"); values.remove("z"); values.put("Id", new StringTag( block.getNbtId())); values.put("Pos", new IntArrayTag( new int[] {rx, ry, rz})); tileEntities .add(new CompoundTag(values)); } String blockKey = block.toImmutableState() .getAsString(); int blockId; if (palette.containsKey(blockKey)) { blockId = palette.get(blockKey); } else { blockId = palette.size(); palette .put(blockKey, palette.size()); } while ((blockId & -128) != 0) { buffer.write(blockId & 127 | 128); blockId >>>= 7; } buffer.write(blockId); if (ry == sy) { BlockVector2 pt = BlockVector2.at(x, z); BiomeType biome = cuboidRegion.getWorld() .getBiome(pt); String biomeStr = biome.getId(); int biomeId; if (biomePalette .containsKey(biomeStr)) { biomeId = biomePalette.get(biomeStr); } else { biomeId = biomePalette.size(); biomePalette .put(biomeStr, biomeId); } while ((biomeId & -128) != 0) { biomeBuffer .write(biomeId & 127 | 128); biomeId >>>= 7; } biomeBuffer.write(biomeId); } } if (xiter.hasNext()) { this.run(); } } }; xTask.run(); } if (ziter.hasNext()) { this.run(); } } }; zTask.run(); } if (yiter.hasNext()) { TaskManager.runTaskLater(this, 1); } else { regionTask.run(); } } }; yTask.run(); } }); } }); } @Override public boolean restoreTile(LocalBlockQueue queue, CompoundTag ct, int x, int y, int z) { return new StateWrapper(ct).restoreTag(queue.getWorld(), x, y, z); } }