Improved land wandering, added path type filtering to pokemon pathing

This commit is contained in:
Paul
2025-03-24 16:15:57 +10:00
parent 9d38ad78bf
commit 7e4054ad8f
16 changed files with 168 additions and 110 deletions
+9
View File
@@ -0,0 +1,9 @@
- Nodemaker bug, it thinks jumps are possible from water up an entire block - they aint
https://gyazo.com/759b8285e7c07046ff2e54adf0d7a7ac
- Why is magikarp trying to path through ice
https://gyazo.com/ae230758c529f885eeb0dd91e60530d2
- We should use mixins to put the path node filtering from OmniPathNodeMaker in the base classes otherwise
NPC wandering will be jank too.
@@ -37,7 +37,7 @@ object CobblemonMemories {
val POKEMON_DROWSY = register("pokemon_drowsy", PrimitiveCodec.BOOL)
val POKEMON_SLEEPING = register("pokemon_sleeping", PrimitiveCodec.BOOL)
val POKEMON_BATTLE = register<UUID>("pokemon_battle")
val PATH_COOLDOWN = register("path_cooldown", PrimitiveCodec.BOOL)
val PATH_COOLDOWN = register<Boolean>("path_cooldown")
val TARGETED_BATTLE_POKEMON = register<UUID>("targeted_battle_pokemon")
val NEAREST_VISIBLE_ATTACKER = register<LivingEntity>("nearest_visible_attacker")
val NEARBY_GROWABLE_CROPS = register<BlockPos>("nearby_growable_crops")
@@ -67,7 +67,7 @@ class FindAirTaskConfig : SingleTaskConfig {
val blockPos = entity.closestPosition(iterable) { isAirPos(entity.level(), it) }
?: return@Trigger false
walkTarget.set(WalkTarget(blockPos.above(), 0.35F, 1))
walkTarget.set(WalkTarget(blockPos.above(), 0.35F, 0))
return@Trigger true
}
}
@@ -12,25 +12,17 @@ import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.canFit
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.core.BlockPos
import net.minecraft.tags.BlockTags
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.RandomStroll
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.entity.ai.util.LandRandomPos
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.Vec3
import net.minecraft.world.level.pathfinder.PathType
class WanderTaskConfig : SingleTaskConfig {
companion object {
@@ -76,69 +68,74 @@ class WanderTaskConfig : SingleTaskConfig {
pathCooldown.setWithExpiry(true, 40L)
val targetVec = getLandTarget(entity) ?: return@Trigger true //LandRandomPos.getPos(entity, horizontalRange.resolveInt(), verticalRange.resolveInt()) ?: return@Trigger false
walkTarget.set(WalkTarget(targetVec, speedMultiplier.resolveFloat(), 1))
lookTarget.set(BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
// val targetVec = getLandTarget(entity) ?: return@Trigger true
val targetVec = LandRandomPos.getPos(entity, horizontalRange.resolveInt(), verticalRange.resolveInt()) ?: return@Trigger false
val pos = BlockPos.containing(targetVec)
walkTarget.set(CobblemonWalkTarget(pos, speedMultiplier.resolveFloat(), 0, { it !in listOf(PathType.WATER, PathType.WATER_BORDER) }))
lookTarget.erase()
return@Trigger true
}
}
}
}
@Suppress("DEPRECATION", "MemberVisibilityCanBePrivate")
fun getLandTarget(entity: PathfinderMob): Vec3? {
val roamDistanceCondition: (BlockPos) -> Boolean = if (entity is PokemonEntity) ({ entity.tethering?.canRoamTo(it) != false }) else ({ true })
val iterable: Iterable<BlockPos> = BlockPos.randomBetweenClosed(entity.random, 64, entity.blockX - 10, entity.blockY, entity.blockZ - 10, entity.blockX + 10, entity.blockY, entity.blockZ + 10)
val condition: (BlockState, BlockPos) -> Boolean = { _, pos -> entity.canFit(pos) && roamDistanceCondition(pos) }
val iterator = iterable.iterator()
position@
while (iterator.hasNext()) {
val pos = iterator.next().mutable()
var blockState = entity.level().getBlockState(pos)
val maxSteps = 16
var steps = 0
var good = false
if (!blockState.isSolid && !blockState.liquid()) {
pos.move(0, -1, 0)
var previousWasAir = true
while (steps++ < maxSteps && pos.y > entity.level().minBuildHeight) {
if (pos.y <= entity.level().minBuildHeight) {
continue@position
}
blockState = entity.level().getBlockState(pos)
if (blockState.isSolid && !blockState.`is`(BlockTags.LEAVES) && previousWasAir) {
pos.move(0, 1, 0)
blockState = entity.level().getBlockState(pos)
good = true
break
} else {
previousWasAir = blockState.isAir
}
pos.move(0, -1, 0)
}
} else {
var previousWasSolid = blockState.isSolid && !blockState.`is`(BlockTags.LEAVES)
pos.move(0, 1, 0)
while (steps++ < maxSteps) {
if (pos.y >= entity.level().maxBuildHeight) {
continue@position
}
blockState = entity.level().getBlockState(pos)
if (blockState.isAir && previousWasSolid) {
good = true
break
}
previousWasSolid = blockState.isSolid && !blockState.`is`(BlockTags.LEAVES)
pos.move(0, 1, 0)
}
}
if (good && condition(blockState, pos)) {
return pos.toVec3d()
}
}
return null
}
/*
* This method is from the old wander goal. It is very verbose but reliably finds decent land wander targets. Its only advantage over
* the LandRandomPos method (with our CobblemonWalkTarget) is that it doesn't lead entities in the direction of water.
*/
// fun getLandTarget(entity: PathfinderMob): Vec3? {
// val roamDistanceCondition: (BlockPos) -> Boolean = if (entity is PokemonEntity) ({ entity.tethering?.canRoamTo(it) != false }) else ({ true })
// val iterable: Iterable<BlockPos> = BlockPos.randomBetweenClosed(entity.random, 64, entity.blockX - 10, entity.blockY, entity.blockZ - 10, entity.blockX + 10, entity.blockY, entity.blockZ + 10)
// val condition: (BlockState, BlockPos) -> Boolean = { _, pos -> entity.canFit(pos) && roamDistanceCondition(pos) }
// val iterator = iterable.iterator()
// position@
// while (iterator.hasNext()) {
// val pos = iterator.next().mutable()
// var blockState = entity.level().getBlockState(pos)
//
// val maxSteps = 16
// var steps = 0
// var good = false
// if (!blockState.isSolid && !blockState.liquid()) {
// pos.move(0, -1, 0)
// var previousWasAir = true
// while (steps++ < maxSteps && pos.y > entity.level().minBuildHeight) {
// if (pos.y <= entity.level().minBuildHeight) {
// continue@position
// }
// blockState = entity.level().getBlockState(pos)
// if (blockState.isSolid && !blockState.`is`(BlockTags.LEAVES) && previousWasAir) {
// pos.move(0, 1, 0)
// blockState = entity.level().getBlockState(pos)
// good = true
// break
// } else {
// previousWasAir = blockState.isAir
// }
// pos.move(0, -1, 0)
// }
// } else {
// var previousWasSolid = blockState.isSolid && !blockState.`is`(BlockTags.LEAVES)
// pos.move(0, 1, 0)
// while (steps++ < maxSteps) {
// if (pos.y >= entity.level().maxBuildHeight) {
// continue@position
// }
// blockState = entity.level().getBlockState(pos)
// if (blockState.isAir && previousWasSolid) {
// good = true
// break
// }
// previousWasSolid = blockState.isSolid && !blockState.`is`(BlockTags.LEAVES)
// pos.move(0, 1, 0)
// }
// }
//
// if (good && condition(blockState, pos)) {
// return pos.toVec3d()
// }
// }
//
// return null
// }
}
@@ -0,0 +1,32 @@
/*
* 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.entity.ai
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.level.pathfinder.PathType
/**
* This is a trick to allow us to instruct the node-maker to only consider specific node types,
* even if the entity can, in a pinch, use more.
*
* @author Hiroku
* @since March 24th, 2025
*/
class CobblemonWalkTarget(
pos: BlockPos,
speedModifier: Float,
completionRange: Int,
val nodeTypeFilter: (PathType) -> Boolean = { true },
) : WalkTarget(
BlockPosTracker(pos),
speedModifier,
completionRange
)
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.entity.ai
import com.cobblemon.mod.common.pokemon.ai.OmniPathNodeMaker
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.PathfinderMob
@@ -18,6 +19,7 @@ import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.ai.util.DefaultRandomPos
import net.minecraft.world.level.pathfinder.Path
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.Vec3
class FollowWalkTargetTask(
@@ -102,9 +104,22 @@ class FollowWalkTargetTask(
}
}
private fun withNodeFilter(entity: PathfinderMob, nodeFilter: (PathType) -> Boolean, block: () -> Unit) {
val nodeEvaluator = entity.navigation.nodeEvaluator
if (nodeEvaluator is OmniPathNodeMaker) {
nodeEvaluator.nodeFilter = nodeFilter
block()
nodeEvaluator.nodeFilter = { true }
} else {
block()
}
}
private fun hasFinishedPath(entity: PathfinderMob, walkTarget: WalkTarget, time: Long): Boolean {
val blockPos = walkTarget.target.currentBlockPosition()
this.path = entity.navigation.createPath(blockPos, 0)
withNodeFilter(entity, (walkTarget as? CobblemonWalkTarget)?.nodeTypeFilter ?: { true }) {
this.path = entity.navigation.createPath(blockPos, 0)
}
this.speed = walkTarget.speedModifier
val brain = entity.brain
if (hasReached(entity, walkTarget)) {
@@ -126,10 +141,12 @@ class FollowWalkTargetTask(
10,
7,
Vec3.atBottomCenterOf(blockPos),
1.5707963705062866
Math.PI / 2F
)
if (vec3d != null) {
this.path = entity.navigation.createPath(vec3d.x, vec3d.y, vec3d.z, 0)
withNodeFilter(entity, (walkTarget as? CobblemonWalkTarget)?.nodeTypeFilter ?: { true }) {
this.path = entity.navigation.createPath(vec3d.x, vec3d.y, vec3d.z, 0)
}
return this.path != null
}
}
@@ -12,13 +12,9 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.FluidTags
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
class StayAfloatTask(private val chance: Float = 0F) : Behavior<PathfinderMob>(
mapOf(
MemoryModuleType.WALK_TARGET to MemoryStatus.VALUE_ABSENT,
)
mapOf()
) {
override fun checkExtraStartConditions(world: ServerLevel, entity: PathfinderMob): Boolean {
return entity.isInWater && entity.getFluidHeight(FluidTags.WATER) > entity.fluidJumpThreshold || entity.isInLava
@@ -59,6 +59,7 @@ class EatGrassTask(
override fun start(world: ServerLevel, entity: PokemonEntity, time: Long) {
timer = 40
entity.brain.setMemoryWithExpiry(CobblemonMemories.PATH_COOLDOWN, true, 40L)
entity.playAnimation("eat")
world.broadcastEntityEvent(entity, 10.toByte())
}
@@ -125,13 +125,9 @@ class Species : ClientDataSynchronizer<Species>, ShowdownIdentifiable {
)
},
ApplyPresets().apply {
presets.add(cobblemonResource("floats"))
presets.addAll(listOf(cobblemonResource("floats"), cobblemonResource("avoids_water")))
condition = Either.left("!q.entity.behaviour.moving.swim.can_breathe_underwater".asExpression())
},
ApplyPresets().apply {
presets.add(cobblemonResource("avoids_water"))
condition = Either.left("q.entity.behaviour.moving.swim.avoids_water".asExpression())
},
ApplyPresets().apply {
condition = Either.left("!q.entity.is_in_party".asExpression())
presets.add(cobblemonResource("pokemon_sleeps"))
@@ -43,6 +43,8 @@ class OmniPathNodeMaker : NodeEvaluator() {
var canPathThroughFire: Boolean = false
var nodeFilter: (PathType) -> Boolean = { true }
override fun prepare(cachedWorld: PathNavigationRegion, entity: Mob) {
super.prepare(cachedWorld, entity)
nodePosToType.clear()
@@ -188,6 +190,10 @@ class OmniPathNodeMaker : NodeEvaluator() {
}
fun isValidPathType(type: PathType): Boolean {
if (!nodeFilter(type)) {
return false
}
return when {
(type == PathType.BREACH || type == PathType.WATER || type == PathType.WATER_BORDER) && canSwimInWater() -> true
type == PathType.OPEN && canFly() -> true
@@ -939,6 +939,7 @@
"cobblemon.command.dialogue.invalid-dialogue": "Invalid dialogue identifier",
"cobblemon.command.pc.invalid-box": "Box number %1$s exceeds max boxes of %2$s.",
"cobblemon.command.unlockablepcboxwallpaper.invalid-wallpaper": "Invalid wallpaper identifier",
"cobblemon.command.brainedit.non_scriptable": "You are not looking at a MoLang scriptable entity.",
"cobblemon.ui.generic.yes": "Yes",
"cobblemon.ui.generic.no": "No",
@@ -2823,6 +2824,9 @@
"cobblemon.brain_preset.avoids_water.name": "Avoids Water",
"cobblemon.brain_preset.avoids_water.desc": "Tries to find air and then gets away from water.",
"cobblemon.brain_preset.pokemon_eats_grass.name": "Eats Grass",
"cobblemon.brain_preset.pokemon_eats_grass.desc": "The Pokémon occasionally eats grass underfoot.",
"cobblemon.move.10000000voltthunderbolt": "10,000,000 Volt Thunderbolt",
"cobblemon.move.10000000voltthunderbolt.desc": "The user, Pikachu wearing a cap, powers up a jolt of electricity using its Z-Power and unleashes it. Critical hits land more easily.",
"cobblemon.move.absorb": "Absorb",
@@ -0,0 +1,15 @@
{
"name": "cobblemon.brain_preset.pokemon_eats_grass.name",
"description": "cobblemon.brain_preset.pokemon_eats_grass.desc",
"configurations": [
{
"type": "add_tasks_to_activity",
"activity": "idle",
"tasksByPriority": {
"2": [
"eat_grass"
]
}
}
]
}
@@ -64,13 +64,8 @@
},
"ai": [
{
"type": "add_tasks_to_activity",
"activity": "idle",
"tasksByPriority": {
"2": [
"eat_grass"
]
}
"type": "apply_presets",
"preset": "cobblemon:eats_grass"
}
],
"drops": {
@@ -64,11 +64,11 @@
{
"type": "add_tasks_to_activity",
"activity": "idle",
"tasksByPriority": {
"2": [
"point_to_spawn"
]
}
"tasksByPriority": {
"2": [
"point_to_spawn"
]
}
}
],
"drops": {
@@ -59,13 +59,8 @@
},
"ai": [
{
"type": "add_tasks_to_activity",
"activity": "idle",
"tasksByPriority": {
"2": [
"eat_grass"
]
}
"type": "apply_presets",
"presets": ["cobblemon:eats_grass"]
}
],
"drops": {
@@ -53,13 +53,8 @@
},
"ai": [
{
"type": "add_tasks_to_activity",
"activity": "idle",
"tasksByPriority": {
"2": [
"eat_grass"
]
}
"type": "apply_presets",
"presets": ["cobblemon:eats_grass"]
}
],
"standingEyeHeight": 0.6,