mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-20 14:16:26 -04:00
Improved land wandering, added path type filtering to pokemon pathing
This commit is contained in:
@@ -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")
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+66
-69
@@ -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
|
||||
|
||||
+1
@@ -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",
|
||||
|
||||
+15
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user