diff --git a/SpigotCore/SpigotCore_14/src/de/steamwar/core/ChaosSchematicReader.kt b/SpigotCore/SpigotCore_14/src/de/steamwar/core/ChaosSchematicReader.kt
new file mode 100644
index 00000000..a709da31
--- /dev/null
+++ b/SpigotCore/SpigotCore_14/src/de/steamwar/core/ChaosSchematicReader.kt
@@ -0,0 +1,355 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2026 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.core
+
+import com.sk89q.jnbt.ByteArrayTag
+import com.sk89q.jnbt.ByteTag
+import com.sk89q.jnbt.CompoundTag
+import com.sk89q.jnbt.DoubleTag
+import com.sk89q.jnbt.EndTag
+import com.sk89q.jnbt.FloatTag
+import com.sk89q.jnbt.IntArrayTag
+import com.sk89q.jnbt.IntTag
+import com.sk89q.jnbt.ListTag
+import com.sk89q.jnbt.LongArrayTag
+import com.sk89q.jnbt.LongTag
+import com.sk89q.jnbt.ShortTag
+import com.sk89q.jnbt.StringTag
+import com.sk89q.jnbt.Tag
+import com.sk89q.worldedit.WorldEdit
+import com.sk89q.worldedit.WorldEditException
+import com.sk89q.worldedit.extension.input.ParserContext
+import com.sk89q.worldedit.extension.platform.Capability
+import com.sk89q.worldedit.extension.platform.Platform
+import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard
+import com.sk89q.worldedit.math.BlockVector3
+import com.sk89q.worldedit.regions.CuboidRegion
+import com.sk89q.worldedit.world.DataFixer
+import com.sk89q.worldedit.world.block.BaseBlock
+import com.sk89q.worldedit.world.block.BlockState
+import com.sk89q.worldedit.world.block.BlockTypes
+import java.io.DataInputStream
+import java.io.IOException
+import kotlin.collections.map
+import kotlin.collections.set
+
+class ChaosSchematicReader(
+ val stream: DataInputStream,
+ val wrapper: WorldEditWrapper14
+) {
+ init {
+ println("ChaosSchematicReader init")
+ }
+
+ /**
+ * In reinen Unit-Tests (ohne Bukkit/WorldEdit-Plugin-Lifecycle) ist keine WorldEdit-Platform registriert.
+ * Daher darf der Reader beim Laden nicht hart daran scheitern.
+ */
+ private val platform: Platform? = runCatching {
+ WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING)
+ }.getOrNull()
+
+ private val liveDataVersion: Int? = platform?.dataVersion
+
+ private var wrapped = false
+
+ var schematicVersion: Int? = null
+ var dataVersion: Int? = null
+ var metadata: Map? = null
+
+ var width: Short? = null
+ var height: Short? = null
+ var length: Short? = null
+
+ var offset: IntArray? = null
+
+ val blocks: Blocks = Blocks(null, null, null)
+ val entities = mutableListOf()
+
+ var fixer: DataFixer? = null
+
+ fun read(): BlockArrayClipboard {
+ require(stream.readByte() == 0x0A.toByte())
+ val tagName = stream.readUTF()
+ if (tagName != "Schematic") {
+ wrapped = true
+ stream.readByte()
+ stream.readUTF()
+ }
+
+ while (stream.readByte() != 0.toByte()) {
+ val name = stream.readUTF()
+ when (name) {
+ "Version" -> schematicVersion = stream.readInt()
+ "DataVersion" -> {
+ dataVersion = stream.readInt()
+ val targetVersion = liveDataVersion
+ if (targetVersion != null && dataVersion!! < targetVersion) {
+ fixer = platform?.dataFixer
+ }
+ }
+ "Metadata" -> metadata = readCompoundTag()
+ "Width" -> width = stream.readShort()
+ "Height" -> height = stream.readShort()
+ "Length" -> length = stream.readShort()
+ "Blocks" -> {
+ while (stream.readByte() != 0.toByte()) {
+ val n = stream.readUTF()
+ when (n) {
+ "Palette" -> blocks.palette = readCompoundTag() as Map
+ "Data" -> blocks.data = readTag(7.toByte()) as ByteArray
+ "BlockEntities" -> blocks.blockEntities = readTagCompoundList()
+ }
+ }
+ }
+ "PaletteMax" -> stream.readInt()
+ "Palette" -> blocks.palette = readCompoundTag() as Map
+ "BlockEntities" -> blocks.blockEntities = readTagCompoundList()
+ "BlockData" -> blocks.data = readTag(7.toByte()) as ByteArray
+ "Offset" -> offset = readTag(11.toByte()) as IntArray
+ "Entities" -> entities.addAll(readTagCompoundList() as List)
+ "BiomePaletteMax" -> stream.readInt()
+ "BiomePalette" -> readCompoundTag()
+ "BiomeData" -> readTag(7.toByte())
+ "Biomes" -> {
+ while (stream.readByte() != 0.toByte()) {
+ val n = stream.readUTF()
+ when (n) {
+ "Palette" -> readCompoundTag()
+ "Data" -> readTag(7.toByte()) as ByteArray
+ }
+ }
+ }
+ else -> {
+ println(this)
+ error("Unknown tag $name")
+ }
+ }
+ }
+
+
+
+ if (wrapped) {
+ stream.readByte()
+ }
+
+ val parserContext = ParserContext()
+ parserContext.setRestricted(false)
+ parserContext.setTryLegacy(false)
+ parserContext.setPreferringWildcard(false)
+
+ val dv = dataVersion
+ val blockStates = blocks.palette!!.map {
+ val fixed = if (fixer != null && dv != null) {
+ fixer!!.fixUp(DataFixer.FixTypes.BLOCK_STATE, it.key, dv)
+ } else {
+ it.key
+ }
+
+ val blockstate = try {
+ WorldEdit.getInstance().blockFactory.parseFromInput(fixed, parserContext).toImmutableState()
+ } catch (e: Exception) {
+ BlockTypes.AIR!!.defaultState
+ }
+
+ it.value to blockstate
+ }.toMap()
+
+ val tileEntities = blocks.blockEntities!!.map {
+ val entity = it as? Map ?: error("Invalid entity")
+ val pos = entity["Pos"] as? IntArray ?: error("Invalid pos")
+ val x = pos[0]
+ val y = pos[1]
+ val z = pos[2]
+
+ val data = mutableMapOf()
+ data.putAll(entity)
+ if (entity.containsKey("Data")) {
+ //data.putAll(entity["Data"] as Map)
+ }
+
+ val fixed = if (fixer != null) {
+ wrapper.applyDataFixer(fixer!!, dataVersion!!, (wrapNbt(data) as CompoundTag).value)
+ } else (wrapNbt(data) as CompoundTag).value
+
+ BlockVector3.at(x, y, z) to fixed
+ }.toMap()
+
+ val offset = BlockVector3.at(offset!![0], offset!![1], offset!![2])
+ val region = CuboidRegion(offset, offset.add(width!! - 1, height!! - 1, length!! - 1))
+
+ val clipboard = BlockArrayClipboard(region)
+ clipboard.setOrigin(offset)
+
+ var index = 0
+ var i = 0
+ var value: Int
+ var varintLength: Int
+ while (i < blocks.data!!.size) {
+ value = 0
+ varintLength = 0
+
+ while (true) {
+ value = value or ((blocks.data!![i].toInt() and 127) shl (varintLength++ * 7))
+ if (varintLength > 5) {
+ throw IOException("VarInt too big (probably corrupted data)")
+ }
+ if ((blocks.data!![i].toInt() and 128) != 128) {
+ i++
+ break
+ }
+ i++
+ }
+ // index = (y * length * width) + (z * width) + x
+ val y = index / (width!! * length!!)
+ val z = (index % (width!! * length!!)) / width!!
+ val x = (index % (width!! * length!!)) % width!!
+ val state: BlockState = blockStates[value] ?: error("Unknown block state $value")
+ val pt = BlockVector3.at(x, y, z)
+ try {
+ if (tileEntities.containsKey(pt)) {
+ clipboard.setBlock(
+ clipboard.minimumPoint.add(pt),
+ state.toBaseBlock(CompoundTag(tileEntities[pt])
+ ))
+ } else {
+ clipboard.setBlock(clipboard.minimumPoint.add(pt), state)
+ }
+ } catch (e: WorldEditException) {
+ throw IOException("Failed to load a block in the schematic")
+ }
+
+ index++
+ }
+
+ return clipboard
+ }
+
+ private fun readCompoundTag(): Map {
+ var tagId = 0.toByte()
+ val map = mutableMapOf()
+
+ while (stream.readByte().also { tagId = it } != 0.toByte()) {
+ val name = stream.readUTF()
+ map[name] = readTag(tagId)!!
+ }
+ return map
+ }
+
+ private fun readTagCompoundList(): List {
+ val typeId = stream.readByte()
+ val length = stream.readInt()
+
+ if (typeId == 0.toByte()) {
+ if (length != 0) {
+ throw IOException("Invalid TAG_List: typeId=TAG_End but length=$length")
+ }
+ return emptyList()
+ }
+
+ if (typeId != 10.toByte()) {
+ throw IOException("Invalid TAG_List element type for (Block)Entities: expected TAG_Compound(10) but got $typeId")
+ }
+
+ val list = ArrayList(length)
+ repeat(length) { list.add(readCompoundTag()) }
+ return list
+ }
+
+ private fun readTag(tagId: Byte): Any? = when (tagId.toInt()) {
+ 0 -> null
+ 1 -> stream.readByte()
+ 2 -> stream.readShort()
+ 3 -> stream.readInt()
+ 4 -> stream.readLong()
+ 5 -> stream.readFloat()
+ 6 -> stream.readDouble()
+ 7 -> {
+ val length = stream.readInt()
+ val ba = ByteArray(length)
+ stream.readFully(ba)
+ ba
+ }
+ 8 -> stream.readUTF()
+ 9 -> {
+ val typeId = stream.readByte()
+ val length = stream.readInt()
+ if (typeId == 0.toByte() && length != 0) {
+ throw IOException("Invalid TAG_List: typeId=TAG_End but length=$length")
+ }
+ val list = mutableListOf()
+ repeat(length) { list.add(readTag(typeId)) }
+ list
+ }
+ 10 -> readCompoundTag()
+ 11 -> {
+ val length = stream.readInt()
+ IntArray(length) { stream.readInt() }
+ }
+ 12 -> {
+ val length = stream.readInt()
+ LongArray(length) { stream.readLong() }
+ }
+ else -> error("Unknown tag type $tagId")
+ }
+
+ private fun wrapNbt(data: Any): Tag = when (data) {
+ is Byte -> ByteTag(data)
+ is Short -> ShortTag(data)
+ is Int -> IntTag(data)
+ is Long -> LongTag(data)
+ is Float -> FloatTag(data)
+ is Double -> DoubleTag(data)
+ is String -> StringTag(data)
+ is ByteArray -> ByteArrayTag(data)
+ is IntArray -> IntArrayTag(data)
+ is LongArray -> LongArrayTag(data)
+ is Map<*, *> -> CompoundTag(data.map { it.key as String to wrapNbt(it.value!!) }.toMap())
+ is List<*> -> ListTag(data.firstOrNull()?.let { nbtClass(it) } ?: EndTag::class.java, data.map { wrapNbt(it!!) })
+ else -> error("Unknown tag type $data")
+ }
+
+ private fun nbtClass(data: Any): Class = when (data) {
+ is Byte -> ByteTag::class.java
+ is Short -> ShortTag::class.java
+ is Int -> IntTag::class.java
+ is Long -> LongTag::class.java
+ is Float -> FloatTag::class.java
+ is Double -> DoubleTag::class.java
+ is String -> StringTag::class.java
+ is ByteArray -> ByteArrayTag::class.java
+ is IntArray -> IntArrayTag::class.java
+ is LongArray -> LongArrayTag::class.java
+ is Map<*, *> -> CompoundTag::class.java
+ is List<*> -> ListTag::class.java
+ else -> error("Unknown tag type $data, ${data::class.java.simpleName}")
+ }
+
+ override fun toString(): String {
+ return "ChaosSchematicReader(platform=$platform, liveDataVersion=$liveDataVersion, wrapped=$wrapped, schematicVersion=$schematicVersion, dataVersion=$dataVersion, metadata=$metadata, width=$width, height=$height, length=$length, offset=${offset.contentToString()}, blocks=$blocks, entities=$entities, fixer=$fixer)"
+ }
+
+
+ data class Blocks(
+ var data: ByteArray?,
+ var palette: Map?,
+ var blockEntities: List?
+ )
+}
\ No newline at end of file
diff --git a/SpigotCore/SpigotCore_14/src/de/steamwar/core/WorldEditWrapper14.java b/SpigotCore/SpigotCore_14/src/de/steamwar/core/WorldEditWrapper14.java
index 60e4758d..08ad6a1a 100644
--- a/SpigotCore/SpigotCore_14/src/de/steamwar/core/WorldEditWrapper14.java
+++ b/SpigotCore/SpigotCore_14/src/de/steamwar/core/WorldEditWrapper14.java
@@ -89,7 +89,7 @@ public class WorldEditWrapper14 implements WorldEditWrapper {
switch (schemFormat) {
case SPONGE_V2:
case SPONGE_V3:
- return new SpongeSchematicReader(new NBTInputStream(is), this).read();
+ return new ChaosSchematicReader(new DataInputStream(is), this).read();
case MCEDIT:
return new MCEditSchematicReader(new NBTInputStream(is)).read();
default:
@@ -576,8 +576,6 @@ public class WorldEditWrapper14 implements WorldEditWrapper {
values.putIfAbsent("z", new IntTag(pt.getBlockZ()));
}
values.putIfAbsent("id", values.get("Id"));
- values.remove("Id");
- values.remove("Pos");
if (fixer != null) {
tileEntity = wrapper.applyDataFixer(fixer, dataVersion, values);
} else {
diff --git a/SpigotCore/SpigotCore_21/src/de/steamwar/core/WorldEditWrapper21.java b/SpigotCore/SpigotCore_21/src/de/steamwar/core/WorldEditWrapper21.java
index 15256d90..14f78b07 100644
--- a/SpigotCore/SpigotCore_21/src/de/steamwar/core/WorldEditWrapper21.java
+++ b/SpigotCore/SpigotCore_21/src/de/steamwar/core/WorldEditWrapper21.java
@@ -20,29 +20,32 @@
package de.steamwar.core;
import com.sk89q.jnbt.NBTInputStream;
+import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.BuiltInClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
import com.sk89q.worldedit.extent.clipboard.io.MCEditSchematicReader;
-import com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV2Reader;
-import com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV3Reader;
import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.session.ClipboardHolder;
+import com.sk89q.worldedit.world.DataFixer;
import de.steamwar.sql.NodeData;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
-import org.enginehub.linbus.stream.LinBinaryIO;
+import org.enginehub.linbus.tree.LinCompoundTag;
+import org.enginehub.linbus.tree.LinTag;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
-public class WorldEditWrapper21 implements WorldEditWrapper {
+public class WorldEditWrapper21 extends WorldEditWrapper18 {
@Override
public InputStream getPlayerClipboard(Player player) {
@@ -72,22 +75,10 @@ public class WorldEditWrapper21 implements WorldEditWrapper {
@Override
@SuppressWarnings("removal")
public Clipboard getClipboard(InputStream is, NodeData.SchematicFormat ignored) throws IOException {
- ResetableInputStream ris = new ResetableInputStream(is);
- for (NodeData.SchematicFormat schemFormat : NodeData.SchematicFormat.values()) {
- try {
- Clipboard clipboard = switch (schemFormat) {
- case MCEDIT -> new MCEditSchematicReader(new NBTInputStream(ris)).read();
- case SPONGE_V2 -> new SpongeSchematicV2Reader(LinBinaryIO.read(new DataInputStream(ris))).read();
- case SPONGE_V3 -> new SpongeSchematicV3Reader(LinBinaryIO.read(new DataInputStream(ris))).read();
- };
- ris.close();
- return clipboard;
- } catch (Exception e) {
- // Ignore
- }
- ris.reset();
- }
- throw new IOException("No clipboard found");
+ return switch (ignored) {
+ case MCEDIT -> new MCEditSchematicReader(new NBTInputStream(is)).read();
+ case SPONGE_V2, SPONGE_V3 -> new ChaosSchematicReader(new DataInputStream(is), this).read();
+ };
}
private class ResetableInputStream extends InputStream {