Archived
1
0
Fork 0

Generate block stub classes

This commit is contained in:
Moritz Ruth 2020-10-06 17:12:23 +02:00
parent 50710d6240
commit edc93f7a80
11 changed files with 178 additions and 73 deletions

View file

@ -40,6 +40,10 @@ dependencies {
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
}
kotlin {
sourceSets["main"].kotlin.srcDir("src/main/generatedKotlin")
}
tasks {
compileKotlin {
kotlinOptions.jvmTarget = "1.8"

View file

@ -1,6 +1,9 @@
package space.blokk.plugin
interface Plugin {
fun onEnable() {}
fun onDisable() {}
companion object {
fun getCalling(): Plugin? {
// TODO: Return the last plugin in the stacktrace

View file

@ -1,7 +1,7 @@
package space.blokk.world
import kotlinx.coroutines.CoroutineScope
import space.blokk.world.block.Block
import space.blokk.world.block.BlockRef
interface Chunk {
/**
@ -9,6 +9,8 @@ interface Chunk {
*/
val key: Key
data class Key(val x: Int, val z: Int)
/**
* [CoroutineScope] of this chunk.
*
@ -16,24 +18,35 @@ interface Chunk {
*/
val scope: CoroutineScope
data class Key(val x: Int, val z: Int)
/**
* The [world][World] this chunk belongs to.
* The [World] this chunk belongs to.
*/
val world: World
// TODO: Allow more than 16 sections
/**
* The 16 [ChunkSection]s this chunk consists of.
*/
val sections: Array<ChunkSection>
/**
* Gets a block in this chunk.
* Whether this chunk must be loaded using [load] before it can be accessed.
*
* This value should not change for the lifetime of a chunk.
*/
val mustBeLoaded: Boolean
/**
* Returns the [BlockRef] for the specified coordinates in this chunk.
*
* **The coordinates are relative to this chunk, not to the entire world.**
*/
fun getBlock(x: Byte, y: Int, z: Byte): Block
fun getBlock(x: Byte, y: Int, z: Byte): BlockRef
/**
* Loads this chunk into memory.
*
* If the implementation does not need to load chunks, this method should do nothing.
*/
suspend fun load()

View file

@ -3,10 +3,13 @@ package space.blokk.world
import kotlinx.coroutines.CoroutineScope
import space.blokk.CoordinatePartOrder
import space.blokk.entity.Entity
import space.blokk.world.block.Block
import space.blokk.world.block.BlockRef
/**
* A Minecraft world, sometimes also called level.
*
* **Note: Although the methods in this class are called `getBlock`, `getBlocksInSphere` and so on, they actually
* return [BlockRef]s.**
*/
abstract class World {
/**
@ -38,6 +41,8 @@ abstract class World {
/**
* Gets the chunk key for the chunk containing the block at [location].
*
* @public
*/
fun getChunkKeyAt(location: BlockLocation) =
Chunk.Key(location.x / Chunk.WIDTH_AND_LENGTH, location.y / Chunk.WIDTH_AND_LENGTH)
@ -55,7 +60,7 @@ abstract class World {
/**
* @return The block at [location].
*/
abstract fun getBlock(location: BlockLocation): Block
abstract fun getBlock(location: BlockLocation): BlockRef
/**
* @param order The order of the arrays.
@ -66,7 +71,7 @@ abstract class World {
firstCorner: BlockLocation,
secondCorner: BlockLocation,
order: CoordinatePartOrder = CoordinatePartOrder.DEFAULT
): Array<Array<Array<Block>>> {
): Array<Array<Array<BlockRef>>> {
val start = firstCorner.withLowestValues(secondCorner)
val end = firstCorner.withHighestValues(secondCorner)
@ -85,7 +90,8 @@ abstract class World {
fun getBlocksInSphere(
center: BlockLocation,
radius: Int
): Array<Block> {
): Array<BlockRef> {
// https://www.reddit.com/r/VoxelGameDev/comments/2cttnt/how_to_create_a_sphere_out_of_voxels/
TODO()
}

View file

@ -1,27 +1,33 @@
package space.blokk.world.block
import space.blokk.world.BlockLocation
import space.blokk.world.Chunk
import space.blokk.world.World
/**
* Represents a block in a chunk of a world.
*
* There is always only one instance for every [location][BlockLocation] in a world.
* @param ref The [BlockRef] referencing this block.
*/
interface Block {
abstract class Block internal constructor(val ref: BlockRef) {
/**
* The [location][BlockLocation] of this block.
* The [Material] of this block.
*/
val location: BlockLocation
abstract val material: Material
/**
* The [World] containing this block.
* Whether [ref] still references this block.
*/
val world: World
val destroyed: Boolean get() = ref.block === this
/**
* The [Chunk] containing this block.
* Replaces this block with air.
*/
val chunk: Chunk get() = world.getChunkAt(location)
fun replaceWithAir() {
checkNotDestroyed()
ref.place(Material.AIR)
}
private fun checkNotDestroyed() {
if (destroyed) throw IllegalStateException("The block was destroyed.")
}
/**
* Called when [ref] no longer references this block.
*/
internal abstract fun destroy()
}

View file

@ -1,3 +0,0 @@
package space.blokk.world.block
interface BlockContent

View file

@ -0,0 +1,57 @@
package space.blokk.world.block
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import space.blokk.world.BlockLocation
import space.blokk.world.Chunk
import space.blokk.world.World
import kotlin.reflect.KClass
import kotlin.reflect.full.primaryConstructor
/**
* A reference to a block in a chunk of a world.
*
* There is always only one instance for every [location][BlockLocation] in a world.
*/
abstract class BlockRef {
/**
* The [location][BlockLocation] of this ref.
*/
abstract val location: BlockLocation
/**
* The [World] containing this ref.
*/
abstract val world: World
/**
* The [Chunk] containing this ref.
*/
val chunk: Chunk get() = world.getChunkAt(location)
/**
* The current [Block] of this ref.
*/
abstract var block: Block; protected set
private val placeMutex by lazy { Mutex() }
/**
* Create a new [Block] and assign it to this ref (i.e. place it in the world).
*/
fun place(material: Material): Block = TODO()
/**
* Create a new [Block] and assign it to this ref (i.e. place it in the world).
*/
suspend fun <T : Block> place(type: KClass<T>): T = type.primaryConstructor!!.call(this).also {
placeMutex.withLock {
block.destroy()
block = it
}
}
companion object {
fun of(world: World, location: BlockLocation) = world.getBlock(location)
}
}

View file

@ -1,28 +0,0 @@
package space.blokk.world.block
import space.blokk.util.KClassToInstanceMap
import kotlin.reflect.KClass
abstract class BlockType internal constructor(
val id: Int,
val name: String,
val hardness: Float,
val contentType: KClass<out BlockContent>,
val transparent: Boolean,
val emittedLight: Byte,
val filteredLight: Byte,
val collisionShape: Array<Array<Double>>,
val maxStackSize: Byte
) {
private val extensions = KClassToInstanceMap<Extension>()
operator fun <T : Extension> get(key: KClassToInstanceMap.Key<T>): T = get(key.actualKey)
operator fun <T : Extension> get(key: KClass<out T>): T =
extensions.getInstance(key) ?: throw ExtensionNotProvidedException()
fun <T : Extension> provideExtension(key: KClassToInstanceMap.Key<T>, value: T) = extensions.putInstance(key, value)
class ExtensionNotProvidedException : Exception("The specified extension is not provided for this BlockType")
abstract class Extension
}

View file

@ -1,4 +0,0 @@
package space.blokk.world.block
interface Stone : BlockContent {
}

View file

@ -2,17 +2,20 @@ package space.blokk.mdsp
import com.google.common.base.CaseFormat
import com.jsoniter.JsonIterator
import com.jsoniter.any.Any
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import java.io.File
typealias JsonAny = com.jsoniter.any.Any
class FilesGenerator(private val dataDir: File, private val outputDir: File) {
class FilesGenerator(private val dataDir: File, private val outputDir: File, private val sourcesDir: File) {
companion object {
const val API_PACKAGE = "space.blokk"
const val BLOCK_PACKAGE = "space.blokk.world.block"
val MATERIAL_TYPE = ClassName(BLOCK_PACKAGE, "Material")
val BLOCK_TYPE = ClassName(BLOCK_PACKAGE, "Block")
val BLOCK_REF_TYPE = ClassName(BLOCK_PACKAGE, "BlockRef")
val K_CLASS_TYPE = ClassName("kotlin.reflect", "KClass")
}
fun generate() {
@ -24,10 +27,12 @@ class FilesGenerator(private val dataDir: File, private val outputDir: File) {
val dataJson = dataDir.resolve("blocks.json").readText()
val blocks = JsonIterator.deserialize(dataJson).asList()
generateBlockStubs(blocks)
generateMaterialEnum(blocks, collisionShapesData.get("blocks").asMap())
}
private fun generateCollisionShapeEnum(shapes: Map<String, Any>) {
private fun generateCollisionShapeEnum(shapes: Map<String, JsonAny>) {
val builder = TypeSpec.enumBuilder("CollisionShape")
.primaryConstructor(
FunSpec.constructorBuilder()
@ -38,6 +43,10 @@ class FilesGenerator(private val dataDir: File, private val outputDir: File) {
)
.build()
)
.addKdoc(
"Explanation what the numbers mean: " +
"https://github.com/PrismarineJS/minecraft-data/blob/master/doc/blockCollisionShapes.md"
)
for (shape in shapes.toList().sortedBy { it.first.toInt() }) {
builder.addEnumConstant(shape.first, TypeSpec.anonymousClassBuilder()
@ -55,21 +64,52 @@ class FilesGenerator(private val dataDir: File, private val outputDir: File) {
.writeTo(outputDir)
}
private fun generateMaterialEnum(blocks: List<JsonAny>, collisionShapeKeyByBlockName: MutableMap<String, Any>) {
private fun generateBlockStubs(blocks: List<JsonAny>) {
for (block in blocks) {
val lowerUnderscoreName = block.get("name").toString()
val upperCamelName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, lowerUnderscoreName)
val upperUnderscoreName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_UNDERSCORE, lowerUnderscoreName)
val specificMaterialType = MATERIAL_TYPE.nestedClass(upperUnderscoreName)
if (sourcesDir.resolve("./${BLOCK_PACKAGE.replace(".", "/")}/$upperCamelName.kt").exists())
continue
val type = TypeSpec.classBuilder(upperCamelName)
.addKdoc("A [$upperUnderscoreName][%T] block", specificMaterialType)
.addModifiers(KModifier.ABSTRACT)
.superclass(BLOCK_TYPE)
.primaryConstructor(
FunSpec.constructorBuilder()
.addParameter("ref", BLOCK_REF_TYPE)
.build()
)
.addSuperclassConstructorParameter("ref")
.addProperty(
PropertySpec.builder("material", MATERIAL_TYPE, KModifier.OVERRIDE)
.initializer("%T", specificMaterialType)
.build()
)
.build()
FileSpec.builder(BLOCK_PACKAGE, upperCamelName)
.addComment("IF YOU CHANGE THIS FILE, MOVE IT FROM `generatedKotlin` TO `kotlin` OR IT WILL BE OVERWRITTEN.")
.addType(type)
.build()
.writeTo(outputDir)
}
}
private fun generateMaterialEnum(blocks: List<JsonAny>, collisionShapeKeyByBlockName: MutableMap<String, JsonAny>) {
val builder = TypeSpec.enumBuilder("Material")
.primaryConstructor(
FunSpec.constructorBuilder()
.addParameter("id", Int::class)
.addParameter("name", String::class)
.addParameter(
"blockType",
K_CLASS_TYPE.parameterizedBy(WildcardTypeName.producerOf(BLOCK_TYPE))
)
.addParameter("hardness", Float::class)
// TODO: Uncomment when all block content interfaces exist
// .addParameter(
// "contentType",
// ClassName(
// "kotlin.reflect",
// "KClass"
// ).parameterizedBy(ClassName(BLOCK_PACKAGE, "BlockContent"))
// )
.addParameter("transparent", Boolean::class)
.addParameter("emittedLight", Byte::class)
.addParameter("filteredLight", Byte::class)
@ -83,10 +123,17 @@ class FilesGenerator(private val dataDir: File, private val outputDir: File) {
val collisionShapeValue = collisionShapeKeyByBlockName[name]!!
builder.addEnumConstant(
CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_UNDERSCORE, block.get("name").toString()),
CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_UNDERSCORE, name),
TypeSpec.anonymousClassBuilder()
.addSuperclassConstructorParameter("%L", block.get("id").toInt())
.addSuperclassConstructorParameter("%S", name)
.addSuperclassConstructorParameter(
"%T::class",
ClassName(
BLOCK_PACKAGE,
CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name)
)
)
.addSuperclassConstructorParameter("%Lf", block.get("hardness").toFloat())
.addSuperclassConstructorParameter("%L", block.get("transparent").toBoolean())
.addSuperclassConstructorParameter("%L", block.get("emitLight").toInt().toByte())

View file

@ -23,7 +23,11 @@ class MinecraftDataSourcesPlugin : Plugin<Project> {
project
.project(":blokk-api")
.projectDir
.resolve("src/main/generatedKotlin")
.resolve("src/main/generatedKotlin"),
project
.project(":blokk-api")
.projectDir
.resolve("src/main/kotlin")
).generate()
}
}