Skip to content

Commit

Permalink
Implement bedrock block state and block trait
Browse files Browse the repository at this point in the history
  • Loading branch information
yuxuanchiadm committed Feb 20, 2025
1 parent 94a0967 commit e923476
Show file tree
Hide file tree
Showing 19 changed files with 286 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package net.easecation.bedrockloader.bedrock.block.state

import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import java.lang.reflect.Type

sealed interface IBlockState {
class Deserializer : JsonDeserializer<IBlockState> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): IBlockState =
when {
json.isJsonObject -> context.deserialize(json, StateRange::class.java)
json.isJsonArray -> json.asJsonArray.let { array ->
when {
!array.isEmpty && array[0].isJsonPrimitive -> array[0].asJsonPrimitive.let { first ->
when {
first.isBoolean -> context.deserialize<StateBoolean>(json, StateBoolean::class.java)
first.isString -> context.deserialize<StateString>(json, StateString::class.java)
first.isNumber -> context.deserialize<StateInt>(json, StateInt::class.java)
else -> throw JsonParseException("Unexpected JSON type for IBlockState")
}
}
else -> throw JsonParseException("Unexpected JSON type for IBlockState")
}
}
else -> throw JsonParseException("Unexpected JSON type for IBlockState")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package net.easecation.bedrockloader.bedrock.block.state

class StateBoolean : IBlockState, ArrayList<Boolean>()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package net.easecation.bedrockloader.bedrock.block.state

class StateInt : IBlockState, ArrayList<Int>()
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.easecation.bedrockloader.bedrock.block.state

data class StateRange(val values: RangeValues) : IBlockState {
data class RangeValues(val min: Int, val max: Int)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package net.easecation.bedrockloader.bedrock.block.state

class StateString : IBlockState, ArrayList<String>()
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.easecation.bedrockloader.bedrock.block.traits

import com.google.gson.annotations.SerializedName

data class BlockTraits(
@SerializedName("minecraft:placement_direction") val minecraftPlacementDirection: TraitPlacementDirection?,
@SerializedName("minecraft:placement_position") val minecraftPlacementPosition: TraitPlacementPosition?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.easecation.bedrockloader.bedrock.block.traits

interface IBlockTrait {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package net.easecation.bedrockloader.bedrock.block.traits

import com.google.gson.annotations.SerializedName

data class TraitPlacementDirection(
val enabled_states: Set<State>,
val y_rotation_offset: Int
) : IBlockTrait {
enum class State {
@SerializedName("minecraft:cardinal_direction")
MINECRAFT_CARDINAL_DIRECTION,
@SerializedName("minecraft:facing_direction")
MINECRAFT_FACING_DIRECTION
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.easecation.bedrockloader.bedrock.block.traits

import com.google.gson.annotations.SerializedName

data class TraitPlacementPosition(
val enabled_states: Set<State>
) : IBlockTrait {
enum class State {
@SerializedName("minecraft:block_face")
MINECRAFT_BLOCK_FACE,
@SerializedName("minecraft:vertical_half")
MINECRAFT_VERTICAL_HALF
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package net.easecation.bedrockloader.bedrock.definition
import com.google.gson.JsonElement
import com.google.gson.annotations.SerializedName
import net.easecation.bedrockloader.bedrock.block.component.BlockComponents
import net.easecation.bedrockloader.bedrock.block.state.IBlockState
import net.easecation.bedrockloader.bedrock.block.traits.BlockTraits
import net.minecraft.util.Identifier

data class BlockBehaviourDefinition(
Expand All @@ -18,9 +20,10 @@ data class BlockBehaviourDefinition(
)

data class Description(
val identifier: Identifier,
val states: Map<String, List<Any>>,
val menu_category: MenuCategory
val identifier: Identifier,
val menu_category: MenuCategory?,
val states: Map<String, IBlockState>?,
val traits: BlockTraits?
)

data class MenuCategory(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package net.easecation.bedrockloader.block

import net.minecraft.state.property.Property
import java.util.*

data class BedrockBooleanProperty(
private val name: String,
private val values: Set<Boolean>
) : Property<Boolean>(name, Boolean::class.java) {
companion object {
fun of(name: String, values: Set<Boolean>): BedrockBooleanProperty {
return BedrockBooleanProperty(name, values)
}
}

override fun getValues(): Collection<Boolean> = values

override fun parse(name: String): Optional<Boolean> = Optional.ofNullable(values.find { it.toString() == name })

override fun name(value: Boolean): String = value.toString()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package net.easecation.bedrockloader.block

import net.minecraft.state.property.Property
import java.util.*

data class BedrockIntProperty(
private val name: String,
private val values: Set<Int>
) : Property<Int>(name, Int::class.java) {
companion object {
fun of(name: String, values: Set<Int>): BedrockIntProperty {
return BedrockIntProperty(name, values)
}
}

override fun getValues(): Collection<Int> = values

override fun parse(name: String): Optional<Int> = Optional.ofNullable(values.find { it.toString() == name })

override fun name(value: Int): String = value.toString()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package net.easecation.bedrockloader.block

import net.minecraft.state.property.Property
import java.util.*

data class BedrockStringProperty(
private val name: String,
private val values: Set<String>
) : Property<String>(name, String::class.java) {
companion object {
fun of(name: String, values: Set<String>): BedrockStringProperty {
return BedrockStringProperty(name, values)
}
}

override fun getValues(): Collection<String> = values

override fun parse(name: String): Optional<String> = Optional.ofNullable(values.find { it == name })

override fun name(value: String): String = value.toString()
}
139 changes: 118 additions & 21 deletions src/main/kotlin/net/easecation/bedrockloader/block/BlockDataDriven.kt
Original file line number Diff line number Diff line change
@@ -1,34 +1,85 @@
package net.easecation.bedrockloader.block

import net.easecation.bedrockloader.bedrock.block.component.BlockComponents
import net.easecation.bedrockloader.bedrock.block.component.ComponentCollisionBox
import net.easecation.bedrockloader.bedrock.block.state.StateBoolean
import net.easecation.bedrockloader.bedrock.block.state.StateInt
import net.easecation.bedrockloader.bedrock.block.state.StateRange
import net.easecation.bedrockloader.bedrock.block.state.StateString
import net.easecation.bedrockloader.bedrock.block.traits.TraitPlacementDirection
import net.easecation.bedrockloader.bedrock.block.traits.TraitPlacementPosition
import net.easecation.bedrockloader.bedrock.definition.BlockBehaviourDefinition
import net.minecraft.block.AbstractBlock.Settings
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.ShapeContext
import net.minecraft.block.enums.BlockHalf
import net.minecraft.item.ItemPlacementContext
import net.minecraft.state.StateManager
import net.minecraft.state.property.DirectionProperty
import net.minecraft.state.property.EnumProperty
import net.minecraft.state.property.Property
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.shape.VoxelShape
import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView

class BlockDataDriven private constructor(val identifier: Identifier, val components: BlockComponents, settings: Settings) : Block(settings) {

data class BlockContext(
val identifier: Identifier,
val behaviour: BlockBehaviourDefinition.BlockBehaviour,
val states: Map<String, Property<*>>
) {
companion object {
fun create(identifier: Identifier, components: BlockComponents): BlockDataDriven {
// minecraft:placement_direction
val MINECRAFT_CARDINAL_DIRECTION = DirectionProperty.of("minecraft_cardinal_direction", Direction.Type.HORIZONTAL)
val MINECRAFT_FACING_DIRECTION = DirectionProperty.of("minecraft_facing_direction", Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN)
// minecraft:placement_position
val MINECRAFT_BLOCK_FACE = DirectionProperty.of("minecraft_block_face", Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP, Direction.DOWN)
val MINECRAFT_VERTICAL_HALF = EnumProperty.of("minecraft_vertical_half", BlockHalf::class.java)

fun create(identifier: Identifier, behaviour: BlockBehaviourDefinition.BlockBehaviour): BlockDataDriven {
// 在这里进行逻辑计算
val settings = calculateSettings(components)
return BlockDataDriven(identifier, components, settings)
val settings = calculateSettings(behaviour)
val states = calculateStates(behaviour)
return BlockContext(identifier, behaviour, states).BlockDataDriven(settings)
}

private fun calculateStates(behaviour: BlockBehaviourDefinition.BlockBehaviour): Map<String, Property<*>> {
val placementDirection = behaviour.description.traits?.minecraftPlacementDirection?.enabled_states?.map { state ->
when (state) {
TraitPlacementDirection.State.MINECRAFT_CARDINAL_DIRECTION -> MINECRAFT_CARDINAL_DIRECTION
TraitPlacementDirection.State.MINECRAFT_FACING_DIRECTION -> MINECRAFT_FACING_DIRECTION
}
}?.associateBy { it.name } ?: emptyMap()
val placementPosition = behaviour.description.traits?.minecraftPlacementPosition?.enabled_states?.map { state ->
when (state) {
TraitPlacementPosition.State.MINECRAFT_BLOCK_FACE -> MINECRAFT_BLOCK_FACE
TraitPlacementPosition.State.MINECRAFT_VERTICAL_HALF -> MINECRAFT_VERTICAL_HALF
}
}?.associateBy { it.name } ?: emptyMap()
val states = behaviour.description.states?.map { (key, state) ->
val name = key.replace(':', '_').lowercase()
when (state) {
is StateBoolean -> BedrockBooleanProperty.of(name, state.toSet())
is StateInt -> BedrockIntProperty.of(name, state.toSet())
is StateString -> BedrockStringProperty.of(name, state.toSet())
is StateRange -> BedrockIntProperty.of(name, (state.values.min..state.values.max).toSet())
}
}?.associateBy { it.name } ?: emptyMap()
return placementDirection + placementPosition + states
}

private fun calculateSettings(components: BlockComponents): Settings {
private fun calculateSettings(behaviour: BlockBehaviourDefinition.BlockBehaviour): Settings {
val settings = Settings.create().hardness(4.0f).nonOpaque() // TODO hardness
components.minecraftCollisionBox?.let {
behaviour.components.minecraftCollisionBox?.let {
when (it) {
is ComponentCollisionBox.ComponentCollisionBoxBoolean -> {
if (!it.value) {
settings.noCollision()
}
}

is ComponentCollisionBox.ComponentCollisionBoxCustom -> {
if (it.size.all { e -> e == 0f }) {
settings.noCollision()
Expand All @@ -38,33 +89,79 @@ class BlockDataDriven private constructor(val identifier: Identifier, val compon

}
// TODO SelectionBox
components.minecraftLightEmission?.let {
behaviour.components.minecraftLightEmission?.let {
settings.luminance { _ -> it }
}
return settings
}
}

override fun getOutlineShape(state: BlockState?, view: BlockView?, pos: BlockPos?, context: ShapeContext?): VoxelShape {
if (components.minecraftCollisionBox != null) {
val it = components.minecraftCollisionBox
when (it) {
is ComponentCollisionBox.ComponentCollisionBoxBoolean -> {
return super.getOutlineShape(state, view, pos, context)
}
is ComponentCollisionBox.ComponentCollisionBoxCustom -> {
return VoxelShapes.cuboid(
inner class BlockDataDriven(settings: Settings) : Block(settings) {
init {
defaultState = stateManager.defaultState
.withIfExists(MINECRAFT_CARDINAL_DIRECTION, Direction.SOUTH)
.withIfExists(MINECRAFT_FACING_DIRECTION, Direction.DOWN)
.withIfExists(MINECRAFT_BLOCK_FACE, Direction.DOWN)
.withIfExists(MINECRAFT_VERTICAL_HALF, BlockHalf.BOTTOM)
}

private fun rotateDirection(direction: Direction, yRotationOffset: Int): Direction {
if (direction.axis.isVertical) return direction
val offset = (yRotationOffset / 90 % 4).let { if (it < 0) it + 4 else it }
return when (offset) {
1 -> direction.rotateYClockwise()
2 -> direction.opposite
3 -> direction.rotateYCounterclockwise()
else -> direction
}
}
override fun getPlacementState(ctx: ItemPlacementContext): BlockState {
val yRotationOffset = behaviour.description.traits?.minecraftPlacementDirection?.y_rotation_offset ?: 0
return defaultState
.withIfExists(MINECRAFT_CARDINAL_DIRECTION, rotateDirection(ctx.horizontalPlayerFacing, yRotationOffset))
.withIfExists(MINECRAFT_FACING_DIRECTION, rotateDirection(ctx.playerLookDirection, yRotationOffset))
.withIfExists(MINECRAFT_BLOCK_FACE, ctx.side)
.withIfExists(
MINECRAFT_VERTICAL_HALF, when {
ctx.side == Direction.DOWN -> BlockHalf.TOP
ctx.side == Direction.UP -> BlockHalf.BOTTOM
ctx.hitPos.y - ctx.blockPos.y > 0.5 -> BlockHalf.TOP
else -> BlockHalf.BOTTOM
}
)
}

override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
states.values.forEach { builder.add(it) }
}

override fun getOutlineShape(
state: BlockState?,
view: BlockView?,
pos: BlockPos?,
context: ShapeContext?
): VoxelShape {
if (behaviour.components.minecraftCollisionBox != null) {
val it = behaviour.components.minecraftCollisionBox
when (it) {
is ComponentCollisionBox.ComponentCollisionBoxBoolean -> {
return super.getOutlineShape(state, view, pos, context)
}

is ComponentCollisionBox.ComponentCollisionBoxCustom -> {
return VoxelShapes.cuboid(
(it.origin[0].toDouble() + 8) / 16,
it.origin[1].toDouble() / 16,
(it.origin[2].toDouble() + 8) / 16,
(it.origin[0].toDouble() + 8) / 16 + it.size[0].toDouble() / 16,
it.origin[1].toDouble() / 16 + it.size[1].toDouble() / 16,
(it.origin[2].toDouble() + 8) / 16 + it.size[2].toDouble() / 16
)
)
}
}
} else {
return super.getOutlineShape(state, view, pos, context)
}
} else {
return super.getOutlineShape(state, view, pos, context)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,13 @@ object BedrockAddonsLoader {

// load resource pack
BedrockLoader.logger.info("Loading resource pack...")
BedrockResourcePackLoader(BedrockLoader.getTmpResourceDir(), context).load()
val resourcePackLoader = BedrockResourcePackLoader(BedrockLoader.getTmpResourceDir(), context)
resourcePackLoader.load()

// load behaviour pack
BedrockLoader.logger.info("Loading behaviour pack...")
val loader = BedrockBehaviorPackLoader(context)
loader.load()
val behaviorPackLoader = BedrockBehaviorPackLoader(context)
behaviorPackLoader.load()

BedrockLoader.logger.info("Loading pack finished! ${BedrockAddonsRegistry.blocks.size} blocks, ${BedrockAddonsRegistry.items.size} items, ${BedrockAddonsRegistry.entities.size} entities")
}
Expand Down
Loading

0 comments on commit e923476

Please sign in to comment.