Add support for integer block attributes
This commit is contained in:
parent
60457f28e4
commit
5ac41b46e4
4 changed files with 110 additions and 16 deletions
|
@ -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)
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
Reference in a new issue