Last active
August 5, 2023 12:00
-
-
Save Mr-EmPee/7673c2cf066aaef6542acaf21a38eff9 to your computer and use it in GitHub Desktop.
[Minecraft World Reader] #Minecraft
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ml.empee.templateplugin.utils.helpers.world; | |
import lombok.Getter; | |
import lombok.RequiredArgsConstructor; | |
import lombok.SneakyThrows; | |
import ml.empee.templateplugin.utils.PaperUtils; | |
import net.minecraft.nbt.CompoundTag; | |
import org.bukkit.Location; | |
import org.bukkit.block.TileState; | |
import org.bukkit.block.data.BlockData; | |
import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlockEntityState; | |
import org.cloudburstmc.nbt.NBTInputStream; | |
import org.cloudburstmc.nbt.NbtMap; | |
import org.cloudburstmc.nbt.NbtType; | |
import org.jetbrains.annotations.Nullable; | |
import java.io.BufferedInputStream; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.RandomAccessFile; | |
import java.nio.file.Files; | |
import java.nio.file.Path; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Optional; | |
import java.util.zip.InflaterInputStream; | |
@RequiredArgsConstructor | |
public class AnvilChunk { | |
private static final int SECTOR_SIZE = 4096; | |
private final Map<Location, NbtMap> tileBlockStates; | |
private final List<AnvilSection> sections; | |
@Getter | |
private final int chunkX; | |
@Getter | |
private final int chunkZ; | |
@Nullable | |
@SneakyThrows | |
public static AnvilChunk deserialize(Path regionFolder, int x, int z) { | |
if(!Files.isDirectory(regionFolder)) { | |
return null; | |
} | |
File regionFile = regionFolder.resolve("r." + (x >> 5) + "." + (z >> 5) + ".mca").toFile(); | |
if(!regionFile.exists()) { | |
return null; | |
} | |
RandomAccessFile file = new RandomAccessFile(regionFile, "r"); | |
//Traverse the chunk pointer table | |
file.seek(4 * ((x & 31) + (z & 31) * 32)); | |
int chunkOffset = file.read() << 16; | |
chunkOffset |= (file.read() & 0xFF) << 8; | |
chunkOffset |= file.read() & 0xFF; | |
int chunkSize = file.readByte(); | |
if (chunkSize == 0) { | |
return null; | |
} | |
file.seek((long) SECTOR_SIZE * chunkOffset); | |
return deserializeChunk(file, x, z); | |
} | |
@SneakyThrows | |
private static AnvilChunk deserializeChunk(RandomAccessFile file, int x, int z) { | |
int chunkLength = file.readInt(); | |
byte compressionType = file.readByte(); | |
if (compressionType != 2) { | |
throw new IllegalArgumentException("Only Zlib compression is supported!"); | |
} | |
try ( | |
NBTInputStream chunkStream = org.cloudburstmc.nbt.NbtUtils.createReader( | |
new BufferedInputStream(new InflaterInputStream(new FileInputStream(file.getFD()))) | |
) | |
) { | |
return parseNbtChunk((NbtMap) chunkStream.readTag(), x, z); | |
} | |
} | |
private static AnvilChunk parseNbtChunk(NbtMap chunk, int x, int z) { | |
Map<Location, NbtMap> tileStates = new HashMap<>(); | |
for (NbtMap tileBlockState : chunk.getList("block_entities", NbtType.COMPOUND)) { | |
Location tileLocation = new Location(null, | |
tileBlockState.getInt("x"), | |
tileBlockState.getInt("y"), | |
tileBlockState.getInt("z") | |
); | |
tileStates.put(tileLocation, tileBlockState); | |
} | |
List<AnvilSection> sections = new ArrayList<>(); | |
for (NbtMap section : chunk.getList("sections", NbtType.COMPOUND)) { | |
NbtMap blocksStates = section.getCompound("block_states"); | |
sections.add( | |
new AnvilSection( | |
section.getByte("Y"), | |
blocksStates.getLongArray("data"), | |
blocksStates.getList("palette", NbtType.COMPOUND).toArray(new NbtMap[0]) | |
) | |
); | |
} | |
return new AnvilChunk(tileStates, sections, x, z); | |
} | |
/** | |
* Retrieve a clean copy of the restore-point data of the block | |
* @param x 0-15 | |
* @param y world minHeight (inclusive) - world maxHeight (exclusive) | |
* @param z 0-15 | |
* @return null if the y-coordinate is invalid | |
*/ | |
@Nullable | |
public BlockData getBlockRelativelyAt(int x, int y, int z) { | |
return getSection(y >> 4).map( //Keep the last 4 bits y rel coord | |
anvilSection -> anvilSection.getBlockRelativelyAt(x, y & 0xF, z) | |
).orElse(null); | |
} | |
/** | |
* Apply the chunk saved tile state to the target | |
*/ | |
@SneakyThrows | |
public boolean applyTileStateTo(TileState tileState) { | |
Location location = tileState.getLocation(); | |
location.setWorld(null); | |
NbtMap state = tileBlockStates.get(location); | |
if(state == null) { | |
return false; | |
} | |
CompoundTag nmsTag = (CompoundTag) NbtUtils.toCompoundTag(state.toString()); | |
CraftBlockEntityState<?> craftState = (CraftBlockEntityState<?>) tileState; | |
craftState.getTileEntity().load(nmsTag); | |
if(PaperUtils.isRunningPaper && !craftState.snapshotDisabled) { | |
craftState.refreshSnapshot(); | |
} | |
tileState.update(true); | |
return true; | |
} | |
private Optional<AnvilSection> getSection(int y) { | |
return sections.stream().filter( | |
s -> s.getY() == y | |
).findFirst(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ml.empee.templateplugin.utils.helpers.world; | |
import lombok.Getter; | |
import org.bukkit.Bukkit; | |
import org.bukkit.block.data.BlockData; | |
import org.cloudburstmc.nbt.NbtMap; | |
import org.jetbrains.annotations.Nullable; | |
public class AnvilSection { | |
private final long[] blocksRawData; | |
private final NbtMap[] blocksPalette; | |
private int bitsUsedForPaletteIndex; | |
//Total group in which every element of blocksRawData is divided | |
private int availableDataGroup; | |
@Getter | |
private final int y; | |
public AnvilSection(int y, @Nullable long[] blocksRawData, NbtMap[] blocksPalette) { | |
this.blocksRawData = blocksRawData; | |
this.blocksPalette = blocksPalette; | |
this.y = y; | |
bitsUsedForPaletteIndex = fastComputeCeilLog2Of(blocksPalette.length); | |
if(bitsUsedForPaletteIndex < 4) { | |
bitsUsedForPaletteIndex = 4; | |
} | |
availableDataGroup = 64 / bitsUsedForPaletteIndex; | |
} | |
private static int fastComputeCeilLog2Of(int n) { | |
return n > 0 ? 32 - Integer.numberOfLeadingZeros(n - 1) : 0; | |
} | |
/** | |
* Get a block using its relative coordinates | |
* @param x 0-15 | |
* @param y 0-15 | |
* @param z 0-15 | |
*/ | |
public BlockData getBlockRelativelyAt(int x, int y, int z) { | |
if(blocksRawData == null || blocksRawData.length == 0) { | |
return parseBlockData(blocksPalette[0]); | |
} | |
int groupNumber = computePosition(x, y, z); | |
int index = groupNumber / availableDataGroup; | |
//Offset starting from the lowest bit | |
int offset = (groupNumber % availableDataGroup) * bitsUsedForPaletteIndex; | |
int mask = -1 >>> 32 - bitsUsedForPaletteIndex; | |
int paletteIndex = ((int) (blocksRawData[index] >>> offset)) & mask; | |
return parseBlockData(blocksPalette[paletteIndex]); | |
} | |
/** | |
* Transform a 3D relative position to an index | |
*/ | |
private static int computePosition(int x, int y, int z) { | |
return (y << 8) | (z << 4) | x; | |
} | |
private static BlockData parseBlockData(NbtMap block) { | |
StringBuilder rawData = new StringBuilder(block.getString("Name")); | |
NbtMap properties = block.getCompound("Properties"); | |
if(properties != null && !properties.isEmpty()) { | |
rawData.append("["); | |
properties.forEach((key, value) -> rawData.append(key).append("=").append(value).append(",")); | |
rawData.setCharAt(rawData.length() - 1, ']'); | |
} | |
return Bukkit.createBlockData(rawData.toString()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ml.empee.templateplugin.utils.helpers.world; | |
import java.nio.file.Files; | |
import java.nio.file.Path; | |
import org.jetbrains.annotations.Nullable; | |
public class AnvilWorld { | |
private final Path worldFolder; | |
private final Path regionFolder; | |
public AnvilWorld(Path worldFolder) { | |
this.worldFolder = worldFolder; | |
if (Files.notExists(worldFolder) || !Files.isDirectory(worldFolder)) { | |
throw new IllegalArgumentException("Level path " + worldFolder + " did not exist or was not a directory!"); | |
} | |
regionFolder = worldFolder.resolve("region"); | |
if (Files.notExists(regionFolder) || !Files.isDirectory(regionFolder)) { | |
throw new IllegalArgumentException("Region path " + regionFolder + " did not exist or was not a directory!"); | |
} | |
} | |
/** | |
* The requested chunk or null if it not exists | |
*/ | |
@Nullable | |
public AnvilChunk getChunk(int x, int z) { | |
return AnvilChunk.deserialize(regionFolder, x, z); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ml.empee.templateplugin.utils.helpers.world; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.Arrays; | |
import lombok.AccessLevel; | |
import lombok.NoArgsConstructor; | |
import net.minecraft.nbt.CompoundTag; | |
/** This class works from 1.17 **/ | |
@NoArgsConstructor(access = AccessLevel.PRIVATE) | |
class NbtUtils { | |
private final static Method snbtToStructure; | |
static { | |
Class<?> compoundClazz = CompoundTag.class; | |
Class<?> nbtUtils = net.minecraft.nbt.NbtUtils.class; | |
snbtToStructure = Arrays.stream(nbtUtils.getMethods()) | |
.filter(m -> m.getParameterCount() == 1) | |
.filter(m -> m.getReturnType().equals(compoundClazz)) | |
.filter(m -> m.getParameters()[0].getType().equals(String.class)) | |
.findFirst().orElseThrow( () -> new RuntimeException("Unable to find the snbtToStructure method!")); | |
} | |
public static Object toCompoundTag(String snbt) throws InvocationTargetException, IllegalAccessException { | |
return snbtToStructure.invoke(null, snbt); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment