Skip to content
This repository was archived by the owner on Nov 28, 2025. It is now read-only.

Commit b41f481

Browse files
authored
Add block entity parsing to schematics
Add block entity parsing to schematics
2 parents 0582dd4 + b3f919b commit b41f481

File tree

9 files changed

+193
-108
lines changed

9 files changed

+193
-108
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.github.dockyardmc.extentions
2+
3+
import io.github.dockyardmc.maths.vectors.Vector3
4+
5+
fun IntArray.toVector3(): Vector3 {
6+
require(this.size == 3) { "IntArray must have exactly 3 elements" }
7+
return Vector3(this[0], this[1], this[2])
8+
}

src/main/kotlin/io/github/dockyardmc/maths/vectors/Vector3.kt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.dockyardmc.maths.vectors
22

3-
import io.github.dockyardmc.extentions.readVarInt
43
import io.github.dockyardmc.extentions.writeVarInt
54
import io.github.dockyardmc.location.Location
65
import io.github.dockyardmc.protocol.NetworkReadable
@@ -146,11 +145,7 @@ data class Vector3(
146145
)
147146

148147
override fun read(buffer: ByteBuf): Vector3 {
149-
return Vector3(
150-
buffer.readVarInt(),
151-
buffer.readVarInt(),
152-
buffer.readVarInt()
153-
)
148+
return STREAM_CODEC.read(buffer)
154149
}
155150
}
156151
}

src/main/kotlin/io/github/dockyardmc/schematics/Schematic.kt

Lines changed: 14 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,84 +4,42 @@ package io.github.dockyardmc.schematics
44

55
import cz.lukynka.prettylog.LogType
66
import cz.lukynka.prettylog.log
7+
import io.github.dockyardmc.extentions.getOrThrow
78
import io.github.dockyardmc.extentions.readVarInt
89
import io.github.dockyardmc.extentions.reversed
910
import io.github.dockyardmc.extentions.toByteBuf
1011
import io.github.dockyardmc.location.Location
11-
import io.github.dockyardmc.registry.Blocks
12-
import io.github.dockyardmc.world.chunk.ChunkUtils
1312
import io.github.dockyardmc.maths.vectors.Vector3
14-
import io.github.dockyardmc.world.chunk.Chunk
13+
import io.github.dockyardmc.registry.Blocks
1514
import io.github.dockyardmc.world.World
1615
import io.github.dockyardmc.world.block.Block
16+
import io.github.dockyardmc.world.chunk.Chunk
17+
import io.github.dockyardmc.world.chunk.ChunkPos
1718
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
18-
import it.unimi.dsi.fastutil.objects.ObjectArrayList
1919
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
20+
import net.kyori.adventure.nbt.CompoundBinaryTag
2021
import java.util.concurrent.CompletableFuture
2122

