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}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
} }
kotlin {
sourceSets["main"].kotlin.srcDir("src/main/generatedKotlin")
}
tasks { tasks {
compileKotlin { compileKotlin {
kotlinOptions.jvmTarget = "1.8" kotlinOptions.jvmTarget = "1.8"

View file

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

View file

@ -1,7 +1,7 @@
package space.blokk.world package space.blokk.world
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import space.blokk.world.block.Block import space.blokk.world.block.BlockRef
interface Chunk { interface Chunk {
/** /**
@ -9,6 +9,8 @@ interface Chunk {
*/ */
val key: Key val key: Key
data class Key(val x: Int, val z: Int)
/** /**
* [CoroutineScope] of this chunk. * [CoroutineScope] of this chunk.
* *
@ -16,24 +18,35 @@ interface Chunk {
*/ */
val scope: CoroutineScope 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 val world: World
// TODO: Allow more than 16 sections
/**
* The 16 [ChunkSection]s this chunk consists of.
*/
val sections: Array<ChunkSection> 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.** * **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. * Loads this chunk into memory.
*
* If the implementation does not need to load chunks, this method should do nothing.
*/ */
suspend fun load() suspend fun load()

View file

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

View file

@ -1,27 +1,33 @@
package space.blokk.world.block 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. * @param ref The [BlockRef] referencing this block.
*
* There is always only one instance for every [location][BlockLocation] in a world.
*/ */
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.google.common.base.CaseFormat
import com.jsoniter.JsonIterator import com.jsoniter.JsonIterator
import com.jsoniter.any.Any
import com.squareup.kotlinpoet.* import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import java.io.File import java.io.File
typealias JsonAny = com.jsoniter.any.Any 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 { companion object {
const val API_PACKAGE = "space.blokk" const val API_PACKAGE = "space.blokk"
const val BLOCK_PACKAGE = "space.blokk.world.block" 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() { fun generate() {
@ -24,10 +27,12 @@ class FilesGenerator(private val dataDir: File, private val outputDir: File) {
val dataJson = dataDir.resolve("blocks.json").readText() val dataJson = dataDir.resolve("blocks.json").readText()
val blocks = JsonIterator.deserialize(dataJson).asList() val blocks = JsonIterator.deserialize(dataJson).asList()
generateBlockStubs(blocks)
generateMaterialEnum(blocks, collisionShapesData.get("blocks").asMap()) 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") val builder = TypeSpec.enumBuilder("CollisionShape")
.primaryConstructor( .primaryConstructor(
FunSpec.constructorBuilder() FunSpec.constructorBuilder()
@ -38,6 +43,10 @@ class FilesGenerator(private val dataDir: File, private val outputDir: File) {
) )
.build() .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() }) { for (shape in shapes.toList().sortedBy { it.first.toInt() }) {
builder.addEnumConstant(shape.first, TypeSpec.anonymousClassBuilder() builder.addEnumConstant(shape.first, TypeSpec.anonymousClassBuilder()
@ -55,21 +64,52 @@ class FilesGenerator(private val dataDir: File, private val outputDir: File) {
.writeTo(outputDir) .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") val builder = TypeSpec.enumBuilder("Material")
.primaryConstructor( .primaryConstructor(
FunSpec.constructorBuilder() FunSpec.constructorBuilder()
.addParameter("id", Int::class) .addParameter("id", Int::class)
.addParameter("name", String::class) .addParameter("name", String::class)
.addParameter(
"blockType",
K_CLASS_TYPE.parameterizedBy(WildcardTypeName.producerOf(BLOCK_TYPE))
)
.addParameter("hardness", Float::class) .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("transparent", Boolean::class)
.addParameter("emittedLight", Byte::class) .addParameter("emittedLight", Byte::class)
.addParameter("filteredLight", Byte::class) .addParameter("filteredLight", Byte::class)
@ -83,10 +123,17 @@ class FilesGenerator(private val dataDir: File, private val outputDir: File) {
val collisionShapeValue = collisionShapeKeyByBlockName[name]!! val collisionShapeValue = collisionShapeKeyByBlockName[name]!!
builder.addEnumConstant( builder.addEnumConstant(
CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_UNDERSCORE, block.get("name").toString()), CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_UNDERSCORE, name),
TypeSpec.anonymousClassBuilder() TypeSpec.anonymousClassBuilder()
.addSuperclassConstructorParameter("%L", block.get("id").toInt()) .addSuperclassConstructorParameter("%L", block.get("id").toInt())
.addSuperclassConstructorParameter("%S", name) .addSuperclassConstructorParameter("%S", name)
.addSuperclassConstructorParameter(
"%T::class",
ClassName(
BLOCK_PACKAGE,
CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name)
)
)
.addSuperclassConstructorParameter("%Lf", block.get("hardness").toFloat()) .addSuperclassConstructorParameter("%Lf", block.get("hardness").toFloat())
.addSuperclassConstructorParameter("%L", block.get("transparent").toBoolean()) .addSuperclassConstructorParameter("%L", block.get("transparent").toBoolean())
.addSuperclassConstructorParameter("%L", block.get("emitLight").toInt().toByte()) .addSuperclassConstructorParameter("%L", block.get("emitLight").toInt().toByte())

View file

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