Archived
1
0
Fork 0

Add support for integer block attributes

This commit is contained in:
Moritz Ruth 2020-11-17 12:54:21 +01:00
parent 60457f28e4
commit 5ac41b46e4
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
4 changed files with 110 additions and 16 deletions

View file

@ -0,0 +1,17 @@
package space.blokk.util
import kotlin.reflect.KProperty
class ClampDelegate(val min: Int, val max: Int, var value: Int) {
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
if (value > max) throw IllegalArgumentException("${property.name} must be less than or equal to $max")
if (value < min) throw IllegalArgumentException("${property.name} must be greater than or equal to $min")
this.value = value
}
}
fun clamp(min: Int, max: Int, value: Int) = ClampDelegate(min, max, value)

View file

@ -1,11 +1,8 @@
package space.blokk.world.block
import space.blokk.util.KPropertyValuePair
import space.blokk.util.getValues
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.starProjectedType
import kotlin.reflect.jvm.jvmErasure
/**
@ -54,11 +51,16 @@ abstract class Block internal constructor(val ref: BlockRef) {
fun getStateID(block: T): Int {
val values = mutableMapOf<KProperty<*>, Any>()
return firstStateID + states.indexOfFirst { propertyValuePairs ->
val index = states.indexOfFirst { propertyValuePairs ->
propertyValuePairs.all {
values.getOrPut(it.property) { it.property.call(block)!! } == it.value
}
}
// If this happens, we did something wrong with adding the attributes or with clamping integer attributes
if (index == -1) throw Error("The state of block (${block.material}) has no ID")
return firstStateID + index
}
class Builder<T : Block> internal constructor(
@ -66,30 +68,51 @@ abstract class Block internal constructor(val ref: BlockRef) {
private val id: Int,
private val firstStateID: Int
) {
private val attributeFields = mutableListOf<KProperty<Any>>()
private val attributes = mutableListOf<BlockAttribute<*>>()
fun attribute(property: KProperty<Any>) = this.apply { attributeFields.add(property) }
@JvmName("booleanAttribute")
fun attribute(property: KProperty<Boolean>) = this.apply {
attributes.add(BlockAttribute.Boolean(property))
}
fun build() = Codec(blockClass, id, firstStateID, generateStates(attributeFields))
@JvmName("enumAttribute")
fun attribute(property: KProperty<Enum<*>>) = this.apply {
attributes.add(BlockAttribute.Enum(property))
}
@JvmName("intAttribute")
fun attribute(property: KProperty<Int>, max: Int) = this.apply {
attributes.add(BlockAttribute.Int(property, max))
}
fun build() = Codec(blockClass, id, firstStateID, generateStates(attributes))
sealed class BlockAttribute<T : Any> {
abstract val property: KProperty<T>
data class Boolean(override val property: KProperty<kotlin.Boolean>) : BlockAttribute<kotlin.Boolean>()
data class Int(override val property: KProperty<kotlin.Int>, val max: kotlin.Int) :
BlockAttribute<kotlin.Int>()
data class Enum(override val property: KProperty<kotlin.Enum<*>>) : BlockAttribute<kotlin.Enum<*>>()
}
companion object {
fun generateStates(properties: List<KProperty<Any>>): List<Array<KPropertyValuePair<*>>> {
fun generateStates(properties: List<BlockAttribute<*>>): List<Array<KPropertyValuePair<*>>> {
if (properties.isEmpty()) return emptyList()
val current = properties[0]
val type = current.returnType
@Suppress("UNCHECKED_CAST")
val allowedValues = when {
type == Boolean::class.starProjectedType -> arrayOf(true, false)
type.isSubtypeOf(Enum::class.starProjectedType) -> (type.jvmErasure as KClass<Enum<*>>).getValues()
else -> TODO()
val allowedValues: Array<out Any> = when (current) {
is BlockAttribute.Boolean -> arrayOf(true, false)
is BlockAttribute.Int -> (0..current.max).toList().toTypedArray()
is BlockAttribute.Enum -> current.property.returnType.jvmErasure.java.enumConstants
}
val rest = generateStates(properties.drop(1))
return allowedValues.flatMap { value ->
if (rest.isEmpty()) listOf(arrayOf(KPropertyValuePair(current, value)))
else rest.map { arrayOf(KPropertyValuePair(current, value), *it) }
if (rest.isEmpty()) listOf(arrayOf(KPropertyValuePair(current.property, value)))
else rest.map { arrayOf(KPropertyValuePair(current.property, value), *it) }
}
}
}

View file

@ -0,0 +1,21 @@
package space.blokk.world.block
import space.blokk.util.clamp
/**
* Material: [DAYLIGHT_DETECTOR][Material.DAYLIGHT_DETECTOR]
*/
class DaylightDetector(
ref: BlockRef
) : Block(ref) {
var inverted: Boolean = false
var power by clamp(0, 15, 0)
companion object : Block.Companion() {
override val codec = Codec
.id<DaylightDetector>(325, 6158)
.attribute(DaylightDetector::inverted)
.attribute(DaylightDetector::power, 15)
.build()
}
}

View file

@ -0,0 +1,33 @@
package space.blokk.world.block
import org.junit.jupiter.api.Test
import space.blokk.world.BlockLocation
import space.blokk.world.World
import strikt.api.expectThat
import strikt.assertions.isEqualTo
class BlockCodecTest {
val ref = object : BlockRef() {
override val location: BlockLocation get() = error("")
override val world: World get() = error("")
override var block: Block = DaylightDetector(this)
override suspend fun place(material: Material) = error("")
}
@Test
fun `getStateID returns 6158 for DaylightDetector(inverted=true, power=0)`() {
val daylightDetector = ref.block as DaylightDetector
daylightDetector.inverted = true
daylightDetector.power = 0
expectThat(DaylightDetector.codec.getStateID(daylightDetector)).isEqualTo(6158)
}
@Test
fun `getStateID returns 6189 for DaylightDetector(inverted=false, power=15)`() {
val daylightDetector = ref.block as DaylightDetector
daylightDetector.inverted = false
daylightDetector.power = 15
expectThat(DaylightDetector.codec.getStateID(daylightDetector)).isEqualTo(6189)
}
}