Compare commits

..
Author SHA1 Message Date
whatsy 2c10a4bd0d merge + small fixes 2026-11-02 17:15:29 -05:00
whatsy 7bed04c42e Merge branch 'bigger-brains' into scarekrow
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/biggerbrain/sense/Sense.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/sense/SenseConfig.kt
2026-11-02 16:46:04 -05:00
whatsy 6c71490c8c Basic scarekrow implementation 2026-10-01 17:00:40 -04:00
22 changed files with 618 additions and 12 deletions

No files matched your search

@@ -47,6 +47,7 @@ import com.cobblemon.mod.common.block.RevivalHerbBlock
import com.cobblemon.mod.common.block.SaccharineLeafBlock
import com.cobblemon.mod.common.block.SaccharineLogBlock
import com.cobblemon.mod.common.block.SaccharineLogSlatheredBlock
import com.cobblemon.mod.common.block.ScarekrowBlock
import com.cobblemon.mod.common.block.StackableItemBlock
import com.cobblemon.mod.common.block.TumblestoneBlock
import com.cobblemon.mod.common.block.TypeGemClusterBlock
@@ -1160,6 +1161,15 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
}
))
@JvmField
val SCAREKROW = this.create("scarekrow", ScarekrowBlock(
BlockBehaviour.Properties.of()
.pushReaction(PushReaction.DESTROY)
.sound(SoundType.GRASS)
.strength(0.1f)
.noOcclusion()
))
init {
/**
* Makes all blocks in array flammable by adding them to FireBlock's flammableRegistry.
@@ -205,6 +205,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val CAMPFIRE_POT_WHITE = campfirePotItem(CobblemonBlocks.WHITE_CAMPFIRE_POT, CampfirePotColor.WHITE)
@JvmField
val SCAREKROW = blockItem("scarekrow", CobblemonBlocks.SCAREKROW)
@JvmField
val HEARTY_GRAINS = compostableItem("hearty_grains", ItemNameBlockItem(CobblemonBlocks.HEARTY_GRAINS, Properties().rarity(Rarity.COMMON)))
@JvmField
@@ -114,6 +114,8 @@ object CobblemonMemories {
val DISABLE_WALK_TO_WANTED_ITEM = register<Boolean>("disable_walk_to_wanted_item")
@JvmField
val TIME_TRYING_TO_REACH_WANTED_ITEM = register<Int>("time_trying_to_wanted_item")
@JvmField
val NEARBY_SCAREKROW = register<BlockPos>("nearby_scarekrow", BlockPos.CODEC)
@JvmStatic
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
@@ -0,0 +1,167 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.objective
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asSimplifiedExpression
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.ConditionalLockFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.ai.util.LandRandomPos
class AvoidScarekrow(
private val speedMultiplier: Expression = "0.5".asExpression(),
) : Objective {
private var movementTask: EntityFixation? = null
private var remainingCooldownTicks = 0
override fun getDescription(entity: LivingEntity) = "Avoiding target block"
override fun perform(
entity: LivingEntity,
brain: BiggerBrain,
activeObjectiveTags: MutableSet<String>
): ObjectiveStatus {
if (entity !is PokemonEntity || !entity.isAlive) return ObjectiveStatus.INVALID
val target = brain.getMemory(CobblemonMemories.NEARBY_SCAREKROW)
?: return ObjectiveStatus.INACTIVE
// if the scarekrow is gone we dont need to be scared anymore
if (!entity.level().getBlockState(target).`is`(CobblemonBlockTags.SCAREKROWS)) {
brain.eraseMemory(CobblemonMemories.NEARBY_SCAREKROW)
return ObjectiveStatus.INACTIVE
}
// finish existing movement task
if (brain.isFixation(movementTask)) {
return ObjectiveStatus.FIXATED
}
// aggro against the player should take priority
if (brain.getMemory(MemoryModuleType.ATTACK_TARGET) != null) {
return ObjectiveStatus.BLOCKED
}
if (remainingCooldownTicks > 0) {
remainingCooldownTicks--
}
val runtime = mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val speed = runtime.resolveFloat(speedMultiplier)
if (!brain.distract(Distraction.FORCE)) return ObjectiveStatus.BLOCKED
var targetPos : BlockPos? = null
// fixate on running from the scarekrow until it is out of range
brain.fixate(ConditionalLockFixation("Avoiding scarekrow") {
brain.getMemory(CobblemonMemories.NEARBY_SCAREKROW) != null
})
// find position
val direction = entity.position().subtract(target.toVec3d())
for (j in 0..9) {
val targetVec3 = LandRandomPos.getPosAway(entity, (Cobblemon.config.scarekrowRadius * 1.2).toInt(), 7, direction)
if (targetVec3 != null) {
targetPos = BlockPos.containing(targetVec3)
break
}
}
// Start a look-and-move fixation only when navigation accepts the selected position.
if (targetPos != null) {
val movementTracker = RegularMovementTracker.attempt(
entity = entity,
walkTarget = WalkTarget(
targetPos,
speed,
0
)
)
if (movementTracker != null) {
movementTask = brain.fixate(
LookAndMoveFixation(
distractionTypes = setOf(
DistractionType.FORCE
),
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(
entity,
movementTracker.walkTarget
),
movementTracker = movementTracker,
description = "Moving away from target block",
)
)
return ObjectiveStatus.FIXATED
}
}
return ObjectiveStatus.FIXATED
}
override fun onRegister(entity: LivingEntity, brain: BiggerBrain) {
brain.subscribe(
priority = Priority.LOW,
memory = CobblemonMemories.NEARBY_SCAREKROW,
) { value, labels ->
if (value == null) {
if (brain.isFixation(movementTask)) {
brain.distract(Distraction.FORCE)
movementTask = null
}
}
return@subscribe true
}
}
class Config : ObjectiveConfig {
val speedMultiplier: ExpressionOrEntityVariable = Either.left("0.5".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf(speedMultiplier).asVariables()
}
override fun create(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): Objective {
return AvoidScarekrow(
speedMultiplier = speedMultiplier.asSimplifiedExpression(entity),
)
}
}
}
@@ -300,7 +300,7 @@ class DefendNest(
description = "patrol method in DefendNest objective",
lookingTracker = LookingTracker.fromNavigationPathOrWalkTarget(entity, tracker.walkTarget),
movementTracker = tracker,
onMovementComplete = { _, _ -> patrolCooldownTicks = PATROL_PAUSE_TICKS + entity.random.nextInt(PATROL_PAUSE_RANDOM_TICKS) }
onMovementComplete = { entity1, brain1, status -> patrolCooldownTicks = PATROL_PAUSE_TICKS + entity.random.nextInt(PATROL_PAUSE_RANDOM_TICKS) }
)
)
return ObjectiveStatus.FIXATED
@@ -33,7 +33,6 @@ import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities
import net.minecraft.world.entity.ai.memory.WalkTarget
@@ -156,7 +155,7 @@ class FriendlyBehaviour(
),
lookingTracker = LookingTracker.forTargetEntity(entity, trackedEntity!!),
movementTracker = movementTracker,
onMovementComplete = { _, _ -> attemptFixation(entity, brain) } // maintains fixation between moving and looking
onMovementComplete = { entity1, brain1, status -> attemptFixation(entity, brain) } // maintains fixation between moving and looking
)
)
return ObjectiveStatus.FIXATED
@@ -14,21 +14,15 @@ import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.biggerbrain.LookingTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.MovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.RegularMovementTracker
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.Distraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.DistractionType
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.comfortDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.distraction.idleBoredomDistraction
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.EntityFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.LookAndMoveFixation
import com.cobblemon.mod.common.api.ai.biggerbrain.fixation.MovementTrackerFixation
import com.cobblemon.mod.common.api.ai.config.objective.ObjectiveConfig
import com.cobblemon.mod.common.api.ai.resolveFloat
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
import com.cobblemon.mod.common.util.asExpression
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
@@ -92,7 +86,7 @@ class WalkTowardsParent(
distractionTypes = setOf(DistractionType.DAMAGE, DistractionType.FORCE), // We are very fine to ignore this movement if forced or hurt
lookingTracker = LookingTracker.forTargetEntity(entity, adult),
movementTracker = movementTracker,
onMovementComplete = { _, _ -> cooldownTicks = 60 }
onMovementComplete = { entity1, brain1, status -> cooldownTicks = 60 }
)
)
return ObjectiveStatus.FIXATED // We are now fixated on
@@ -0,0 +1,105 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.biggerbrain.sense
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.biggerbrain.BiggerBrain
import com.cobblemon.mod.common.api.ai.config.sense.SenseConfig
import com.cobblemon.mod.common.api.ai.resolveInt
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.distanceTo
import com.cobblemon.mod.common.util.getMemorySafely
import com.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
import net.minecraft.core.Vec3i
import net.minecraft.world.entity.LivingEntity
class ScarekrowSense(
private var cooldownTicks: Int = SCAN_INTERVAL
) : Sense {
override var ticksUntilNextExecution: Int = cooldownTicks
override val memories = setOf(
CobblemonMemories.NEARBY_SCAREKROW,
)
override fun merge(other: Sense): Boolean {
if (other is ScarekrowSense) {
this.cooldownTicks = minOf(this.cooldownTicks, other.cooldownTicks)
return true
}
return false
}
override fun tick(entity: LivingEntity, brain: BiggerBrain) {
ticksUntilNextExecution = cooldownTicks
val brain = entity.brain
val searchRadius = Cobblemon.config.scarekrowRadius
val currPos: BlockPos? = brain.getMemorySafely(CobblemonMemories.NEARBY_SCAREKROW).orElse(null)
if (currPos != null && entity.level().getBlockState(currPos).`is`(CobblemonBlockTags.SCAREKROWS)) {
val distance = entity.distanceTo(currPos)
if (distance > searchRadius) {
brain.eraseMemory(CobblemonMemories.NEARBY_SCAREKROW)
}
}
val centerPos = entity.blockPosition()
var scarekrowPos: BlockPos? = null
var shortestDist = Double.MAX_VALUE
val world = entity.level()
BlockPos.betweenClosedStream(
centerPos.offset(-searchRadius, -searchRadius, -searchRadius),
centerPos.offset(searchRadius, searchRadius, searchRadius)
).forEach { pos ->
val state = world.getBlockState(pos)
if (state.`is`(CobblemonBlockTags.SCAREKROWS)) {
val distance = pos.distSqr(Vec3i(centerPos.x, centerPos.y, centerPos.z))
if (distance < shortestDist) {
scarekrowPos = BlockPos(pos)
shortestDist = distance
}
}
}
if (scarekrowPos != null) {
brain.setMemory(CobblemonMemories.NEARBY_SCAREKROW, scarekrowPos)
} else {
brain.eraseMemory(CobblemonMemories.NEARBY_SCAREKROW)
}
}
class Config : SenseConfig {
var condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
var cooldownTicks: ExpressionOrEntityVariable = Either.left("3".asExpression())
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
) = listOf(cooldownTicks).asVariables()
override fun create(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): Sense {
val cooldownTicks = cooldownTicks.resolveInt(behaviourConfigurationContext.runtime)
return ScarekrowSense(
cooldownTicks = cooldownTicks
)
}
}
companion object {
const val SCAN_INTERVAL = 3
}
}
@@ -40,6 +40,7 @@ import com.cobblemon.mod.common.api.ai.biggerbrain.objective.WanderAround
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.JumpOutOfWater
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.NeutralBehaviour
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.PointToSpawn
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.AvoidScarekrow
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.SkittishBehaviour
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.TerritorialBehaviour
import com.cobblemon.mod.common.api.ai.biggerbrain.objective.WalkTowardsParent
@@ -86,7 +87,7 @@ interface ObjectiveConfig {
cobblemonResource("combat_neutral") to NeutralBehaviour.Config::class.java,
cobblemonResource("combat_skittish") to SkittishBehaviour.Config::class.java,
cobblemonResource("behaviour_friendly") to FriendlyBehaviour.Config::class.java,
cobblemonResource("avoid_scarekrow") to AvoidScarekrow.Config::class.java,
)
}
@@ -25,6 +25,7 @@ import com.cobblemon.mod.common.api.ai.biggerbrain.sense.NearbyEntitiesSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.PokemonAdultSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.PokemonItemSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.SaccLeafSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.ScarekrowSense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.Sense
import com.cobblemon.mod.common.api.ai.biggerbrain.sense.SweetBerryBushSense
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
@@ -57,6 +58,7 @@ interface SenseConfig {
cobblemonResource("flower_sense") to FlowerSense.Config::class.java,
cobblemonResource("beehive_sense") to BeeHiveSense.Config::class.java,
cobblemonResource("sacc_leaf_sense") to SaccLeafSense.Config::class.java,
cobblemonResource("scarekrow_sense") to ScarekrowSense.Config::class.java,
)
}
@@ -79,6 +79,7 @@ object CobblemonBlockTags {
@JvmField val SEES_SKY = createTag("sees_sky")
@JvmField val SHINY_STONE_ORES = createTag("shiny_stone_ores")
@JvmField val SIGNS = createTag("signs")
@JvmField val SCAREKROWS = createTag("scarekrows")
@JvmField val SKY_TUMBLESTONE_BRICKS = createTag("sky_tumblestone_bricks")
@JvmField val SKY_TUMBLESTONES = createTag("sky_tumblestones")
@JvmField val SMALL_FLOWERS = createTag("small_flowers")
@@ -0,0 +1,151 @@
package com.cobblemon.mod.common.block
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.util.StringRepresentable
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.LevelEvent
import net.minecraft.world.level.block.Mirror
import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.level.storage.loot.LootParams
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.pathfinder.PathComputationType
/**
* Based off PCBlock's double block functionality, but for less specific use cases
*
* @author whatsy
* @since September 30, 2026
* @see PCBlock
*/
open class DoubleBlock(properties: Properties): Block(properties) {
companion object {
val HALF: Property<Half> = EnumProperty.create("half", Half::class.java)
}
enum class Half(private val label: String): StringRepresentable {
TOP("top"),
BOTTOM("bottom"), ;
override fun getSerializedName(): String = label
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HALF, Half.BOTTOM)
.setValue(HorizontalDirectionalBlock.FACING, Direction.NORTH)
)
}
fun getPositionOfOtherPart(state: BlockState, pos: BlockPos): BlockPos {
return if(state.getValue(HALF) == Half.BOTTOM) {
pos.above()
} else {
pos.below()
}
}
fun getBasePosition(state: BlockState, pos: BlockPos): BlockPos {
return if (isBase(state)) {
pos
} else {
pos.below()
}
}
private fun isBase(state: BlockState): Boolean = state.getValue(HALF) == Half.BOTTOM
override fun setPlacedBy(level: Level, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
level.setBlock(pos.above(),state.setValue(HALF, Half.TOP), 3)
level.blockUpdated(pos, Blocks.AIR)
state.updateNeighbourShapes(level, pos, 3)
}
override fun getDrops(state: BlockState, params: LootParams.Builder): List<ItemStack> {
return if (state.getValue(HALF) == Half.TOP) emptyList() else super.getDrops(state, params)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val abovePosition = context.clickedPos.above()
val world = context.level
if (world.getBlockState(abovePosition).canBeReplaced(context) && !world.isOutsideBuildHeight(abovePosition)) {
return defaultBlockState()
.setValue(HorizontalDirectionalBlock.FACING, context.horizontalDirection)
.setValue(HALF, Half.BOTTOM)
}
return null
}
override fun playerWillDestroy(world: Level, pos: BlockPos, state: BlockState, player: Player): BlockState {
if (!world.isClientSide && player.isCreative) {
var blockPos: BlockPos = BlockPos.ZERO
var blockState: BlockState = state
val part = state.getValue(HALF)
if (part == Half.TOP && world.getBlockState(pos.below().also { blockPos = it }).also { blockState = it }.`is`(state.block) && blockState.getValue(HALF) == Half.BOTTOM) {
val blockState2 = if (blockState.fluidState.`is`(Fluids.WATER)) Blocks.WATER.defaultBlockState() else Blocks.AIR.defaultBlockState()
world.setBlock(blockPos, blockState2, UPDATE_ALL or UPDATE_SUPPRESS_DROPS)
world.levelEvent(player, LevelEvent.PARTICLES_DESTROY_BLOCK, blockPos, getId(blockState))
}
}
return super.playerWillDestroy(world, pos, state, player)
}
@Deprecated("Deprecated in Java")
override fun isPathfindable(
blockState: BlockState,
pathComputationType: PathComputationType
): Boolean = false
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
builder.add(HorizontalDirectionalBlock.FACING)
builder.add(HALF)
}
@Deprecated("Deprecated in Java")
override fun rotate(blockState: BlockState, rotation: Rotation) =
blockState.setValue(
HorizontalDirectionalBlock.FACING, rotation.rotate(blockState.getValue(
HorizontalDirectionalBlock.FACING)))
@Deprecated("Deprecated in Java")
override fun mirror(blockState: BlockState, mirror: Mirror): BlockState {
return blockState.rotate(mirror.getRotation(blockState.getValue(HorizontalDirectionalBlock.FACING)))
}
override fun onRemove(state: BlockState, world: Level, pos: BlockPos, newState: BlockState, moved: Boolean) {
if (!state.`is`(newState.block)) super.onRemove(state, world, pos, newState, moved)
}
override fun updateShape(
state: BlockState,
direction: Direction,
neighborState: BlockState,
world: LevelAccessor,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
val isThis = neighborState.`is`(this)
val part = state.getValue(HALF)
if (!isThis && part == Half.TOP && neighborPos == pos.below()) {
return Blocks.AIR.defaultBlockState()
} else if (!isThis && part == Half.BOTTOM && neighborPos == pos.above()) {
return Blocks.AIR.defaultBlockState()
}
return state
}
}
@@ -0,0 +1,75 @@
package com.cobblemon.mod.common.block
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.StringRepresentable
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.level.pathfinder.PathComputationType
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
class ScarekrowBlock(properties: Properties): DoubleBlock(properties) {
companion object {
val POSE = EnumProperty.create("pose", Pose::class.java)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HALF, Half.BOTTOM)
.setValue(POSE, Pose.DEFAULT)
.setValue(HorizontalDirectionalBlock.FACING, Direction.NORTH)
)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
builder.add(POSE)
super.createBlockStateDefinition(builder)
}
@Deprecated("Deprecated in Java")
override fun isPathfindable(
blockState: BlockState,
pathComputationType: PathComputationType
): Boolean = false
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult {
if (player !is ServerPlayer) return InteractionResult.SUCCESS
level.setBlock(pos, state.cycle(POSE), 3)
return InteractionResult.SUCCESS
}
enum class Pose(private val label: String): StringRepresentable {
DEFAULT("default"),
RELAXED("relaxed"),
SCARY("scary");
override fun getSerializedName(): String = label
}
override fun getShape(
state: BlockState,
level: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape {
return super.getShape(state, level, pos, context)
}
}
@@ -441,6 +441,9 @@ class CobblemonConfig {
@CobblemonConfigField(Category.Spawning, lang = "data_monitor_shiny_rate", SERVER)
var monitorShinyRate = 200
@CobblemonConfigField(Category.Pokemon, lang = "scarekrow_radius", SERVER)
var scarekrowRadius = 16
@Transient
val struct = ObjectValue(this).also {
it.addFunction("last_saved_version") { StringValue(lastSavedVersion) }
@@ -364,7 +364,7 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.SACCHARINE_HANGING_SIGN)
entries.accept(CobblemonItems.SACCHARINE_LEAVES)
//entries.accept(CobblemonItems.SACCHARINE_SAPLING)
entries.accept(CobblemonItems.SCAREKROW)
entries.accept(CobblemonItems.HEARTY_GRAIN_BALE)
entries.accept(CobblemonItems.TATAMI_BLOCK)
@@ -0,0 +1,34 @@
{
"variants": {
"facing=south,half=top": {
"model": "cobblemon:block/pc_top"
},
"facing=west,half=top": {
"model": "cobblemon:block/pc_top",
"y": 90
},
"facing=north,half=top": {
"model": "cobblemon:block/pc_top",
"y": 180
},
"facing=east,half=top": {
"model": "cobblemon:block/pc_top",
"y": 270
},
"facing=south,half=bottom": {
"model": "cobblemon:block/pc_bottom"
},
"facing=west,half=bottom": {
"model": "cobblemon:block/pc_bottom",
"y": 90
},
"facing=north,half=bottom": {
"model": "cobblemon:block/pc_bottom",
"y": 180
},
"facing=east,half=bottom": {
"model": "cobblemon:block/pc_bottom",
"y": 270
}
}
}
@@ -0,0 +1,6 @@
{
"parent": "block/cube_all",
"textures": {
"all": "cobblemon:block/evolution/moon_stone_ore"
}
}
@@ -0,0 +1,6 @@
{
"parent": "block/cube_all",
"textures": {
"all": "cobblemon:block/evolution/moon_stone_ore"
}
}
@@ -0,0 +1,6 @@
{
"parent": "block/cube_all",
"textures": {
"all": "cobblemon:block/evolution/moon_stone_ore"
}
}
@@ -71,6 +71,22 @@
"senses": [
"cobblemon:pokemon_nearby_wanted_item"
]
},
{
"type": "cobblemon:add_senses",
"condition": "!q.entity.is_in_party && !q.entity.is_alpha",
"senses": [
"cobblemon:scarekrow_sense"
]
},
{
"type": "cobblemon:add_objectives",
"condition": "!q.entity.is_in_party && !q.entity.is_alpha",
"objectivesByPriority": {
"0": [
"cobblemon:avoid_scarekrow"
]
}
}
]
}
@@ -0,0 +1,20 @@
{
"type": "minecraft:block",
"pools": [
{
"rolls": 1.0,
"bonus_rolls": 0.0,
"entries": [
{
"type": "minecraft:item",
"name": "cobblemon:scarekrow"
}
],
"conditions": [
{
"condition": "minecraft:survives_explosion"
}
]
}
]
}
@@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"cobblemon:scarekrow"
]
}