diff --git a/core/src/main/java/com/boydti/fawe/FaweCache.java b/core/src/main/java/com/boydti/fawe/FaweCache.java index f8101127..6ac7b4eb 100644 --- a/core/src/main/java/com/boydti/fawe/FaweCache.java +++ b/core/src/main/java/com/boydti/fawe/FaweCache.java @@ -737,7 +737,6 @@ public class FaweCache { return map; } - public static ShortTag asTag(short value) { return new ShortTag(value); } diff --git a/core/src/main/java/com/boydti/fawe/command/CFICommands.java b/core/src/main/java/com/boydti/fawe/command/CFICommands.java index 0de2f8e1..5cbe3a28 100644 --- a/core/src/main/java/com/boydti/fawe/command/CFICommands.java +++ b/core/src/main/java/com/boydti/fawe/command/CFICommands.java @@ -19,7 +19,9 @@ import com.intellectualcrafters.plot.PS; import com.intellectualcrafters.plot.commands.Auto; import com.intellectualcrafters.plot.config.C; import com.intellectualcrafters.plot.config.Settings; +import com.intellectualcrafters.plot.database.DBFunc; import com.intellectualcrafters.plot.object.Plot; +import com.intellectualcrafters.plot.object.PlotArea; import com.intellectualcrafters.plot.object.PlotId; import com.intellectualcrafters.plot.object.PlotPlayer; import com.intellectualcrafters.plot.object.worlds.PlotAreaManager; @@ -139,6 +141,23 @@ public class CFICommands extends MethodCommands { fp.sendMessage(BBC.getPrefix() + "Cancelled!"); } + @Deprecated + public static void autoClaimFromDatabase(PlotPlayer player, PlotArea area, PlotId start, com.intellectualcrafters.plot.object.RunnableVal whenDone) { + final Plot plot = area.getNextFreePlot(player, start); + if (plot == null) { + whenDone.run(null); + return; + } + whenDone.value = plot; + plot.owner = player.getUUID(); + DBFunc.createPlotSafe(plot, whenDone, new Runnable() { + @Override + public void run() { + autoClaimFromDatabase(player, area, plot.getId(), whenDone); + } + }); + } + @Command( aliases = {"done", "create"}, usage = "", @@ -164,6 +183,7 @@ public class CFICommands extends MethodCommands { C.CANT_CLAIM_MORE_PLOTS_NUM.send(player, -diff); return; } + if (area.getMeta("lastPlot") == null) { area.setMeta("lastPlot", new PlotId(0, 0)); } diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java index 78b417e1..65e41ef8 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java @@ -530,6 +530,35 @@ public class MCAChunk extends FaweChunk { streamer.readFully(); } + public void filterBlocks(MutableMCABackedBaseBlock mutableBlock, MCAFilter filter) { + mutableBlock.setChunk(this); + int bx = getX() << 4; + int bz = getZ() << 4; + int tx = bx + 15; + int tz = bz + 15; + for (int layer = 0; layer < ids.length; layer++) { + if (doesSectionExist(layer)) { + mutableBlock.setArrays(layer); + int yStart = layer << 4; + int yEnd = yStart + 15; + for (int y = yStart, y0 = (yStart & 15); y <= yEnd; y++, y0++) { + int yIndex = ((y0) << 8); + mutableBlock.setY(y); + for (int z = bz, z0 = bz & 15; z <= tz; z++, z0++) { + int zIndex = yIndex + ((z0) << 4); + mutableBlock.setZ(z); + for (int x = bx, x0 = bx & 15; x <= tx; x++, x0++) { + int xIndex = zIndex + x0; + mutableBlock.setX(x); + mutableBlock.setIndex(xIndex); + filter.applyBlock(x, y, z, mutableBlock, null); + } + } + } + } + } + } + public int[] getHeightMapArray() { return heightMap; } diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueueMap.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueueMap.java index 1a7d3fec..5c7bff3d 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueueMap.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueueMap.java @@ -10,6 +10,7 @@ import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.object.exception.FaweException; import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.SetQueue; +import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -40,7 +41,7 @@ public class MCAQueueMap implements IFaweQueueMap { private int lastX = Integer.MIN_VALUE; private int lastZ = Integer.MIN_VALUE; - public synchronized MCAFile getMCAFile(int cx, int cz) { + public synchronized MCAFile getMCAFile(int cx, int cz, boolean create) { int mcaX = cx >> 5; int mcaZ = cz >> 5; if (mcaX == lastFileX && mcaZ == lastFileZ) { @@ -52,7 +53,13 @@ public class MCAQueueMap implements IFaweQueueMap { if (lastFile == null) { try { queue.setMCA(lastFileX, lastFileZ, RegionWrapper.GLOBAL(), null, true, false); - lastFile = tmp = new MCAFile(queue, lastFileX, lastFileZ); + File file = new File(queue.getSaveFolder(), "r." + lastFileX + "." + lastFileZ + ".mca"); + if (create) { + File parent = file.getParentFile(); + if (!parent.exists()) parent.mkdirs(); + if (!file.exists()) file.createNewFile(); + } + lastFile = tmp = new MCAFile(queue, file); } catch (FaweException.FaweChunkLoadException ignore) { lastFile = null; return null; @@ -85,8 +92,7 @@ public class MCAQueueMap implements IFaweQueueMap { } } - @Override - public FaweChunk getFaweChunk(int cx, int cz) { + public FaweChunk getFaweChunk(int cx, int cz, boolean create) { if (cx == lastX && cz == lastZ) { if (nullChunk == lastChunk) { nullChunk.setLoc(queue, lastX, lastZ); @@ -103,12 +109,17 @@ public class MCAQueueMap implements IFaweQueueMap { } } try { - MCAFile mcaFile = getMCAFile(cx, cz); + MCAFile mcaFile = getMCAFile(cx, cz, create); if (mcaFile != null) { mcaFile.init(); lastChunk = mcaFile.getChunk(cx, cz); if (lastChunk != null) { return lastChunk; + } else if (create) { + MCAChunk chunk = new MCAChunk(queue, cx, cz); + mcaFile.setChunk(chunk); + lastChunk = chunk; + return chunk; } } } catch (Throwable ignore) { @@ -122,6 +133,11 @@ public class MCAQueueMap implements IFaweQueueMap { return lastChunk = nullChunk; } + @Override + public FaweChunk getFaweChunk(int cx, int cz) { + return getFaweChunk(cx, cz, !isHybridQueue); + } + @Override public FaweChunk getCachedFaweChunk(int cx, int cz) { int mcaX = cx >> 5; @@ -171,6 +187,7 @@ public class MCAQueueMap implements IFaweQueueMap { lastZ = Integer.MIN_VALUE; lastFileX = Integer.MIN_VALUE; lastFileZ = Integer.MIN_VALUE; + System.out.println("Files " + mcaFileMap); if (!mcaFileMap.isEmpty()) { Iterator> iter = mcaFileMap.entrySet().iterator(); boolean result; diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/RemapFilter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/RemapFilter.java index db35d99b..1679082a 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/RemapFilter.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/RemapFilter.java @@ -48,7 +48,7 @@ public class RemapFilter extends MCAFilterCounter { @Override public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong cache) { int id = block.getId(); - if (remapper.hasRemap(block.getId())) { + if (remapper.hasRemap(id)) { BaseBlock result = remapper.remap(block); if (result != block) { cache.add(1); @@ -63,71 +63,74 @@ public class RemapFilter extends MCAFilterCounter { block.setData(result.getData()); } } - outer: - switch (from) { - case PC: { - int newLight = 0; - switch (id) { - case 29: - case 33: - Map map = new HashMap<>(); - map.put("Progress", 0f); - map.put("State", (byte) 0); - map.put("LastProgress", 0f); - map.put("NewState", (byte) 0); - map.put("isMoveable", (byte) 1); - map.put("id", "PistonArm"); - map.put("AttachedBlocks", new ArrayList<>()); - map.put("Sticky", (byte) (id == 29 ? 1 : 0)); - map.put("x", x); - map.put("y", y); - map.put("z", z); - block.setNbtData(FaweCache.asTag(map)); - break; - case 44: - case 182: - case 158: - case 53: - case 67: - case 108: - case 109: - case 114: - case 128: - case 134: - case 135: - case 136: - case 156: - case 163: - case 164: - case 180: - case 203: - case 198: - MutableMCABackedBaseBlock mcaBlock = (MutableMCABackedBaseBlock) block; - MCAChunk chunk = mcaBlock.getChunk(); - int currentLight = chunk.getSkyLight(x, y, z); - if (currentLight >= 14) { + if (from != null) { + outer: + switch (from) { + case PC: { + int newLight = 0; + switch (id) { + case 29: + case 33: + Map map = new HashMap<>(); + map.put("Progress", 0f); + map.put("State", (byte) 0); + map.put("LastProgress", 0f); + map.put("NewState", (byte) 0); + map.put("isMoveable", (byte) 1); + map.put("id", "PistonArm"); + map.put("AttachedBlocks", new ArrayList<>()); + map.put("Sticky", (byte) (id == 29 ? 1 : 0)); + map.put("x", x); + map.put("y", y); + map.put("z", z); + block.setNbtData(FaweCache.asTag(map)); break; - } - newLight = chunk.getSkyLight(x, (y + 1) & 0xFF, z); - if (newLight > currentLight) break; - if (x > 0) { - if ((newLight = chunk.getSkyLight(x - 1, y, z)) > currentLight) break; - } - if (x < 16) { - if ((newLight = chunk.getSkyLight(x + 1, y, z)) > currentLight) break; - } - if (z > 0) { - if ((newLight = chunk.getSkyLight(x, y, z - 1)) > currentLight) break; - } - if (z < 16) { - if ((newLight = chunk.getSkyLight(x, y, z + 1)) > currentLight) break; - } - default: break outer; + case 44: + case 182: + case 158: + case 53: + case 67: + case 108: + case 109: + case 114: + case 128: + case 134: + case 135: + case 136: + case 156: + case 163: + case 164: + case 180: + case 203: + case 198: + MutableMCABackedBaseBlock mcaBlock = (MutableMCABackedBaseBlock) block; + MCAChunk chunk = mcaBlock.getChunk(); + int currentLight = chunk.getSkyLight(x, y, z); + if (currentLight >= 14) { + break; + } + newLight = chunk.getSkyLight(x, (y + 1) & 0xFF, z); + if (newLight > currentLight) break; + if (x > 0) { + if ((newLight = chunk.getSkyLight(x - 1, y, z)) > currentLight) break; + } + if (x < 16) { + if ((newLight = chunk.getSkyLight(x + 1, y, z)) > currentLight) break; + } + if (z > 0) { + if ((newLight = chunk.getSkyLight(x, y, z - 1)) > currentLight) break; + } + if (z < 16) { + if ((newLight = chunk.getSkyLight(x, y, z + 1)) > currentLight) break; + } + default: + break outer; + } + if (newLight != 0) { + ((MutableMCABackedBaseBlock) block).getChunk().setSkyLight(x, y, z, newLight); + } + break; } - if (newLight != 0) { - ((MutableMCABackedBaseBlock) block).getChunk().setSkyLight(x, y, z, newLight); - } - break; } } } diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/ClipboardRemapper.java b/core/src/main/java/com/boydti/fawe/object/clipboard/ClipboardRemapper.java index 788a57bd..a488d105 100644 --- a/core/src/main/java/com/boydti/fawe/object/clipboard/ClipboardRemapper.java +++ b/core/src/main/java/com/boydti/fawe/object/clipboard/ClipboardRemapper.java @@ -10,6 +10,7 @@ import com.sk89q.worldedit.regions.Region; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import static com.boydti.fawe.FaweCache.getBlock; public class ClipboardRemapper { public enum RemapPlatform { @@ -65,6 +66,11 @@ public class ClipboardRemapper { mapPEtoPC.put(new BaseBlock(peId,5), new BaseBlock(pcId,3)); } + for (int id : new int[] {29, 33}) { + addBoth(getBlock(id, 3), getBlock(id, 4)); + addBoth(getBlock(id, 10), getBlock(id, 11)); + } + mapPEtoPC.put(new BaseBlock(236,-1), new BaseBlock(251,-1)); mapPEtoPC.put(new BaseBlock(237,-1), new BaseBlock(252,-1)); mapPEtoPC.put(new BaseBlock(240,-1), new BaseBlock(199,-1)); @@ -114,6 +120,7 @@ public class ClipboardRemapper { for (int data = 4; data < 8; data++) mapPEtoPC.put(new BaseBlock(18,data + 8), new BaseBlock(161,data)); for (int id : new int[] {96, 167}) { // trapdoor + for (int data = 0; data < 4; data++) mapPEtoPC.put(new BaseBlock(id, data), new BaseBlock(id, 3 - data)); for (int data = 4; data < 12; data++) mapPEtoPC.put(new BaseBlock(id, data), new BaseBlock(id, 15 - data)); for (int data = 12; data < 15; data++) mapPEtoPC.put(new BaseBlock(id, data), new BaseBlock(id, 27 - data)); } @@ -137,6 +144,11 @@ public class ClipboardRemapper { } } + public void addBoth(BaseBlock from, BaseBlock to) { + add(from, to); + add(to, from); + } + public void apply(Clipboard clipboard) throws WorldEditException { if (clipboard instanceof BlockArrayClipboard) { BlockArrayClipboard bac = (BlockArrayClipboard) clipboard; @@ -214,8 +226,6 @@ public class ClipboardRemapper { } } - - public BaseBlock remap(BaseBlock block) { int combined = block.getCombined(); if (remap[combined]) { diff --git a/core/src/main/java/com/boydti/fawe/util/MainUtil.java b/core/src/main/java/com/boydti/fawe/util/MainUtil.java index 7b2255f4..ab991972 100644 --- a/core/src/main/java/com/boydti/fawe/util/MainUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/MainUtil.java @@ -496,8 +496,8 @@ public class MainUtil { private static final Class[] parameters = new Class[]{URL.class}; public static void loadURLClasspath(URL u) throws IOException { + ClassLoader sysloader = ClassLoader.getSystemClassLoader(); - URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); Class sysclass = URLClassLoader.class; try { diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java index 4f327950..b1c6e697 100644 --- a/core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -316,8 +316,12 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting if (this.limit.SPEED_REDUCTION > 0) { this.bypassHistory = new SlowExtent(this.bypassHistory, this.limit.SPEED_REDUCTION); } + if (changeSet instanceof NullChangeSet && Fawe.imp().getBlocksHubApi() != null && player != null) { + changeSet = LoggingChangeSet.wrap(player, changeSet); + } + System.out.println("Changeset " + changeSet); if (!(changeSet instanceof NullChangeSet)) { - if (player != null && Fawe.imp().getBlocksHubApi() != null) { + if (!(changeSet instanceof LoggingChangeSet) && player != null && Fawe.imp().getBlocksHubApi() != null) { changeSet = LoggingChangeSet.wrap(player, changeSet); } if (this.blockBag != null) { @@ -1269,6 +1273,15 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting editSession.changes = 1; } + public void setBlocks(ChangeSet changeSet, ChangeSetExecutor.Type type) { + final UndoContext context = new UndoContext(); + Extent bypass = (history == null) ? bypassAll : history; + context.setExtent(bypass); + Operations.completeBlindly(ChangeSetExecutor.create(changeSet, context, type, getBlockBag(), getLimit().INVENTORY_MODE)); + flushQueue(); + changes = 1; + } + /** * Sets to new state. * diff --git a/core/src/main/java/com/sk89q/worldedit/LocalSession.java b/core/src/main/java/com/sk89q/worldedit/LocalSession.java index f7f8c480..12ca7fcf 100644 --- a/core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -52,6 +52,7 @@ import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Masks; +import com.sk89q.worldedit.function.operation.ChangeSetExecutor; import com.sk89q.worldedit.history.changeset.ChangeSet; import com.sk89q.worldedit.internal.cui.CUIEvent; import com.sk89q.worldedit.internal.cui.CUIRegion; @@ -516,13 +517,13 @@ public class LocalSession { EditSession newEditSession = new EditSessionBuilder(changeSet.getWorld()) .allowedRegionsEverywhere() .checkMemory(false) - .changeSet(changeSet) + .changeSetNull() .fastmode(false) .limitUnprocessed(fp) .player(fp) .blockBag(getBlockBag(player)) .build(); - newEditSession.undo(newEditSession); + newEditSession.setBlocks(changeSet, ChangeSetExecutor.Type.UNDO); setDirty(); historyNegativeIndex++; return newEditSession; @@ -565,13 +566,13 @@ public class LocalSession { EditSession newEditSession = new EditSessionBuilder(changeSet.getWorld()) .allowedRegionsEverywhere() .checkMemory(false) - .changeSet(changeSet) + .changeSetNull() .fastmode(false) .limitUnprocessed(fp) .player(fp) .blockBag(getBlockBag(player)) .build(); - newEditSession.redo(newEditSession); + newEditSession.setBlocks(changeSet, ChangeSetExecutor.Type.REDO); return newEditSession; } return null; diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConvertCommands.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConvertCommands.java index 7ed460c4..c44a1eb4 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConvertCommands.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConvertCommands.java @@ -41,7 +41,7 @@ public class ConvertCommands extends MethodCommands { RemapFilter filter = new RemapFilter(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE); FaweQueue defaultQueue = SetQueue.IMP.getNewQueue(folder, true, false); - try (MCAFile2LevelDB converter = new MCAFile2LevelDB(defaultQueue.getSaveFolder().getParentFile())) { + try (MCAFile2LevelDB converter = new MCAFile2LevelDB(null, defaultQueue.getSaveFolder().getParentFile())) { DelegateMCAFilter delegate = new DelegateMCAFilter(filter) { @Override diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java index e116db5c..29ee89ee 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java @@ -11,8 +11,6 @@ import com.boydti.fawe.installer.MinimizeButton; import com.boydti.fawe.installer.MovablePanel; import com.boydti.fawe.installer.TextAreaOutputStream; import com.boydti.fawe.installer.URLButton; -import com.boydti.fawe.jnbt.anvil.MCAFilter; -import com.boydti.fawe.jnbt.anvil.MCAQueue; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.wrappers.FakePlayer; import java.awt.BorderLayout; @@ -88,7 +86,7 @@ public class ConverterFrame extends JFrame { JPanel topBarCenter = new InvisiblePanel(); JPanel topBarRight = new InvisiblePanel(); - JLabel title = new JLabel("(FAWE) Anvil to LevelDB converter"); + JLabel title = new JLabel("(FAWE) Anvil and LevelDB converter"); title.setHorizontalAlignment(SwingConstants.CENTER); title.setAlignmentX(Component.RIGHT_ALIGNMENT); title.setForeground(Color.LIGHT_GRAY); @@ -345,32 +343,9 @@ public class ConverterFrame extends JFrame { MainUtil.loadURLClasspath(leveldb.toURL()); File newWorldFile = new File(output, dirMc.getName()); - try (MCAFile2LevelDB converter = new MCAFile2LevelDB(newWorldFile)) { - debug("Starting world conversion"); - MCAFilter filter = converter.toFilter(); - MCAQueue queue = new MCAQueue(null, new File(dirMc, "region"), true); - MCAFilter result = queue.filterWorld(filter); - - File levelDat = new File(dirMc, "level.dat"); - if (levelDat.exists()) { - converter.copyLevelDat(levelDat); - } - converter.close(); - prompt( - "Conversion complete!\n" + - " - The world save is still being compacted, but you can close the program anytime\n" + - " - There will be another prompt when this finishes\n" + - "\n" + - "What is not converted?\n" + - " - Inventory is not copied\n" + - " - Some block nbt may not copy\n" + - " - Any custom generator settings may not work\n" + - " - May not match up with new terrain" - ); - converter.compact(); - prompt("Compaction complete!"); - } + MapConverter converter = MapConverter.get(dirMc, newWorldFile); + converter.accept(ConverterFrame.this); } catch (Throwable e) { e.printStackTrace(); prompt("[ERROR] Conversion failed, you will have to do it manually (Nukkit server + anvil2leveldb command)"); diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/LevelDBToMCAFile.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/LevelDBToMCAFile.java index f252cd05..3e3e0a7f 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/LevelDBToMCAFile.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/LevelDBToMCAFile.java @@ -1,33 +1,57 @@ package com.boydti.fawe.nukkit.core.converter; import com.boydti.fawe.Fawe; +import com.boydti.fawe.jnbt.anvil.MCAChunk; +import com.boydti.fawe.jnbt.anvil.MCAQueue; +import com.boydti.fawe.jnbt.anvil.MCAQueueMap; +import com.boydti.fawe.jnbt.anvil.MutableMCABackedBaseBlock; +import com.boydti.fawe.jnbt.anvil.filters.RemapFilter; import com.boydti.fawe.object.clipboard.ClipboardRemapper; +import com.boydti.fawe.object.io.PGZIPOutputStream; import com.boydti.fawe.util.MemUtil; +import com.boydti.fawe.util.ReflectionUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.sk89q.jnbt.ByteTag; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.IntTag; +import com.sk89q.jnbt.LongTag; +import com.sk89q.jnbt.NBTInputStream; +import com.sk89q.jnbt.NBTOutputStream; +import com.sk89q.jnbt.NamedTag; +import com.sk89q.jnbt.StringTag; import com.sk89q.worldedit.world.registry.BundledBlockData; -import java.io.Closeable; +import java.io.DataInput; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.Map; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import org.iq80.leveldb.DB; import org.iq80.leveldb.Options; import org.iq80.leveldb.impl.Iq80DBFactory; -public class LevelDBToMCAFile implements Closeable, Runnable{ +public class LevelDBToMCAFile extends MapConverter { private final DB db; private final ClipboardRemapper remapper; private final ForkJoinPool pool; - public LevelDBToMCAFile(File folder) { + public LevelDBToMCAFile(File from, File to) { + super(from, to); try { - this.pool = new ForkJoinPool(); - this.remapper = new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE); BundledBlockData.getInstance().loadFromResource(); + this.pool = new ForkJoinPool(); + this.remapper = new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PE, ClipboardRemapper.RemapPlatform.PC); int bufferSize = (int) Math.min(Integer.MAX_VALUE, Math.max((long) (MemUtil.getFreeBytes() * 0.8), 134217728)); - this.db = Iq80DBFactory.factory.open(new File(folder, "db"), + this.db = Iq80DBFactory.factory.open(new File(from, "db"), new Options() .createIfMissing(false) .verifyChecksums(false) @@ -57,17 +81,259 @@ public class LevelDBToMCAFile implements Closeable, Runnable{ } @Override - public void run() { - db.forEach(new Consumer>() { - @Override - public void accept(Map.Entry entry) { + public void accept(ConverterFrame app) { + try { + // World name + String worldName; + File levelName = new File(folderFrom, "levelname.txt"); + if (levelName.exists()) { + byte[] encoded = Files.readAllBytes(levelName.toPath()); + worldName = new String(encoded); + } else { + worldName = folderFrom.toString(); + } + File worldOut = new File(folderTo, worldName); + + // Level dat + File levelDat = new File(folderFrom, "level.dat"); + copyLevelDat(levelDat); + + + // Chunks + MCAQueue queue = new MCAQueue(worldName, new File(worldOut, "region"), true); + RemapFilter filter = new RemapFilter(this.remapper); + db.forEach(entry -> { byte[] key = entry.getKey(); - if (key.length != 10) { + Tag tag = Tag.get(key); + if (tag == null) { + if (key.length > 8) { + String name = new String(key); + } return; } -// byte[] value = entry.getValue(); + int cx = tag.getX(key); + int cz = tag.getZ(key); + byte[] value = entry.getValue(); + MCAChunk chunk = (MCAChunk) queue.getFaweChunk(cx, cz); + + switch (tag) { + case Data2D: + // height + ByteBuffer buffer = ByteBuffer.wrap(value); + int[] heightArray = chunk.getHeightMapArray(); + for (int i = 0, j = 0; i < heightArray.length; i++, j += 2) { + heightArray[i] = buffer.getShort(); + } + // biome + int biomeOffset = (heightArray.length << 1); + if (value.length > biomeOffset) { + System.arraycopy(value, biomeOffset, chunk.biomes, 0, chunk.biomes.length); + } + break; + case SubChunkPrefix: + int layer = key[9]; + byte[] ids = getOrCreate(chunk.ids, layer, 4096); + byte[] data = getOrCreate(chunk.data, layer, 2048); + byte[] blockLight = getOrCreate(chunk.blockLight, layer, 2048); + byte[] skyLight = getOrCreate(chunk.skyLight, layer, 2048); + + copySection(ids, value, 1); + copySection(data, value, 1 + 4096); + copySection(skyLight, value, 1 + 4096 + 2048); + copySection(blockLight, value, 1 + 4096 + 2048 + 2048); + + chunk.filterBlocks(new MutableMCABackedBaseBlock(), filter); + break; + case BlockEntity: + break; + case Entity: + break; + // Ignore + case LegacyTerrain: + case BiomeState: + case Data2DLegacy: + System.out.println("Legacy terrain not supported, please update."); + case FinalizedState: + case PendingTicks: + case BlockExtraData: + case Version: + break; + } + }); + MCAQueueMap map = (MCAQueueMap) queue.getFaweQueueMap(); + while (map.next(0, Long.MAX_VALUE)); + queue.clear(); + } catch (Throwable e) { + e.printStackTrace(); + } finally { + close(); + app.prompt("Compaction complete!"); + } + } + + private void copySection(byte[] dest, byte[] src, int srcPos) { + if (src.length <= srcPos) return; + switch (dest.length) { + case 4096: { + int index = 0; + int i1, i2, i3; + for (int y = 0; y < 16; y++) { + i1 = y; + for (int z = 0; z < 16; z++) { + i2 = i1 + (z << 4); + for (int x = 0; x < 16; x++) { + i3 = i2 + (x << 8); + dest[index] = src[srcPos + i3]; + index++; + } + } + } + break; } - }); - // TODO + case 2048: { + int index = 0; + int i1, i2, i3, i4; + for (int x = 0; x < 16;) { + { + i1 = x; + for (int z = 0; z < 16; z++) { + i2 = i1 + (z << 4); + for (int y = 0; y < 16; y += 2) { + i3 = i2 + (y << 8); + i4 = i2 + ((y + 1) << 8); + byte newVal = (byte) ((src[srcPos + (i3 >> 1)] & 0xF) + ((src[srcPos + (i4 >> 1)] & 0xF) << 4)); + dest[index] = newVal; + index++; + } + } + } + x++; + { + i1 = x; + for (int z = 0; z < 16; z++) { + i2 = i1 + (z << 4); + for (int y = 0; y < 16; y += 2) { + i3 = i2 + (y << 8); + i4 = i2 + ((y + 1) << 8); + byte newVal = (byte) (((src[srcPos + (i3 >> 1)] & 0xF0) >> 4) + ((src[srcPos + (i4 >> 1)] & 0xF0))); + dest[index] = newVal; + index++; + } + } + } + x++; + + } + break; + } + default: + System.arraycopy(src, srcPos, dest, 0, dest.length); + } + } + + private byte[] getOrCreate(byte[][] arr, int index, int len) { + byte[] data = arr[index]; + if (data == null) { + arr[index] = data = new byte[len]; + } + return data; + } + + public void copyLevelDat(File in) throws IOException { + File levelDat = new File(folderTo, "level.dat"); + if (!levelDat.exists()) { + levelDat.createNewFile(); + } + try (LittleEndianDataInputStream ledis = new LittleEndianDataInputStream(new FileInputStream(in))) { + int version = ledis.readInt(); // Ignored + int length = ledis.readInt(); // Ignored + NBTInputStream nis = new NBTInputStream((DataInput) ledis); + NamedTag named = nis.readNamedTag(); + com.sk89q.jnbt.CompoundTag tag = (CompoundTag) named.getTag(); + Map map = ReflectionUtils.getMap(tag.getValue()); + + Map gameRules = new HashMap<>(); + gameRules.put("firedamage", "firedamage"); + gameRules.put("falldamage", "falldamage"); + gameRules.put("dofiretick", "doFireTick"); + gameRules.put("drowningdamage", "drowningdamage"); + gameRules.put("doentitydrops", "doEntityDrops"); + gameRules.put("keepinventory", "keepInventory"); + gameRules.put("sendcommandfeedback", "sendCommandFeedback"); + gameRules.put("dodaylightcycle", "doDaylightCycle"); + gameRules.put("commandblockoutput", "commandBlockOutput"); + gameRules.put("domobloot", "doMobLoot"); + gameRules.put("domobspawning", "doMobSpawning"); + gameRules.put("doweathercycle", "doWeatherCycle"); + gameRules.put("mobgriefing", "mobGriefing"); + gameRules.put("dotiledrops", "doTileDrops"); + + HashMap ruleTagValue = new HashMap<>(); + for (Map.Entry rule : gameRules.entrySet()) { + com.sk89q.jnbt.Tag value = map.remove(rule.getKey()); + if (value instanceof ByteTag) { + value = new StringTag((Byte) value.getValue() == 1 ? "true" : "false"); + } + ruleTagValue.put(rule.getValue(), value); + } + + HashSet allowed = new HashSet<>(Arrays.asList( + "lightningTime", "pvp", "LevelName", "Difficulty", "GameType", "Generator", "LastPlayed", "RandomSeed", "StorageVersion", "Time", "commandsEnabled", "currentTick", "rainTime", "SpawnX", "SpawnY", "SpawnZ", "SizeOnDisk" + )); + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (!allowed.contains(entry.getKey())) { + System.out.println("TODO (Unsupported): " + entry.getKey() + " | " + entry.getValue()); + iterator.remove(); + } + } + + { + map.put("GameRules", new CompoundTag(ruleTagValue)); + + map.put("version", new IntTag(19133)); + map.put("DataVersion", new IntTag(1343)); + map.put("initialized", new ByteTag((byte) 1)); + map.putIfAbsent("SizeOnDisk", new LongTag(0)); + + // generator + int generator = tag.getInt("Generator"); + String name; + switch (generator) { + default: + case 1: + name = "default"; + break; + case 2: + name = "flat"; + break; + } + map.put("generatorName", new StringTag(name)); + map.put("generatorOptions", new StringTag("")); + map.put("generatorVersion", new IntTag(1)); + map.put("Difficulty", new ByteTag((byte) tag.getInt("Difficulty"))); + map.put("DifficultyLocked", new ByteTag((byte) 0)); + map.put("MapFeatures", new ByteTag((byte) 1)); + map.put("allowCommands", new ByteTag(tag.getByte("commandsEnabled"))); + long time = tag.getLong("Time"); + if (time == 0) time = tag.getLong("CurrentTick"); + map.put("Time", new LongTag(time)); + map.put("spawnMobs", new ByteTag((byte) 1)); + Long lastPlayed = tag.getLong("LastPlayed"); + if (lastPlayed != null && lastPlayed < Integer.MAX_VALUE) { + lastPlayed = lastPlayed * 1000; + map.put("LastPlayed", new LongTag(lastPlayed)); + } + + HashMap data = new HashMap<>(); + data.put("Data", new CompoundTag(map)); + CompoundTag root = new CompoundTag(data); + + try (NBTOutputStream nos = new NBTOutputStream(new PGZIPOutputStream(new FileOutputStream(levelDat)))) { + nos.writeNamedTag("level.dat", root); + } + } + } } } diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java index 1dc2352c..9511d40b 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java @@ -5,6 +5,7 @@ import com.boydti.fawe.config.BBC; import com.boydti.fawe.jnbt.anvil.MCAChunk; import com.boydti.fawe.jnbt.anvil.MCAFile; import com.boydti.fawe.jnbt.anvil.MCAFilter; +import com.boydti.fawe.jnbt.anvil.MCAQueue; import com.boydti.fawe.jnbt.anvil.filters.DelegateMCAFilter; import com.boydti.fawe.jnbt.anvil.filters.RemapFilter; import com.boydti.fawe.object.RunnableVal; @@ -26,7 +27,6 @@ import com.sk89q.jnbt.StringTag; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.world.registry.BundledBlockData; import java.io.ByteArrayOutputStream; -import java.io.Closeable; import java.io.DataOutput; import java.io.File; import java.io.FileInputStream; @@ -49,7 +49,7 @@ import org.iq80.leveldb.DB; import org.iq80.leveldb.Options; import org.iq80.leveldb.impl.Iq80DBFactory; -public class MCAFile2LevelDB implements Closeable { +public class MCAFile2LevelDB extends MapConverter { private final ByteStore bufFinalizedState = new ByteStore(4); private final ByteStore keyStore9 = new ByteStore(9); @@ -63,26 +63,25 @@ public class MCAFile2LevelDB implements Closeable { private final DB db; private final ClipboardRemapper remapper; private final ForkJoinPool pool; - private final File folder; private boolean closed; private LongAdder submitted = new LongAdder(); private boolean remap; - public MCAFile2LevelDB(File folder) { + public MCAFile2LevelDB(File folderFrom, File folderTo) { + super(folderFrom, folderTo); try { - this.folder = folder; - if (!folder.exists()) { - folder.mkdirs(); + if (!folderTo.exists()) { + folderTo.mkdirs(); } - String worldName = folder.getName(); - try (PrintStream out = new PrintStream(new FileOutputStream(new File(folder, "levelname.txt")))) { + String worldName = folderTo.getName(); + try (PrintStream out = new PrintStream(new FileOutputStream(new File(folderTo, "levelname.txt")))) { out.print(worldName); } this.pool = new ForkJoinPool(); this.remapper = new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE); BundledBlockData.getInstance().loadFromResource(); - this.db = Iq80DBFactory.factory.open(new File(folder, "db"), + this.db = Iq80DBFactory.factory.open(new File(folderTo, "db"), new Options() .createIfMissing(true) .verifyChecksums(false) @@ -116,6 +115,37 @@ public class MCAFile2LevelDB implements Closeable { return delegate; } + + @Override + public void accept(ConverterFrame app) { + MCAFilter filter = toFilter(); + MCAQueue queue = new MCAQueue(null, new File(folderFrom, "region"), true); + MCAFilter result = queue.filterWorld(filter); + + File levelDat = new File(folderFrom, "level.dat"); + if (levelDat.exists()) { + try { + copyLevelDat(levelDat); + } catch (IOException e) { + e.printStackTrace(); + } + } + close(); + app.prompt( + "Conversion complete!\n" + + " - The world save is still being compacted, but you can close the program anytime\n" + + " - There will be another prompt when this finishes\n" + + "\n" + + "What is not converted?\n" + + " - Inventory is not copied\n" + + " - Some block nbt may not copy\n" + + " - Any custom generator settings may not work\n" + + " - May not match up with new terrain" + ); + compact(); + app.prompt("Compaction complete!"); + } + @Override public void close() { try { @@ -133,7 +163,7 @@ public class MCAFile2LevelDB implements Closeable { public void compact() { // Since the library doesn't support it, only way to flush the cache is to loop over everything - try (DB newDb = Iq80DBFactory.factory.open(new File(folder, "db"), new Options() + try (DB newDb = Iq80DBFactory.factory.open(new File(folderTo, "db"), new Options() .verifyChecksums(false) .blockSize(262144) // 256K .cacheSize(8388608) // 8MB @@ -147,7 +177,7 @@ public class MCAFile2LevelDB implements Closeable { } public void copyLevelDat(File in) throws IOException { - File levelDat = new File(folder, "level.dat"); + File levelDat = new File(folderTo, "level.dat"); try (NBTInputStream nis = new NBTInputStream(new GZIPInputStream(new FileInputStream(in)))) { if (!levelDat.exists()) { levelDat.createNewFile(); @@ -175,7 +205,7 @@ public class MCAFile2LevelDB implements Closeable { map.put(key, new ByteTag((byte) (value.equals("true") ? 1 : 0))); } } - map.put("LevelName", new StringTag(folder.getName())); + map.put("LevelName", new StringTag(folderTo.getName())); map.put("StorageVersion", new IntTag(5)); Byte difficulty = tag.getByte("Difficulty"); map.put("Difficulty", new IntTag(difficulty == null ? 2 : difficulty)); @@ -300,7 +330,6 @@ public class MCAFile2LevelDB implements Closeable { } private void copySection(byte[] src, byte[] dest, int destPos) { - int len = src.length; switch (src.length) { case 4096: { int index = 0; @@ -355,41 +384,7 @@ public class MCAFile2LevelDB implements Closeable { break; } default: - System.arraycopy(src, 0, dest, destPos, len); - } - } - - private enum Tag { - Data2D(45), - @Deprecated Data2DLegacy(46), - SubChunkPrefix(47), - @Deprecated LegacyTerrain(48), - BlockEntity(49), - Entity(50), - PendingTicks(51), - BlockExtraData(52), - BiomeState(53), - FinalizedState(54), - Version(118), - - ; - public final byte value; - - Tag(int value) { - this.value = (byte) value; - } - - public byte[] fill(int chunkX, int chunkZ, byte[] key) { - key[0] = (byte) (chunkX & 255); - key[1] = (byte) (chunkX >>> 8 & 255); - key[2] = (byte) (chunkX >>> 16 & 255); - key[3] = (byte) (chunkX >>> 24 & 255); - key[4] = (byte) (chunkZ & 255); - key[5] = (byte) (chunkZ >>> 8 & 255); - key[6] = (byte) (chunkZ >>> 16 & 255); - key[7] = (byte) (chunkZ >>> 24 & 255); - key[8] = value; - return key; + System.arraycopy(src, 0, dest, destPos, src.length); } } diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MapConverter.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MapConverter.java new file mode 100644 index 00000000..a6a2c802 --- /dev/null +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MapConverter.java @@ -0,0 +1,79 @@ +package com.boydti.fawe.nukkit.core.converter; + +import java.io.Closeable; +import java.io.File; +import java.util.function.Consumer; + +public abstract class MapConverter implements Consumer, Closeable { + + protected final File folderTo; + protected final File folderFrom; + + public MapConverter(File worldFrom, File worldTo) { + this.folderFrom = worldFrom; + this.folderTo = worldTo; + } + + public static MapConverter get(File worldFrom, File worldTo) { + if (new File(worldFrom, "db").exists()) { + return new LevelDBToMCAFile(worldFrom, worldTo); + } else { + return new MCAFile2LevelDB(worldFrom, worldTo); + } + } + + private static Tag[] tags = new Tag[256]; + + public enum Tag { + Data2D(45), + @Deprecated Data2DLegacy(46), + SubChunkPrefix(47), + @Deprecated LegacyTerrain(48), + BlockEntity(49), + Entity(50), + PendingTicks(51), + BlockExtraData(52), + BiomeState(53), + FinalizedState(54), + Version(118), + + ; + + public final byte value; + + Tag(int value) { + this.value = (byte) value; + tags[value & 0xFF] = this; + } + + public static Tag valueOf(byte key) { + return tags[key & 0xFF]; + } + + public static Tag get(byte[] key) { + if (key.length != 9 && key.length != 10) return null; + return valueOf(key[8]); + } + + public int getX(byte[] key) { + return ((key[3] & 0xFF << 24) + (key[2] & 0xFF << 16) + (key[1] & 0xFF << 8) + (key[0] & 0xFF << 0)); + } + + public int getZ(byte[] key) { + return ((key[7] & 0xFF << 24) + (key[6] & 0xFF << 16) + (key[5] & 0xFF << 8) + (key[4] & 0xFF << 0)); + } + + public byte[] fill(int chunkX, int chunkZ, byte[] key) { + key[0] = (byte) (chunkX & 255); + key[1] = (byte) (chunkX >>> 8 & 255); + key[2] = (byte) (chunkX >>> 16 & 255); + key[3] = (byte) (chunkX >>> 24 & 255); + key[4] = (byte) (chunkZ & 255); + key[5] = (byte) (chunkZ >>> 8 & 255); + key[6] = (byte) (chunkZ >>> 16 & 255); + key[7] = (byte) (chunkZ >>> 24 & 255); + key[8] = value; + return key; + } + } +}