2223
data class Schematic(
2324
var size: Vector3,
2425
var offset: Vector3,
2526
var palette: Object2IntOpenHashMap<Block>,
2627
var blocks: ByteArray,
28+
var blockEntities: Map<Vector3, CompoundBinaryTag>
2729
) {
2830

29-
companion object {
30-
val empty = Schematic(Vector3(), Vector3(), Object2IntOpenHashMap(), ByteArray(0))
31-
val RED_STAINED_GLASS = Blocks.RED_STAINED_GLASS.toBlock()
31+
sealed interface SchematicBlock {
32+
33+
data class Normal(val localSpaceLocation: Location, val location: Location, val id: Int) : SchematicBlock
34+
35+
data class BlockEntity(val localSpaceLocation: Location, val location: Location, val block: Block, val data: CompoundBinaryTag) : SchematicBlock
3236
}
33-
}
3437

35-
fun World.placeSchematicAsync(schematic: Schematic, location: Location): CompletableFuture<Unit> {
36-
return location.world.scheduler.runAsync {
37-
location.world.placeSchematic(schematic, location)
38+
companion object {
39+
val empty = Schematic(Vector3(), Vector3(), Object2IntOpenHashMap(), ByteArray(0), mapOf())
40+
val RED_STAINED_GLASS = Blocks.RED_STAINED_GLASS.toBlock()
3841
}
3942
}
4043

41-
fun World.placeSchematic(
42-
schematic: Schematic,
43-
location: Location,
44-
) {
45-
val blocks = schematic.blocks.toByteBuf()
46-
val updateChunks = ObjectOpenHashSet<Chunk>()
47-
val loadChunk = ObjectOpenHashSet<Pair<Int, Int>>()
48-
val batchBlockUpdate = ObjectArrayList<Pair<Location, Int>>()
49-
val flippedPallet = schematic.palette.reversed()
50-
51-
for (y in 0 until schematic.size.y) {
52-
for (z in 0 until schematic.size.z) {
53-
for (x in 0 until schematic.size.x) {
54-
55-
val placeLoc = Location(x, y, z, location.world).add(location)
56-
val id = blocks.readVarInt()
57-
val block = flippedPallet[id] ?: Schematic.RED_STAINED_GLASS
58-
59-
val chunkX = ChunkUtils.getChunkCoordinate(placeLoc.x)
60-
val chunkZ = ChunkUtils.getChunkCoordinate(placeLoc.z)
6144

62-
loadChunk.add(chunkX to chunkZ)
6345

64-
val chunk = placeLoc.world.getOrGenerateChunk(
65-
ChunkUtils.getChunkCoordinate(placeLoc.x),
66-
ChunkUtils.getChunkCoordinate(placeLoc.z)
67-
)
68-
updateChunks.add(chunk)
69-
batchBlockUpdate.add(placeLoc to block.getProtocolId())
70-
}
71-
}
72-
}
73-
74-
batchBlockUpdate.forEach {
75-
try {
76-
this.setBlockRaw(it.first, it.second, false)
77-
} catch (ex: Exception) {
78-
log("Error while placing block in schematic at ${it.first}: $ex", LogType.ERROR)
79-
log(ex)
80-
}
81-
}
82-
83-
updateChunks.forEach { chunk ->
84-
chunk.updateCache()
85-
chunk.sendUpdateToViewers()
86-
}
87-
}

src/main/kotlin/io/github/dockyardmc/schematics/SchematicReader.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package io.github.dockyardmc.schematics
22

3+
import io.github.dockyardmc.extentions.toByteArraySafe
4+
import io.github.dockyardmc.extentions.toVector3
35
import io.github.dockyardmc.maths.vectors.Vector3
46
import io.github.dockyardmc.scroll.extensions.contains
57
import io.github.dockyardmc.world.block.Block
8+
import io.netty.buffer.ByteBuf
69
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
710
import net.kyori.adventure.nbt.BinaryTagIO
811
import net.kyori.adventure.nbt.CompoundBinaryTag
@@ -12,11 +15,15 @@ import java.io.File
1215

1316
object SchematicReader {
1417

15-
val READER = BinaryTagIO.unlimitedReader()
18+
private val READER = BinaryTagIO.unlimitedReader()
1619

1720
fun read(file: File): Schematic {
18-
if (!file.exists()) throw Exception("File $file does not exist!")
19-
return read(file.readBytes())
21+
require(file.exists()) { "File $file does not exist!" }
22+
return this.read(file.readBytes())
23+
}
24+
25+
fun read(buffer: ByteBuf): Schematic {
26+
return this.read(buffer.toByteArraySafe())
2027
}
2128

2229
fun read(byteArray: ByteArray): Schematic {
@@ -62,11 +69,20 @@ object SchematicReader {
6269
blocks[block] = id
6370
}
6471

72+
val blockEntities = mutableMapOf<Vector3, CompoundBinaryTag>()
73+
val blockEntitiesCompound = nbt.getCompound("Blocks").getList("BlockEntities")
74+
blockEntitiesCompound.forEach { blockEntity ->
75+
if (blockEntity !is CompoundBinaryTag) return@forEach
76+
val pos = blockEntity.getIntArray("Pos")
77+
blockEntities[pos.toVector3()] = blockEntity
78+
}
79+
6580
val schematic = Schematic(
6681
size = Vector3(width, height, length),
6782
offset = offset,
6883
palette = blocks,
69-
blocks = blockArray.copyOf()
84+
blocks = blockArray.copyOf(),
85+
blockEntities
7086
)
7187

7288
return schematic

src/main/kotlin/io/github/dockyardmc/world/World.kt

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ import io.github.dockyardmc.entity.EntityManager.despawnEntity
99
import io.github.dockyardmc.entity.EntityManager.spawnEntity
1010
import io.github.dockyardmc.entity.LightningBolt
1111
import io.github.dockyardmc.events.*
12-
import io.github.dockyardmc.extentions.SHA256Long
13-
import io.github.dockyardmc.extentions.hasUpperCase
14-
import io.github.dockyardmc.extentions.removeIfPresent
12+
import io.github.dockyardmc.extentions.*
1513
import io.github.dockyardmc.location.Location
1614
import io.github.dockyardmc.maths.vectors.Vector2f
1715
import io.github.dockyardmc.maths.vectors.Vector3
@@ -30,17 +28,21 @@ import io.github.dockyardmc.registry.registries.RegistryBlock
3028
import io.github.dockyardmc.scheduler.CustomRateScheduler
3129
import io.github.dockyardmc.scheduler.runLaterAsync
3230
import io.github.dockyardmc.scheduler.runnables.ticks
31+
import io.github.dockyardmc.schematics.Schematic
3332
import io.github.dockyardmc.sounds.playSound
3433
import io.github.dockyardmc.utils.*
3534
import io.github.dockyardmc.world.WorldManager.mainWorld
3635
import io.github.dockyardmc.world.block.BatchBlockUpdate
3736
import io.github.dockyardmc.world.block.Block
37+
import io.github.dockyardmc.world.block.BlockEntity
3838
import io.github.dockyardmc.world.chunk.Chunk
3939
import io.github.dockyardmc.world.chunk.ChunkPos
4040
import io.github.dockyardmc.world.chunk.ChunkUtils
4141
import io.github.dockyardmc.world.generators.VoidWorldGenerator
4242
import io.github.dockyardmc.world.generators.WorldGenerator
4343
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
44+
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
45+
import net.kyori.adventure.nbt.CompoundBinaryTag
4446
import java.util.*
4547
import java.util.concurrent.CompletableFuture
4648

@@ -56,6 +58,7 @@ class World(var name: String, var generator: WorldGenerator, var dimensionType:
5658

5759
val worldSeed = UUID.randomUUID().leastSignificantBits.toString()
5860
var seed: Long = worldSeed.SHA256Long()
61+
val random = Random(seed)
5962

6063
val difficulty: Bindable<Difficulty> = bindablePool.provideBindable(Difficulty.NORMAL)
6164
val worldBorder = WorldBorder(this)
@@ -285,7 +288,6 @@ class World(var name: String, var generator: WorldGenerator, var dimensionType:
285288
fun setBlock(x: Int, y: Int, z: Int, block: Block) {
286289
val chunk = getChunkAt(x, z) ?: throw IllegalStateException("Chunk at $x, $y is does not exist!")
287290
chunk.setBlock(x, y, z, block, true)
288-
chunk.sendUpdateToViewers()
289291
}
290292

291293
fun getBlock(location: Location): Block = this.getBlock(location.x.toInt(), location.y.toInt(), location.z.toInt())
@@ -340,10 +342,27 @@ class World(var name: String, var generator: WorldGenerator, var dimensionType:
340342
setBlockRaw(location.x.toInt(), location.y.toInt(), location.z.toInt(), blockStateId, updateChunk)
341343
}
342344

345+
fun getBlockEntityDataOrNull(location: Location): BlockEntity? {
346+
return getBlockEntityDataOrNull(location.x.toInt(), location.y.toInt(), location.z.toInt())
347+
}
348+
349+
fun getBlockEntityDataOrNull(x: Int, y: Int, z: Int): BlockEntity? {
350+
val chunk = getChunkAt(x, z) ?: throw IllegalStateException("Chunk at $x, $y is does not exist!")
351+
return chunk.getBlockEntityDataOrNull(x, y, z)
352+
}
353+
354+
fun setBlockEntityData(location: Location, data: CompoundBinaryTag, registryBlock: RegistryBlock, shouldCache: Boolean) {
355+
this.setBlockEntityData(location.x.toInt(), location.y.toInt(), location.z.toInt(), data, registryBlock, shouldCache)
356+
}
357+
358+
fun setBlockEntityData(x: Int, y: Int, z: Int, data: CompoundBinaryTag, registryBlock: RegistryBlock, shouldCache: Boolean = true) {
359+
val chunk = getChunkAt(x, z) ?: throw IllegalStateException("Chunk at $x, $y is does not exist!")
360+
chunk.setBlockEntityData(x, y, z, data, registryBlock, shouldCache)
361+
}
362+
343363
fun setBlockRaw(x: Int, y: Int, z: Int, blockStateId: Int, updateChunk: Boolean = true) {
344364
val chunk = getChunkAt(x, z) ?: return
345365
chunk.setBlockRaw(x, y, z, blockStateId, updateChunk)
346-
if (updateChunk) chunk.sendUpdateToViewers()
347366
}
348367

349368
inline fun batchBlockUpdate(builder: BatchBlockUpdate.() -> Unit): CompletableFuture<World> {
@@ -367,8 +386,7 @@ class World(var name: String, var generator: WorldGenerator, var dimensionType:
367386
setBlockRaw(location, block.getProtocolId(), false)
368387
}
369388
chunks.forEach { chunk ->
370-
chunk.updateCache()
371-
chunk.sendUpdateToViewers()
389+
chunk.update()
372390
}
373391

374392
future.complete(this)
@@ -429,7 +447,7 @@ class World(var name: String, var generator: WorldGenerator, var dimensionType:
429447
}
430448
}
431449
}
432-
chunk.updateCache()
450+
chunk.update()
433451
if (getChunk(x, z) == null) {
434452
synchronized(chunks) {
435453
chunks[ChunkUtils.getChunkIndex(x, z)] = (chunk)
@@ -463,7 +481,71 @@ class World(var name: String, var generator: WorldGenerator, var dimensionType:
463481
return Location(vector.x, vector.y, vector.z, this)
464482
}
465483

466-
fun getRandom(): Random = Random(seed)
484+
fun World.placeSchematicAsync(schematic: Schematic, location: Location): CompletableFuture<Unit> {
485+
return this.scheduler.runAsync {
486+
this.placeSchematic(schematic, location)
487+
}
488+
}
489+
490+
fun placeSchematic(
491+
schematic: Schematic,
492+
location: Location,
493+
) {
494+
val blocks = schematic.blocks.toByteBuf()
495+
val updateChunks = ObjectOpenHashSet<Chunk>()
496+
val loadChunk = ObjectOpenHashSet<ChunkPos>()
497+
val batchBlockUpdate = ObjectOpenHashSet<Schematic.SchematicBlock>()
498+
val flippedPallet = schematic.palette.reversed()
499+
500+
for (y in 0 until schematic.size.y) {
501+
for (z in 0 until schematic.size.z) {
502+
for (x in 0 until schematic.size.x) {
503+
504+
val localSpacePlaceVec = Vector3(x, y, z)
505+
val localSpacePlaceLoc = localSpacePlaceVec.toLocation(location.world)
506+
val placeLoc = localSpacePlaceLoc.add(location)
507+
val id = blocks.readVarInt()
508+
val block = flippedPallet[id] ?: Schematic.RED_STAINED_GLASS
509+
510+
val chunkPos = ChunkPos.fromLocation(placeLoc)
511+
val chunk = placeLoc.world.getOrGenerateChunk(chunkPos)
512+
513+
loadChunk.add(chunkPos)
514+
updateChunks.add(chunk)
515+
516+
val schematicBlock: Schematic.SchematicBlock = if (schematic.blockEntities.containsKey(localSpacePlaceVec)) {
517+
Schematic.SchematicBlock.BlockEntity(localSpacePlaceLoc, placeLoc, block, schematic.blockEntities.getOrThrow(localSpacePlaceVec))
518+
} else {
519+
Schematic.SchematicBlock.Normal(localSpacePlaceLoc, placeLoc, block.getProtocolId())
520+
}
521+
batchBlockUpdate.add(schematicBlock)
522+
}
523+
}
524+
}
525+
526+
batchBlockUpdate.forEach { schematicBlock ->
527+
try {
528+
when (schematicBlock) {
529+
is Schematic.SchematicBlock.Normal -> {
530+
this.setBlockRaw(schematicBlock.location, schematicBlock.id, false)
531+
}
532+
533+
is Schematic.SchematicBlock.BlockEntity -> {
534+
this.setBlockRaw(schematicBlock.location, schematicBlock.block.getProtocolId(), false)
535+
this.setBlockEntityData(schematicBlock.location, schematicBlock.data, schematicBlock.block.registryBlock, false)
536+
}
537+
}
538+
539+
} catch (ex: Exception) {
540+
log("Error while placing block in schematic at ${location}: $ex", LogType.ERROR)
541+
log(ex)
542+
}
543+
}
544+
545+
updateChunks.forEach { chunk ->
546+
chunk.update()
547+
}
548+
}
467549

468550
override fun dispose() {
469551
players.forEach { player ->

0 commit comments

Comments
 (0)