Compare commits

...
15 changed files with 354 additions and 6 deletions
@@ -20,6 +20,8 @@ object CobblemonActivities {
val POKEMON_SLEEP_ON_TRAINER_BED = register(Activity("pokemon_sleep_on_trainer_bed"))
val POKEMON_HERD = register(Activity("pokemon_herd"))
val POKEMON_POLLINATION = register(Activity("pokemon_pollination"))
val POKEMON_STEAL_HONEY = register(Activity("pokemon_steal_honey"))
fun register(activity: Activity): Activity {
activities.add(activity)
@@ -54,6 +54,8 @@ object CobblemonMemories {
val HERD_SIZE = register<Int>("herd_size") // Don't bother saving it, we'll try to keep count roughly.
val ATTACK_TARGET_DATA = register<CobblemonAttackTargetData>("attack_target_data") // This is used to store additional information about the attack target, such as when to give up pursuit.
val WANDER_CONTROL = register<CobblemonWanderControl>("wander_control")
val HAS_HONEY = register<Boolean>("has_honey")
val SACCHARINE_LOG_LOCATION = register<BlockPos>("saccharine_log_location")
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
val memoryModule = MemoryModuleType(Optional.of(codec))
@@ -37,6 +37,8 @@ object CobblemonSensors {
val NEARBY_FLOWER = register("nearby_flower", ::FlowerSensor)
val NEARBY_SACCHARINE_LOG = register("nearby_saccharine_log", ::SaccharineLogSensor)
fun <E : Entity, U : Sensor<E>> register(id: String, supplier: Supplier<U>): SensorType<U> {
val sensor = SensorType(supplier)
sensors[id] = sensor
@@ -0,0 +1,29 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToSaccharineLogTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class PathToSaccharineLogTaskConfig : SingleTaskConfig {
companion object { const val SLATHER = "slather" }
private val condition = booleanVariable(SLATHER, "can_slather_honey", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(condition).asVariables()
override fun createTask(entity: LivingEntity, ctx: BehaviourConfigurationContext): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) return null
if (!checkCondition(entity, condition)) return null
ctx.addMemories(
CobblemonMemories.SACCHARINE_LOG_LOCATION,
CobblemonMemories.HAS_HONEY
)
ctx.addSensors(CobblemonSensors.NEARBY_SACCHARINE_LOG)
return PathToSaccharineLogTask.create()
}
}
@@ -0,0 +1,21 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.SlatherHoneyOnLogTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class SlatherHoneyOnLogTaskConfig : SingleTaskConfig {
companion object { const val SLATHER = "slather" }
private val condition = booleanVariable(SLATHER, "can_slather_honey", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(condition).asVariables()
override fun createTask(entity: LivingEntity, ctx: BehaviourConfigurationContext): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) return null
if (!checkCondition(entity, condition)) return null
return SlatherHoneyOnLogTask.create()
}
}
@@ -0,0 +1,21 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.StealHoneyFromHiveTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class StealHoneyFromHiveTaskConfig : SingleTaskConfig {
companion object { const val STEAL = "steal" }
private val condition = booleanVariable(STEAL, "can_steal_honey", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(condition).asVariables()
override fun createTask(entity: LivingEntity, ctx: BehaviourConfigurationContext): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) return null
if (!checkCondition(entity, condition)) return null
return StealHoneyFromHiveTask.create()
}
}
@@ -117,7 +117,10 @@ interface TaskConfig {
cobblemonResource("target_entity") to TargetEntityTaskConfig::class.java,
cobblemonResource("memory_aspect") to MemoryAspectTaskConfig::class.java,
cobblemonResource("activity_change") to ActivityChangeTaskConfig::class.java,
)
cobblemonResource("steal_honey_from_hive") to StealHoneyFromHiveTaskConfig::class.java,
cobblemonResource("path_to_saccharine_log") to PathToSaccharineLogTaskConfig::class.java,
cobblemonResource("slather_honey_on_log") to SlatherHoneyOnLogTaskConfig::class.java,
)
val runtime = MoLangRuntime().setup()
}
@@ -0,0 +1,77 @@
package com.cobblemon.mod.common.entity.pokemon.ai.sensors
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.ai.sensing.Sensor
import net.minecraft.world.level.block.RotatedPillarBlock
class SaccharineLogSensor : Sensor<PokemonEntity>(200) {
override fun requires() = setOf(CobblemonMemories.SACCHARINE_LOG_LOCATION, CobblemonMemories.HAS_HONEY)
override fun doTick(world: ServerLevel, entity: PokemonEntity) {
val brain = entity.brain
// Only search if we're carrying honey
if (!brain.hasMemoryValue(CobblemonMemories.HAS_HONEY)) {
brain.eraseMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION)
return
}
val current = brain.getMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION).orElse(null)
if (current != null && isValidLog(world, entity, current)) return
if (current != null) brain.eraseMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION)
val searchRadius = 16
val center = entity.blockPosition()
var best: BlockPos? = null
var bestDist = Double.MAX_VALUE
BlockPos.betweenClosedStream(
center.offset(-searchRadius, -2, -searchRadius),
center.offset(searchRadius, 2, searchRadius)
).forEach { pos ->
val state = world.getBlockState(pos)
if (state.block == CobblemonBlocks.SACCHARINE_LOG && // plain log only
state.getValue(RotatedPillarBlock.AXIS) == Direction.Axis.Y &&
hasReachableFace(world, entity, pos)
) {
val d = pos.distToCenterSqr(center.x + 0.5, center.y + 0.5, center.z + 0.5)
if (d < bestDist) {
bestDist = d
best = pos.immutable()
}
}
}
if (best != null) brain.setMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION, best)
}
private fun isValidLog(world: ServerLevel, entity: PokemonEntity, pos: BlockPos): Boolean {
val state = world.getBlockState(pos)
return state.block == CobblemonBlocks.SACCHARINE_LOG &&
state.getValue(RotatedPillarBlock.AXIS) == Direction.Axis.Y &&
hasReachableFace(world, entity, pos)
}
private fun hasReachableFace(world: ServerLevel, entity: PokemonEntity, pos: BlockPos): Boolean {
for (dir in Direction.Plane.HORIZONTAL) {
val at = pos.relative(dir)
if (world.isEmptyBlock(at)) {
val path = entity.navigation.createPath(at, 0)
if (path != null && path.canReach()) return true
}
}
for (dir in arrayOf(Direction.UP, Direction.DOWN)) {
val at = pos.relative(dir)
if (world.isEmptyBlock(at)) {
val path = entity.navigation.createPath(at, 0)
if (path != null && path.canReach()) return true
}
}
return false
}
}
@@ -27,15 +27,23 @@ object PathToBeeHiveTask {
it.group(
it.registered(MemoryModuleType.LOOK_TARGET),
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.POLLINATED),
it.registered(CobblemonMemories.POLLINATED),
it.registered(CobblemonMemories.HAS_HONEY),
it.present(CobblemonMemories.HIVE_LOCATION),
it.absent(CobblemonMemories.HIVE_COOLDOWN)
).apply(it) { lookTarget, walkTarget, pollinated, hiveMemory, hiveCooldown ->
).apply(it) { lookTarget, walkTarget, pollinated, hasHoney, hiveMemory, hiveCooldown ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) {
return@Trigger false
}
val brain = entity.brain
val hasPollinated = brain.hasMemoryValue(CobblemonMemories.POLLINATED)
val hasHoneyMem = brain.hasMemoryValue(CobblemonMemories.HAS_HONEY)
if (!hasPollinated && !hasHoneyMem) {
return@Trigger false
}
val hiveLocation = it.get(hiveMemory)
val sidesByClosest = listOf(
hiveLocation.north(),
@@ -0,0 +1,36 @@
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
object PathToSaccharineLogTask {
fun create(): OneShot<in LivingEntity> = BehaviorBuilder.create {
it.group(
it.registered(MemoryModuleType.LOOK_TARGET),
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.SACCHARINE_LOG_LOCATION),
it.present(CobblemonMemories.HAS_HONEY)
).apply(it) { lookTarget, walkTarget, logMem, hasHoney ->
Trigger { world, entity, _ ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
val log = it.get(logMem)
val target = Vec3.atCenterOf(log)
// if already close, let the slather task handle it
if (entity.distanceToSqr(target) <= 2.0) return@Trigger false
walkTarget.set(WalkTarget(target, 0.35F, 0))
lookTarget.set(BlockPosTracker(target.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
true
}
}
}
}
@@ -0,0 +1,54 @@
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.block.SaccharineLogSlatheredBlock
import net.minecraft.core.Direction
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.level.block.RotatedPillarBlock
import net.minecraft.world.phys.Vec3
import kotlin.random.Random
object SlatherHoneyOnLogTask {
fun create(): OneShot<in LivingEntity> = BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.SACCHARINE_LOG_LOCATION),
it.present(CobblemonMemories.HAS_HONEY)
).apply(it) { walkTarget, logMem, hasHoney ->
Trigger { world, entity, _ ->
if (entity !is PathfinderMob) return@Trigger false
val pos = it.get(logMem)
val target = Vec3.atCenterOf(pos)
if (entity.distanceToSqr(target) > 2.0) return@Trigger false
val state = world.getBlockState(pos)
if (state.block != CobblemonBlocks.SACCHARINE_LOG) return@Trigger false
if (state.getValue(RotatedPillarBlock.AXIS) != Direction.Axis.Y) return@Trigger false
// pick a horizontal face with air in front
val face = Direction.Plane.HORIZONTAL.find {
world.getBlockState(pos.relative(it)).isAir
} ?: return@Trigger false
val newState = CobblemonBlocks.SACCHARINE_LOG_SLATHERED.defaultBlockState()
.setValue(RotatedPillarBlock.AXIS, Direction.Axis.Y)
.setValue(SaccharineLogSlatheredBlock.HONEY_TYPE, Random.nextInt(0, SaccharineLogSlatheredBlock.HONEY_TYPE_MAX + 1))
world.setBlock(pos, newState, 3)
world.playSound(null, pos, SoundEvents.HONEY_BLOCK_PLACE, SoundSource.BLOCKS, 1f, 1f)
entity.brain.eraseMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION)
entity.brain.eraseMemory(CobblemonMemories.HAS_HONEY)
true
}
}
}
}
@@ -0,0 +1,42 @@
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.level.block.BeehiveBlock
import net.minecraft.world.phys.Vec3
object StealHoneyFromHiveTask {
fun create(): OneShot<in LivingEntity> = BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.HIVE_LOCATION),
it.absent(CobblemonMemories.HAS_HONEY),
it.absent(CobblemonMemories.HIVE_COOLDOWN)
).apply(it) { walkTarget, hiveMem, hasHoney, cooldown ->
Trigger { world, entity, _ ->
if (entity !is PathfinderMob) return@Trigger false
val hive = it.get(hiveMem)
val center = Vec3.atCenterOf(hive)
if (entity.distanceToSqr(center) > 2.0) return@Trigger false
val state = world.getBlockState(hive)
val block = state.block
if (block is BeehiveBlock) {
val level = state.getValue(BeehiveBlock.HONEY_LEVEL)
if (level > 0) {
world.setBlock(hive, state.setValue(BeehiveBlock.HONEY_LEVEL, level - 1), 3)
entity.brain.setMemory(CobblemonMemories.HAS_HONEY, true)
entity.brain.setMemoryWithExpiry(CobblemonMemories.HIVE_COOLDOWN, true, 1200L)
return@Trigger true
}
}
false
}
}
}
}
@@ -70,7 +70,8 @@ object PokemonBrain {
SensorType.IS_IN_WATER,
CobblemonSensors.NEARBY_GROWABLE_CROPS,
CobblemonSensors.NEARBY_BEE_HIVE,
CobblemonSensors.NEARBY_FLOWER
CobblemonSensors.NEARBY_FLOWER,
CobblemonSensors.NEARBY_SACCHARINE_LOG
// CobblemonSensors.BATTLING_POKEMON,
// CobblemonSensors.NPC_BATTLING
@@ -193,7 +194,8 @@ object PokemonBrain {
CobblemonMemories.RECENTLY_ATE_GRASS,
CobblemonMemories.HERD_LEADER,
CobblemonMemories.HERD_SIZE,
CobblemonMemories.ATTACK_TARGET_DATA
CobblemonMemories.ATTACK_TARGET_DATA,
CobblemonMemories.SACCHARINE_LOG_LOCATION
)
private fun coreTasks(pokemon: Pokemon) = buildList<Pair<Int, BehaviorControl<in PokemonEntity>>> {
@@ -0,0 +1,41 @@
{
"name": "cobblemon.behaviour.pokemon_honey_heist.name",
"description": "cobblemon.behaviour.pokemon_honey_heist.desc",
"entityType": "cobblemon:pokemon",
"configurations": [
{
"type": "add_tasks_to_activity",
"activity": "cobblemon:pokemon_steal_honey",
"condition": "!q.entity.is_in_party",
"tasksByPriority": {
"0": [
{ "type": "cobblemon:activity_change",
"activity": "minecraft:idle",
"condition": "q.entity.has_memory_value('cobblemon:hive_cooldown')"
}
],
"2": [ "steal_honey_from_hive" ],
"3": [ "path_to_hive" ]
}
},
{
"type": "add_tasks_to_activity",
"activity": "minecraft:idle",
"condition": "!q.entity.is_in_party",
"tasksByPriority": {
"0": [
{ "type": "cobblemon:activity_change",
"activity": "cobblemon:pokemon_steal_honey",
"condition": "!q.entity.has_memory_value('cobblemon:has_honey') && !q.entity.has_memory_value('cobblemon:hive_cooldown') && q.entity.has_memory_value('cobblemon:hive_location')"
}
],
"1": [
{ "type": "cobblemon:run_script", "script": "cobblemon:wander_around_hive" }
],
"2": [ "slather_honey_on_log" ],
"3": [ "path_to_saccharine_log" ]
}
}
]
}
@@ -53,7 +53,15 @@
"canSleep": true,
"willSleepOnBed": true,
"light": "0-4"
}
},
"ai": [
{
"type": "apply_behaviours",
"behaviours": [
"cobblemon:pokemon_honey_heist"
]
}
]
},
"drops": {
"amount": 6,