Generate block stub classes
This commit is contained in:
parent
50710d6240
commit
edc93f7a80
11 changed files with 178 additions and 73 deletions
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,33 @@
|
|||
package space.blokk.world.block
|
||||
|
||||
import space.blokk.world.BlockLocation
|
||||
import space.blokk.world.Chunk
|
||||
import space.blokk.world.World
|
||||
/**
|
||||
* @param ref The [BlockRef] referencing this block.
|
||||
*/
|
||||
abstract class Block internal constructor(val ref: BlockRef) {
|
||||
/**
|
||||
* The [Material] of this block.
|
||||
*/
|
||||
abstract val material: Material
|
||||
|
||||
/**
|
||||
* Represents a block in a chunk of a world.
|
||||
*
|
||||
* There is always only one instance for every [location][BlockLocation] in a world.
|
||||
* Whether [ref] still references this block.
|
||||
*/
|
||||
interface Block {
|
||||
/**
|
||||
* The [location][BlockLocation] of this block.
|
||||
*/
|
||||
val location: BlockLocation
|
||||
val destroyed: Boolean get() = ref.block === this
|
||||
|
||||
/**
|
||||
* The [World] containing this block.
|
||||
* Replaces this block with air.
|
||||
*/
|
||||
val world: World
|
||||
|
||||
/**
|
||||
* The [Chunk] containing this block.
|
||||
*/
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
package space.blokk.world.block
|
||||
|
||||
interface BlockContent
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
package space.blokk.world.block
|
||||
|
||||
interface Stone : BlockContent {
|
||||
}
|
|
@ -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())
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue