MCPE fixes

Fix some MCPE mappings
Support MCPE -> PC
This commit is contained in:
Jesse Boyd 2017-09-30 15:40:02 +10:00
parent ad593deb0f
commit 845ea0e9dc
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
14 changed files with 578 additions and 171 deletions

View File

@ -737,7 +737,6 @@ public class FaweCache {
return map; return map;
} }
public static ShortTag asTag(short value) { public static ShortTag asTag(short value) {
return new ShortTag(value); return new ShortTag(value);
} }

View File

@ -19,7 +19,9 @@ import com.intellectualcrafters.plot.PS;
import com.intellectualcrafters.plot.commands.Auto; import com.intellectualcrafters.plot.commands.Auto;
import com.intellectualcrafters.plot.config.C; import com.intellectualcrafters.plot.config.C;
import com.intellectualcrafters.plot.config.Settings; import com.intellectualcrafters.plot.config.Settings;
import com.intellectualcrafters.plot.database.DBFunc;
import com.intellectualcrafters.plot.object.Plot; import com.intellectualcrafters.plot.object.Plot;
import com.intellectualcrafters.plot.object.PlotArea;
import com.intellectualcrafters.plot.object.PlotId; import com.intellectualcrafters.plot.object.PlotId;
import com.intellectualcrafters.plot.object.PlotPlayer; import com.intellectualcrafters.plot.object.PlotPlayer;
import com.intellectualcrafters.plot.object.worlds.PlotAreaManager; import com.intellectualcrafters.plot.object.worlds.PlotAreaManager;
@ -139,6 +141,23 @@ public class CFICommands extends MethodCommands {
fp.sendMessage(BBC.getPrefix() + "Cancelled!"); fp.sendMessage(BBC.getPrefix() + "Cancelled!");
} }
@Deprecated
public static void autoClaimFromDatabase(PlotPlayer player, PlotArea area, PlotId start, com.intellectualcrafters.plot.object.RunnableVal<Plot> 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( @Command(
aliases = {"done", "create"}, aliases = {"done", "create"},
usage = "", usage = "",
@ -164,6 +183,7 @@ public class CFICommands extends MethodCommands {
C.CANT_CLAIM_MORE_PLOTS_NUM.send(player, -diff); C.CANT_CLAIM_MORE_PLOTS_NUM.send(player, -diff);
return; return;
} }
if (area.getMeta("lastPlot") == null) { if (area.getMeta("lastPlot") == null) {
area.setMeta("lastPlot", new PlotId(0, 0)); area.setMeta("lastPlot", new PlotId(0, 0));
} }

View File

@ -530,6 +530,35 @@ public class MCAChunk extends FaweChunk<Void> {
streamer.readFully(); 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() { public int[] getHeightMapArray() {
return heightMap; return heightMap;
} }

View File

@ -10,6 +10,7 @@ import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.exception.FaweException; import com.boydti.fawe.object.exception.FaweException;
import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.SetQueue;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
@ -40,7 +41,7 @@ public class MCAQueueMap implements IFaweQueueMap {
private int lastX = Integer.MIN_VALUE; private int lastX = Integer.MIN_VALUE;
private int lastZ = 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 mcaX = cx >> 5;
int mcaZ = cz >> 5; int mcaZ = cz >> 5;
if (mcaX == lastFileX && mcaZ == lastFileZ) { if (mcaX == lastFileX && mcaZ == lastFileZ) {
@ -52,7 +53,13 @@ public class MCAQueueMap implements IFaweQueueMap {
if (lastFile == null) { if (lastFile == null) {
try { try {
queue.setMCA(lastFileX, lastFileZ, RegionWrapper.GLOBAL(), null, true, false); 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) { } catch (FaweException.FaweChunkLoadException ignore) {
lastFile = null; lastFile = null;
return null; return null;
@ -85,8 +92,7 @@ public class MCAQueueMap implements IFaweQueueMap {
} }
} }
@Override public FaweChunk getFaweChunk(int cx, int cz, boolean create) {
public FaweChunk getFaweChunk(int cx, int cz) {
if (cx == lastX && cz == lastZ) { if (cx == lastX && cz == lastZ) {
if (nullChunk == lastChunk) { if (nullChunk == lastChunk) {
nullChunk.setLoc(queue, lastX, lastZ); nullChunk.setLoc(queue, lastX, lastZ);
@ -103,12 +109,17 @@ public class MCAQueueMap implements IFaweQueueMap {
} }
} }
try { try {
MCAFile mcaFile = getMCAFile(cx, cz); MCAFile mcaFile = getMCAFile(cx, cz, create);
if (mcaFile != null) { if (mcaFile != null) {
mcaFile.init(); mcaFile.init();
lastChunk = mcaFile.getChunk(cx, cz); lastChunk = mcaFile.getChunk(cx, cz);
if (lastChunk != null) { if (lastChunk != null) {
return lastChunk; return lastChunk;
} else if (create) {
MCAChunk chunk = new MCAChunk(queue, cx, cz);
mcaFile.setChunk(chunk);
lastChunk = chunk;
return chunk;
} }
} }
} catch (Throwable ignore) { } catch (Throwable ignore) {
@ -122,6 +133,11 @@ public class MCAQueueMap implements IFaweQueueMap {
return lastChunk = nullChunk; return lastChunk = nullChunk;
} }
@Override
public FaweChunk getFaweChunk(int cx, int cz) {
return getFaweChunk(cx, cz, !isHybridQueue);
}
@Override @Override
public FaweChunk getCachedFaweChunk(int cx, int cz) { public FaweChunk getCachedFaweChunk(int cx, int cz) {
int mcaX = cx >> 5; int mcaX = cx >> 5;
@ -171,6 +187,7 @@ public class MCAQueueMap implements IFaweQueueMap {
lastZ = Integer.MIN_VALUE; lastZ = Integer.MIN_VALUE;
lastFileX = Integer.MIN_VALUE; lastFileX = Integer.MIN_VALUE;
lastFileZ = Integer.MIN_VALUE; lastFileZ = Integer.MIN_VALUE;
System.out.println("Files " + mcaFileMap);
if (!mcaFileMap.isEmpty()) { if (!mcaFileMap.isEmpty()) {
Iterator<Map.Entry<Long, MCAFile>> iter = mcaFileMap.entrySet().iterator(); Iterator<Map.Entry<Long, MCAFile>> iter = mcaFileMap.entrySet().iterator();
boolean result; boolean result;

View File

@ -48,7 +48,7 @@ public class RemapFilter extends MCAFilterCounter {
@Override @Override
public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong cache) { public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong cache) {
int id = block.getId(); int id = block.getId();
if (remapper.hasRemap(block.getId())) { if (remapper.hasRemap(id)) {
BaseBlock result = remapper.remap(block); BaseBlock result = remapper.remap(block);
if (result != block) { if (result != block) {
cache.add(1); cache.add(1);
@ -63,6 +63,7 @@ public class RemapFilter extends MCAFilterCounter {
block.setData(result.getData()); block.setData(result.getData());
} }
} }
if (from != null) {
outer: outer:
switch (from) { switch (from) {
case PC: { case PC: {
@ -122,7 +123,8 @@ public class RemapFilter extends MCAFilterCounter {
if (z < 16) { if (z < 16) {
if ((newLight = chunk.getSkyLight(x, y, z + 1)) > currentLight) break; if ((newLight = chunk.getSkyLight(x, y, z + 1)) > currentLight) break;
} }
default: break outer; default:
break outer;
} }
if (newLight != 0) { if (newLight != 0) {
((MutableMCABackedBaseBlock) block).getChunk().setSkyLight(x, y, z, newLight); ((MutableMCABackedBaseBlock) block).getChunk().setSkyLight(x, y, z, newLight);
@ -132,3 +134,4 @@ public class RemapFilter extends MCAFilterCounter {
} }
} }
} }
}

View File

@ -10,6 +10,7 @@ import com.sk89q.worldedit.regions.Region;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static com.boydti.fawe.FaweCache.getBlock;
public class ClipboardRemapper { public class ClipboardRemapper {
public enum RemapPlatform { public enum RemapPlatform {
@ -65,6 +66,11 @@ public class ClipboardRemapper {
mapPEtoPC.put(new BaseBlock(peId,5), new BaseBlock(pcId,3)); 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(236,-1), new BaseBlock(251,-1));
mapPEtoPC.put(new BaseBlock(237,-1), new BaseBlock(252,-1)); mapPEtoPC.put(new BaseBlock(237,-1), new BaseBlock(252,-1));
mapPEtoPC.put(new BaseBlock(240,-1), new BaseBlock(199,-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 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 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 = 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)); 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 { public void apply(Clipboard clipboard) throws WorldEditException {
if (clipboard instanceof BlockArrayClipboard) { if (clipboard instanceof BlockArrayClipboard) {
BlockArrayClipboard bac = (BlockArrayClipboard) clipboard; BlockArrayClipboard bac = (BlockArrayClipboard) clipboard;
@ -214,8 +226,6 @@ public class ClipboardRemapper {
} }
} }
public BaseBlock remap(BaseBlock block) { public BaseBlock remap(BaseBlock block) {
int combined = block.getCombined(); int combined = block.getCombined();
if (remap[combined]) { if (remap[combined]) {

View File

@ -496,8 +496,8 @@ public class MainUtil {
private static final Class[] parameters = new Class[]{URL.class}; private static final Class[] parameters = new Class[]{URL.class};
public static void loadURLClasspath(URL u) throws IOException { public static void loadURLClasspath(URL u) throws IOException {
ClassLoader sysloader = ClassLoader.getSystemClassLoader();
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class; Class sysclass = URLClassLoader.class;
try { try {

View File

@ -316,8 +316,12 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
if (this.limit.SPEED_REDUCTION > 0) { if (this.limit.SPEED_REDUCTION > 0) {
this.bypassHistory = new SlowExtent(this.bypassHistory, this.limit.SPEED_REDUCTION); 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 (!(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); changeSet = LoggingChangeSet.wrap(player, changeSet);
} }
if (this.blockBag != null) { if (this.blockBag != null) {
@ -1269,6 +1273,15 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
editSession.changes = 1; 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. * Sets to new state.
* *

View File

@ -52,6 +52,7 @@ import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.Masks; 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.history.changeset.ChangeSet;
import com.sk89q.worldedit.internal.cui.CUIEvent; import com.sk89q.worldedit.internal.cui.CUIEvent;
import com.sk89q.worldedit.internal.cui.CUIRegion; import com.sk89q.worldedit.internal.cui.CUIRegion;
@ -516,13 +517,13 @@ public class LocalSession {
EditSession newEditSession = new EditSessionBuilder(changeSet.getWorld()) EditSession newEditSession = new EditSessionBuilder(changeSet.getWorld())
.allowedRegionsEverywhere() .allowedRegionsEverywhere()
.checkMemory(false) .checkMemory(false)
.changeSet(changeSet) .changeSetNull()
.fastmode(false) .fastmode(false)
.limitUnprocessed(fp) .limitUnprocessed(fp)
.player(fp) .player(fp)
.blockBag(getBlockBag(player)) .blockBag(getBlockBag(player))
.build(); .build();
newEditSession.undo(newEditSession); newEditSession.setBlocks(changeSet, ChangeSetExecutor.Type.UNDO);
setDirty(); setDirty();
historyNegativeIndex++; historyNegativeIndex++;
return newEditSession; return newEditSession;
@ -565,13 +566,13 @@ public class LocalSession {
EditSession newEditSession = new EditSessionBuilder(changeSet.getWorld()) EditSession newEditSession = new EditSessionBuilder(changeSet.getWorld())
.allowedRegionsEverywhere() .allowedRegionsEverywhere()
.checkMemory(false) .checkMemory(false)
.changeSet(changeSet) .changeSetNull()
.fastmode(false) .fastmode(false)
.limitUnprocessed(fp) .limitUnprocessed(fp)
.player(fp) .player(fp)
.blockBag(getBlockBag(player)) .blockBag(getBlockBag(player))
.build(); .build();
newEditSession.redo(newEditSession); newEditSession.setBlocks(changeSet, ChangeSetExecutor.Type.REDO);
return newEditSession; return newEditSession;
} }
return null; return null;

View File

@ -41,7 +41,7 @@ public class ConvertCommands extends MethodCommands {
RemapFilter filter = new RemapFilter(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE); RemapFilter filter = new RemapFilter(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE);
FaweQueue defaultQueue = SetQueue.IMP.getNewQueue(folder, true, false); 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<MutableLong> delegate = new DelegateMCAFilter<MutableLong>(filter) { DelegateMCAFilter<MutableLong> delegate = new DelegateMCAFilter<MutableLong>(filter) {
@Override @Override

View File

@ -11,8 +11,6 @@ import com.boydti.fawe.installer.MinimizeButton;
import com.boydti.fawe.installer.MovablePanel; import com.boydti.fawe.installer.MovablePanel;
import com.boydti.fawe.installer.TextAreaOutputStream; import com.boydti.fawe.installer.TextAreaOutputStream;
import com.boydti.fawe.installer.URLButton; 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.util.MainUtil;
import com.boydti.fawe.wrappers.FakePlayer; import com.boydti.fawe.wrappers.FakePlayer;
import java.awt.BorderLayout; import java.awt.BorderLayout;
@ -88,7 +86,7 @@ public class ConverterFrame extends JFrame {
JPanel topBarCenter = new InvisiblePanel(); JPanel topBarCenter = new InvisiblePanel();
JPanel topBarRight = 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.setHorizontalAlignment(SwingConstants.CENTER);
title.setAlignmentX(Component.RIGHT_ALIGNMENT); title.setAlignmentX(Component.RIGHT_ALIGNMENT);
title.setForeground(Color.LIGHT_GRAY); title.setForeground(Color.LIGHT_GRAY);
@ -345,32 +343,9 @@ public class ConverterFrame extends JFrame {
MainUtil.loadURLClasspath(leveldb.toURL()); MainUtil.loadURLClasspath(leveldb.toURL());
File newWorldFile = new File(output, dirMc.getName()); File newWorldFile = new File(output, dirMc.getName());
try (MCAFile2LevelDB converter = new MCAFile2LevelDB(newWorldFile)) {
debug("Starting world conversion");
MCAFilter filter = converter.toFilter(); MapConverter converter = MapConverter.get(dirMc, newWorldFile);
MCAQueue queue = new MCAQueue(null, new File(dirMc, "region"), true); converter.accept(ConverterFrame.this);
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!");
}
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
prompt("[ERROR] Conversion failed, you will have to do it manually (Nukkit server + anvil2leveldb command)"); prompt("[ERROR] Conversion failed, you will have to do it manually (Nukkit server + anvil2leveldb command)");

View File

@ -1,33 +1,57 @@
package com.boydti.fawe.nukkit.core.converter; package com.boydti.fawe.nukkit.core.converter;
import com.boydti.fawe.Fawe; 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.clipboard.ClipboardRemapper;
import com.boydti.fawe.object.io.PGZIPOutputStream;
import com.boydti.fawe.util.MemUtil; 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 com.sk89q.worldedit.world.registry.BundledBlockData;
import java.io.Closeable; import java.io.DataInput;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; 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.Map;
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.iq80.leveldb.DB; import org.iq80.leveldb.DB;
import org.iq80.leveldb.Options; import org.iq80.leveldb.Options;
import org.iq80.leveldb.impl.Iq80DBFactory; import org.iq80.leveldb.impl.Iq80DBFactory;
public class LevelDBToMCAFile implements Closeable, Runnable{ public class LevelDBToMCAFile extends MapConverter {
private final DB db; private final DB db;
private final ClipboardRemapper remapper; private final ClipboardRemapper remapper;
private final ForkJoinPool pool; private final ForkJoinPool pool;
public LevelDBToMCAFile(File folder) { public LevelDBToMCAFile(File from, File to) {
super(from, to);
try { try {
this.pool = new ForkJoinPool();
this.remapper = new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE);
BundledBlockData.getInstance().loadFromResource(); 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)); 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() new Options()
.createIfMissing(false) .createIfMissing(false)
.verifyChecksums(false) .verifyChecksums(false)
@ -57,17 +81,259 @@ public class LevelDBToMCAFile implements Closeable, Runnable{
} }
@Override @Override
public void run() { public void accept(ConverterFrame app) {
db.forEach(new Consumer<Map.Entry<byte[], byte[]>>() { try {
@Override // World name
public void accept(Map.Entry<byte[], byte[]> entry) { 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(); 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; 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;
} }
}); });
// TODO 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;
}
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<String, com.sk89q.jnbt.Tag> map = ReflectionUtils.getMap(tag.getValue());
Map<String, String> 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<String, com.sk89q.jnbt.Tag> ruleTagValue = new HashMap<>();
for (Map.Entry<String, String> 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<String> allowed = new HashSet<>(Arrays.asList(
"lightningTime", "pvp", "LevelName", "Difficulty", "GameType", "Generator", "LastPlayed", "RandomSeed", "StorageVersion", "Time", "commandsEnabled", "currentTick", "rainTime", "SpawnX", "SpawnY", "SpawnZ", "SizeOnDisk"
));
Iterator<Map.Entry<String, com.sk89q.jnbt.Tag>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, com.sk89q.jnbt.Tag> 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<String, com.sk89q.jnbt.Tag> 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);
}
}
}
} }
} }

View File

@ -5,6 +5,7 @@ import com.boydti.fawe.config.BBC;
import com.boydti.fawe.jnbt.anvil.MCAChunk; import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFile; import com.boydti.fawe.jnbt.anvil.MCAFile;
import com.boydti.fawe.jnbt.anvil.MCAFilter; 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.DelegateMCAFilter;
import com.boydti.fawe.jnbt.anvil.filters.RemapFilter; import com.boydti.fawe.jnbt.anvil.filters.RemapFilter;
import com.boydti.fawe.object.RunnableVal; 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.blocks.BaseBlock;
import com.sk89q.worldedit.world.registry.BundledBlockData; import com.sk89q.worldedit.world.registry.BundledBlockData;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataOutput; import java.io.DataOutput;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -49,7 +49,7 @@ import org.iq80.leveldb.DB;
import org.iq80.leveldb.Options; import org.iq80.leveldb.Options;
import org.iq80.leveldb.impl.Iq80DBFactory; 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 bufFinalizedState = new ByteStore(4);
private final ByteStore keyStore9 = new ByteStore(9); private final ByteStore keyStore9 = new ByteStore(9);
@ -63,26 +63,25 @@ public class MCAFile2LevelDB implements Closeable {
private final DB db; private final DB db;
private final ClipboardRemapper remapper; private final ClipboardRemapper remapper;
private final ForkJoinPool pool; private final ForkJoinPool pool;
private final File folder;
private boolean closed; private boolean closed;
private LongAdder submitted = new LongAdder(); private LongAdder submitted = new LongAdder();
private boolean remap; private boolean remap;
public MCAFile2LevelDB(File folder) { public MCAFile2LevelDB(File folderFrom, File folderTo) {
super(folderFrom, folderTo);
try { try {
this.folder = folder; if (!folderTo.exists()) {
if (!folder.exists()) { folderTo.mkdirs();
folder.mkdirs();
} }
String worldName = folder.getName(); String worldName = folderTo.getName();
try (PrintStream out = new PrintStream(new FileOutputStream(new File(folder, "levelname.txt")))) { try (PrintStream out = new PrintStream(new FileOutputStream(new File(folderTo, "levelname.txt")))) {
out.print(worldName); out.print(worldName);
} }
this.pool = new ForkJoinPool(); this.pool = new ForkJoinPool();
this.remapper = new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE); this.remapper = new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE);
BundledBlockData.getInstance().loadFromResource(); BundledBlockData.getInstance().loadFromResource();
this.db = Iq80DBFactory.factory.open(new File(folder, "db"), this.db = Iq80DBFactory.factory.open(new File(folderTo, "db"),
new Options() new Options()
.createIfMissing(true) .createIfMissing(true)
.verifyChecksums(false) .verifyChecksums(false)
@ -116,6 +115,37 @@ public class MCAFile2LevelDB implements Closeable {
return delegate; 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 @Override
public void close() { public void close() {
try { try {
@ -133,7 +163,7 @@ public class MCAFile2LevelDB implements Closeable {
public void compact() { public void compact() {
// Since the library doesn't support it, only way to flush the cache is to loop over everything // 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) .verifyChecksums(false)
.blockSize(262144) // 256K .blockSize(262144) // 256K
.cacheSize(8388608) // 8MB .cacheSize(8388608) // 8MB
@ -147,7 +177,7 @@ public class MCAFile2LevelDB implements Closeable {
} }
public void copyLevelDat(File in) throws IOException { 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)))) { try (NBTInputStream nis = new NBTInputStream(new GZIPInputStream(new FileInputStream(in)))) {
if (!levelDat.exists()) { if (!levelDat.exists()) {
levelDat.createNewFile(); levelDat.createNewFile();
@ -175,7 +205,7 @@ public class MCAFile2LevelDB implements Closeable {
map.put(key, new ByteTag((byte) (value.equals("true") ? 1 : 0))); 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)); map.put("StorageVersion", new IntTag(5));
Byte difficulty = tag.getByte("Difficulty"); Byte difficulty = tag.getByte("Difficulty");
map.put("Difficulty", new IntTag(difficulty == null ? 2 : 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) { private void copySection(byte[] src, byte[] dest, int destPos) {
int len = src.length;
switch (src.length) { switch (src.length) {
case 4096: { case 4096: {
int index = 0; int index = 0;
@ -355,41 +384,7 @@ public class MCAFile2LevelDB implements Closeable {
break; break;
} }
default: default:
System.arraycopy(src, 0, dest, destPos, len); System.arraycopy(src, 0, dest, destPos, src.length);
}
}
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;
} }
} }

View File

@ -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<ConverterFrame>, 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;
}
}
}