Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-11-17 00:20:09 +01:00
MVP for anvil
Dieser Commit ist enthalten in:
Ursprung
5c975eda3c
Commit
e3ea0c2b4a
@ -630,6 +630,16 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
|
|||||||
return CraftItemStack.asCraftMirror(stack);
|
return CraftItemStack.asCraftMirror(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] getIbdToStateOrdinal() {
|
||||||
|
return new char[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getOrdinalToIbdID() {
|
||||||
|
return new int[0];
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
|
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
|
||||||
final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack);
|
final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack);
|
||||||
|
@ -629,6 +629,16 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
|
|||||||
return CraftItemStack.asCraftMirror(stack);
|
return CraftItemStack.asCraftMirror(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] getIbdToStateOrdinal() {
|
||||||
|
return new char[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getOrdinalToIbdID() {
|
||||||
|
return new int[0];
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
|
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
|
||||||
final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack);
|
final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack);
|
||||||
|
@ -644,6 +644,16 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
|
|||||||
return CraftItemStack.asCraftMirror(stack);
|
return CraftItemStack.asCraftMirror(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] getIbdToStateOrdinal() {
|
||||||
|
return new char[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getOrdinalToIbdID() {
|
||||||
|
return new int[0];
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
|
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
|
||||||
var registryAccess = DedicatedServer.getServer().registryAccess();
|
var registryAccess = DedicatedServer.getServer().registryAccess();
|
||||||
|
@ -654,6 +654,16 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
|
|||||||
return CraftItemStack.asCraftMirror(stack);
|
return CraftItemStack.asCraftMirror(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] getIbdToStateOrdinal() {
|
||||||
|
return new char[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getOrdinalToIbdID() {
|
||||||
|
return new int[0];
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
|
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
|
||||||
var registryAccess = DedicatedServer.getServer().registryAccess();
|
var registryAccess = DedicatedServer.getServer().registryAccess();
|
||||||
|
@ -45,6 +45,7 @@ import org.bukkit.plugin.Plugin;
|
|||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -312,6 +313,16 @@ public class FaweBukkit implements IFawe, Listener {
|
|||||||
return platformAdapter;
|
return platformAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path getWorldsFolder() {
|
||||||
|
return Bukkit.getWorldContainer().toPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWorldLoaded(String world) {
|
||||||
|
return Bukkit.getWorld(world) != null;
|
||||||
|
}
|
||||||
|
|
||||||
private void setupPlotSquared() {
|
private void setupPlotSquared() {
|
||||||
Plugin plotSquared = this.plugin.getServer().getPluginManager().getPlugin("PlotSquared");
|
Plugin plotSquared = this.plugin.getServer().getPluginManager().getPlugin("PlotSquared");
|
||||||
if (plotSquared == null) {
|
if (plotSquared == null) {
|
||||||
|
@ -100,8 +100,4 @@ public abstract class CachedBukkitAdapter implements IBukkitAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract char[] getIbdToStateOrdinal();
|
|
||||||
|
|
||||||
protected abstract int[] getOrdinalToIbdID();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -396,4 +396,14 @@ public interface IBukkitAdapter {
|
|||||||
return TaskManager.taskManager().sync(world::getEntities);
|
return TaskManager.taskManager().sync(world::getEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a char array of minecraft internal IDs against FAWE char IDs
|
||||||
|
*/
|
||||||
|
char[] getIbdToStateOrdinal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an int array of FAWE char IDs against minecraft internal IDs
|
||||||
|
*/
|
||||||
|
int[] getOrdinalToIbdID();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,12 @@ public class SimpleBukkitAdapter extends CachedBukkitAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected char[] getIbdToStateOrdinal() {
|
public char[] getIbdToStateOrdinal() {
|
||||||
return new char[Character.MAX_VALUE + 1];
|
return new char[Character.MAX_VALUE + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int[] getOrdinalToIbdID() {
|
public int[] getOrdinalToIbdID() {
|
||||||
return new int[Character.MAX_VALUE + 1];
|
return new int[Character.MAX_VALUE + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,4 +495,20 @@ public enum BukkitAdapter {
|
|||||||
return getAdapter().adapt(item);
|
return getAdapter().adapt(item);
|
||||||
//FAWE end
|
//FAWE end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//FAWE start
|
||||||
|
/**
|
||||||
|
* Get a char array of minecraft internal IDs against FAWE char IDs
|
||||||
|
*/
|
||||||
|
public static char[] getIbdToStateOrdinal() {
|
||||||
|
return getAdapter().getIbdToStateOrdinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an int array of FAWE char IDs against minecraft internal IDs
|
||||||
|
*/
|
||||||
|
public static int[] getOrdinalToIbdID() {
|
||||||
|
return getAdapter().getOrdinalToIbdID();
|
||||||
|
}
|
||||||
|
//FAWE end
|
||||||
}
|
}
|
||||||
|
@ -309,5 +309,17 @@ public class BukkitServerInterface extends AbstractPlatform implements MultiUser
|
|||||||
}
|
}
|
||||||
return this.plugin.getBukkitImplAdapter().getTickingPostProcessor();
|
return this.plugin.getBukkitImplAdapter().getTickingPostProcessor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public char[] getIbdToStateOrdinal() {
|
||||||
|
return BukkitAdapter.getIbdToStateOrdinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public int[] getOrdinalToIbdID() {
|
||||||
|
return BukkitAdapter.getOrdinalToIbdID();
|
||||||
|
}
|
||||||
//FAWE end
|
//FAWE end
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import com.fastasyncworldedit.core.util.image.ImageViewer;
|
|||||||
import com.sk89q.worldedit.entity.Player;
|
import com.sk89q.worldedit.entity.Player;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -49,4 +50,8 @@ public interface IFawe {
|
|||||||
|
|
||||||
FAWEPlatformAdapterImpl getPlatformAdapter();
|
FAWEPlatformAdapterImpl getPlatformAdapter();
|
||||||
|
|
||||||
|
Path getWorldsFolder();
|
||||||
|
|
||||||
|
boolean isWorldLoaded(String world);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
1144
worldedit-core/src/main/java/com/fastasyncworldedit/core/anvil/MCAChunk.java
Normale Datei
1144
worldedit-core/src/main/java/com/fastasyncworldedit/core/anvil/MCAChunk.java
Normale Datei
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
@ -0,0 +1,594 @@
|
|||||||
|
package com.fastasyncworldedit.core.anvil;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.math.FastBitSet;
|
||||||
|
import com.fastasyncworldedit.core.util.task.RunnableVal4;
|
||||||
|
import com.plotsquared.core.util.task.RunnableVal;
|
||||||
|
import com.sk89q.jnbt.NBTInputStream;
|
||||||
|
import com.sk89q.worldedit.MissingWorldException;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.Flushable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
import java.util.zip.InflaterInputStream;
|
||||||
|
|
||||||
|
@SuppressWarnings({"removal"})
|
||||||
|
public class MCAFile implements Closeable, Flushable {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
|
private static final int CHUNK_HEADER_SIZE = 5;
|
||||||
|
private static final byte VERSION_GZIP = 1;
|
||||||
|
private static final byte VERSION_DEFLATE = 2;
|
||||||
|
private static final byte VERSION_UNCOMPRESSED = 3;
|
||||||
|
private static final int SECTOR_BYTES = 4096;
|
||||||
|
private static final int SECTOR_INTS = SECTOR_BYTES / 4;
|
||||||
|
|
||||||
|
private final Int2IntOpenHashMap offsetMap;
|
||||||
|
private final Path file;
|
||||||
|
private RandomAccessFile raf;
|
||||||
|
private int[] offsets;
|
||||||
|
private boolean deleted;
|
||||||
|
private final int X, Z;
|
||||||
|
private final Int2ObjectOpenHashMap<MCAChunk> chunks = new Int2ObjectOpenHashMap<>();
|
||||||
|
private FastBitSet sectorFree;
|
||||||
|
private boolean closed = false;
|
||||||
|
private volatile boolean init = false;
|
||||||
|
|
||||||
|
public MCAFile(Path file) {
|
||||||
|
this.file = file;
|
||||||
|
if (!Files.exists(file)) {
|
||||||
|
throw new MissingWorldException();
|
||||||
|
}
|
||||||
|
String[] split = file.getFileName().toString().split("\\.");
|
||||||
|
X = Integer.parseInt(split[1]);
|
||||||
|
Z = Integer.parseInt(split[2]);
|
||||||
|
offsetMap = new Int2IntOpenHashMap();
|
||||||
|
offsetMap.defaultReturnValue(Integer.MAX_VALUE);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MCAFile(int mcrX, int mcrZ, Path file) {
|
||||||
|
this.file = file;
|
||||||
|
X = mcrX;
|
||||||
|
Z = mcrZ;
|
||||||
|
offsetMap = new Int2IntOpenHashMap();
|
||||||
|
offsetMap.defaultReturnValue(Integer.MAX_VALUE);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
if (raf != null) {
|
||||||
|
try {
|
||||||
|
raf.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronized (chunks) {
|
||||||
|
chunks.clear();
|
||||||
|
}
|
||||||
|
offsetMap.clear();
|
||||||
|
offsets = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set if the file should be delete
|
||||||
|
*/
|
||||||
|
public void setDeleted(boolean deleted) {
|
||||||
|
if (!init) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
this.deleted = deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get if the file has been set to be deleted
|
||||||
|
*/
|
||||||
|
public boolean isDeleted() {
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the RandomAccessFile and loads the location header from disk if not done yet
|
||||||
|
*/
|
||||||
|
public synchronized void init() {
|
||||||
|
try {
|
||||||
|
if (raf == null) {
|
||||||
|
this.offsets = new int[SECTOR_INTS];
|
||||||
|
if (file != null) {
|
||||||
|
this.raf = new RandomAccessFile(file.toFile(), "rw");
|
||||||
|
final int nSectors = (int) Math.round(Math.ceil((double) raf.length() / SECTOR_BYTES));
|
||||||
|
sectorFree = new FastBitSet(nSectors);
|
||||||
|
sectorFree.setAll();
|
||||||
|
sectorFree.set(0, false);
|
||||||
|
sectorFree.set(1, false);
|
||||||
|
if (raf.length() < 8192) {
|
||||||
|
raf.setLength(8192);
|
||||||
|
} else {
|
||||||
|
if ((raf.length() & 0xFFF) != 0) {
|
||||||
|
raf.setLength(((raf.length() + 0xFFF) >> 12) << 12);
|
||||||
|
}
|
||||||
|
raf.seek(0);
|
||||||
|
for (int i = 0; i < SECTOR_INTS; i++) {
|
||||||
|
final int offset = raf.readInt();
|
||||||
|
offsets[i] = offset;
|
||||||
|
int sectorStart = offset >> 8;
|
||||||
|
int numSectors = offset & 0xFF;
|
||||||
|
if (offset != 0 && sectorStart + numSectors <= sectorFree.size()) {
|
||||||
|
offsetMap.put(offset, i);
|
||||||
|
for (int sectorNum = 0; sectorNum < (offset & 0xFF); sectorNum++) {
|
||||||
|
sectorFree.set((offset >> 8) + sectorNum, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
init = true;
|
||||||
|
closed = false;
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the region file X
|
||||||
|
*/
|
||||||
|
public int getX() {
|
||||||
|
return X;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the region file Z
|
||||||
|
*/
|
||||||
|
public int getZ() {
|
||||||
|
return Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the RandomAccessFile of the MCA region file
|
||||||
|
*/
|
||||||
|
public RandomAccessFile getRandomAccessFile() {
|
||||||
|
return raf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the MCA region file
|
||||||
|
*/
|
||||||
|
public Path getFile() {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a cached {@link MCAChunk} if present else returns null
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public MCAChunk getCachedChunk(int cx, int cz) {
|
||||||
|
if (!init) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
short pair = (short) ((cx & 31) + ((cz & 31) << 5));
|
||||||
|
synchronized (chunks) {
|
||||||
|
return chunks.get(pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new empty {@link MCAChunk}.
|
||||||
|
*/
|
||||||
|
public MCAChunk newChunk(int cx, int cz) {
|
||||||
|
if (!init) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
short pair = (short) ((cx & 31) + ((cz & 31) << 5));
|
||||||
|
MCAChunk chunk;
|
||||||
|
synchronized (chunks) {
|
||||||
|
chunks.put(pair, chunk = new MCAChunk(this, cx, cx));
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a {@link MCAChunk} into the cache.
|
||||||
|
*/
|
||||||
|
public void setChunk(MCAChunk chunk) {
|
||||||
|
if (!init) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
int cx = chunk.getX();
|
||||||
|
int cz = chunk.getZ();
|
||||||
|
short pair = (short) ((cx & 31) + ((cz & 31) << 5));
|
||||||
|
synchronized (chunks) {
|
||||||
|
chunks.put(pair, chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load data from the mca region into the given {@link MCAChunk}.
|
||||||
|
*/
|
||||||
|
public void loadIntoChunkFromFile(MCAChunk chunk) throws IOException {
|
||||||
|
if (!init) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
int cx = chunk.getX();
|
||||||
|
int cz = chunk.getZ();
|
||||||
|
int i = (cx & 31) + ((cz & 31) << 5);
|
||||||
|
int offset = offsets[i];
|
||||||
|
synchronized (this) {
|
||||||
|
if (offset == 0) {
|
||||||
|
chunk.setEmpty(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chunk.loadFromNIS(getChunkIS(offset >> 8), false);
|
||||||
|
if (offset == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (i < 2) {
|
||||||
|
int length;
|
||||||
|
byte version;
|
||||||
|
byte[] data;
|
||||||
|
synchronized (this) {
|
||||||
|
raf.seek((long) (offset >> 8) << 12);
|
||||||
|
length = raf.readInt();
|
||||||
|
version = raf.readByte();
|
||||||
|
data = new byte[length - 1];
|
||||||
|
raf.read(data);
|
||||||
|
}
|
||||||
|
FastByteArrayInputStream bais = new FastByteArrayInputStream(data);
|
||||||
|
BufferedInputStream bis = switch (version) {
|
||||||
|
case VERSION_GZIP -> new BufferedInputStream(new GZIPInputStream(bais));
|
||||||
|
case VERSION_DEFLATE -> new BufferedInputStream(new InflaterInputStream(bais));
|
||||||
|
case VERSION_UNCOMPRESSED -> new BufferedInputStream(bais);
|
||||||
|
default -> throw new IllegalStateException("Unexpected compression version: " + version);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public MCAChunk getChunk(int cx, int cz) throws IOException {
|
||||||
|
if (!init) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
MCAChunk cached = getCachedChunk(cx, cz);
|
||||||
|
if (cached != null) {
|
||||||
|
return cached;
|
||||||
|
} else {
|
||||||
|
return readChunk(cx, cz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MCAChunk readChunk(int cx, int cz) throws IOException {
|
||||||
|
if (!init) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
int i = (cx & 31) + ((cz & 31) << 5);
|
||||||
|
int offset = offsets[i];
|
||||||
|
if (offset == 0) {
|
||||||
|
return newChunk(cx, cz);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
MCAChunk chunk;
|
||||||
|
synchronized (this) {
|
||||||
|
chunk = getChunkIS(offset >> 8, cx, cz);
|
||||||
|
}
|
||||||
|
if (i < 2) {
|
||||||
|
int length;
|
||||||
|
byte version;
|
||||||
|
byte[] data;
|
||||||
|
synchronized (this) {
|
||||||
|
raf.seek((long) (offset >> 8) << 12);
|
||||||
|
length = raf.readInt();
|
||||||
|
version = raf.readByte();
|
||||||
|
data = new byte[length - 1];
|
||||||
|
raf.read(data);
|
||||||
|
}
|
||||||
|
FastByteArrayInputStream bais = new FastByteArrayInputStream(data);
|
||||||
|
BufferedInputStream bis = switch (version) {
|
||||||
|
case VERSION_GZIP -> new BufferedInputStream(new GZIPInputStream(bais));
|
||||||
|
case VERSION_DEFLATE -> new BufferedInputStream(new InflaterInputStream(bais));
|
||||||
|
case VERSION_UNCOMPRESSED -> new BufferedInputStream(bais);
|
||||||
|
default -> throw new IllegalStateException("Unexpected compression version: " + version);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
short pair = (short) ((cx & 31) + ((cz & 31) << 5));
|
||||||
|
synchronized (chunks) {
|
||||||
|
chunks.put(pair, chunk);
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error attempting to read chunk locally located at `" + (cx & 31) + "," + (cz & 31) + "`" +
|
||||||
|
" in file `" + file.getFileName() + "` at offset: `" + (offset >> 8) + "`", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param onEach cx, cz, offset, size (in kB)
|
||||||
|
*/
|
||||||
|
public void forEachChunk(RunnableVal4<Integer, Integer, Integer, Integer> onEach) {
|
||||||
|
if (!init) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
for (int z = 0; z < 32; z++) {
|
||||||
|
for (int x = 0; x < 32; x++, i += 4) {
|
||||||
|
int offset = offsets[x + (z << 5)];
|
||||||
|
if (offset != 0) {
|
||||||
|
int size = offset & 0xFF;
|
||||||
|
onEach.run(x, z, offset >> 8, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forEachChunk(RunnableVal<MCAChunk> onEach) {
|
||||||
|
if (!init) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
int rx = X << 5;
|
||||||
|
int rz = Z << 5;
|
||||||
|
for (int z = 0; z < 32; z++) {
|
||||||
|
for (int x = 0; x < 32; x++) {
|
||||||
|
int offset = offsets[x + (z << 5)];
|
||||||
|
if (offset != 0) {
|
||||||
|
try {
|
||||||
|
onEach.run(getChunk(rx + x, rz + z));
|
||||||
|
} catch (Throwable ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private NBTInputStream getChunkIS(int offset) throws IOException {
|
||||||
|
int length = -1;
|
||||||
|
byte version = -1;
|
||||||
|
byte[] data;
|
||||||
|
try {
|
||||||
|
if (offset == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
raf.seek((long) offset << 12);
|
||||||
|
length = raf.readInt();
|
||||||
|
version = raf.readByte();
|
||||||
|
data = new byte[length - 1];
|
||||||
|
raf.read(data);
|
||||||
|
}
|
||||||
|
FastByteArrayInputStream bais = new FastByteArrayInputStream(data);
|
||||||
|
BufferedInputStream bis = switch (version) {
|
||||||
|
case VERSION_GZIP -> new BufferedInputStream(new GZIPInputStream(bais));
|
||||||
|
case VERSION_DEFLATE -> new BufferedInputStream(new InflaterInputStream(bais));
|
||||||
|
case VERSION_UNCOMPRESSED -> new BufferedInputStream(bais);
|
||||||
|
default -> throw new IllegalStateException("Unexpected compression version: " + version);
|
||||||
|
};
|
||||||
|
return new NBTInputStream(bis);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOException("Length: " + length + ", version: " + version + ", offset: " + offset, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MCAChunk getChunkIS(int offset, int cx, int cz) throws IOException {
|
||||||
|
int length = -1;
|
||||||
|
byte version = -1;
|
||||||
|
byte[] data;
|
||||||
|
try {
|
||||||
|
if (offset == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
raf.seek((long) offset << 12);
|
||||||
|
length = raf.readInt();
|
||||||
|
version = raf.readByte();
|
||||||
|
data = new byte[length - 1];
|
||||||
|
raf.read(data);
|
||||||
|
}
|
||||||
|
FastByteArrayInputStream bais = new FastByteArrayInputStream(data);
|
||||||
|
BufferedInputStream bis = switch (version) {
|
||||||
|
case VERSION_GZIP -> new BufferedInputStream(new GZIPInputStream(bais));
|
||||||
|
case VERSION_DEFLATE -> new BufferedInputStream(new InflaterInputStream(bais));
|
||||||
|
case VERSION_UNCOMPRESSED -> new BufferedInputStream(bais);
|
||||||
|
default -> throw new IllegalStateException("Unexpected compression version: " + version);
|
||||||
|
};
|
||||||
|
return new MCAChunk(this, new NBTInputStream(bis), cx, cz, false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException("Length: " + length + ", version: " + version + ", offset: " + offset, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MCAChunk> getCachedChunks() {
|
||||||
|
synchronized (chunks) {
|
||||||
|
return new ArrayList<>(chunks.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uncache(int cx, int cz) {
|
||||||
|
int pair = (cx & 31) + ((cz & 31) << 5);
|
||||||
|
synchronized (chunks) {
|
||||||
|
chunks.remove(pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void close() throws IOException {
|
||||||
|
if (raf == null || closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
try {
|
||||||
|
raf.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
raf = null;
|
||||||
|
offsets = null;
|
||||||
|
offsetMap.clear();
|
||||||
|
closed = true;
|
||||||
|
init = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isModified() {
|
||||||
|
if (isDeleted()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
synchronized (chunks) {
|
||||||
|
for (Int2ObjectMap.Entry<MCAChunk> entry : chunks.int2ObjectEntrySet()) {
|
||||||
|
MCAChunk chunk = entry.getValue();
|
||||||
|
if (chunk.isModified() || chunk.isDeleted()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setOffset(final int x, final int z, final int offset)
|
||||||
|
throws IOException {
|
||||||
|
int i = (x & 31) + ((z & 31) << 5);
|
||||||
|
if (offset == 0) {
|
||||||
|
offsetMap.remove(offsets[i]);
|
||||||
|
} else {
|
||||||
|
offsetMap.put(offset, i);
|
||||||
|
}
|
||||||
|
offsets[x + (z << 5)] = offset;
|
||||||
|
raf.seek((long) i << 2);
|
||||||
|
raf.writeInt(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the chunk to the file
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void flush() throws IOException {
|
||||||
|
boolean delete = true;
|
||||||
|
int currentSector = 2;
|
||||||
|
Queue<Integer> offsets =
|
||||||
|
new LinkedBlockingDeque<>(Arrays.stream(this.offsets).boxed().sorted(Comparator.comparingInt(i -> (i >> 8))).toList());
|
||||||
|
int offset;
|
||||||
|
int count = 0;
|
||||||
|
while (offsets.peek() != null) {
|
||||||
|
count++;
|
||||||
|
offset = offsets.poll();
|
||||||
|
if (offset == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
delete = false;
|
||||||
|
int pair = offsetMap.get(offset);
|
||||||
|
int sectorNumber = offset >> 8;
|
||||||
|
int sectorsAllocated = offset & 0xFF;
|
||||||
|
if (sectorNumber < 2) {
|
||||||
|
throw new IllegalStateException("Sector number cannot be < 2!");
|
||||||
|
}
|
||||||
|
if (pair == Integer.MAX_VALUE) {
|
||||||
|
sectorFree.setRange(sectorNumber, sectorNumber + sectorsAllocated);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MCAChunk chunk = chunks.remove(pair);
|
||||||
|
|
||||||
|
byte[] data;
|
||||||
|
int sectorsNeeded;
|
||||||
|
boolean writeChunkHeader;
|
||||||
|
|
||||||
|
if (currentSector <= sectorNumber) { // Only set free if we definitely won't be overwriting
|
||||||
|
sectorFree.setRange(sectorNumber, sectorNumber + sectorsAllocated);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Current sector number being written to cannot exceed sector number of chunk to" +
|
||||||
|
" be written!");
|
||||||
|
}
|
||||||
|
if (chunk == null) {
|
||||||
|
if (currentSector != sectorNumber) {
|
||||||
|
writeChunkHeader = false;
|
||||||
|
data = new byte[sectorsAllocated << 12];
|
||||||
|
sectorsNeeded = sectorsAllocated;
|
||||||
|
raf.seek((long) sectorNumber << 12);
|
||||||
|
raf.read(data);
|
||||||
|
} else {
|
||||||
|
sectorFree.clearRange(currentSector, currentSector + sectorsAllocated);
|
||||||
|
currentSector += sectorsAllocated;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (chunk.isDeleted()) {
|
||||||
|
int x = pair & 31;
|
||||||
|
int z = (pair >> 5) & 31;
|
||||||
|
setOffset(x, z, 0);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
data = chunk.toBytes(null);
|
||||||
|
writeChunkHeader = true;
|
||||||
|
sectorsNeeded = ((data.length + CHUNK_HEADER_SIZE) >> 12) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasSpace = sectorsNeeded <= sectorsAllocated;
|
||||||
|
long position = (long) currentSector << 12;
|
||||||
|
if (!hasSpace) {
|
||||||
|
hasSpace = true;
|
||||||
|
for (int i = currentSector; i < currentSector + sectorsNeeded; i++) {
|
||||||
|
if (!sectorFree.get(i) || i > sectorFree.size()) {
|
||||||
|
hasSpace = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasSpace) {
|
||||||
|
if (sectorNumber > 2500 || count > 30000) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
sectorNumber = sectorFree.size() + 1;
|
||||||
|
offset = (sectorNumber << 8) | (sectorsNeeded & 0xFF);
|
||||||
|
setOffset(pair & 31, (pair >> 5) & 31, offset);
|
||||||
|
position = (long) sectorNumber << 12;
|
||||||
|
raf.setLength((long) (sectorNumber + sectorsNeeded + 1) << 12);
|
||||||
|
sectorFree.expandTo(sectorNumber + sectorsNeeded, true);
|
||||||
|
sectorFree.clearRange(sectorNumber, sectorNumber + sectorsNeeded);
|
||||||
|
offsets.add(offset); // Come back later to clean up.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raf.seek(position);
|
||||||
|
if (writeChunkHeader) {
|
||||||
|
raf.writeInt(data.length + 1);
|
||||||
|
raf.writeByte(VERSION_DEFLATE);
|
||||||
|
}
|
||||||
|
raf.write(data);
|
||||||
|
if (hasSpace) {
|
||||||
|
offset = (currentSector << 8) | (sectorsNeeded & 0xFF);
|
||||||
|
setOffset(pair & 31, (pair >> 5) & 31, offset);
|
||||||
|
sectorFree.clearRange(currentSector, currentSector + sectorsNeeded);
|
||||||
|
currentSector += sectorsNeeded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int size = 0;
|
||||||
|
for (int i = sectorFree.size(); i > 0; i--) {
|
||||||
|
if (!sectorFree.get(i)) {
|
||||||
|
size = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raf.setLength((long) (size + 1) * SECTOR_BYTES);
|
||||||
|
if (delete || size < 3) {
|
||||||
|
clear();
|
||||||
|
Files.delete(file);
|
||||||
|
}
|
||||||
|
synchronized (chunks) {
|
||||||
|
chunks.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,202 @@
|
|||||||
|
package com.fastasyncworldedit.core.anvil;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
|
import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket;
|
||||||
|
import com.fastasyncworldedit.core.util.MathMan;
|
||||||
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.worldedit.EditSession;
|
||||||
|
import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||||
|
import com.sk89q.worldedit.WorldEditException;
|
||||||
|
import com.sk89q.worldedit.blocks.BaseItemStack;
|
||||||
|
import com.sk89q.worldedit.entity.Player;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldedit.math.Vector3;
|
||||||
|
import com.sk89q.worldedit.util.SideEffect;
|
||||||
|
import com.sk89q.worldedit.util.SideEffectSet;
|
||||||
|
import com.sk89q.worldedit.util.TreeGenerator;
|
||||||
|
import com.sk89q.worldedit.world.AbstractWorld;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class MCAWorld extends AbstractWorld {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
|
private static final Int2ObjectOpenHashMap<MCAWorld> worldMap = new Int2ObjectOpenHashMap<>();
|
||||||
|
private final String name;
|
||||||
|
private final Path folder;
|
||||||
|
private final Path regionFolder;
|
||||||
|
private final Int2ObjectOpenHashMap<MCAFile> mcaFileCache = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
private MCAWorld(String name, Path folder) {
|
||||||
|
this.name = name;
|
||||||
|
this.folder = folder;
|
||||||
|
this.regionFolder = folder.resolve("region");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New MCAWorld instance.
|
||||||
|
*
|
||||||
|
* @param name World name
|
||||||
|
*/
|
||||||
|
public static synchronized MCAWorld of(String name) {
|
||||||
|
return of(name, Fawe.platform().getWorldsFolder().resolve(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New MCAWorld instance.
|
||||||
|
*
|
||||||
|
* @param name World name
|
||||||
|
* @param folder World file/folder
|
||||||
|
*/
|
||||||
|
public static synchronized MCAWorld of(String name, Path folder) {
|
||||||
|
if (Fawe.platform().isWorldLoaded(name)) {
|
||||||
|
throw new IllegalStateException("World " + name + " is loaded. Anvil operations cannot be completed on a loaded world.");
|
||||||
|
}
|
||||||
|
// World could be the same name but in a different folder
|
||||||
|
int combinedHash = Objects.hash(name, folder);
|
||||||
|
return worldMap.computeIfAbsent(combinedHash, (i) -> new MCAWorld(name, folder));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<MCAFile> getMCAs() {
|
||||||
|
getRegionFileFiles().forEach(file -> {
|
||||||
|
String[] split = file.getFileName().toString().split("\\.");
|
||||||
|
short regionX = Short.parseShort(split[1]);
|
||||||
|
short regionZ = Short.parseShort(split[2]);
|
||||||
|
int paired = MathMan.pair(regionX, regionZ);
|
||||||
|
mcaFileCache.computeIfAbsent(
|
||||||
|
paired,
|
||||||
|
(i) -> new MCAFile(regionX, regionZ, regionFolder.resolve("r." + regionX + "." + regionZ + ".mca"))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return mcaFileCache.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Path> getRegionFileFiles() {
|
||||||
|
try {
|
||||||
|
return Files.list(regionFolder).filter(p -> p.toString().endsWith(".mca")).toList();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Error listing region files", e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setTile(final int x, final int y, final int z, final CompoundTag tile) throws WorldEditException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNameUnsafe() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getFolder() {
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <B extends BlockStateHolder<B>> boolean setBlock(
|
||||||
|
final BlockVector3 position,
|
||||||
|
final B block,
|
||||||
|
final SideEffectSet sideEffects
|
||||||
|
) throws WorldEditException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<SideEffect> applySideEffects(
|
||||||
|
final BlockVector3 position,
|
||||||
|
final BlockState previousType,
|
||||||
|
final SideEffectSet sideEffectSet
|
||||||
|
) throws WorldEditException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean clearContainerBlockContents(final BlockVector3 position) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dropItem(final Vector3 position, final BaseItemStack item) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void simulateBlockMine(final BlockVector3 position) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean generateTree(
|
||||||
|
final TreeGenerator.TreeType type,
|
||||||
|
final EditSession editSession,
|
||||||
|
final BlockVector3 position
|
||||||
|
) throws MaxChangedBlocksException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockVector3 getSpawnPosition() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshChunk(final int chunkX, final int chunkZ) {
|
||||||
|
throw new UnsupportedOperationException("Not supported in anvil operations.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IChunkGet get(final int chunkX, final int chunkZ) {
|
||||||
|
short regionX = (short) (chunkX >> 5);
|
||||||
|
short regionZ = (short) (chunkZ >> 5);
|
||||||
|
int paired = MathMan.pair(regionX, regionZ);
|
||||||
|
MCAFile mca = mcaFileCache.computeIfAbsent(
|
||||||
|
paired,
|
||||||
|
(i) -> new MCAFile(regionX, regionZ, regionFolder.resolve("r." + regionX + "." + regionZ + ".mca"))
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
return mca.getChunk(chunkX, chunkZ);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Error loading chunk. Creating empty chunk.", e);
|
||||||
|
return mca.newChunk(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendFakeChunk(@Nullable final Player player, final ChunkPacket packet) {
|
||||||
|
throw new UnsupportedOperationException("Not supported in anvil operations.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void flush() {
|
||||||
|
for (MCAFile mca : mcaFileCache.values()) {
|
||||||
|
try {
|
||||||
|
mca.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Could not flush MCAFile {}", mca.getFile().getFileName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,434 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.fastasyncworldedit.core.internal.io;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A <code>BufferedRandomAccessFile</code> is like a
|
||||||
|
* <code>RandomAccessFile</code>, but it uses a private buffer so that most
|
||||||
|
* operations do not require a disk access.
|
||||||
|
* <P>
|
||||||
|
*
|
||||||
|
* Note: The operations on this class are unmonitored. Also, the correct
|
||||||
|
* functioning of the <code>RandomAccessFile</code> methods that are not
|
||||||
|
* overridden here relies on the implementation of those methods in the
|
||||||
|
* superclass.
|
||||||
|
* Author : Avinash Lakshman ( alakshman@facebook.com) & Prashant Malik ( pmalik@facebook.com )
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class BufferedRandomAccessFile extends RandomAccessFile {
|
||||||
|
static final int LogBuffSz_ = 16; // 64K buffer
|
||||||
|
public static final int BuffSz_ = (1 << LogBuffSz_);
|
||||||
|
static final long BuffMask_ = ~(((long) BuffSz_) - 1L);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This implementation is based on the buffer implementation in Modula-3's
|
||||||
|
* "Rd", "Wr", "RdClass", and "WrClass" interfaces.
|
||||||
|
*/
|
||||||
|
private boolean dirty_; // true iff unflushed bytes exist
|
||||||
|
private boolean closed_; // true iff the file is closed
|
||||||
|
private long curr_; // current position in file
|
||||||
|
private long lo_, hi_; // bounds on characters in "buff"
|
||||||
|
private byte[] buff_; // local buffer
|
||||||
|
private long maxHi_; // this.lo + this.buff.length
|
||||||
|
private boolean hitEOF_; // buffer contains last file block?
|
||||||
|
private long diskPos_; // disk position
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To describe the above fields, we introduce the following abstractions for
|
||||||
|
* the file "f":
|
||||||
|
*
|
||||||
|
* len(f) the length of the file curr(f) the current position in the file
|
||||||
|
* c(f) the abstract contents of the file disk(f) the contents of f's
|
||||||
|
* backing disk file closed(f) true iff the file is closed
|
||||||
|
*
|
||||||
|
* "curr(f)" is an index in the closed interval [0, len(f)]. "c(f)" is a
|
||||||
|
* character sequence of length "len(f)". "c(f)" and "disk(f)" may differ if
|
||||||
|
* "c(f)" contains unflushed writes not reflected in "disk(f)". The flush
|
||||||
|
* operation has the effect of making "disk(f)" identical to "c(f)".
|
||||||
|
*
|
||||||
|
* A file is said to be *valid* if the following conditions hold:
|
||||||
|
*
|
||||||
|
* V1. The "closed" and "curr" fields are correct:
|
||||||
|
*
|
||||||
|
* f.closed == closed(f) f.curr == curr(f)
|
||||||
|
*
|
||||||
|
* V2. The current position is either contained in the buffer, or just past
|
||||||
|
* the buffer:
|
||||||
|
*
|
||||||
|
* f.lo <= f.curr <= f.hi
|
||||||
|
*
|
||||||
|
* V3. Any (possibly) unflushed characters are stored in "f.buff":
|
||||||
|
*
|
||||||
|
* (forall i in [f.lo, f.curr): c(f)[i] == f.buff[i - f.lo])
|
||||||
|
*
|
||||||
|
* V4. For all characters not covered by V3, c(f) and disk(f) agree:
|
||||||
|
*
|
||||||
|
* (forall i in [f.lo, len(f)): i not in [f.lo, f.curr) => c(f)[i] ==
|
||||||
|
* disk(f)[i])
|
||||||
|
*
|
||||||
|
* V5. "f.dirty" is true iff the buffer contains bytes that should be
|
||||||
|
* flushed to the file; by V3 and V4, only part of the buffer can be dirty.
|
||||||
|
*
|
||||||
|
* f.dirty == (exists i in [f.lo, f.curr): c(f)[i] != f.buff[i - f.lo])
|
||||||
|
*
|
||||||
|
* V6. this.maxHi == this.lo + this.buff.length
|
||||||
|
*
|
||||||
|
* Note that "f.buff" can be "null" in a valid file, since the range of
|
||||||
|
* characters in V3 is empty when "f.lo == f.curr".
|
||||||
|
*
|
||||||
|
* A file is said to be *ready* if the buffer contains the current position,
|
||||||
|
* i.e., when:
|
||||||
|
*
|
||||||
|
* R1. !f.closed && f.buff != null && f.lo <= f.curr && f.curr < f.hi
|
||||||
|
*
|
||||||
|
* When a file is ready, reading or writing a single byte can be performed
|
||||||
|
* by reading or writing the in-memory buffer without performing a disk
|
||||||
|
* operation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a new <code>BufferedRandomAccessFile</code> on <code>file</code>
|
||||||
|
* in mode <code>mode</code>, which should be "r" for reading only, or
|
||||||
|
* "rw" for reading and writing.
|
||||||
|
*/
|
||||||
|
public BufferedRandomAccessFile(File file, String mode) throws IOException {
|
||||||
|
super(file, mode);
|
||||||
|
this.init(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedRandomAccessFile(File file, String mode, int size) throws IOException {
|
||||||
|
super(file, mode);
|
||||||
|
this.init(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a new <code>BufferedRandomAccessFile</code> on the file named
|
||||||
|
* <code>name</code> in mode <code>mode</code>, which should be "r" for
|
||||||
|
* reading only, or "rw" for reading and writing.
|
||||||
|
*/
|
||||||
|
public BufferedRandomAccessFile(String name, String mode) throws IOException {
|
||||||
|
super(name, mode);
|
||||||
|
this.init(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedRandomAccessFile(String name, String mode, int size) throws FileNotFoundException {
|
||||||
|
super(name, mode);
|
||||||
|
this.init(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedRandomAccessFile(File file, String mode, byte[] buf) throws FileNotFoundException {
|
||||||
|
super(file, mode);
|
||||||
|
this.dirty_ = this.closed_ = false;
|
||||||
|
this.lo_ = this.curr_ = this.hi_ = 0;
|
||||||
|
this.buff_ = buf;
|
||||||
|
this.maxHi_ = (long) BuffSz_;
|
||||||
|
this.hitEOF_ = false;
|
||||||
|
this.diskPos_ = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int size) {
|
||||||
|
this.dirty_ = this.closed_ = false;
|
||||||
|
this.lo_ = this.curr_ = this.hi_ = 0;
|
||||||
|
this.buff_ = (size > BuffSz_) ? new byte[size] : new byte[BuffSz_];
|
||||||
|
this.maxHi_ = (long) BuffSz_;
|
||||||
|
this.hitEOF_ = false;
|
||||||
|
this.diskPos_ = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
this.flush();
|
||||||
|
this.closed_ = true;
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush any bytes in the file's buffer that have not yet been written to
|
||||||
|
* disk. If the file was created read-only, this method is a no-op.
|
||||||
|
*/
|
||||||
|
public void flush() throws IOException {
|
||||||
|
this.flushBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flush any dirty bytes in the buffer to disk. */
|
||||||
|
private void flushBuffer() throws IOException {
|
||||||
|
if (this.dirty_) {
|
||||||
|
if (this.diskPos_ != this.lo_)
|
||||||
|
super.seek(this.lo_);
|
||||||
|
int len = (int) (this.curr_ - this.lo_);
|
||||||
|
super.write(this.buff_, 0, len);
|
||||||
|
this.diskPos_ = this.curr_;
|
||||||
|
this.dirty_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read at most "this.buff.length" bytes into "this.buff", returning the
|
||||||
|
* number of bytes read. If the return result is less than
|
||||||
|
* "this.buff.length", then EOF was read.
|
||||||
|
*/
|
||||||
|
private int fillBuffer() throws IOException {
|
||||||
|
int cnt = 0;
|
||||||
|
int rem = this.buff_.length;
|
||||||
|
while (rem > 0) {
|
||||||
|
int n = super.read(this.buff_, cnt, rem);
|
||||||
|
if (n < 0)
|
||||||
|
break;
|
||||||
|
cnt += n;
|
||||||
|
rem -= n;
|
||||||
|
}
|
||||||
|
if ((cnt < 0) && (this.hitEOF_ = (cnt < this.buff_.length))) {
|
||||||
|
// make sure buffer that wasn't read is initialized with -1
|
||||||
|
Arrays.fill(this.buff_, cnt, this.buff_.length, (byte) 0xff);
|
||||||
|
}
|
||||||
|
this.diskPos_ += cnt;
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This method positions <code>this.curr</code> at position <code>pos</code>.
|
||||||
|
* If <code>pos</code> does not fall in the current buffer, it flushes the
|
||||||
|
* current buffer and loads the correct one.<p>
|
||||||
|
*
|
||||||
|
* On exit from this routine <code>this.curr == this.hi</code> iff <code>pos</code>
|
||||||
|
* is at or past the end-of-file, which can only happen if the file was
|
||||||
|
* opened in read-only mode.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void seek(long pos) throws IOException {
|
||||||
|
if (pos >= this.hi_ || pos < this.lo_) {
|
||||||
|
// seeking outside of current buffer -- flush and read
|
||||||
|
this.flushBuffer();
|
||||||
|
this.lo_ = pos & BuffMask_; // start at BuffSz boundary
|
||||||
|
this.maxHi_ = this.lo_ + (long) this.buff_.length;
|
||||||
|
if (this.diskPos_ != this.lo_) {
|
||||||
|
super.seek(this.lo_);
|
||||||
|
this.diskPos_ = this.lo_;
|
||||||
|
}
|
||||||
|
int n = this.fillBuffer();
|
||||||
|
this.hi_ = this.lo_ + (long) n;
|
||||||
|
} else {
|
||||||
|
// seeking inside current buffer -- no read required
|
||||||
|
if (pos < this.curr_) {
|
||||||
|
// if seeking backwards, we must flush to maintain V4
|
||||||
|
this.flushBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.curr_ = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Does not maintain V4 (i.e. buffer differs from disk contents if previously written to)
|
||||||
|
* - Assumes no writes were made
|
||||||
|
* @param pos
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public void seekUnsafe(long pos) throws IOException {
|
||||||
|
if (pos >= this.hi_ || pos < this.lo_) {
|
||||||
|
// seeking outside of current buffer -- flush and read
|
||||||
|
this.flushBuffer();
|
||||||
|
this.lo_ = pos & BuffMask_; // start at BuffSz boundary
|
||||||
|
this.maxHi_ = this.lo_ + (long) this.buff_.length;
|
||||||
|
if (this.diskPos_ != this.lo_) {
|
||||||
|
super.seek(this.lo_);
|
||||||
|
this.diskPos_ = this.lo_;
|
||||||
|
}
|
||||||
|
int n = this.fillBuffer();
|
||||||
|
this.hi_ = this.lo_ + (long) n;
|
||||||
|
}
|
||||||
|
this.curr_ = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getFilePointer() {
|
||||||
|
return this.curr_;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long length() throws IOException {
|
||||||
|
return Math.max(this.curr_, super.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (this.curr_ >= this.hi_) {
|
||||||
|
// test for EOF
|
||||||
|
// if (this.hi < this.maxHi) return -1;
|
||||||
|
if (this.hitEOF_)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// slow path -- read another buffer
|
||||||
|
this.seek(this.curr_);
|
||||||
|
if (this.curr_ == this.hi_)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
byte res = this.buff_[(int) (this.curr_ - this.lo_)];
|
||||||
|
this.curr_++;
|
||||||
|
return ((int) res) & 0xFF; // convert byte -> int
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte read1() throws IOException {
|
||||||
|
if (this.curr_ >= this.hi_) {
|
||||||
|
// test for EOF
|
||||||
|
// if (this.hi < this.maxHi) return -1;
|
||||||
|
if (this.hitEOF_)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// slow path -- read another buffer
|
||||||
|
this.seek(this.curr_);
|
||||||
|
if (this.curr_ == this.hi_)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
byte res = this.buff_[(int) (this.curr_ - this.lo_)];
|
||||||
|
this.curr_++;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b) throws IOException {
|
||||||
|
return this.read(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (this.curr_ >= this.hi_) {
|
||||||
|
// test for EOF
|
||||||
|
// if (this.hi < this.maxHi) return -1;
|
||||||
|
if (this.hitEOF_)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// slow path -- read another buffer
|
||||||
|
this.seek(this.curr_);
|
||||||
|
if (this.curr_ == this.hi_)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
len = Math.min(len, (int) (this.hi_ - this.curr_));
|
||||||
|
int buffOff = (int) (this.curr_ - this.lo_);
|
||||||
|
System.arraycopy(this.buff_, buffOff, b, off, len);
|
||||||
|
this.curr_ += len;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte readCurrent() throws IOException {
|
||||||
|
if (this.curr_ >= this.hi_) {
|
||||||
|
// test for EOF
|
||||||
|
// if (this.hi < this.maxHi) return -1;
|
||||||
|
if (this.hitEOF_)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// slow path -- read another buffer
|
||||||
|
this.seek(this.curr_);
|
||||||
|
if (this.curr_ == this.hi_)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
byte res = this.buff_[(int) (this.curr_ - this.lo_)];
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeCurrent(byte b) throws IOException {
|
||||||
|
if (this.curr_ >= this.hi_) {
|
||||||
|
if (this.hitEOF_ && this.hi_ < this.maxHi_) {
|
||||||
|
// at EOF -- bump "hi"
|
||||||
|
this.hi_++;
|
||||||
|
} else {
|
||||||
|
// slow path -- write current buffer; read next one
|
||||||
|
this.seek(this.curr_);
|
||||||
|
if (this.curr_ == this.hi_) {
|
||||||
|
// appending to EOF -- bump "hi"
|
||||||
|
this.hi_++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.buff_[(int) (this.curr_ - this.lo_)] = (byte) b;
|
||||||
|
this.dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeUnsafe(int b) throws IOException {
|
||||||
|
this.buff_[(int) (this.curr_ - this.lo_)] = (byte) b;
|
||||||
|
this.curr_++;
|
||||||
|
this.dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
if (this.curr_ >= this.hi_) {
|
||||||
|
if (this.hitEOF_ && this.hi_ < this.maxHi_) {
|
||||||
|
// at EOF -- bump "hi"
|
||||||
|
this.hi_++;
|
||||||
|
} else {
|
||||||
|
// slow path -- write current buffer; read next one
|
||||||
|
this.seek(this.curr_);
|
||||||
|
if (this.curr_ == this.hi_) {
|
||||||
|
// appending to EOF -- bump "hi"
|
||||||
|
this.hi_++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.buff_[(int) (this.curr_ - this.lo_)] = (byte) b;
|
||||||
|
this.curr_++;
|
||||||
|
this.dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b) throws IOException {
|
||||||
|
this.write(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
|
while (len > 0) {
|
||||||
|
int n = this.writeAtMost(b, off, len);
|
||||||
|
off += n;
|
||||||
|
len -= n;
|
||||||
|
this.dirty_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write at most "len" bytes to "b" starting at position "off", and return
|
||||||
|
* the number of bytes written.
|
||||||
|
*/
|
||||||
|
private int writeAtMost(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (this.curr_ >= this.hi_) {
|
||||||
|
if (this.hitEOF_ && this.hi_ < this.maxHi_) {
|
||||||
|
// at EOF -- bump "hi"
|
||||||
|
this.hi_ = this.maxHi_;
|
||||||
|
} else {
|
||||||
|
// slow path -- write current buffer; read next one
|
||||||
|
this.seek(this.curr_);
|
||||||
|
if (this.curr_ == this.hi_) {
|
||||||
|
// appending to EOF -- bump "hi"
|
||||||
|
this.hi_ = this.maxHi_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
len = Math.min(len, (int) (this.hi_ - this.curr_));
|
||||||
|
int buffOff = (int) (this.curr_ - this.lo_);
|
||||||
|
System.arraycopy(b, off, this.buff_, buffOff, len);
|
||||||
|
this.curr_ += len;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
}
|
@ -2,13 +2,18 @@ package com.fastasyncworldedit.core.jnbt.streamer;
|
|||||||
|
|
||||||
import com.sk89q.jnbt.NBTConstants;
|
import com.sk89q.jnbt.NBTConstants;
|
||||||
import com.sk89q.jnbt.NBTInputStream;
|
import com.sk89q.jnbt.NBTInputStream;
|
||||||
|
import com.sk89q.jnbt.Tag;
|
||||||
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes", "removal"})
|
||||||
public class StreamDelegate {
|
public class StreamDelegate {
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
@ -16,6 +21,8 @@ public class StreamDelegate {
|
|||||||
private static final byte[][] ZERO_KEYS = new byte[0][];
|
private static final byte[][] ZERO_KEYS = new byte[0][];
|
||||||
private static final StreamDelegate[] ZERO_VALUES = new StreamDelegate[0];
|
private static final StreamDelegate[] ZERO_VALUES = new StreamDelegate[0];
|
||||||
|
|
||||||
|
private Map<String, Tag> retained = null;
|
||||||
|
|
||||||
private byte[] buffer;
|
private byte[] buffer;
|
||||||
private byte[][] keys;
|
private byte[][] keys;
|
||||||
private StreamDelegate[] values;
|
private StreamDelegate[] values;
|
||||||
@ -25,25 +32,36 @@ public class StreamDelegate {
|
|||||||
private InfoReader infoReader;
|
private InfoReader infoReader;
|
||||||
private ValueReader valueReader;
|
private ValueReader valueReader;
|
||||||
|
|
||||||
|
private String retainedName = null;
|
||||||
|
private String currentName = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to read a streamed {@link NBTInputStream}
|
||||||
|
*/
|
||||||
public StreamDelegate() {
|
public StreamDelegate() {
|
||||||
keys = ZERO_KEYS;
|
keys = ZERO_KEYS;
|
||||||
values = ZERO_VALUES;
|
values = ZERO_VALUES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set that keys not added to this StreamDelegate instance should still be retained alongside their value retained. They can
|
||||||
|
* be accessed via {@link StreamDelegate#getRetained}
|
||||||
|
*/
|
||||||
|
public StreamDelegate retainOthers() {
|
||||||
|
retained = new LinkedHashMap<>();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public StreamDelegate addAndGetParent(String name) {
|
public StreamDelegate addAndGetParent(String name) {
|
||||||
add(name);
|
add(name);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamDelegate add() {
|
public StreamDelegate add(@Nullable String name) {
|
||||||
return add("");
|
|
||||||
}
|
|
||||||
|
|
||||||
public StreamDelegate add(String name) {
|
|
||||||
return add(name, new StreamDelegate());
|
return add(name, new StreamDelegate());
|
||||||
}
|
}
|
||||||
|
|
||||||
private StreamDelegate add(String name, StreamDelegate scope) {
|
private StreamDelegate add(@Nullable String name, StreamDelegate scope) {
|
||||||
if (valueReader != null) {
|
if (valueReader != null) {
|
||||||
LOGGER.warn(
|
LOGGER.warn(
|
||||||
"Scope {} | {} may not run, as the stream is only read once, and a value reader is already set",
|
"Scope {} | {} may not run, as the stream is only read once, and a value reader is already set",
|
||||||
@ -51,7 +69,7 @@ public class StreamDelegate {
|
|||||||
scope
|
scope
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
byte[] bytes = name.getBytes(NBTConstants.CHARSET);
|
byte[] bytes = name == null ? new byte[0] : name.getBytes(NBTConstants.CHARSET);
|
||||||
int maxSize = bytes.length;
|
int maxSize = bytes.length;
|
||||||
|
|
||||||
byte[][] tmpKeys = new byte[keys.length + 1][];
|
byte[][] tmpKeys = new byte[keys.length + 1][];
|
||||||
@ -96,10 +114,12 @@ public class StreamDelegate {
|
|||||||
public StreamDelegate get(DataInputStream is) throws IOException {
|
public StreamDelegate get(DataInputStream is) throws IOException {
|
||||||
int nameLength = is.readShort() & 0xFFFF;
|
int nameLength = is.readShort() & 0xFFFF;
|
||||||
if (nameLength == 0 && keys.length > 0 && keys[0].length == 0) {
|
if (nameLength == 0 && keys.length > 0 && keys[0].length == 0) {
|
||||||
|
currentName = "";
|
||||||
|
retainedName = null;
|
||||||
return values[0];
|
return values[0];
|
||||||
}
|
}
|
||||||
if (nameLength > buffer.length) {
|
if (nameLength > buffer.length) {
|
||||||
is.skipBytes(nameLength);
|
setRetained(is, nameLength);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int index = 0;
|
int index = 0;
|
||||||
@ -139,34 +159,59 @@ public class StreamDelegate {
|
|||||||
continue middle;
|
continue middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
currentName = new String(key);
|
||||||
|
retainedName = null;
|
||||||
return values[i];
|
return values[i];
|
||||||
}
|
}
|
||||||
|
currentName = null;
|
||||||
|
retainedName = new String(Arrays.copyOf(buffer, nameLength), NBTConstants.CHARSET);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// fall through
|
||||||
}
|
}
|
||||||
case 1: {
|
case 1: {
|
||||||
byte[] key = keys[index];
|
byte[] key = keys[index];
|
||||||
if (key.length == nameLength) {
|
if (key.length == nameLength) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
boolean retain = false;
|
||||||
for (; nameLength > 0; nameLength--, i++) {
|
for (; nameLength > 0; nameLength--, i++) {
|
||||||
byte b = is.readByte();
|
byte b = is.readByte();
|
||||||
buffer[i] = b;
|
buffer[i] = b;
|
||||||
if (b != key[i]) {
|
if (!retain && b != key[i]) {
|
||||||
|
if (retained == null) {
|
||||||
nameLength--;
|
nameLength--;
|
||||||
break outer;
|
break outer;
|
||||||
}
|
}
|
||||||
|
retain = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (!retain) {
|
||||||
|
currentName = new String(key);
|
||||||
|
retainedName = null;
|
||||||
return values[index];
|
return values[index];
|
||||||
}
|
}
|
||||||
|
retainedName = new String(Arrays.copyOf(buffer, i), NBTConstants.CHARSET);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is.skipBytes(nameLength);
|
setRetained(is, nameLength);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setRetained(DataInputStream is, int nameLength) throws IOException {
|
||||||
|
if (retained == null) {
|
||||||
|
is.skipBytes(nameLength);
|
||||||
|
} else {
|
||||||
|
byte[] nameBytes = new byte[nameLength];
|
||||||
|
is.readFully(nameBytes);
|
||||||
|
retainedName = new String(nameBytes, NBTConstants.CHARSET);
|
||||||
|
}
|
||||||
|
currentName = null;
|
||||||
|
}
|
||||||
|
|
||||||
public StreamDelegate withLong(LongValueReader valueReader) {
|
public StreamDelegate withLong(LongValueReader valueReader) {
|
||||||
return withElem(valueReader);
|
return withElem(valueReader);
|
||||||
}
|
}
|
||||||
@ -220,6 +265,19 @@ public class StreamDelegate {
|
|||||||
return elemReader;
|
return elemReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Map<String, Tag> getRetained() {
|
||||||
|
return retained;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void retain(Tag tag) {
|
||||||
|
if (retainedName == null) {
|
||||||
|
throw new IllegalStateException("Retained name null?!");
|
||||||
|
}
|
||||||
|
retained.put(retainedName, tag);
|
||||||
|
retainedName = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void acceptInfo(int length, int type) throws IOException {
|
public void acceptInfo(int length, int type) throws IOException {
|
||||||
if (infoReader != null) {
|
if (infoReader != null) {
|
||||||
infoReader.apply(length, type);
|
infoReader.apply(length, type);
|
||||||
@ -234,4 +292,12 @@ public class StreamDelegate {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCurrentName() {
|
||||||
|
return currentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRetainedName() {
|
||||||
|
return retainedName;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package com.fastasyncworldedit.core.math;
|
package com.fastasyncworldedit.core.math;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class FastBitSet {
|
public class FastBitSet {
|
||||||
|
|
||||||
private final int size;
|
private int size;
|
||||||
private final long[] bits;
|
private long[] bits;
|
||||||
|
|
||||||
public FastBitSet(int size) {
|
public FastBitSet(int size) {
|
||||||
this.size = size;
|
this.size = size;
|
||||||
@ -93,6 +94,28 @@ public class FastBitSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void expandTo(int newSize, boolean value) {
|
||||||
|
//System.out.println(newSize);
|
||||||
|
int newLength = (newSize + 64) >> 6;
|
||||||
|
if (newLength <= this.bits.length) {
|
||||||
|
if (this.size > newSize) {
|
||||||
|
this.size = newSize;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long[] tmp = new long[newLength];
|
||||||
|
if (value) {
|
||||||
|
Arrays.fill(tmp, -1L);
|
||||||
|
}
|
||||||
|
System.arraycopy(bits, 0, tmp, 0, bits.length);
|
||||||
|
this.bits = tmp;
|
||||||
|
this.size = newSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAll() {
|
||||||
|
setAll(bits);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean get(final int i) {
|
public boolean get(final int i) {
|
||||||
return (bits[i >> 6] & (1L << (i & 0x3F))) != 0;
|
return (bits[i >> 6] & (1L << (i & 0x3F))) != 0;
|
||||||
}
|
}
|
||||||
@ -197,4 +220,16 @@ public class FastBitSet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (int i = 0; i < this.bits.length; i++) {
|
||||||
|
String bits = new StringBuilder(String.format("%064d", new BigInteger(Long.toBinaryString(this.bits[i]))))
|
||||||
|
.reverse()
|
||||||
|
.toString();
|
||||||
|
builder.append(i * 64).append(":").append(bits).append(" ");
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import com.sk89q.worldedit.math.BlockVector3;
|
|||||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||||
import com.sk89q.worldedit.world.block.BlockState;
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.storage.InvalidFormatException;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -173,4 +173,6 @@ public interface IQueueExtent<T extends IChunk> extends Flushable, Trimable, ICh
|
|||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addFlushTask(Runnable task);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,279 @@
|
|||||||
|
package com.fastasyncworldedit.core.queue.implementation;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
|
import com.fastasyncworldedit.core.FaweCache;
|
||||||
|
import com.fastasyncworldedit.core.anvil.MCAWorld;
|
||||||
|
import com.fastasyncworldedit.core.configuration.Settings;
|
||||||
|
import com.fastasyncworldedit.core.extent.NullExtent;
|
||||||
|
import com.fastasyncworldedit.core.extent.PassthroughExtent;
|
||||||
|
import com.fastasyncworldedit.core.extent.clipboard.WorldCopyClipboard;
|
||||||
|
import com.fastasyncworldedit.core.extent.filter.CountFilter;
|
||||||
|
import com.fastasyncworldedit.core.extent.filter.DistrFilter;
|
||||||
|
import com.fastasyncworldedit.core.extent.filter.LinkedFilter;
|
||||||
|
import com.fastasyncworldedit.core.extent.filter.block.ChunkFilterBlock;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.BatchProcessorHolder;
|
||||||
|
import com.fastasyncworldedit.core.extent.processor.MultiBatchProcessor;
|
||||||
|
import com.fastasyncworldedit.core.function.mask.BlockMaskBuilder;
|
||||||
|
import com.fastasyncworldedit.core.internal.exception.FaweException;
|
||||||
|
import com.fastasyncworldedit.core.queue.Filter;
|
||||||
|
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
||||||
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
|
import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
|
import com.sk89q.worldedit.function.mask.BlockMask;
|
||||||
|
import com.sk89q.worldedit.function.mask.ExistingBlockMask;
|
||||||
|
import com.sk89q.worldedit.function.mask.Mask;
|
||||||
|
import com.sk89q.worldedit.function.pattern.BlockPattern;
|
||||||
|
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector2;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldedit.regions.Region;
|
||||||
|
import com.sk89q.worldedit.util.Countable;
|
||||||
|
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockType;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ForkJoinTask;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
public class ParallelAnvilQueueExtent extends PassthroughExtent {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
|
private final MCAWorld world;
|
||||||
|
private final QueueHandler handler;
|
||||||
|
private final BatchProcessorHolder processor;
|
||||||
|
private final BatchProcessorHolder postProcessor;
|
||||||
|
// Array for lazy avoidance of concurrent modification exceptions and needless overcomplication of code (synchronisation is
|
||||||
|
// not very important)
|
||||||
|
private final boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length];
|
||||||
|
private final boolean fastmode;
|
||||||
|
private int changes;
|
||||||
|
private int lastException = Integer.MIN_VALUE;
|
||||||
|
private int exceptionCount = 0;
|
||||||
|
|
||||||
|
public ParallelAnvilQueueExtent(QueueHandler handler, MCAWorld world, boolean fastmode) {
|
||||||
|
super(handler.getQueue(world, new BatchProcessorHolder(), new BatchProcessorHolder()));
|
||||||
|
this.world = world;
|
||||||
|
this.handler = handler;
|
||||||
|
this.processor = (BatchProcessorHolder) getExtent().getProcessor();
|
||||||
|
if (this.processor.getProcessor() instanceof MultiBatchProcessor) {
|
||||||
|
((MultiBatchProcessor) this.processor.getProcessor()).setFaweExceptionArray(faweExceptionReasonsUsed);
|
||||||
|
}
|
||||||
|
this.postProcessor = (BatchProcessorHolder) getExtent().getPostProcessor();
|
||||||
|
if (this.postProcessor.getProcessor() instanceof MultiBatchProcessor) {
|
||||||
|
((MultiBatchProcessor) this.postProcessor.getProcessor()).setFaweExceptionArray(faweExceptionReasonsUsed);
|
||||||
|
}
|
||||||
|
this.fastmode = fastmode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
public IQueueExtent<IQueueChunk> getExtent() {
|
||||||
|
return (IQueueExtent<IQueueChunk>) super.getExtent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean cancel() {
|
||||||
|
if (super.cancel()) {
|
||||||
|
processor.setProcessor(new NullExtent(this, FaweCache.MANUAL));
|
||||||
|
postProcessor.setPostProcessor(new NullExtent(this, FaweCache.MANUAL));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private IQueueExtent<IQueueChunk> getNewQueue() {
|
||||||
|
return handler.getMCAQueue(world, this.processor, this.postProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public <T extends Filter> T apply(Region region, T filter, boolean full) {
|
||||||
|
// The chunks positions to iterate over
|
||||||
|
final Set<BlockVector2> chunks = region.getChunks();
|
||||||
|
final Iterator<BlockVector2> chunksIter = chunks.iterator();
|
||||||
|
|
||||||
|
// Get a pool, to operate on the chunks in parallel
|
||||||
|
final int size = Math.min(chunks.size(), Settings.settings().QUEUE.PARALLEL_THREADS);
|
||||||
|
if (size <= 1 && chunksIter.hasNext()) {
|
||||||
|
BlockVector2 pos = chunksIter.next();
|
||||||
|
getExtent().apply(null, filter, region, pos.getX(), pos.getZ(), full);
|
||||||
|
} else {
|
||||||
|
final ForkJoinTask[] tasks = IntStream.range(0, size).mapToObj(i -> handler.submit(() -> {
|
||||||
|
try {
|
||||||
|
final Filter newFilter = filter.fork();
|
||||||
|
// Create a chunk that we will reuse/reset for each operation
|
||||||
|
final SingleThreadQueueExtent queue = (SingleThreadQueueExtent) getNewQueue();
|
||||||
|
queue.setFastMode(fastmode);
|
||||||
|
queue.setFaweExceptionArray(faweExceptionReasonsUsed);
|
||||||
|
synchronized (queue) {
|
||||||
|
try {
|
||||||
|
ChunkFilterBlock block = null;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// Get the next chunk posWeakChunk
|
||||||
|
final int chunkX;
|
||||||
|
final int chunkZ;
|
||||||
|
synchronized (chunksIter) {
|
||||||
|
if (!chunksIter.hasNext()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final BlockVector2 pos = chunksIter.next();
|
||||||
|
chunkX = pos.getX();
|
||||||
|
chunkZ = pos.getZ();
|
||||||
|
}
|
||||||
|
block = queue.apply(block, newFilter, region, chunkX, chunkZ, full);
|
||||||
|
}
|
||||||
|
queue.flush();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (t instanceof FaweException) {
|
||||||
|
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) t, LOGGER);
|
||||||
|
} else if (t.getCause() instanceof FaweException) {
|
||||||
|
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) t.getCause(), LOGGER);
|
||||||
|
} else {
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
String message = e.getMessage();
|
||||||
|
int hash = message != null ? message.hashCode() : 0;
|
||||||
|
if (lastException != hash) {
|
||||||
|
lastException = hash;
|
||||||
|
exceptionCount = 0;
|
||||||
|
LOGGER.catching(e);
|
||||||
|
} else if (exceptionCount < Settings.settings().QUEUE.PARALLEL_THREADS) {
|
||||||
|
exceptionCount++;
|
||||||
|
LOGGER.warn(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})).toArray(ForkJoinTask[]::new);
|
||||||
|
// Join filters
|
||||||
|
for (ForkJoinTask task : tasks) {
|
||||||
|
if (task != null) {
|
||||||
|
task.quietlyJoin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filter.join();
|
||||||
|
}
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int countBlocks(Region region, Mask searchMask) {
|
||||||
|
return
|
||||||
|
// Apply a filter over a region
|
||||||
|
apply(region, searchMask
|
||||||
|
.toFilter(new CountFilter()), searchMask.replacesAir()) // Adapt the mask to a filter which counts
|
||||||
|
.getParent() // Get the counter of this mask
|
||||||
|
.getTotal(); // Get the total from the counter
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <B extends BlockStateHolder<B>> int setBlocks(Region region, B block) throws MaxChangedBlocksException {
|
||||||
|
Mask mask = new BlockMaskBuilder().add(block).build(this).inverse();
|
||||||
|
return this.changes = apply(region, mask.toFilter(block), mask.replacesAir())
|
||||||
|
.getBlocksApplied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int setBlocks(Region region, Pattern pattern) throws MaxChangedBlocksException {
|
||||||
|
return this.changes = apply(region, new LinkedFilter<>(pattern, new CountFilter()), true).getChild().getTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int setBlocks(Set<BlockVector3> vset, Pattern pattern) {
|
||||||
|
if (vset instanceof Region) {
|
||||||
|
this.changes = setBlocks((Region) vset, pattern);
|
||||||
|
return this.changes;
|
||||||
|
}
|
||||||
|
// TODO optimize parallel
|
||||||
|
for (BlockVector3 blockVector3 : vset) {
|
||||||
|
if (pattern.apply(this, blockVector3, blockVector3)) {
|
||||||
|
this.changes++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int replaceBlocks(Region region, Mask mask, Pattern pattern)
|
||||||
|
throws MaxChangedBlocksException {
|
||||||
|
boolean full = mask.replacesAir();
|
||||||
|
return this.changes = apply(region, mask.toFilter(pattern), full).getBlocksApplied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Countable<BlockState>> getBlockDistributionWithData(Region region) {
|
||||||
|
return apply(region, new DistrFilter(), true).getDistribution();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Countable<BlockType>> getBlockDistribution(Region region) {
|
||||||
|
return apply(region, new DistrFilter(), true).getTypeDistribution();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily copy a region
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Clipboard lazyCopy(Region region) {
|
||||||
|
Clipboard clipboard = new WorldCopyClipboard(() -> this, region);
|
||||||
|
clipboard.setOrigin(region.getMinimumPoint());
|
||||||
|
return clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of blocks of a list of types in a region.
|
||||||
|
*
|
||||||
|
* @param region the region
|
||||||
|
* @param searchBlocks the list of blocks to search
|
||||||
|
* @return the number of blocks that matched the block
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int countBlocks(Region region, Set<BaseBlock> searchBlocks) {
|
||||||
|
Mask mask = new BlockMask(this, searchBlocks);
|
||||||
|
return countBlocks(region, mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces all the blocks matching a given filter, within a given region, to a block
|
||||||
|
* returned by a given pattern.
|
||||||
|
*
|
||||||
|
* @param region the region to replace the blocks within
|
||||||
|
* @param filter a list of block types to match, or null to use {@link ExistingBlockMask}
|
||||||
|
* @param replacement the replacement block
|
||||||
|
* @return number of blocks affected
|
||||||
|
* @throws MaxChangedBlocksException thrown if too many blocks are changed
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <B extends BlockStateHolder<B>> int replaceBlocks(Region region, Set<BaseBlock> filter, B replacement) throws
|
||||||
|
MaxChangedBlocksException {
|
||||||
|
return replaceBlocks(region, filter, new BlockPattern(replacement));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces all the blocks matching a given filter, within a given region, to a block
|
||||||
|
* returned by a given pattern.
|
||||||
|
*
|
||||||
|
* @param region the region to replace the blocks within
|
||||||
|
* @param filter a list of block types to match, or null to use {@link ExistingBlockMask}
|
||||||
|
* @param pattern the pattern that provides the new blocks
|
||||||
|
* @return number of blocks affected
|
||||||
|
* @throws MaxChangedBlocksException thrown if too many blocks are changed
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int replaceBlocks(Region region, Set<BaseBlock> filter, Pattern pattern) throws MaxChangedBlocksException {
|
||||||
|
Mask mask = filter == null ? new ExistingBlockMask(this) : new BlockMask(this, filter);
|
||||||
|
return replaceBlocks(region, mask, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -10,6 +10,7 @@ import com.fastasyncworldedit.core.queue.IChunkSet;
|
|||||||
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
||||||
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
import com.fastasyncworldedit.core.queue.Trimable;
|
import com.fastasyncworldedit.core.queue.Trimable;
|
||||||
|
import com.fastasyncworldedit.core.anvil.MCAWorld;
|
||||||
import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache;
|
import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache;
|
||||||
import com.fastasyncworldedit.core.util.MemUtil;
|
import com.fastasyncworldedit.core.util.MemUtil;
|
||||||
import com.fastasyncworldedit.core.util.TaskManager;
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
@ -400,6 +401,26 @@ public abstract class QueueHandler implements Trimable, Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create the MCA WorldChunkCache for a world
|
||||||
|
*/
|
||||||
|
public IChunkCache<IChunkGet> getOrCreateMCAWorldCache(World world) {
|
||||||
|
world = WorldWrapper.unwrap(world);
|
||||||
|
|
||||||
|
synchronized (chunkGetCache) {
|
||||||
|
final WeakReference<IChunkCache<IChunkGet>> ref = chunkGetCache.get(world);
|
||||||
|
if (ref != null) {
|
||||||
|
final IChunkCache<IChunkGet> cached = ref.get();
|
||||||
|
if (cached != null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final IChunkCache<IChunkGet> created = new ChunkCache<>(world);
|
||||||
|
chunkGetCache.put(world, new WeakReference<>(created));
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IQueueExtent<IQueueChunk> create() {
|
public IQueueExtent<IQueueChunk> create() {
|
||||||
return new SingleThreadQueueExtent();
|
return new SingleThreadQueueExtent();
|
||||||
}
|
}
|
||||||
@ -456,6 +477,24 @@ public abstract class QueueHandler implements Trimable, Runnable {
|
|||||||
*/
|
*/
|
||||||
public abstract void endUnsafe(boolean parallel);
|
public abstract void endUnsafe(boolean parallel);
|
||||||
|
|
||||||
|
public IQueueExtent<IQueueChunk> getMCAQueue(MCAWorld world) {
|
||||||
|
return getMCAQueue(world, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueueExtent<IQueueChunk> getMCAQueue(MCAWorld world, IBatchProcessor processor, IBatchProcessor postProcessor) {
|
||||||
|
final IQueueExtent<IQueueChunk> queue = pool();
|
||||||
|
IChunkCache<IChunkGet> cache = getOrCreateMCAWorldCache(world);
|
||||||
|
queue.init(world, cache, null);
|
||||||
|
if (processor != null) {
|
||||||
|
queue.setProcessor(processor);
|
||||||
|
}
|
||||||
|
if (postProcessor != null) {
|
||||||
|
queue.setPostProcessor(postProcessor);
|
||||||
|
}
|
||||||
|
queue.addFlushTask(world::flush);
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new queue for a given world.
|
* Create a new queue for a given world.
|
||||||
*/
|
*/
|
||||||
|
@ -31,6 +31,8 @@ import com.sk89q.worldedit.world.World;
|
|||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
@ -68,6 +70,7 @@ public final class SingleThreadQueueExtent extends ExtentBatchProcessorHolder im
|
|||||||
private boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length];
|
private boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length];
|
||||||
private int lastException = Integer.MIN_VALUE;
|
private int lastException = Integer.MIN_VALUE;
|
||||||
private int exceptionCount = 0;
|
private int exceptionCount = 0;
|
||||||
|
private List<Runnable> flushTasks = null;
|
||||||
|
|
||||||
public SingleThreadQueueExtent() {
|
public SingleThreadQueueExtent() {
|
||||||
}
|
}
|
||||||
@ -198,6 +201,14 @@ public final class SingleThreadQueueExtent extends ExtentBatchProcessorHolder im
|
|||||||
return chunks.isEmpty() && submissions.isEmpty();
|
return chunks.isEmpty() && submissions.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addFlushTask(final Runnable task) {
|
||||||
|
if (flushTasks == null) {
|
||||||
|
flushTasks = new ArrayList<>();
|
||||||
|
}
|
||||||
|
flushTasks.add(task);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <V extends Future<V>> V submit(IQueueChunk chunk) {
|
public <V extends Future<V>> V submit(IQueueChunk chunk) {
|
||||||
if (lastChunk == chunk) {
|
if (lastChunk == chunk) {
|
||||||
@ -470,6 +481,11 @@ public final class SingleThreadQueueExtent extends ExtentBatchProcessorHolder im
|
|||||||
getChunkLock.unlock();
|
getChunkLock.unlock();
|
||||||
}
|
}
|
||||||
pollSubmissions(0, true);
|
pollSubmissions(0, true);
|
||||||
|
if (flushTasks != null) {
|
||||||
|
for (Runnable r : flushTasks) {
|
||||||
|
r.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -440,7 +440,7 @@ public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks {
|
|||||||
@Override
|
@Override
|
||||||
public boolean hasBiomes(int layer) {
|
public boolean hasBiomes(int layer) {
|
||||||
layer -= minSectionPosition;
|
layer -= minSectionPosition;
|
||||||
return layer >= 0 && layer < biomes.length && biomes[layer] != null && biomes[layer].length > 0;
|
return layer >= 0 && biomes != null && layer < biomes.length && biomes[layer] != null && biomes[layer].length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,281 @@
|
|||||||
|
package com.fastasyncworldedit.core.regions;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.anvil.MCAFile;
|
||||||
|
import com.fastasyncworldedit.core.anvil.MCAWorld;
|
||||||
|
import com.fastasyncworldedit.core.math.MutableBlockVector3;
|
||||||
|
import com.fastasyncworldedit.core.util.task.RunnableVal4;
|
||||||
|
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.Region;
|
||||||
|
import com.sk89q.worldedit.regions.RegionOperationException;
|
||||||
|
import com.sk89q.worldedit.world.World;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.AbstractSet;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class WorldRegionsRegion implements Region {
|
||||||
|
|
||||||
|
private final MCAWorld world;
|
||||||
|
private final BlockVector3 min;
|
||||||
|
private final BlockVector3 max;
|
||||||
|
private Set<BlockVector2> chunks = null;
|
||||||
|
|
||||||
|
public WorldRegionsRegion(@Nonnull final MCAWorld world) {
|
||||||
|
this.world = Objects.requireNonNull(world);
|
||||||
|
List<Path> regions = world.getRegionFileFiles();
|
||||||
|
int minX = Integer.MAX_VALUE;
|
||||||
|
int minZ = Integer.MAX_VALUE;
|
||||||
|
int maxX = Integer.MIN_VALUE;
|
||||||
|
int maxZ = Integer.MIN_VALUE;
|
||||||
|
for (Path p : regions) {
|
||||||
|
String[] split = p.getFileName().toString().split("\\.");
|
||||||
|
int x = Integer.parseInt(split[1]);
|
||||||
|
int z = Integer.parseInt(split[2]);
|
||||||
|
minX = Math.min(x, minX);
|
||||||
|
minZ = Math.min(z, minZ);
|
||||||
|
maxX = Math.max(x, maxX);
|
||||||
|
maxZ = Math.max(z, maxZ);
|
||||||
|
}
|
||||||
|
this.min = BlockVector3.at(
|
||||||
|
minX,
|
||||||
|
WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).versionMinY(),
|
||||||
|
minZ
|
||||||
|
);
|
||||||
|
this.max = BlockVector3.at(
|
||||||
|
maxX,
|
||||||
|
WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).versionMaxY(),
|
||||||
|
maxZ
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<BlockVector3> iterator() {
|
||||||
|
Queue<BlockVector2> queue = new ArrayDeque<>(getChunks());
|
||||||
|
return new Iterator<>() {
|
||||||
|
private final int by = min.getY();
|
||||||
|
private final int ty = max.getY();
|
||||||
|
private final MutableBlockVector3 mutable = new MutableBlockVector3();
|
||||||
|
|
||||||
|
private BlockVector2 chunk = queue.poll();
|
||||||
|
private int cx = chunk.getX() << 4;
|
||||||
|
private int cz = chunk.getZ() << 4;
|
||||||
|
private int x;
|
||||||
|
private int y;
|
||||||
|
private int z;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return x < 15 || y < ty || z < 15 || queue.peek() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableBlockVector3 next() {
|
||||||
|
int curX = x;
|
||||||
|
int curY = y;
|
||||||
|
int curZ = z;
|
||||||
|
if (++x > 15) {
|
||||||
|
if (++z > 15) {
|
||||||
|
if (++y > ty) {
|
||||||
|
if (!hasNext()) {
|
||||||
|
throw new NoSuchElementException("End of iterator") {
|
||||||
|
@Override
|
||||||
|
public Throwable fillInStackTrace() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
chunk = queue.poll();
|
||||||
|
x = 0;
|
||||||
|
y = by;
|
||||||
|
z = 0;
|
||||||
|
cx = chunk.getX() << 4;
|
||||||
|
cz = chunk.getZ() << 4;
|
||||||
|
return mutable.setComponents(cx + x, y, cz + z);
|
||||||
|
} else {
|
||||||
|
x = 0;
|
||||||
|
z = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mutable.setComponents(cx + curX, curY, cz + curZ);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWorld(final World world) {
|
||||||
|
throw new UnsupportedOperationException("Cannot modify WorldRegionsRegion - Immutable");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Region clone() {
|
||||||
|
return new WorldRegionsRegion(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<BlockVector2> polygonize(final int maxPoints) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shift(final BlockVector3 change) throws RegionOperationException {
|
||||||
|
throw new UnsupportedOperationException("Cannot modify WorldRegionsRegion - Immutable");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockVector3 getMinimumPoint() {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockVector3 getMaximumPoint() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get X-size.
|
||||||
|
*
|
||||||
|
* @return width
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getWidth() {
|
||||||
|
BlockVector3 min = getMinimumPoint();
|
||||||
|
BlockVector3 max = getMaximumPoint();
|
||||||
|
|
||||||
|
return max.getX() - min.getX() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Y-size.
|
||||||
|
*
|
||||||
|
* @return height
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getHeight() {
|
||||||
|
BlockVector3 min = getMinimumPoint();
|
||||||
|
BlockVector3 max = getMaximumPoint();
|
||||||
|
|
||||||
|
return max.getY() - min.getY() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Z-size.
|
||||||
|
*
|
||||||
|
* @return length
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getLength() {
|
||||||
|
BlockVector3 min = getMinimumPoint();
|
||||||
|
BlockVector3 max = getMaximumPoint();
|
||||||
|
|
||||||
|
return max.getZ() - min.getZ() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expand(final BlockVector3... changes) throws RegionOperationException {
|
||||||
|
throw new UnsupportedOperationException("Cannot modify WorldRegionsRegion - Immutable");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void contract(final BlockVector3... changes) throws RegionOperationException {
|
||||||
|
throw new UnsupportedOperationException("Cannot modify WorldRegionsRegion - Immutable");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(final BlockVector3 position) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<BlockVector2> getChunks() {
|
||||||
|
if (chunks == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (chunks != null) {
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
Set<BlockVector2> tmp = new HashSet<>();
|
||||||
|
for (MCAFile mca : world.getMCAs()) {
|
||||||
|
mca.forEachChunk(new RunnableVal4<>() {
|
||||||
|
@Override
|
||||||
|
public void run(Integer x, Integer z, Integer offset, Integer size) {
|
||||||
|
if (offset != 0 && size > 0) {
|
||||||
|
tmp.add(BlockVector2.at(x, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
chunks = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<BlockVector3> getChunkCubes() {
|
||||||
|
return new AbstractSet<>() {
|
||||||
|
@Override
|
||||||
|
public Iterator<BlockVector3> iterator() {
|
||||||
|
Queue<BlockVector2> chunks = new ArrayDeque<>(getChunks());
|
||||||
|
|
||||||
|
return new Iterator<>() {
|
||||||
|
private final MutableBlockVector3 mutable = new MutableBlockVector3();
|
||||||
|
private final int by = min.getY() >> 4;
|
||||||
|
private final int ty = max.getY() >> 4;
|
||||||
|
|
||||||
|
private BlockVector2 chunk = chunks.poll();
|
||||||
|
private int y;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return y < ty || chunks.peek() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockVector3 next() {
|
||||||
|
int curY = y;
|
||||||
|
if (++y > ty) {
|
||||||
|
if (!hasNext()) {
|
||||||
|
throw new NoSuchElementException("End of iterator") {
|
||||||
|
@Override
|
||||||
|
public Throwable fillInStackTrace() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
y = by;
|
||||||
|
chunk = chunks.poll();
|
||||||
|
return mutable.setComponents(chunk.getX(), y, chunk.getZ());
|
||||||
|
}
|
||||||
|
return mutable.setComponents(chunk.getX(), curY, chunk.getZ());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return getChunks().size() * ((max.getY() >> 4) - (min.getY() >> 4));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public World getWorld() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -63,6 +63,7 @@ public final class NBTInputStream implements Closeable {
|
|||||||
this.is = new DataInputStream(is);
|
this.is = new DataInputStream(is);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//FAWE start
|
||||||
public NBTInputStream(DataInputStream dis) {
|
public NBTInputStream(DataInputStream dis) {
|
||||||
this.is = dis;
|
this.is = dis;
|
||||||
}
|
}
|
||||||
@ -75,12 +76,14 @@ public final class NBTInputStream implements Closeable {
|
|||||||
is.reset();
|
is.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//FAWE end
|
||||||
/**
|
/**
|
||||||
* Reads an NBT tag from the stream.
|
* Reads an NBT tag from the stream.
|
||||||
*
|
*
|
||||||
* @return The tag that was read.
|
* @return The tag that was read.
|
||||||
*/
|
*/
|
||||||
public NamedTag readNamedTag() throws IOException {
|
public NamedTag readNamedTag() throws IOException {
|
||||||
|
//FAWE start
|
||||||
return readNamedTag(0);
|
return readNamedTag(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +104,7 @@ public final class NBTInputStream implements Closeable {
|
|||||||
return readTagPayload(type, 0);
|
return readTagPayload(type, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readNamedTagLazy(StreamDelegate scope) throws IOException {
|
public void readNamedTagLazy(StreamDelegate scope) {
|
||||||
try {
|
try {
|
||||||
int type = is.readByte();
|
int type = is.readByte();
|
||||||
if (type == NBTConstants.TYPE_END) {
|
if (type == NBTConstants.TYPE_END) {
|
||||||
@ -112,19 +115,30 @@ public final class NBTInputStream implements Closeable {
|
|||||||
if (child != null) {
|
if (child != null) {
|
||||||
child.acceptRoot(this, type, 0);
|
child.acceptRoot(this, type, 0);
|
||||||
} else {
|
} else {
|
||||||
readTagPayloadLazy(type, 0);
|
readTagPayloadLazy(type, 0, scope, scope.getRetained() != null);
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void readNamedTagLazyExceptionally(StreamDelegate scope) throws IOException {
|
||||||
|
int type = is.readByte();
|
||||||
|
if (type == NBTConstants.TYPE_END) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamDelegate child = scope.get(is);
|
||||||
|
if (child != null) {
|
||||||
|
child.acceptRoot(this, type, 0);
|
||||||
|
} else {
|
||||||
|
readTagPayloadLazy(type, 0, scope, scope.getRetained() != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String readNamedTagName(int type) throws IOException {
|
public String readNamedTagName(int type) throws IOException {
|
||||||
if (type != NBTConstants.TYPE_END) {
|
if (type != NBTConstants.TYPE_END) {
|
||||||
int nameLength = is.readShort() & 0xFFFF;
|
return is.readUTF();
|
||||||
byte[] nameBytes = new byte[nameLength];
|
|
||||||
is.readFully(nameBytes);
|
|
||||||
return new String(nameBytes, NBTConstants.CHARSET);
|
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -133,43 +147,70 @@ public final class NBTInputStream implements Closeable {
|
|||||||
private byte[] buf;
|
private byte[] buf;
|
||||||
|
|
||||||
public void readTagPayloadLazy(int type, int depth) throws IOException {
|
public void readTagPayloadLazy(int type, int depth) throws IOException {
|
||||||
|
readTagPayloadLazy(type, depth, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readTagPayloadLazy(int type, int depth, StreamDelegate scope, boolean retain) throws IOException {
|
||||||
|
int length;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case NBTConstants.TYPE_END:
|
case NBTConstants.TYPE_END -> {
|
||||||
return;
|
}
|
||||||
case NBTConstants.TYPE_BYTE:
|
case NBTConstants.TYPE_BYTE -> {
|
||||||
|
if (!retain) {
|
||||||
is.skipBytes(1);
|
is.skipBytes(1);
|
||||||
return;
|
} else {
|
||||||
case NBTConstants.TYPE_SHORT:
|
scope.retain(readTagPayload(type, depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case NBTConstants.TYPE_SHORT -> {
|
||||||
|
if (!retain) {
|
||||||
is.skipBytes(2);
|
is.skipBytes(2);
|
||||||
return;
|
} else {
|
||||||
case NBTConstants.TYPE_INT:
|
scope.retain(readTagPayload(type, depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case NBTConstants.TYPE_INT, NBTConstants.TYPE_FLOAT -> {
|
||||||
|
if (!retain) {
|
||||||
is.skipBytes(4);
|
is.skipBytes(4);
|
||||||
return;
|
} else {
|
||||||
case NBTConstants.TYPE_LONG:
|
scope.retain(readTagPayload(type, depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case NBTConstants.TYPE_LONG, NBTConstants.TYPE_DOUBLE -> {
|
||||||
|
if (!retain) {
|
||||||
is.skipBytes(8);
|
is.skipBytes(8);
|
||||||
return;
|
} else {
|
||||||
case NBTConstants.TYPE_FLOAT:
|
scope.retain(readTagPayload(type, depth));
|
||||||
is.skipBytes(4);
|
}
|
||||||
return;
|
}
|
||||||
case NBTConstants.TYPE_DOUBLE:
|
case NBTConstants.TYPE_STRING -> {
|
||||||
is.skipBytes(8);
|
length = is.readShort() & 0xFFFF;
|
||||||
return;
|
if (!retain) {
|
||||||
case NBTConstants.TYPE_STRING:
|
|
||||||
int length = is.readShort() & 0xFFFF;
|
|
||||||
is.skipBytes(length);
|
is.skipBytes(length);
|
||||||
return;
|
} else {
|
||||||
case NBTConstants.TYPE_BYTE_ARRAY:
|
scope.retain(readTagPayload(type, depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case NBTConstants.TYPE_BYTE_ARRAY -> {
|
||||||
|
if (!retain) {
|
||||||
is.skipBytes(is.readInt());
|
is.skipBytes(is.readInt());
|
||||||
return;
|
} else {
|
||||||
case NBTConstants.TYPE_LIST: {
|
scope.retain(readTagPayload(type, depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case NBTConstants.TYPE_LIST -> {
|
||||||
|
if (!retain) {
|
||||||
int childType = is.readByte();
|
int childType = is.readByte();
|
||||||
length = is.readInt();
|
length = is.readInt();
|
||||||
for (int i = 0; i < length; ++i) {
|
for (int i = 0; i < length; ++i) {
|
||||||
readTagPayloadLazy(childType, depth + 1);
|
readTagPayloadLazy(childType, depth + 1, scope, retain);
|
||||||
}
|
}
|
||||||
return;
|
} else {
|
||||||
|
scope.retain(readTagPayload(type, depth));
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_COMPOUND: {
|
}
|
||||||
|
case NBTConstants.TYPE_COMPOUND -> {
|
||||||
|
if (!retain) {
|
||||||
// readDataPayload
|
// readDataPayload
|
||||||
depth++;
|
depth++;
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -178,27 +219,35 @@ public final class NBTInputStream implements Closeable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
is.skipBytes(is.readShort() & 0xFFFF);
|
is.skipBytes(is.readShort() & 0xFFFF);
|
||||||
readTagPayloadLazy(childType, depth + 1);
|
readTagPayloadLazy(childType, depth + 1, scope, retain);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scope.retain(readTagPayload(type, depth));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_INT_ARRAY: {
|
case NBTConstants.TYPE_INT_ARRAY -> {
|
||||||
|
if (!retain) {
|
||||||
is.skipBytes(is.readInt() << 2);
|
is.skipBytes(is.readInt() << 2);
|
||||||
return;
|
} else {
|
||||||
|
scope.retain(readTagPayload(type, depth));
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_LONG_ARRAY: {
|
}
|
||||||
|
case NBTConstants.TYPE_LONG_ARRAY -> {
|
||||||
|
if (!retain) {
|
||||||
is.skipBytes(is.readInt() << 3);
|
is.skipBytes(is.readInt() << 3);
|
||||||
return;
|
} else {
|
||||||
|
scope.retain(readTagPayload(type, depth));
|
||||||
}
|
}
|
||||||
default:
|
}
|
||||||
throw new IOException("Invalid tag type: " + type + ".");
|
default -> throw new IOException("Invalid tag type: " + type + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readTagPayloadLazy(int type, int depth, StreamDelegate scope) throws IOException {
|
public void readTagPayloadLazy(int type, int depth, StreamDelegate scope) throws IOException {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case NBTConstants.TYPE_END:
|
case NBTConstants.TYPE_END -> {
|
||||||
return;
|
}
|
||||||
case NBTConstants.TYPE_BYTE: {
|
case NBTConstants.TYPE_BYTE -> {
|
||||||
ValueReader value = scope.getValueReader();
|
ValueReader value = scope.getValueReader();
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = scope.getElemReader();
|
value = scope.getElemReader();
|
||||||
@ -208,9 +257,8 @@ public final class NBTInputStream implements Closeable {
|
|||||||
} else {
|
} else {
|
||||||
is.skipBytes(1);
|
is.skipBytes(1);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_SHORT: {
|
case NBTConstants.TYPE_SHORT -> {
|
||||||
ValueReader value = scope.getValueReader();
|
ValueReader value = scope.getValueReader();
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = scope.getElemReader();
|
value = scope.getElemReader();
|
||||||
@ -220,9 +268,8 @@ public final class NBTInputStream implements Closeable {
|
|||||||
} else {
|
} else {
|
||||||
is.skipBytes(2);
|
is.skipBytes(2);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_INT: {
|
case NBTConstants.TYPE_INT -> {
|
||||||
ValueReader value = scope.getValueReader();
|
ValueReader value = scope.getValueReader();
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = scope.getElemReader();
|
value = scope.getElemReader();
|
||||||
@ -232,9 +279,8 @@ public final class NBTInputStream implements Closeable {
|
|||||||
} else {
|
} else {
|
||||||
is.skipBytes(4);
|
is.skipBytes(4);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_LONG: {
|
case NBTConstants.TYPE_LONG -> {
|
||||||
ValueReader value = scope.getValueReader();
|
ValueReader value = scope.getValueReader();
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = scope.getElemReader();
|
value = scope.getElemReader();
|
||||||
@ -244,9 +290,8 @@ public final class NBTInputStream implements Closeable {
|
|||||||
} else {
|
} else {
|
||||||
is.skipBytes(8);
|
is.skipBytes(8);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_FLOAT: {
|
case NBTConstants.TYPE_FLOAT -> {
|
||||||
ValueReader value = scope.getValueReader();
|
ValueReader value = scope.getValueReader();
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = scope.getElemReader();
|
value = scope.getElemReader();
|
||||||
@ -256,9 +301,8 @@ public final class NBTInputStream implements Closeable {
|
|||||||
} else {
|
} else {
|
||||||
is.skipBytes(4);
|
is.skipBytes(4);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_DOUBLE: {
|
case NBTConstants.TYPE_DOUBLE -> {
|
||||||
ValueReader value = scope.getValueReader();
|
ValueReader value = scope.getValueReader();
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = scope.getElemReader();
|
value = scope.getElemReader();
|
||||||
@ -268,24 +312,20 @@ public final class NBTInputStream implements Closeable {
|
|||||||
} else {
|
} else {
|
||||||
is.skipBytes(8);
|
is.skipBytes(8);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_STRING: {
|
case NBTConstants.TYPE_STRING -> {
|
||||||
ValueReader value = scope.getValueReader();
|
ValueReader value = scope.getValueReader();
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = scope.getElemReader();
|
value = scope.getElemReader();
|
||||||
}
|
}
|
||||||
int length = is.readShort() & 0xFFFF;
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
byte[] bytes = new byte[length];
|
value.apply(0, is.readUTF());
|
||||||
is.readFully(bytes);
|
|
||||||
value.apply(0, new String(bytes, NBTConstants.CHARSET));
|
|
||||||
} else {
|
} else {
|
||||||
|
int length = is.readShort() & 0xFFFF;
|
||||||
is.skipBytes(length);
|
is.skipBytes(length);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_LIST: {
|
case NBTConstants.TYPE_LIST -> {
|
||||||
int childType = is.readByte();
|
int childType = is.readByte();
|
||||||
int length = is.readInt();
|
int length = is.readInt();
|
||||||
StreamDelegate child;
|
StreamDelegate child;
|
||||||
@ -306,16 +346,15 @@ public final class NBTInputStream implements Closeable {
|
|||||||
child = scope.get0();
|
child = scope.get0();
|
||||||
if (child == null) {
|
if (child == null) {
|
||||||
for (int i = 0; i < length; ++i) {
|
for (int i = 0; i < length; ++i) {
|
||||||
readTagPayloadLazy(childType, depth + 1);
|
readTagPayloadLazy(childType, depth + 1, scope, scope.getRetained() != null);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < length; ++i) {
|
for (int i = 0; i < length; ++i) {
|
||||||
readTagPayloadLazy(childType, depth + 1, child);
|
readTagPayloadLazy(childType, depth + 1, child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_COMPOUND: {
|
case NBTConstants.TYPE_COMPOUND -> {
|
||||||
// readDataPayload
|
// readDataPayload
|
||||||
scope.acceptInfo(-1, NBTConstants.TYPE_BYTE);
|
scope.acceptInfo(-1, NBTConstants.TYPE_BYTE);
|
||||||
ValueReader valueReader = scope.getValueReader();
|
ValueReader valueReader = scope.getValueReader();
|
||||||
@ -342,14 +381,23 @@ public final class NBTInputStream implements Closeable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StreamDelegate child = scope.get(is);
|
StreamDelegate child = scope.get(is);
|
||||||
|
try {
|
||||||
if (child == null) {
|
if (child == null) {
|
||||||
readTagPayloadLazy(childType, depth + 1);
|
readTagPayloadLazy(childType, depth + 1, scope, scope.getRetained() != null);
|
||||||
} else {
|
} else {
|
||||||
readTagPayloadLazy(childType, depth + 1, child);
|
readTagPayloadLazy(childType, depth + 1, child);
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
String cur = scope.getCurrentName() == null ? scope.getRetainedName() : scope.getCurrentName();
|
||||||
|
if (cur != null) {
|
||||||
|
throw new IOException("Error reading child scope: `" + scope.getCurrentName() + "`", e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_BYTE_ARRAY: {
|
}
|
||||||
|
}
|
||||||
|
case NBTConstants.TYPE_BYTE_ARRAY -> {
|
||||||
int length = is.readInt();
|
int length = is.readInt();
|
||||||
scope.acceptInfo(length, NBTConstants.TYPE_BYTE);
|
scope.acceptInfo(length, NBTConstants.TYPE_BYTE);
|
||||||
if (scope.acceptLazy(length, this)) {
|
if (scope.acceptLazy(length, this)) {
|
||||||
@ -384,9 +432,8 @@ public final class NBTInputStream implements Closeable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
is.skipBytes(length);
|
is.skipBytes(length);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_INT_ARRAY: {
|
case NBTConstants.TYPE_INT_ARRAY -> {
|
||||||
int length = is.readInt();
|
int length = is.readInt();
|
||||||
scope.acceptInfo(length, NBTConstants.TYPE_INT);
|
scope.acceptInfo(length, NBTConstants.TYPE_INT);
|
||||||
if (scope.acceptLazy(length, this)) {
|
if (scope.acceptLazy(length, this)) {
|
||||||
@ -405,9 +452,8 @@ public final class NBTInputStream implements Closeable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
is.skipBytes(length << 2);
|
is.skipBytes(length << 2);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_LONG_ARRAY: {
|
case NBTConstants.TYPE_LONG_ARRAY -> {
|
||||||
int length = is.readInt();
|
int length = is.readInt();
|
||||||
scope.acceptInfo(length, NBTConstants.TYPE_LONG);
|
scope.acceptInfo(length, NBTConstants.TYPE_LONG);
|
||||||
if (scope.acceptLazy(length, this)) {
|
if (scope.acceptLazy(length, this)) {
|
||||||
@ -426,11 +472,8 @@ public final class NBTInputStream implements Closeable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
is.skipBytes(length << 3);
|
is.skipBytes(length << 3);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
default -> throw new IOException("Invalid tag type: " + type + ".");
|
||||||
default:
|
|
||||||
throw new IOException("Invalid tag type: " + type + ".");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,65 +490,60 @@ public final class NBTInputStream implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int getSize(int type) {
|
public static int getSize(int type) {
|
||||||
switch (type) {
|
return switch (type) {
|
||||||
default:
|
default -> 1;
|
||||||
case NBTConstants.TYPE_END:
|
case NBTConstants.TYPE_BYTE_ARRAY, NBTConstants.TYPE_STRING, NBTConstants.TYPE_LIST, NBTConstants.TYPE_COMPOUND, NBTConstants.TYPE_INT_ARRAY, NBTConstants.TYPE_LONG_ARRAY, NBTConstants.TYPE_SHORT ->
|
||||||
case NBTConstants.TYPE_BYTE:
|
2;
|
||||||
return 1;
|
case NBTConstants.TYPE_FLOAT, NBTConstants.TYPE_INT -> 4;
|
||||||
case NBTConstants.TYPE_BYTE_ARRAY:
|
case NBTConstants.TYPE_DOUBLE, NBTConstants.TYPE_LONG -> 8;
|
||||||
case NBTConstants.TYPE_STRING:
|
};
|
||||||
case NBTConstants.TYPE_LIST:
|
|
||||||
case NBTConstants.TYPE_COMPOUND:
|
|
||||||
case NBTConstants.TYPE_INT_ARRAY:
|
|
||||||
case NBTConstants.TYPE_LONG_ARRAY:
|
|
||||||
case NBTConstants.TYPE_SHORT:
|
|
||||||
return 2;
|
|
||||||
case NBTConstants.TYPE_FLOAT:
|
|
||||||
case NBTConstants.TYPE_INT:
|
|
||||||
return 4;
|
|
||||||
case NBTConstants.TYPE_DOUBLE:
|
|
||||||
case NBTConstants.TYPE_LONG:
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object readTagPayloadRaw(int type, int depth) throws IOException {
|
public Object readTagPayloadRaw(int type, int depth) throws IOException {
|
||||||
|
int length;
|
||||||
|
byte[] bytes;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case NBTConstants.TYPE_END:
|
case NBTConstants.TYPE_END -> {
|
||||||
if (depth == 0) {
|
if (depth == 0) {
|
||||||
throw new IOException(
|
throw new IOException(
|
||||||
"TAG_End found without a TAG_Compound/TAG_List tag preceding it.");
|
"TAG_End found without a TAG_Compound/TAG_List tag preceding it.");
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_BYTE:
|
}
|
||||||
|
case NBTConstants.TYPE_BYTE -> {
|
||||||
return (is.readByte());
|
return (is.readByte());
|
||||||
case NBTConstants.TYPE_SHORT:
|
}
|
||||||
|
case NBTConstants.TYPE_SHORT -> {
|
||||||
return (is.readShort());
|
return (is.readShort());
|
||||||
case NBTConstants.TYPE_INT:
|
}
|
||||||
|
case NBTConstants.TYPE_INT -> {
|
||||||
return (is.readInt());
|
return (is.readInt());
|
||||||
case NBTConstants.TYPE_LONG:
|
}
|
||||||
|
case NBTConstants.TYPE_LONG -> {
|
||||||
return (is.readLong());
|
return (is.readLong());
|
||||||
case NBTConstants.TYPE_FLOAT:
|
}
|
||||||
|
case NBTConstants.TYPE_FLOAT -> {
|
||||||
return (is.readFloat());
|
return (is.readFloat());
|
||||||
case NBTConstants.TYPE_DOUBLE:
|
}
|
||||||
|
case NBTConstants.TYPE_DOUBLE -> {
|
||||||
return (is.readDouble());
|
return (is.readDouble());
|
||||||
case NBTConstants.TYPE_BYTE_ARRAY:
|
}
|
||||||
int length = is.readInt();
|
case NBTConstants.TYPE_BYTE_ARRAY -> {
|
||||||
byte[] bytes = new byte[length];
|
length = is.readInt();
|
||||||
is.readFully(bytes);
|
|
||||||
return (bytes);
|
|
||||||
case NBTConstants.TYPE_STRING:
|
|
||||||
length = is.readShort() & 0xFFFF;
|
|
||||||
bytes = new byte[length];
|
bytes = new byte[length];
|
||||||
is.readFully(bytes);
|
is.readFully(bytes);
|
||||||
return (new String(bytes, NBTConstants.CHARSET));
|
return (bytes);
|
||||||
case NBTConstants.TYPE_LIST: {
|
}
|
||||||
|
case NBTConstants.TYPE_STRING -> {
|
||||||
|
return is.readUTF();
|
||||||
|
}
|
||||||
|
case NBTConstants.TYPE_LIST -> {
|
||||||
int childType = is.readByte();
|
int childType = is.readByte();
|
||||||
length = is.readInt();
|
length = is.readInt();
|
||||||
return readListRaw(depth, childType, length);
|
return readListRaw(depth, childType, length);
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_COMPOUND: {
|
case NBTConstants.TYPE_COMPOUND -> {
|
||||||
Map<String, Object> tagMap = new HashMap<>();
|
Map<String, Object> tagMap = new HashMap<>();
|
||||||
while (true) {
|
while (true) {
|
||||||
int childType = is.readByte();
|
int childType = is.readByte();
|
||||||
@ -517,16 +555,15 @@ public final class NBTInputStream implements Closeable {
|
|||||||
tagMap.put(name, value);
|
tagMap.put(name, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_INT_ARRAY: {
|
case NBTConstants.TYPE_INT_ARRAY -> {
|
||||||
length = is.readInt();
|
length = is.readInt();
|
||||||
return readIntArrayRaw(length);
|
return readIntArrayRaw(length);
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_LONG_ARRAY: {
|
case NBTConstants.TYPE_LONG_ARRAY -> {
|
||||||
length = is.readInt();
|
length = is.readInt();
|
||||||
return readLongArrayRaw(length);
|
return readLongArrayRaw(length);
|
||||||
}
|
}
|
||||||
default:
|
default -> throw new IOException("Invalid tag type: " + type + ".");
|
||||||
throw new IOException("Invalid tag type: " + type + ".");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,7 +579,7 @@ public final class NBTInputStream implements Closeable {
|
|||||||
for (int i = 0; i < toRead; i += 4, index++) {
|
for (int i = 0; i < toRead; i += 4, index++) {
|
||||||
data[index] = ((buf[i] & 0xFF) << 24) + ((buf[i + 1] & 0xFF) << 16) + ((buf[i + 2] & 0xFF) << 8) + (buf[i + 3] & 0xFF);
|
data[index] = ((buf[i] & 0xFF) << 24) + ((buf[i + 1] & 0xFF) << 16) + ((buf[i + 2] & 0xFF) << 8) + (buf[i + 3] & 0xFF);
|
||||||
}
|
}
|
||||||
length -= toRead;
|
length -= (toRead >> 2);
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -559,7 +596,7 @@ public final class NBTInputStream implements Closeable {
|
|||||||
for (int i = 0; i < toRead; i += 8, index++) {
|
for (int i = 0; i < toRead; i += 8, index++) {
|
||||||
data[index] = (((long) buf[i] << 56) | ((long) (buf[i + 1] & 255) << 48) | ((long) (buf[i + 2] & 255) << 40) | ((long) (buf[i + 3] & 255) << 32) | ((long) (buf[i + 4] & 255) << 24) | ((buf[i + 5] & 255) << 16) | ((buf[i + 6] & 255) << 8) | (buf[i + 7] & 255));
|
data[index] = (((long) buf[i] << 56) | ((long) (buf[i + 1] & 255) << 48) | ((long) (buf[i + 2] & 255) << 40) | ((long) (buf[i + 3] & 255) << 32) | ((long) (buf[i + 4] & 255) << 24) | ((buf[i + 5] & 255) << 16) | ((buf[i + 6] & 255) << 8) | (buf[i + 7] & 255));
|
||||||
}
|
}
|
||||||
length -= toRead;
|
length -= (toRead >> 3);
|
||||||
}
|
}
|
||||||
return (data);
|
return (data);
|
||||||
}
|
}
|
||||||
@ -573,40 +610,47 @@ public final class NBTInputStream implements Closeable {
|
|||||||
* @throws IOException if an I/O error occurs.
|
* @throws IOException if an I/O error occurs.
|
||||||
*/
|
*/
|
||||||
public Tag readTagPayload(int type, int depth) throws IOException { //FAWE - public
|
public Tag readTagPayload(int type, int depth) throws IOException { //FAWE - public
|
||||||
|
int length;
|
||||||
|
byte[] bytes;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case NBTConstants.TYPE_END:
|
case NBTConstants.TYPE_END -> {
|
||||||
if (depth == 0) {
|
if (depth == 0) {
|
||||||
throw new IOException(
|
throw new IOException(
|
||||||
"TAG_End found without a TAG_Compound/TAG_List tag preceding it.");
|
"TAG_End found without a TAG_Compound/TAG_List tag preceding it.");
|
||||||
} else {
|
} else {
|
||||||
return new EndTag();
|
return new EndTag();
|
||||||
}
|
}
|
||||||
case NBTConstants.TYPE_BYTE:
|
}
|
||||||
|
case NBTConstants.TYPE_BYTE -> {
|
||||||
return new ByteTag(is.readByte());
|
return new ByteTag(is.readByte());
|
||||||
case NBTConstants.TYPE_SHORT:
|
}
|
||||||
|
case NBTConstants.TYPE_SHORT -> {
|
||||||
return new ShortTag(is.readShort());
|
return new ShortTag(is.readShort());
|
||||||
case NBTConstants.TYPE_INT:
|
}
|
||||||
|
case NBTConstants.TYPE_INT -> {
|
||||||
return new IntTag(is.readInt());
|
return new IntTag(is.readInt());
|
||||||
case NBTConstants.TYPE_LONG:
|
}
|
||||||
|
case NBTConstants.TYPE_LONG -> {
|
||||||
return new LongTag(is.readLong());
|
return new LongTag(is.readLong());
|
||||||
case NBTConstants.TYPE_FLOAT:
|
}
|
||||||
|
case NBTConstants.TYPE_FLOAT -> {
|
||||||
return new FloatTag(is.readFloat());
|
return new FloatTag(is.readFloat());
|
||||||
case NBTConstants.TYPE_DOUBLE:
|
}
|
||||||
|
case NBTConstants.TYPE_DOUBLE -> {
|
||||||
return new DoubleTag(is.readDouble());
|
return new DoubleTag(is.readDouble());
|
||||||
case NBTConstants.TYPE_BYTE_ARRAY:
|
}
|
||||||
int length = is.readInt();
|
case NBTConstants.TYPE_BYTE_ARRAY -> {
|
||||||
byte[] bytes = new byte[length];
|
length = is.readInt();
|
||||||
is.readFully(bytes);
|
|
||||||
return new ByteArrayTag(bytes);
|
|
||||||
case NBTConstants.TYPE_STRING:
|
|
||||||
length = is.readShort() & 0xFFFF;
|
|
||||||
bytes = new byte[length];
|
bytes = new byte[length];
|
||||||
is.readFully(bytes);
|
is.readFully(bytes);
|
||||||
return new StringTag(new String(bytes, NBTConstants.CHARSET));
|
return new ByteArrayTag(bytes);
|
||||||
case NBTConstants.TYPE_LIST:
|
}
|
||||||
|
case NBTConstants.TYPE_STRING -> {
|
||||||
|
return new StringTag(is.readUTF());
|
||||||
|
}
|
||||||
|
case NBTConstants.TYPE_LIST -> {
|
||||||
int childType = is.readByte();
|
int childType = is.readByte();
|
||||||
length = is.readInt();
|
length = is.readInt();
|
||||||
|
|
||||||
List<Tag> tagList = new ArrayList<>();
|
List<Tag> tagList = new ArrayList<>();
|
||||||
for (int i = 0; i < length; ++i) {
|
for (int i = 0; i < length; ++i) {
|
||||||
Tag tag = readTagPayload(childType, depth + 1);
|
Tag tag = readTagPayload(childType, depth + 1);
|
||||||
@ -615,9 +659,9 @@ public final class NBTInputStream implements Closeable {
|
|||||||
}
|
}
|
||||||
tagList.add(tag);
|
tagList.add(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ListTag(NBTUtils.getTypeClass(childType), tagList);
|
return new ListTag(NBTUtils.getTypeClass(childType), tagList);
|
||||||
case NBTConstants.TYPE_COMPOUND:
|
}
|
||||||
|
case NBTConstants.TYPE_COMPOUND -> {
|
||||||
Map<String, Tag<?, ?>> tagMap = new HashMap<>();
|
Map<String, Tag<?, ?>> tagMap = new HashMap<>();
|
||||||
while (true) {
|
while (true) {
|
||||||
NamedTag namedTag = readNamedTag(depth + 1);
|
NamedTag namedTag = readNamedTag(depth + 1);
|
||||||
@ -628,24 +672,25 @@ public final class NBTInputStream implements Closeable {
|
|||||||
tagMap.put(namedTag.getName(), tag);
|
tagMap.put(namedTag.getName(), tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CompoundTag(tagMap);
|
return new CompoundTag(tagMap);
|
||||||
case NBTConstants.TYPE_INT_ARRAY:
|
}
|
||||||
|
case NBTConstants.TYPE_INT_ARRAY -> {
|
||||||
length = is.readInt();
|
length = is.readInt();
|
||||||
int[] data = new int[length];
|
int[] data = new int[length];
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
data[i] = is.readInt();
|
data[i] = is.readInt();
|
||||||
}
|
}
|
||||||
return new IntArrayTag(data);
|
return new IntArrayTag(data);
|
||||||
case NBTConstants.TYPE_LONG_ARRAY:
|
}
|
||||||
|
case NBTConstants.TYPE_LONG_ARRAY -> {
|
||||||
length = is.readInt();
|
length = is.readInt();
|
||||||
long[] longData = new long[length];
|
long[] longData = new long[length];
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
longData[i] = is.readLong();
|
longData[i] = is.readLong();
|
||||||
}
|
}
|
||||||
return new LongArrayTag(longData);
|
return new LongArrayTag(longData);
|
||||||
default:
|
}
|
||||||
throw new IOException("Invalid tag type: " + type + ".");
|
default -> throw new IOException("Invalid tag type: " + type + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,9 +112,7 @@ public final class NBTOutputStream extends OutputStream implements Closeable, Da
|
|||||||
checkNotNull(value);
|
checkNotNull(value);
|
||||||
int type = NBTConstants.TYPE_STRING;
|
int type = NBTConstants.TYPE_STRING;
|
||||||
writeNamedTagName(name, type);
|
writeNamedTagName(name, type);
|
||||||
byte[] bytes = value.getBytes(NBTConstants.CHARSET);
|
os.writeUTF(value);
|
||||||
os.writeShort(bytes.length);
|
|
||||||
os.write(bytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeNamedTag(String name, int value) throws IOException {
|
public void writeNamedTag(String name, int value) throws IOException {
|
||||||
@ -163,6 +161,16 @@ public final class NBTOutputStream extends OutputStream implements Closeable, Da
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void writeNamedTag(String name, long[] data) throws IOException {
|
||||||
|
checkNotNull(name);
|
||||||
|
int type = NBTConstants.TYPE_LONG_ARRAY;
|
||||||
|
writeNamedTagName(name, type);
|
||||||
|
os.writeInt(data.length);
|
||||||
|
for (long aData : data) {
|
||||||
|
os.writeLong(aData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void writeNamedEmptyList(String name) throws IOException {
|
public void writeNamedEmptyList(String name) throws IOException {
|
||||||
writeNamedEmptyList(name, NBTConstants.TYPE_COMPOUND);
|
writeNamedEmptyList(name, NBTConstants.TYPE_COMPOUND);
|
||||||
}
|
}
|
||||||
@ -183,10 +191,13 @@ public final class NBTOutputStream extends OutputStream implements Closeable, Da
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void writeLazyCompoundTag(String name, LazyWrite next) throws IOException {
|
public void writeLazyCompoundTag(String name, LazyWrite next) throws IOException {
|
||||||
byte[] nameBytes = name.getBytes(NBTConstants.CHARSET);
|
|
||||||
os.writeByte(NBTConstants.TYPE_COMPOUND);
|
os.writeByte(NBTConstants.TYPE_COMPOUND);
|
||||||
os.writeShort(nameBytes.length);
|
os.writeUTF(name);
|
||||||
os.write(nameBytes);
|
next.write(this);
|
||||||
|
os.writeByte(NBTConstants.TYPE_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeLazyListedCompoundTag(LazyWrite next) throws IOException {
|
||||||
next.write(this);
|
next.write(this);
|
||||||
os.writeByte(NBTConstants.TYPE_END);
|
os.writeByte(NBTConstants.TYPE_END);
|
||||||
}
|
}
|
||||||
@ -327,9 +338,7 @@ public final class NBTOutputStream extends OutputStream implements Closeable, Da
|
|||||||
* @throws IOException if an I/O error occurs.
|
* @throws IOException if an I/O error occurs.
|
||||||
*/
|
*/
|
||||||
private void writeStringTagPayload(StringTag tag) throws IOException {
|
private void writeStringTagPayload(StringTag tag) throws IOException {
|
||||||
byte[] bytes = tag.getValue().getBytes(NBTConstants.CHARSET);
|
os.writeUTF(tag.getValue());
|
||||||
os.writeShort(bytes.length);
|
|
||||||
os.write(bytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,12 +1,24 @@
|
|||||||
package com.fastasyncworldedit.core.command;
|
package com.sk89q.worldedit.command;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
|
import com.fastasyncworldedit.core.anvil.MCAChunk;
|
||||||
|
import com.fastasyncworldedit.core.anvil.MCAWorld;
|
||||||
|
import com.fastasyncworldedit.core.configuration.Caption;
|
||||||
|
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
||||||
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
|
import com.fastasyncworldedit.core.regions.WorldRegionsRegion;
|
||||||
|
import com.fastasyncworldedit.core.util.MaskTraverser;
|
||||||
import com.sk89q.worldedit.EditSession;
|
import com.sk89q.worldedit.EditSession;
|
||||||
|
import com.sk89q.worldedit.EditSessionBuilder;
|
||||||
import com.sk89q.worldedit.LocalSession;
|
import com.sk89q.worldedit.LocalSession;
|
||||||
import com.sk89q.worldedit.WorldEdit;
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
import com.sk89q.worldedit.WorldEditException;
|
import com.sk89q.worldedit.WorldEditException;
|
||||||
import com.sk89q.worldedit.command.util.CommandPermissions;
|
import com.sk89q.worldedit.command.util.CommandPermissions;
|
||||||
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
|
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
|
||||||
import com.sk89q.worldedit.entity.Player;
|
import com.sk89q.worldedit.entity.Player;
|
||||||
|
import com.sk89q.worldedit.extension.platform.Actor;
|
||||||
|
import com.sk89q.worldedit.function.mask.AbstractExtentMask;
|
||||||
|
import com.sk89q.worldedit.function.mask.Mask;
|
||||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||||
import com.sk89q.worldedit.internal.annotation.Selection;
|
import com.sk89q.worldedit.internal.annotation.Selection;
|
||||||
import com.sk89q.worldedit.regions.Region;
|
import com.sk89q.worldedit.regions.Region;
|
||||||
@ -20,13 +32,6 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Anvil classes were used on versions prior to 1.13 to trim chunks.
|
|
||||||
* The way how it's been done was unsafe and led to issues back the years, hence it
|
|
||||||
* hasn't been implemented in any modern version. Therefore the current
|
|
||||||
* implementation is deprecated for removal without replacement.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
|
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
|
||||||
public class AnvilCommands {
|
public class AnvilCommands {
|
||||||
|
|
||||||
@ -116,57 +121,22 @@ public class AnvilCommands {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
name = "replaceall",
|
name = "replaceall", aliases = {"rea", "repall"}, desc = "Replace all blocks in the selection with another")
|
||||||
aliases = {"rea", "repall"},
|
|
||||||
desc = "Replace all blocks in the selection with another"
|
|
||||||
)
|
|
||||||
@CommandPermissions("worldedit.anvil.replaceall")
|
@CommandPermissions("worldedit.anvil.replaceall")
|
||||||
public void replaceAll(
|
public void replaceAll(
|
||||||
Player player, String folder,
|
Actor actor,
|
||||||
@Arg(name = "from", desc = "String", def = "")
|
@Arg(name = "world", desc = "Unloaded world") MCAWorld world,
|
||||||
String fromPattern,
|
@Arg(name = "from", desc = "Mask") Mask from,
|
||||||
String toPatternStr,
|
@Arg(name = "to", desc = "Pattern") Pattern to
|
||||||
@Switch(name = 'd', desc = "Disable wildcard data matching")
|
|
||||||
boolean useData
|
|
||||||
) throws WorldEditException {
|
) throws WorldEditException {
|
||||||
// final FaweBlockMatcher matchFrom; TODO NOT IMPLEMENTED
|
IQueueExtent<IQueueChunk> queueExtent = Fawe.instance().getQueueHandler().getMCAQueue(world);
|
||||||
// if (from == null) {
|
new MaskTraverser(from).setNewExtent(queueExtent);
|
||||||
// matchFrom = FaweBlockMatcher.NOT_AIR;
|
queueExtent.replaceBlocks(new WorldRegionsRegion(world), from, to);
|
||||||
// } else {
|
queueExtent.flush();
|
||||||
// if (from.contains(":")) {
|
actor.print(Caption.of("fawe.worldedit.anvil.replaceall.complete"));
|
||||||
// useData = true; //override d flag, if they specified data they want it
|
|
||||||
// }
|
|
||||||
// matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData);
|
|
||||||
// }
|
|
||||||
// final FaweBlockMatcher matchTo = FaweBlockMatcher.setBlocks(worldEdit.getBlocks(player, to, true));
|
|
||||||
// ReplaceSimpleFilter filter = new ReplaceSimpleFilter(matchFrom, matchTo);
|
|
||||||
// ReplaceSimpleFilter result = runWithWorld(player, folder, filter, true);
|
|
||||||
// if (result != null) player.print(Caption.of("fawe.worldedit.visitor.visitor.block", (result.getTotal())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(name = "deleteallunvisited", aliases = {"delunvisited"}, desc = "Delete all chunks which haven't been occupied",
|
||||||
name = "remapall",
|
|
||||||
descFooter = "Remap the world between MCPE/PC values",
|
|
||||||
desc = "Remap the world between MCPE/PC values"
|
|
||||||
)
|
|
||||||
@CommandPermissions("worldedit.anvil.remapall")
|
|
||||||
public void remapall(Player player, String folder) throws WorldEditException {
|
|
||||||
// ClipboardRemapper.RemapPlatform from; TODO NOT IMPLEMENTED
|
|
||||||
// ClipboardRemapper.RemapPlatform to;
|
|
||||||
// from = ClipboardRemapper.RemapPlatform.PE;
|
|
||||||
// to = ClipboardRemapper.RemapPlatform.PC;
|
|
||||||
// RemapFilter filter = new RemapFilter(from, to);
|
|
||||||
// RemapFilter result = runWithWorld(player, folder, filter, true);
|
|
||||||
// if (result != null) {
|
|
||||||
// player.print(Caption.of("fawe.worldedit.visitor.visitor.block", (result.getTotal())));
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Command(
|
|
||||||
name = "deleteallunvisited",
|
|
||||||
aliases = {"delunvisited"},
|
|
||||||
desc = "Delete all chunks which haven't been occupied",
|
|
||||||
descFooter = "occupied for `age-ticks` (20t = 1s) and \n"
|
descFooter = "occupied for `age-ticks` (20t = 1s) and \n"
|
||||||
+ "Have not been accessed since `file-duration` (ms) after creation and\n"
|
+ "Have not been accessed since `file-duration` (ms) after creation and\n"
|
||||||
+ "Have not been used in the past `chunk-inactivity` (ms)"
|
+ "Have not been used in the past `chunk-inactivity` (ms)"
|
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.command.argument;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
|
import com.fastasyncworldedit.core.anvil.MCAWorld;
|
||||||
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
|
import com.sk89q.worldedit.extension.platform.Capability;
|
||||||
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
|
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||||
|
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||||
|
import com.sk89q.worldedit.world.World;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.enginehub.piston.CommandManager;
|
||||||
|
import org.enginehub.piston.converter.ArgumentConverter;
|
||||||
|
import org.enginehub.piston.converter.ConversionResult;
|
||||||
|
import org.enginehub.piston.converter.FailedConversion;
|
||||||
|
import org.enginehub.piston.converter.SuccessfulConversion;
|
||||||
|
import org.enginehub.piston.inject.InjectedValueAccess;
|
||||||
|
import org.enginehub.piston.inject.Key;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class MCAWorldConverter implements ArgumentConverter<MCAWorld> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
|
public static void register(CommandManager commandManager) {
|
||||||
|
commandManager.registerConverter(Key.of(MCAWorld.class), WORLD_CONVERTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
//FAWE start - Accessed by LocationConverter
|
||||||
|
public static final MCAWorldConverter WORLD_CONVERTER = new MCAWorldConverter();
|
||||||
|
//FAWE end
|
||||||
|
|
||||||
|
private final TextComponent choices;
|
||||||
|
|
||||||
|
private MCAWorldConverter() {
|
||||||
|
this.choices = TextComponent.of("any world");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component describeAcceptableArguments() {
|
||||||
|
return this.choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<? extends String> getWorlds() {
|
||||||
|
try {
|
||||||
|
return Files.list(Fawe.platform().getWorldsFolder()).filter(p -> Files.isDirectory(p) && Files.exists(p.resolve(
|
||||||
|
"level.dat"))).map(p -> p.getFileName().toString());
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Error accessing worlds", e);
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getSuggestions(String input, InjectedValueAccess context) {
|
||||||
|
return getWorlds()
|
||||||
|
.filter(world -> world.startsWith(input))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConversionResult<MCAWorld> convert(String s, InjectedValueAccess injectedValueAccess) {
|
||||||
|
String result = getWorlds()
|
||||||
|
.filter(world -> world.equals(s))
|
||||||
|
.findAny().orElse(null);
|
||||||
|
return result == null
|
||||||
|
? FailedConversion.from(new IllegalArgumentException(
|
||||||
|
"Not a valid world: " + s))
|
||||||
|
: SuccessfulConversion.fromSingle(MCAWorld.of(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -276,5 +276,21 @@ public interface Platform extends Keyed {
|
|||||||
default IBatchProcessor getPlatformPostProcessor(boolean fastMode) {
|
default IBatchProcessor getPlatformPostProcessor(boolean fastMode) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a char array of minecraft internal IDs against FAWE char IDs
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
default char[] getIbdToStateOrdinal() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an int array of FAWE char IDs against minecraft internal IDs
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
default int[] getOrdinalToIbdID() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
//FAWE end
|
//FAWE end
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,8 @@ import com.sk89q.worldedit.LocalConfiguration;
|
|||||||
import com.sk89q.worldedit.LocalSession;
|
import com.sk89q.worldedit.LocalSession;
|
||||||
import com.sk89q.worldedit.MissingWorldException;
|
import com.sk89q.worldedit.MissingWorldException;
|
||||||
import com.sk89q.worldedit.WorldEdit;
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
|
import com.sk89q.worldedit.command.AnvilCommands;
|
||||||
|
import com.sk89q.worldedit.command.AnvilCommandsRegistration;
|
||||||
import com.sk89q.worldedit.command.ApplyBrushCommands;
|
import com.sk89q.worldedit.command.ApplyBrushCommands;
|
||||||
import com.sk89q.worldedit.command.BiomeCommands;
|
import com.sk89q.worldedit.command.BiomeCommands;
|
||||||
import com.sk89q.worldedit.command.BiomeCommandsRegistration;
|
import com.sk89q.worldedit.command.BiomeCommandsRegistration;
|
||||||
@ -98,6 +100,7 @@ import com.sk89q.worldedit.command.argument.ExpressionConverter;
|
|||||||
import com.sk89q.worldedit.command.argument.FactoryConverter;
|
import com.sk89q.worldedit.command.argument.FactoryConverter;
|
||||||
import com.sk89q.worldedit.command.argument.HeightConverter;
|
import com.sk89q.worldedit.command.argument.HeightConverter;
|
||||||
import com.sk89q.worldedit.command.argument.LocationConverter;
|
import com.sk89q.worldedit.command.argument.LocationConverter;
|
||||||
|
import com.sk89q.worldedit.command.argument.MCAWorldConverter;
|
||||||
import com.sk89q.worldedit.command.argument.OffsetConverter;
|
import com.sk89q.worldedit.command.argument.OffsetConverter;
|
||||||
import com.sk89q.worldedit.command.argument.RegionFactoryConverter;
|
import com.sk89q.worldedit.command.argument.RegionFactoryConverter;
|
||||||
import com.sk89q.worldedit.command.argument.RegistryConverter;
|
import com.sk89q.worldedit.command.argument.RegistryConverter;
|
||||||
@ -275,6 +278,7 @@ public final class PlatformCommandManager {
|
|||||||
ClipboardFormatConverter.register(commandManager);
|
ClipboardFormatConverter.register(commandManager);
|
||||||
ClipboardShareDestinationConverter.register(commandManager);
|
ClipboardShareDestinationConverter.register(commandManager);
|
||||||
//FAWE start
|
//FAWE start
|
||||||
|
MCAWorldConverter.register(commandManager);
|
||||||
commandManager.registerConverter(
|
commandManager.registerConverter(
|
||||||
Key.of(com.sk89q.worldedit.function.pattern.Pattern.class, Annotations.patternList()),
|
Key.of(com.sk89q.worldedit.function.pattern.Pattern.class, Annotations.patternList()),
|
||||||
CommaSeparatedValuesConverter.wrap(commandManager.getConverter(Key.of(
|
CommaSeparatedValuesConverter.wrap(commandManager.getConverter(Key.of(
|
||||||
@ -536,6 +540,13 @@ public final class PlatformCommandManager {
|
|||||||
HistorySubCommandsRegistration.builder(),
|
HistorySubCommandsRegistration.builder(),
|
||||||
new HistorySubCommands(history)
|
new HistorySubCommands(history)
|
||||||
);
|
);
|
||||||
|
registerSubCommands(
|
||||||
|
"/anvil",
|
||||||
|
ImmutableList.of(),
|
||||||
|
"Anvil commands",
|
||||||
|
AnvilCommandsRegistration.builder(),
|
||||||
|
new AnvilCommands(worldEdit)
|
||||||
|
);
|
||||||
//FAWE end
|
//FAWE end
|
||||||
this.registration.register(
|
this.registration.register(
|
||||||
commandManager,
|
commandManager,
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
"fawe.worldedit.history.command.undo.disabled": "Undo disabled, use: //fast",
|
"fawe.worldedit.history.command.undo.disabled": "Undo disabled, use: //fast",
|
||||||
"fawe.worldedit.selection.selection.count": "Counted {0} blocks.",
|
"fawe.worldedit.selection.selection.count": "Counted {0} blocks.",
|
||||||
"fawe.worldedit.anvil.world.is.loaded": "The world shouldn't be in use when executing. Unload the world, or use -f to override (save first)",
|
"fawe.worldedit.anvil.world.is.loaded": "The world shouldn't be in use when executing. Unload the world, or use -f to override (save first)",
|
||||||
|
"fawe.worldedit.anvil.replaceall.complete": "Replaceall with anvil completed.",
|
||||||
"fawe.worldedit.brush.brush.reset": "Reset your brush. (SHIFT + Click)",
|
"fawe.worldedit.brush.brush.reset": "Reset your brush. (SHIFT + Click)",
|
||||||
"fawe.worldedit.brush.brush.none": "You aren't holding a brush!",
|
"fawe.worldedit.brush.brush.none": "You aren't holding a brush!",
|
||||||
"fawe.worldedit.brush.brush.scroll.action.set": "Set scroll action to {0}",
|
"fawe.worldedit.brush.brush.scroll.action.set": "Set scroll action to {0}",
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren