Skip to content

Instantly share code, notes, and snippets.

@CutestNekoAqua
Created December 31, 2021 20:36
Show Gist options
  • Save CutestNekoAqua/d0325fb4bec3fe0edb1b6c2a4af625e0 to your computer and use it in GitHub Desktop.
Save CutestNekoAqua/d0325fb4bec3fe0edb1b6c2a4af625e0 to your computer and use it in GitHub Desktop.
Schematic Loading in Minestom
package me.hugo.kweebecparty.schem;
import net.minestom.server.coordinate.Point;
import org.jetbrains.annotations.NotNull;
public interface ISchematic {
ErrorMessage read();
ErrorMessage write(@NotNull Region region);
ErrorMessage build(@NotNull Point position, Runnable blocksCompleted);
enum ErrorMessage {
NoSuchFile,
NotLoaded,
NBTName,
NBTWidth,
NBTHeight,
NBTLength,
NBTMaxPalette,
NBTPalette,
PaletteNotEqualsMaxPalette,
PaletteGetInt,
NBTBlockData,
NBTEntities,
BadRead,
BadWrite,
VarIntSize,
NoBlocks,
BadMaterials,
Instance,
BlockBatch,
None
}
}
package me.hugo.kweebecparty.schem;
/**
* Code based on https://github.com/sejtam-dev/MineSchem
* Licensed under Apache v2
*/
package me.hugo.kweebecparty.schem;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
import java.util.*;
public class Region implements Iterable<Region.RegionBlock>, Cloneable {
private Point pos1;
private Point pos2;
private Instance instance;
public Region(@NotNull Point position1, @NotNull Point position2) {
this(position1, position2, new ArrayList<>(MinecraftServer.getInstanceManager().getInstances()).get(0));
}
public Region(@NotNull Point position1, @NotNull Point position2, @NotNull Instance instance) {
this.pos1 = new Pos(Math.min(position1.x(), position2.x()), Math.min(position1.y(), position2.y()), Math.min(position1.z(), position2.z()));
this.pos2 = new Pos(Math.max(position1.x(), position2.x()), Math.max(position1.y(), position2.y()), Math.max(position1.z(), position2.z()));
this.instance = instance;
}
// Width
public double getSizeX() {
return (this.pos2.x() - this.pos1.x()) + 1;
}
// Height
public double getSizeY() {
return (this.pos2.y() - this.pos1.y()) + 1;
}
// Length
public double getSizeZ() {
return (this.pos2.z() - this.pos1.z()) + 1;
}
public double getLowerX() {
return this.pos1.x();
}
public double getLowerY() {
return this.pos1.y();
}
public double getLowerZ() {
return this.pos1.z();
}
public Point getLower() {
return new Pos(this.pos1.x(), this.pos1.y(), this.pos1.z());
}
public double getUpperX() {
return this.pos2.x();
}
public double getUpperY() {
return this.pos2.y();
}
public double getUpperZ() {
return this.pos2.z();
}
public Point getUpper() {
return new Pos(this.pos2.x(), this.pos2.y(), this.pos2.z());
}
public short[] cornerBlocksStateId() {
short[] corners = new short[8];
corners[0] = instance.getBlock((int) this.pos1.x(), (int) this.pos1.y(), (int) this.pos1.z()).stateId();
corners[1] = instance.getBlock((int) this.pos1.x(), (int) this.pos1.y(), (int) this.pos2.z()).stateId();
corners[2] = instance.getBlock((int) this.pos1.x(), (int) this.pos2.y(), (int) this.pos1.z()).stateId();
corners[3] = instance.getBlock((int) this.pos1.x(), (int) this.pos2.y(), (int) this.pos2.z()).stateId();
corners[4] = instance.getBlock((int) this.pos2.x(), (int) this.pos1.y(), (int) this.pos1.z()).stateId();
corners[5] = instance.getBlock((int) this.pos2.x(), (int) this.pos1.y(), (int) this.pos2.z()).stateId();
corners[6] = instance.getBlock((int) this.pos2.x(), (int) this.pos2.y(), (int) this.pos1.z()).stateId();
corners[7] = instance.getBlock((int) this.pos2.x(), (int) this.pos2.y(), (int) this.pos2.z()).stateId();
return corners;
}
public Point[] cornerBlocksPosition() {
Point[] corners = new Point[8];
corners[0] = new Pos(this.pos1.x(), this.pos1.y(), this.pos1.z());
corners[1] = new Pos(this.pos1.x(), this.pos1.y(), this.pos2.z());
corners[2] = new Pos(this.pos1.x(), this.pos2.y(), this.pos1.z());
corners[3] = new Pos(this.pos1.x(), this.pos2.y(), this.pos2.z());
corners[4] = new Pos(this.pos2.x(), this.pos1.y(), this.pos1.z());
corners[5] = new Pos(this.pos2.x(), this.pos1.y(), this.pos2.z());
corners[6] = new Pos(this.pos2.x(), this.pos2.y(), this.pos1.z());
corners[7] = new Pos(this.pos2.x(), this.pos2.y(), this.pos2.z());
return corners;
}
public Iterator<RegionBlock> iterator() {
return new RegionIterator((int) this.pos1.x(), (int) this.pos1.y(), (int) pos1.z(), (int) this.pos2.x(), (int) pos2.y(), (int) pos2.z(), this.instance);
}
public List<Short> getStateIds() {
Iterator<RegionBlock> iterator = this.iterator();
List<Short> list = new ArrayList<>();
while (iterator.hasNext())
list.add(iterator.next().getStateId());
return list;
}
public Map<Point, Short> getStateIdsMap() {
Iterator<RegionBlock> iterator = this.iterator();
Map<Point, Short> map = new HashMap<>();
while (iterator.hasNext()) {
RegionBlock block = iterator.next();
map.put(block.getPosition(), block.getStateId());
}
return map;
}
public List<Block> getBlocks() {
Iterator<RegionBlock> iterator = this.iterator();
List<Block> list = new ArrayList<>();
while (iterator.hasNext())
list.add(Block.fromStateId(iterator.next().getStateId()));
return list;
}
public Map<Point, Block> getBlocksMap() {
Iterator<RegionBlock> iterator = this.iterator();
Map<Point, Block> map = new HashMap<>();
while (iterator.hasNext()) {
RegionBlock block = iterator.next();
map.put(block.getPosition(), Block.fromStateId(block.getStateId()));
}
return map;
}
public Instance getInstance() {
return this.instance;
}
public void setInstance(@NotNull Instance instance) {
this.instance = instance;
}
public String toString() {
return "{Pos1:" + pos1.toString() + ", Pos2:" + pos2.toString() + "}";
}
public class RegionIterator implements Iterator<RegionBlock> {
private Instance instance;
private int baseX, baseY, baseZ;
private int x, y, z;
private int sizeX, sizeY, sizeZ;
public RegionIterator(int x1, int y1, int z1, int x2, int y2, int z2, @NotNull Instance instance) {
this.instance = instance;
this.baseX = x1;
this.baseY = y1;
this.baseZ = z1;
this.sizeX = Math.abs(x2 - x1) + 1;
this.sizeY = Math.abs(y2 - y1) + 1;
this.sizeZ = Math.abs(z2 - z1) + 1;
this.x = this.y = this.z = 0;
}
public boolean hasNext() {
return this.x < this.sizeX && this.y < this.sizeY && this.z < this.sizeZ;
}
public RegionBlock next() {
Point blockPosition = new Pos(this.baseX + this.x, this.baseY + this.y, this.baseZ + this.z);
short state = this.instance.getBlock(blockPosition).stateId();
RegionBlock regionBlock = new RegionBlock(blockPosition, state);
if (++x >= this.sizeX) {
this.x = 0;
if (++this.z >= this.sizeZ) {
this.z = 0;
++this.y;
}
}
return regionBlock;
}
public void remove() {}
}
public static class RegionBlock {
private Point blockPosition;
private short stateId;
public RegionBlock(@NotNull Point blockPosition, short stateId) {
this.blockPosition = blockPosition;
this.stateId = stateId;
}
public Point getPosition() {
return this.blockPosition;
}
public short getStateId() {
return this.stateId;
}
}
}
package me.hugo.kweebecparty.schem;
import net.minestom.server.coordinate.Point;
import net.minestom.server.instance.Instance;
import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.jglrxavpok.hephaistos.nbt.NBTException;
import org.jglrxavpok.hephaistos.nbt.NBTReader;
import java.io.File;
import java.io.IOException;
public class Schematic implements ISchematic {
public static final String MAIN_FOLDER = "structures/";
private File schematicFile;
private Instance instance;
private ISchematic schematic;
public Schematic(@NotNull String schematicName, @NotNull Instance instance) {
this(new File(MAIN_FOLDER + schematicName + ".schem"), instance);
}
public Schematic(@NotNull File schematicFile, @NotNull Instance instance) {
if(schematicFile.exists())
this.schematicFile = schematicFile;
else
this.schematicFile = new File(schematicFile.getPath() + "atic");
this.instance = instance;
}
public ErrorMessage read() {
// Check is file exists
if(!this.schematicFile.exists())
return ErrorMessage.NoSuchFile;
// Check is file
if(!this.schematicFile.isFile())
return ErrorMessage.NoSuchFile;
try (NBTReader reader = new NBTReader(this.schematicFile)) {
// Get Main NBT
this.schematic = new SpongeSchematic(this.schematicFile, this.instance);
} catch (IOException ex) {
return ErrorMessage.BadRead;
}
return this.schematic.read();
}
public ErrorMessage write(@NotNull Region region) {
if(this.schematic == null)
this.schematic = new SpongeSchematic(this.schematicFile, this.instance);
region.setInstance(this.instance);
return this.schematic.write(region);
}
public ErrorMessage build(@NotNull Point position, Runnable blocksCompleted) {
if(this.schematic == null)
this.schematic = new SpongeSchematic(this.schematicFile, this.instance);
return this.schematic.build(position, blocksCompleted);
}
}
package me.hugo.kweebecparty.schem;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.batch.Batch;
import net.minestom.server.instance.batch.BatchOption;
import net.minestom.server.instance.batch.ChunkBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.utils.chunk.ChunkUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class SchemBlockBatch implements Batch<Runnable> {
// In the form of <Chunk Index, Batch>
public final Long2ObjectMap<ChunkBatch> chunkBatchesMap = new Long2ObjectOpenHashMap<>();
// Available for other implementations to handle.
protected final CountDownLatch readyLatch;
private final BatchOption options;
private volatile BatchOption inverseOption = new BatchOption();
public SchemBlockBatch() {
this(new BatchOption());
}
public SchemBlockBatch(BatchOption options) {
this(options, true);
}
private SchemBlockBatch(BatchOption options, boolean ready) {
this.readyLatch = new CountDownLatch(ready ? 0 : 1);
this.options = options;
}
@Override
public void setBlock(int x, int y, int z, @NotNull Block block) {
final int chunkX = ChunkUtils.getChunkCoordinate(x);
final int chunkZ = ChunkUtils.getChunkCoordinate(z);
final long chunkIndex = ChunkUtils.getChunkIndex(chunkX, chunkZ);
final ChunkBatch chunkBatch;
synchronized (chunkBatchesMap) {
chunkBatch = chunkBatchesMap.computeIfAbsent(chunkIndex, i -> new ChunkBatch(this.options));
}
final int relativeX = x - (chunkX * Chunk.CHUNK_SIZE_X);
final int relativeZ = z - (chunkZ * Chunk.CHUNK_SIZE_Z);
chunkBatch.setBlock(relativeX, y, relativeZ, block);
}
@Override
public void clear() {
synchronized (chunkBatchesMap) {
this.chunkBatchesMap.clear();
}
}
@Override
public boolean isReady() {
return this.readyLatch.getCount() == 0;
}
@Override
public void awaitReady() {
try {
this.readyLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException("#awaitReady interrupted!", e);
}
}
/**
* Applies this batch to the given instance.
*
* @param instance The instance in which the batch should be applied
* @param callback The callback to be executed when the batch is applied
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
*/
@Override
public SchemBlockBatch apply(@NotNull Instance instance, @Nullable Runnable callback) {
return apply(instance, callback, true);
}
/**
* Applies this batch to the given instance, and execute the callback immediately when the
* blocks have been applied, in an unknown thread.
*
* @param instance The instance in which the batch should be applied
* @param callback The callback to be executed when the batch is applied
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
*/
public SchemBlockBatch unsafeApply(@NotNull Instance instance, @Nullable Runnable callback) {
return apply(instance, callback, false);
}
/**
* Applies this batch to the given instance, and execute the callback depending on safeCallback.
*
* @param instance The instance in which the batch should be applied
* @param callback The callback to be executed when the batch is applied
* @param safeCallback If true, the callback will be executed in the next instance update.
* Otherwise it will be executed immediately upon completion
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
*/
protected SchemBlockBatch apply(@NotNull Instance instance, @Nullable Runnable callback, boolean safeCallback) {
if (!this.options.isUnsafeApply()) this.awaitReady();
final SchemBlockBatch inverse = this.options.shouldCalculateInverse() ? new SchemBlockBatch(inverseOption) : null;
synchronized (chunkBatchesMap) {
AtomicInteger counter = new AtomicInteger();
for (var entry : Long2ObjectMaps.fastIterable(chunkBatchesMap)) {
final long chunkIndex = entry.getLongKey();
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
final ChunkBatch batch = entry.getValue();
ChunkBatch chunkInverse = batch.apply(instance, chunkX, chunkZ, c -> {
final boolean isLast = counter.incrementAndGet() == chunkBatchesMap.size();
// Execute the callback if this was the last chunk to process
if (isLast) {
if (inverse != null) inverse.readyLatch.countDown();
if (instance instanceof InstanceContainer) {
// FIXME: put method in Instance instead
((InstanceContainer) instance).refreshLastBlockChangeTime();
}
if (callback != null) {
if (safeCallback) {
instance.scheduleNextTick(inst -> callback.run());
} else {
callback.run();
}
}
}
});
if (inverse != null) inverse.chunkBatchesMap.put(chunkIndex, chunkInverse);
}
}
return inverse;
}
@ApiStatus.Experimental
public @NotNull BatchOption getInverseOption() {
return inverseOption;
}
@ApiStatus.Experimental
public void setInverseOption(@NotNull BatchOption inverseOption) {
this.inverseOption = inverseOption;
}
}
package me.hugo.kweebecparty.schem;
import kotlin.Pair;
import me.hugo.kweebecparty.light.LightEngine;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.*;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.Section;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.collections.ImmutableByteArray;
import org.jglrxavpok.hephaistos.nbt.*;
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class SpongeSchematic implements ISchematic {
private File schematicFile;
private Instance instance;
private AbsoluteBlockBatch blockBatch;
private Integer version = 2;
// Sizes
private Short width;
private Short height;
private Short length;
private int[] offset;
// Blocks
private Integer maxPalette;
private Map<String, Integer> palette = new HashMap<>();
private ImmutableByteArray blocksData;
private List<Region.RegionBlock> regionBlocks = new ArrayList<>();
private boolean isLoaded = false;
public SpongeSchematic(@NotNull File schematicFile, @NotNull Instance instance) {
this.schematicFile = schematicFile;
this.instance = instance;
}
public ErrorMessage read() {
if(this.isLoaded)
return ErrorMessage.None;
// Check is file exists
if(!this.schematicFile.exists())
return ErrorMessage.NoSuchFile;
// Check is file
if(!this.schematicFile.isFile())
return ErrorMessage.NoSuchFile;
try (NBTReader reader = new NBTReader(this.schematicFile)) {
// Get Main NBT Name
Pair<String, NBT> pair = reader.readNamed();
// Get Main NBT
NBTCompound nbtTag = (NBTCompound) pair.getSecond();
// Is SpongeSchematic
if(!pair.getFirst().equals("Schematic"))
return ErrorMessage.NBTName;
this.version = nbtTag.getInt("Version");
if(this.version == null)
this.version = 2;
// TODO: Read and Check Data Version
ErrorMessage errorMessage = readSizes(nbtTag);
if(errorMessage != ErrorMessage.None)
return errorMessage;
errorMessage = readBlockPalette(nbtTag);
if(errorMessage != ErrorMessage.None)
return errorMessage;
// TODO: Read block data
errorMessage = readBlocks();
if(errorMessage != ErrorMessage.None)
return errorMessage;
if(this.version != 2)
return ErrorMessage.None;
// TODO: Read Biome Palette
// TODO: Read BiomeData
} catch (IOException | NBTException ex) {
return ErrorMessage.BadRead;
}
this.isLoaded = true;
return ErrorMessage.None;
}
private ErrorMessage readSizes(@NotNull NBTCompound nbtTag) {
// Get Width
this.width = nbtTag.getShort("Width");
if(this.width == null)
return ErrorMessage.NBTWidth;
// Get Height
this.height = nbtTag.getShort("Height");
if(this.height == null)
return ErrorMessage.NBTHeight;
// Get Length
this.length = nbtTag.getShort("Length");
if(this.length == null)
return ErrorMessage.NBTLength;
// Get offset
this.offset = nbtTag.getIntArray("Offset").copyArray();
if(this.offset == null || this.offset.length != 3)
this.offset = new int[] {0, 0, 0};
return ErrorMessage.None;
}
private ErrorMessage readBlockPalette(@NotNull NBTCompound nbtTag) {
// Get Max Palette
this.maxPalette = nbtTag.getInt("PaletteMax");
if(this.maxPalette == null)
return ErrorMessage.NBTMaxPalette;
// Get Palette
NBTCompound nbtPalette = (NBTCompound) nbtTag.get("Palette");
if(nbtPalette == null)
return ErrorMessage.NBTPalette;
// Is Palette same size
List<String> keys = nbtPalette.getKeys().stream().toList();
if(keys.size() != maxPalette)
return ErrorMessage.PaletteNotEqualsMaxPalette;
// Create map from nbtPalette
for(String key : keys) {
Integer value = nbtPalette.getInt(key);
if(value == null)
return ErrorMessage.PaletteGetInt;
this.palette.put(key, value);
}
// Sort Palette map by values
this.palette = this.palette.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry::getValue))
.collect(LinkedHashMap::new,(map, entry) -> map.put(entry.getKey(), entry.getValue()), LinkedHashMap::putAll);
// Get block data
this.blocksData = nbtTag.getByteArray("BlockData");
if(this.blocksData == null || this.blocksData.getSize() == 0)
return ErrorMessage.NBTBlockData;
return ErrorMessage.None;
}
// https://github.com/EngineHub/WorldEdit/blob/303f5a76b2df70d63480f2126c9ef4b228eb3c59/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java#L261-L297
private ErrorMessage readBlocks() {
int index = 0;
int i = 0;
int value;
int varintLength;
List<String> paletteKeys = new ArrayList<>(palette.keySet());
while (i < this.blocksData.getSize()) {
value = 0;
varintLength = 0;
while (true) {
value |= (this.blocksData.get(i) & 127) << (varintLength++ * 7);
if (varintLength > 5) {
return ErrorMessage.VarIntSize;
}
if ((this.blocksData.get(i) & 128) != 128) {
i++;
break;
}
i++;
}
// Offset is not needed
int x = (index % (width * length)) % width
//- offset[0]
;
int y = index / (width * length)
//- offset[1]
;
int z = (index % (width * length)) / width
//- offset[2]
;
String block = paletteKeys.get(value);
short stateId = getStateId(block);
this.regionBlocks.add(new Region.RegionBlock(
new Pos(x, y, z),
stateId
));
index++;
}
return ErrorMessage.None;
}
public ErrorMessage write(@NotNull Region region) {
MutableNBTCompound nbtTag = new MutableNBTCompound();
// Set Version
nbtTag.setInt("Version", this.version);
// TODO: Write Data entities
ErrorMessage errorMessage = writeSizes(nbtTag, region);
if(errorMessage != ErrorMessage.None)
return errorMessage;
errorMessage = writeBlockPalette(nbtTag, region);
if(errorMessage != ErrorMessage.None)
return errorMessage;
// TODO: Write Block entities
// TODO: Write entities
// TODO: Write Biome Palette
// TODO: Write BiomeData
try(NBTWriter writer = new NBTWriter(this.schematicFile)) {
writer.writeNamed("Schematic", nbtTag.toCompound());
} catch (IOException ex) {
return ErrorMessage.BadWrite;
}
return ErrorMessage.None;
}
private ErrorMessage writeSizes(@NotNull MutableNBTCompound nbtTag, @NotNull Region region) {
// Set Width
nbtTag.setShort("Width", (short)region.getSizeX());
// Set Height
nbtTag.setShort("Height", (short)region.getSizeY());
// Set Length
nbtTag.setShort("Length", (short)region.getSizeZ());
// Set offset
Point lower = region.getLower();
nbtTag.setIntArray("Offset", new int[] {
(int) lower.x(),
(int) lower.y(),
(int) lower.z()
});
return ErrorMessage.None;
}
private ErrorMessage writeBlockPalette(@NotNull MutableNBTCompound nbtTag, @NotNull Region region) {
// Generate Palette
int paletteMax = 0;
Map<String, Integer> palette = new HashMap<>();
ByteArrayOutputStream buffer = new ByteArrayOutputStream((int) (region.getSizeX() * region.getSizeY() * region.getSizeZ()));
Iterator<Region.RegionBlock> regionIterator = region.iterator();
while(regionIterator.hasNext()) {
Region.RegionBlock regionBlock = regionIterator.next();
short stateId = this.instance.getBlock(regionBlock.getPosition()).stateId();
String name = getNameFromStateId(stateId);
int blockId;
if (palette.containsKey(name)) {
blockId = palette.get(name);
} else {
blockId = paletteMax;
palette.put(name, blockId);
paletteMax++;
}
while ((blockId & -128) != 0) {
buffer.write(blockId & 127 | 128);
blockId >>>= 7;
}
buffer.write(blockId);
}
// Set PaletteMax
nbtTag.setInt("PaletteMax", paletteMax);
// Palette items to NBTTag
MutableNBTCompound paletteItems = new MutableNBTCompound();
palette.forEach(paletteItems::setInt);
// Set Palette
nbtTag.set("Palette", paletteItems.toCompound());
// Set Block Data
nbtTag.setByteArray("BlockData", buffer.toByteArray());
return ErrorMessage.None;
}
public ErrorMessage build(@NotNull Point position, Runnable blocksCompleted) {
if(!this.isLoaded)
return ErrorMessage.NotLoaded;
if(this.instance == null)
return ErrorMessage.Instance;
if(this.regionBlocks == null || this.regionBlocks.size() == 0)
return ErrorMessage.NoBlocks;
this.blockBatch = new AbsoluteBlockBatch();
for (Region.RegionBlock regionBlock : this.regionBlocks) {
Point blockPosition = regionBlock.getPosition();
short stateId = regionBlock.getStateId();
this.blockBatch.setBlock((int) (blockPosition.x() + (int)position.x()), (int) (blockPosition.y() + (int)position.y()), (int) (blockPosition.z() + (int)position.z()), Block.fromStateId(stateId));
}
//Light
this.blockBatch.apply(this.instance, () -> {
new LightEngine().recalculateInstance(instance);
blocksCompleted.run();
});
return ErrorMessage.None;
}
private Block getBlock(@NotNull String input) {
String fullName = input.split("\\[")[0];
//String name = fullName.split(":")[1];
return Block.fromNamespaceId(fullName);
}
private short getStateId(@NotNull String input) {
Block block = getBlock(input);
String states = input.replaceAll(block.name(), "");
if(states.startsWith("[")) {
String[] stateArray = states.substring(1, states.length() - 1).split(",");
List<String> keys = new ArrayList<>();
List<String> values = new ArrayList<>();
for (String s : stateArray) {
String[] x = s.split("=");
keys.add(x[0]);
values.add(x[1]);
}
HashMap<String, String> hashMap = new HashMap<>();
for (int i = 0; i < keys.size(); i++) {
hashMap.put(keys.get(i), values.get(i));
}
return block.withProperties(hashMap).stateId();
} else return block.stateId();
}
@Nullable
private String getNameFromStateId(short stateId) {
Block block = Block.fromStateId(stateId);
StringBuilder blockName = new StringBuilder(block.name());
List<Block> alternatives = block.possibleStates().stream().toList();
for(Block blockAlternative : alternatives) {
if(blockAlternative.stateId() != stateId)
continue;
List<String> properties = blockAlternative.properties().values().stream().toList();
if(properties.size() == 0)
return blockName.toString();
blockName.append("[");
for(int i = 0; i < properties.size(); i++) {
blockName.append(properties.get(i));
if(!(i + 1 == properties.size()))
blockName.append(",");
}
blockName.append("]");
return blockName.toString();
}
return null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment