Skip to content
This repository was archived by the owner on Nov 28, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.github.dockyardmc.extentions

import io.github.dockyardmc.maths.vectors.Vector3

fun IntArray.toVector3(): Vector3 {
require(this.size == 3) { "IntArray must have exactly 3 elements" }
return Vector3(this[0], this[1], this[2])
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.github.dockyardmc.maths.vectors

import io.github.dockyardmc.extentions.readVarInt
import io.github.dockyardmc.extentions.writeVarInt
import io.github.dockyardmc.location.Location
import io.github.dockyardmc.protocol.NetworkReadable
Expand Down Expand Up @@ -146,11 +145,7 @@ data class Vector3(
)

override fun read(buffer: ByteBuf): Vector3 {
return Vector3(
buffer.readVarInt(),
buffer.readVarInt(),
buffer.readVarInt()
)
return STREAM_CODEC.read(buffer)
}
}
}
70 changes: 14 additions & 56 deletions src/main/kotlin/io/github/dockyardmc/schematics/Schematic.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,84 +4,42 @@ package io.github.dockyardmc.schematics

import cz.lukynka.prettylog.LogType
import cz.lukynka.prettylog.log
import io.github.dockyardmc.extentions.getOrThrow
import io.github.dockyardmc.extentions.readVarInt
import io.github.dockyardmc.extentions.reversed
import io.github.dockyardmc.extentions.toByteBuf
import io.github.dockyardmc.location.Location
import io.github.dockyardmc.registry.Blocks
import io.github.dockyardmc.world.chunk.ChunkUtils
import io.github.dockyardmc.maths.vectors.Vector3
import io.github.dockyardmc.world.chunk.Chunk
import io.github.dockyardmc.registry.Blocks
import io.github.dockyardmc.world.World
import io.github.dockyardmc.world.block.Block
import io.github.dockyardmc.world.chunk.Chunk
import io.github.dockyardmc.world.chunk.ChunkPos
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import net.kyori.adventure.nbt.CompoundBinaryTag
import java.util.concurrent.CompletableFuture

data class Schematic(
var size: Vector3,
var offset: Vector3,
var palette: Object2IntOpenHashMap<Block>,
var blocks: ByteArray,
var blockEntities: Map<Vector3, CompoundBinaryTag>
) {

companion object {
val empty = Schematic(Vector3(), Vector3(), Object2IntOpenHashMap(), ByteArray(0))
val RED_STAINED_GLASS = Blocks.RED_STAINED_GLASS.toBlock()
sealed interface SchematicBlock {

data class Normal(val localSpaceLocation: Location, val location: Location, val id: Int) : SchematicBlock

data class BlockEntity(val localSpaceLocation: Location, val location: Location, val block: Block, val data: CompoundBinaryTag) : SchematicBlock
}
}

fun World.placeSchematicAsync(schematic: Schematic, location: Location): CompletableFuture<Unit> {
return location.world.scheduler.runAsync {
location.world.placeSchematic(schematic, location)
companion object {
val empty = Schematic(Vector3(), Vector3(), Object2IntOpenHashMap(), ByteArray(0), mapOf())
val RED_STAINED_GLASS = Blocks.RED_STAINED_GLASS.toBlock()
}
}

fun World.placeSchematic(
schematic: Schematic,
location: Location,
) {
val blocks = schematic.blocks.toByteBuf()
val updateChunks = ObjectOpenHashSet<Chunk>()
val loadChunk = ObjectOpenHashSet<Pair<Int, Int>>()
val batchBlockUpdate = ObjectArrayList<Pair<Location, Int>>()
val flippedPallet = schematic.palette.reversed()

for (y in 0 until schematic.size.y) {
for (z in 0 until schematic.size.z) {
for (x in 0 until schematic.size.x) {

val placeLoc = Location(x, y, z, location.world).add(location)
val id = blocks.readVarInt()
val block = flippedPallet[id] ?: Schematic.RED_STAINED_GLASS

val chunkX = ChunkUtils.getChunkCoordinate(placeLoc.x)
val chunkZ = ChunkUtils.getChunkCoordinate(placeLoc.z)

loadChunk.add(chunkX to chunkZ)

val chunk = placeLoc.world.getOrGenerateChunk(
ChunkUtils.getChunkCoordinate(placeLoc.x),
ChunkUtils.getChunkCoordinate(placeLoc.z)
)
updateChunks.add(chunk)
batchBlockUpdate.add(placeLoc to block.getProtocolId())
}
}
}

batchBlockUpdate.forEach {
try {
this.setBlockRaw(it.first, it.second, false)
} catch (ex: Exception) {
log("Error while placing block in schematic at ${it.first}: $ex", LogType.ERROR)
log(ex)
}
}

updateChunks.forEach { chunk ->
chunk.updateCache()
chunk.sendUpdateToViewers()
}
}
24 changes: 20 additions & 4 deletions src/main/kotlin/io/github/dockyardmc/schematics/SchematicReader.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.github.dockyardmc.schematics

import io.github.dockyardmc.extentions.toByteArraySafe
import io.github.dockyardmc.extentions.toVector3
import io.github.dockyardmc.maths.vectors.Vector3
import io.github.dockyardmc.scroll.extensions.contains
import io.github.dockyardmc.world.block.Block
import io.netty.buffer.ByteBuf
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import net.kyori.adventure.nbt.BinaryTagIO
import net.kyori.adventure.nbt.CompoundBinaryTag
Expand All @@ -12,11 +15,15 @@ import java.io.File

object SchematicReader {

val READER = BinaryTagIO.unlimitedReader()
private val READER = BinaryTagIO.unlimitedReader()

fun read(file: File): Schematic {
if (!file.exists()) throw Exception("File $file does not exist!")
return read(file.readBytes())
require(file.exists()) { "File $file does not exist!" }
return this.read(file.readBytes())
}

fun read(buffer: ByteBuf): Schematic {
return this.read(buffer.toByteArraySafe())
}

fun read(byteArray: ByteArray): Schematic {
Expand Down Expand Up @@ -62,11 +69,20 @@ object SchematicReader {
blocks[block] = id
}

val blockEntities = mutableMapOf<Vector3, CompoundBinaryTag>()
val blockEntitiesCompound = nbt.getCompound("Blocks").getList("BlockEntities")
blockEntitiesCompound.forEach { blockEntity ->
if (blockEntity !is CompoundBinaryTag) return@forEach
val pos = blockEntity.getIntArray("Pos")
blockEntities[pos.toVector3()] = blockEntity
}

val schematic = Schematic(
size = Vector3(width, height, length),
offset = offset,
palette = blocks,
blocks = blockArray.copyOf()
blocks = blockArray.copyOf(),
blockEntities
)

return schematic
Expand Down
100 changes: 91 additions & 9 deletions src/main/kotlin/io/github/dockyardmc/world/World.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import io.github.dockyardmc.entity.EntityManager.despawnEntity
import io.github.dockyardmc.entity.EntityManager.spawnEntity
import io.github.dockyardmc.entity.LightningBolt
import io.github.dockyardmc.events.*
import io.github.dockyardmc.extentions.SHA256Long
import io.github.dockyardmc.extentions.hasUpperCase
import io.github.dockyardmc.extentions.removeIfPresent
import io.github.dockyardmc.extentions.*
import io.github.dockyardmc.location.Location
import io.github.dockyardmc.maths.vectors.Vector2f
import io.github.dockyardmc.maths.vectors.Vector3
Expand All @@ -30,17 +28,21 @@ import io.github.dockyardmc.registry.registries.RegistryBlock
import io.github.dockyardmc.scheduler.CustomRateScheduler
import io.github.dockyardmc.scheduler.runLaterAsync
import io.github.dockyardmc.scheduler.runnables.ticks
import io.github.dockyardmc.schematics.Schematic
import io.github.dockyardmc.sounds.playSound
import io.github.dockyardmc.utils.*
import io.github.dockyardmc.world.WorldManager.mainWorld
import io.github.dockyardmc.world.block.BatchBlockUpdate
import io.github.dockyardmc.world.block.Block
import io.github.dockyardmc.world.block.BlockEntity
import io.github.dockyardmc.world.chunk.Chunk
import io.github.dockyardmc.world.chunk.ChunkPos
import io.github.dockyardmc.world.chunk.ChunkUtils
import io.github.dockyardmc.world.generators.VoidWorldGenerator
import io.github.dockyardmc.world.generators.WorldGenerator
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import net.kyori.adventure.nbt.CompoundBinaryTag
import java.util.*
import java.util.concurrent.CompletableFuture

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

val worldSeed = UUID.randomUUID().leastSignificantBits.toString()
var seed: Long = worldSeed.SHA256Long()
val random = Random(seed)

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

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

fun getBlockEntityDataOrNull(location: Location): BlockEntity? {
return getBlockEntityDataOrNull(location.x.toInt(), location.y.toInt(), location.z.toInt())
}

fun getBlockEntityDataOrNull(x: Int, y: Int, z: Int): BlockEntity? {
val chunk = getChunkAt(x, z) ?: throw IllegalStateException("Chunk at $x, $y is does not exist!")
return chunk.getBlockEntityDataOrNull(x, y, z)
}

fun setBlockEntityData(location: Location, data: CompoundBinaryTag, registryBlock: RegistryBlock, shouldCache: Boolean) {
this.setBlockEntityData(location.x.toInt(), location.y.toInt(), location.z.toInt(), data, registryBlock, shouldCache)
}

fun setBlockEntityData(x: Int, y: Int, z: Int, data: CompoundBinaryTag, registryBlock: RegistryBlock, shouldCache: Boolean = true) {
val chunk = getChunkAt(x, z) ?: throw IllegalStateException("Chunk at $x, $y is does not exist!")
chunk.setBlockEntityData(x, y, z, data, registryBlock, shouldCache)
}

fun setBlockRaw(x: Int, y: Int, z: Int, blockStateId: Int, updateChunk: Boolean = true) {
val chunk = getChunkAt(x, z) ?: return
chunk.setBlockRaw(x, y, z, blockStateId, updateChunk)
if (updateChunk) chunk.sendUpdateToViewers()
}

inline fun batchBlockUpdate(builder: BatchBlockUpdate.() -> Unit): CompletableFuture<World> {
Expand All @@ -367,8 +386,7 @@ class World(var name: String, var generator: WorldGenerator, var dimensionType:
setBlockRaw(location, block.getProtocolId(), false)
}
chunks.forEach { chunk ->
chunk.updateCache()
chunk.sendUpdateToViewers()
chunk.update()
}

future.complete(this)
Expand Down Expand Up @@ -429,7 +447,7 @@ class World(var name: String, var generator: WorldGenerator, var dimensionType:
}
}
}
chunk.updateCache()
chunk.update()
if (getChunk(x, z) == null) {
synchronized(chunks) {
chunks[ChunkUtils.getChunkIndex(x, z)] = (chunk)
Expand Down Expand Up @@ -463,7 +481,71 @@ class World(var name: String, var generator: WorldGenerator, var dimensionType:
return Location(vector.x, vector.y, vector.z, this)
}

fun getRandom(): Random = Random(seed)
fun World.placeSchematicAsync(schematic: Schematic, location: Location): CompletableFuture<Unit> {
return this.scheduler.runAsync {
this.placeSchematic(schematic, location)
}
}

fun placeSchematic(
schematic: Schematic,
location: Location,
) {
val blocks = schematic.blocks.toByteBuf()
val updateChunks = ObjectOpenHashSet<Chunk>()
val loadChunk = ObjectOpenHashSet<ChunkPos>()
val batchBlockUpdate = ObjectOpenHashSet<Schematic.SchematicBlock>()
val flippedPallet = schematic.palette.reversed()

for (y in 0 until schematic.size.y) {
for (z in 0 until schematic.size.z) {
for (x in 0 until schematic.size.x) {

val localSpacePlaceVec = Vector3(x, y, z)
val localSpacePlaceLoc = localSpacePlaceVec.toLocation(location.world)
val placeLoc = localSpacePlaceLoc.add(location)
val id = blocks.readVarInt()
val block = flippedPallet[id] ?: Schematic.RED_STAINED_GLASS

val chunkPos = ChunkPos.fromLocation(placeLoc)
val chunk = placeLoc.world.getOrGenerateChunk(chunkPos)

loadChunk.add(chunkPos)
updateChunks.add(chunk)

val schematicBlock: Schematic.SchematicBlock = if (schematic.blockEntities.containsKey(localSpacePlaceVec)) {
Schematic.SchematicBlock.BlockEntity(localSpacePlaceLoc, placeLoc, block, schematic.blockEntities.getOrThrow(localSpacePlaceVec))
} else {
Schematic.SchematicBlock.Normal(localSpacePlaceLoc, placeLoc, block.getProtocolId())
}
batchBlockUpdate.add(schematicBlock)
}
}
}

batchBlockUpdate.forEach { schematicBlock ->
try {
when (schematicBlock) {
is Schematic.SchematicBlock.Normal -> {
this.setBlockRaw(schematicBlock.location, schematicBlock.id, false)
}

is Schematic.SchematicBlock.BlockEntity -> {
this.setBlockRaw(schematicBlock.location, schematicBlock.block.getProtocolId(), false)
this.setBlockEntityData(schematicBlock.location, schematicBlock.data, schematicBlock.block.registryBlock, false)
}
}

} catch (ex: Exception) {
log("Error while placing block in schematic at ${location}: $ex", LogType.ERROR)
log(ex)
}
}

updateChunks.forEach { chunk ->
chunk.update()
}
}

override fun dispose() {
players.forEach { player ->
Expand Down
Loading
Loading