Skip to content

Commit

Permalink
feat: support sponge schematics v2/3
Browse files Browse the repository at this point in the history
  • Loading branch information
mworzala committed Nov 30, 2023
1 parent fe09894 commit dae7bb8
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 28 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {

testImplementation(platform("org.junit:junit-bom:5.9.1"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation(libs.bundles.logback)
}

tasks.test {
Expand Down
9 changes: 8 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
metadata.format.version = "1.1"

[versions]
minestom = "e9d0098418"
minestom = "8715f4305d"
logback = "1.4.5" # For tests only

[libraries]
minestom = { group = "dev.hollowcube", name = "minestom-ce", version.ref = "minestom" }

logback-core = { group = "ch.qos.logback", name = "logback-core", version.ref = "logback" }
logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" }

[bundles]
logback = ["logback-core", "logback-classic"]
85 changes: 58 additions & 27 deletions src/main/java/net/hollowcube/schem/SchematicReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


import net.minestom.server.command.builder.arguments.minecraft.ArgumentBlockState;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.instance.block.Block;
import net.minestom.server.utils.validate.Check;
Expand Down Expand Up @@ -43,48 +44,78 @@ private SchematicReader() {
try {
NBTCompound tag = (NBTCompound) reader.read();

Short width = tag.getShort("Width");
Check.notNull(width, "Missing required field 'Width'");
Short height = tag.getShort("Height");
Check.notNull(height, "Missing required field 'Height'");
Short length = tag.getShort("Length");
Check.notNull(length, "Missing required field 'Length'");
// If it has a Schematic tag is sponge v2 or 3
var schematicTag = tag.getCompound("Schematic");
if (schematicTag != null) {
Integer version = schematicTag.getInt("Version");
Check.notNull(version, "Missing required field 'Schematic.Version'");
return read(schematicTag, version);
}

NBTCompound metadata = tag.getCompound("Metadata");
Check.notNull(metadata, "Missing required field 'Metadata'");
// Otherwise it is hopefully v1
return read(tag, 1);
} catch (Exception e) {
throw new SchematicReadException("Invalid schematic file", e);
}
}

private static @NotNull Schematic read(@NotNull NBTCompound tag, int version) {
Short width = tag.getShort("Width");
Check.notNull(width, "Missing required field 'Width'");
Short height = tag.getShort("Height");
Check.notNull(height, "Missing required field 'Height'");
Short length = tag.getShort("Length");
Check.notNull(length, "Missing required field 'Length'");

NBTCompound metadata = tag.getCompound("Metadata");

var offset = Vec.ZERO;
if (metadata != null && metadata.containsKey("WEOffsetX")) {
Integer offsetX = metadata.getInt("WEOffsetX");
Check.notNull(offsetX, "Missing required field 'Metadata.WEOffsetX'");
Integer offsetY = metadata.getInt("WEOffsetY");
Check.notNull(offsetY, "Missing required field 'Metadata.WEOffsetY'");
Integer offsetZ = metadata.getInt("WEOffsetZ");
Check.notNull(offsetZ, "Missing required field 'Metadata.WEOffsetZ'");

NBTCompound palette = tag.getCompound("Palette");
offset = new Vec(offsetX, offsetY, offsetZ);
} //todo handle sponge Offset

NBTCompound palette;
ImmutableByteArray blockArray;
Integer paletteSize;
if (version == 1) {
palette = tag.getCompound("Palette");
Check.notNull(palette, "Missing required field 'Palette'");
ImmutableByteArray blockArray = tag.getByteArray("BlockData");
blockArray = tag.getByteArray("BlockData");
Check.notNull(blockArray, "Missing required field 'BlockData'");

Integer paletteSize = tag.getInt("PaletteMax");
paletteSize = tag.getInt("PaletteMax");
Check.notNull(paletteSize, "Missing required field 'PaletteMax'");
} else {
var blockEntries = tag.getCompound("Blocks");
Check.notNull(blockEntries, "Missing required field 'Blocks'");

palette = blockEntries.getCompound("Palette");
Check.notNull(palette, "Missing required field 'Blocks.Palette'");
blockArray = blockEntries.getByteArray("Data");
Check.notNull(blockArray, "Missing required field 'Blocks.Data'");
paletteSize = palette.getSize();
}

Block[] paletteBlocks = new Block[paletteSize];
Block[] paletteBlocks = new Block[paletteSize];

palette.forEach((key, value) -> {
int assigned = ((NBTInt) value).getValue();
Block block = ArgumentBlockState.staticParse(key);
paletteBlocks[assigned] = block;
});
palette.forEach((key, value) -> {
int assigned = ((NBTInt) value).getValue();
Block block = ArgumentBlockState.staticParse(key);
paletteBlocks[assigned] = block;
});

return new Schematic(
new Vec(width, height, length),
new Vec(offsetX, offsetY, offsetZ),
paletteBlocks,
blockArray.copyArray()
);
} catch (Exception e) {
throw new SchematicReadException("Invalid schematic file", e);
}
return new Schematic(
new Vec(width, height, length),
offset,
paletteBlocks,
blockArray.copyArray()
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package net.hollowcube.schem;

import net.minestom.server.coordinate.Vec;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

public class TestSchematicReaderRegressions {

@Test
public void testReadFail1_20_1() {
var schem = assertReadSchematic("/regression/1_20_1_read_fail.schem");
assertEquals(new Vec(15, 16, 20), schem.size());
}

@Test
public void testSpongeV1() {
var schem = assertReadSchematic("/regression/sponge_1.schem");
assertEquals(new Vec(217, 70, 173), schem.size());
}

private @NotNull Schematic assertReadSchematic(@NotNull String path) {
try (var is = getClass().getResourceAsStream(path)) {
assertNotNull(is, "Failed to load resource: " + path);
return SchematicReader.read(is);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

}
61 changes: 61 additions & 0 deletions src/test/java/net/hollowcube/schem/demo/DemoServer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package net.hollowcube.schem.demo;

import net.hollowcube.schem.Rotation;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.CommandSender;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandContext;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerLoginEvent;
import net.minestom.server.event.player.PlayerSpawnEvent;
import net.minestom.server.instance.LightingChunk;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;

public class DemoServer {
public static void main(String[] args) {
var server = MinecraftServer.init();

var instances = MinecraftServer.getInstanceManager();
var instance = instances.createInstanceContainer();
instance.setChunkSupplier(LightingChunk::new);
instance.setGenerator(unit -> unit.modifier().fillHeight(0, 39, Block.STONE));

var events = MinecraftServer.getGlobalEventHandler();
events.addListener(PlayerLoginEvent.class, event -> {
event.setSpawningInstance(instance);
event.getPlayer().setRespawnPoint(new Pos(0, 40, 0));
});
events.addListener(PlayerSpawnEvent.class, event -> {
var player = event.getPlayer();

player.setGameMode(GameMode.CREATIVE);
});

var commands = MinecraftServer.getCommandManager();
commands.register(new Command("paste") {
{
addSyntax(this::execute, ArgumentType.StringArray("path"));
}

public void execute(@NotNull CommandSender sender, @NotNull CommandContext context) {
var player = (Player) sender;

try (var is = getClass().getResourceAsStream("/" + String.join(" ", context.<String[]>get("path")) + ".schem")) {
var schem = net.hollowcube.schem.SchematicReader.read(is);
schem.build(Rotation.NONE, false).apply(instance, player.getPosition(), () -> {
player.sendMessage("Done!");
});
} catch (Exception e) {
player.sendMessage("Failed to paste schematic: " + e.getMessage());
e.printStackTrace();
}
}
});

server.start("localhost", 25565);
}
}
Binary file not shown.
Binary file added src/test/resources/regression/sponge_1.schem
Binary file not shown.

0 comments on commit dae7bb8

Please sign in to comment.