Compare commits

...
Author SHA1 Message Date
broccoli a091290eb8 work on swapping to sbl 2024-05-13 15:16:37 +01:00
broccoli a86ae09dc9 Merge branch 'main' into smarterer-pokemons 2024-05-12 15:18:56 +01:00
Paul a5fbafe64d Import corrections 2024-04-11 12:48:43 +10:00
Paul 1de93b4933 Merge remote-tracking branch 'origin/main' into smarterer-pokemons
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
2024-04-11 12:46:21 +10:00
Paul f8dfc7c6cc Implemented owner following on Brain pokemon AI 2024-04-11 11:58:46 +10:00
Paul accbed9c2b Set up entity AI for battles on Brain system 2024-04-08 13:15:33 +10:00
Paul 8b0e615777 Merge remote-tracking branch 'origin/main' into smarterer-pokemons
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
2024-04-08 11:13:12 +10:00
Paul 746f12c724 Migrated the eat grass goal to the brain system 2024-04-06 21:22:26 +10:00
Paul a11d306a1b Merge remote-tracking branch 'origin/main' into smarterer-pokemons
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
2024-04-06 17:49:24 +10:00
Paul 40d140e84d Added find resting place AI task 2024-03-25 10:08:55 +10:00
Paul ffdb4842ac Merge remote-tracking branch 'origin/main' into smarterer-pokemons 2024-03-18 15:48:32 +10:00
Paul 6baf60d07c Added CobblemonMemories and Sensors from NPC branch (merge conflicts are future me's problem) 2024-03-18 10:04:43 +10:00
Paul cb3b7784b0 Fixed pokemon speeds on new brain 2024-03-18 10:04:20 +10:00
Paul ee4f3b1629 Started giving pokemon brains 2024-03-17 16:43:32 +10:00
Paul 518a7daa0e Reverted partial run change 2024-03-17 12:46:08 +10:00
33 changed files with 1267 additions and 59 deletions
+1
View File
@@ -43,6 +43,7 @@ dependencies {
modCompileOnly(libs.bundles.fabric.integrations.compileOnly) {
isTransitive = false
}
modImplementation(libs.sbl.fabric)
// Flywheel has no common dep so just pick one and don't use any platform specific code in common
// modCompileOnly(libs.flywheelFabric)
@@ -0,0 +1,25 @@
/*
* 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;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.entity.ai.brain.Brain;
import net.minecraft.entity.ai.brain.MemoryModuleType;
import net.minecraft.entity.ai.brain.sensor.Sensor;
import net.minecraft.entity.ai.brain.sensor.SensorType;
import java.util.Collection;
public class GenericsCheatClass {
// For some reason, doing this in the PokemonEntity class in Kotlin gives confusing generic errors I could not resolve.
public static Brain.Profile<PokemonEntity> createPokemonBrain(Collection<MemoryModuleType<?>> memories, Collection<SensorType<? extends Sensor<? super PokemonEntity>>> sensors) {
return Brain.createProfile(memories, sensors);
}
}
@@ -12,7 +12,9 @@ import net.minecraft.entity.ai.brain.Activity
object CobblemonActivities {
val activities = mutableListOf<Activity>()
val BATTLING_ACTIVITY = Activity("pokemon_battling")
val BATTLING_ACTIVITY = Activity("pokemon_battle")
val POKEMON_SLEEPING_ACTIVITY = Activity("pokemon_sleeping")
val POKEMON_COMBAT_ACTIVITY = Activity("pokemon_combat")
fun register(activity: Activity): Activity {
activities.add(activity)
@@ -0,0 +1,38 @@
/*
* 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
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.PrimitiveCodec
import java.util.Optional
import java.util.UUID
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.util.Uuids
object CobblemonMemories {
val memories = mutableMapOf<String, MemoryModuleType<*>>()
val POKEMON_FLYING = register("pokemon_flying", PrimitiveCodec.BOOL)
val POKEMON_DROWSY = register("pokemon_drowsy", PrimitiveCodec.BOOL)
val POKEMON_BATTLE = register<UUID>("pokemon_battle") // No codec because it shouldn't survive relogs
val REST_PATH_COOLDOWN = register("rest_path_cooldown", PrimitiveCodec.BOOL)
val TARGETED_BATTLE_POKEMON = register<UUID>("targeted_battle_pokemon")
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
val memoryModule = MemoryModuleType(Optional.of(codec))
memories[id] = memoryModule
return memoryModule
}
fun <U> register(id: String): MemoryModuleType<U> {
val memoryModule = MemoryModuleType<U>(Optional.empty())
memories[id] = memoryModule
return memoryModule
}
}
@@ -0,0 +1,28 @@
/*
* 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
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.DrowsySensor
import java.util.function.Supplier
import net.minecraft.entity.Entity
import net.minecraft.entity.ai.brain.sensor.Sensor
import net.minecraft.entity.ai.brain.sensor.SensorType
object CobblemonSensors {
val sensors = mutableMapOf<String, SensorType<*>>()
// val NPC_BATTLING = register("npc_battling", ::NPCBattlingSensor)
val POKEMON_DROWSY = register("pokemon_drowsy", ::DrowsySensor)
fun <E : Entity, U : Sensor<E>> register(id: String, supplier: Supplier<U>): SensorType<U> {
val sensor = SensorType(supplier)
sensors[id] = sensor
return sensor
}
}
@@ -15,5 +15,21 @@ package com.cobblemon.mod.common.battles.dispatch
* @since July 31st, 2022
*/
class UntilDispatch(val condition: () -> Boolean) : DispatchResult {
override fun canProceed() = condition()
var afterAction: (() -> Unit) = {}
override fun canProceed(): Boolean {
val shouldContinue = condition()
if (shouldContinue) {
afterAction()
}
return shouldContinue
}
fun andThen(action: () -> Unit): UntilDispatch {
val old = afterAction
afterAction = {
old()
action()
}
return this
}
}
@@ -10,10 +10,10 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.interpreter.Effect
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunction
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.moves.Moves
@@ -66,6 +66,11 @@ class MoveInstruction(
val pokemonName = userPokemon.getName()
ShowdownInterpreter.lastCauser[battle.battleId] = message
val targetPokemonEntity = targetPokemon?.entity
if (targetPokemonEntity != null) {
userPokemon.entity?.brain?.remember(CobblemonMemories.TARGETED_BATTLE_POKEMON, targetPokemonEntity.uuid)
}
userPokemon.effectedPokemon.let { pokemon ->
if (UseMoveEvolutionProgress.supports(pokemon, move)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is UseMoveEvolutionProgress && it.currentProgress().move == move }) { UseMoveEvolutionProgress() }
@@ -125,7 +130,11 @@ class MoveInstruction(
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
future.thenApply { holds.clear() }
return@dispatch UntilDispatch { "effects" !in holds }
return@dispatch UntilDispatch { "effects" !in holds }.andThen {
val userPokemonId = userPokemon.entity?.uuid ?: return@andThen
val targets = hurtTargets.mapNotNull { it.entity }
userPokemonId.let { id -> targets.forEach { it.brain.remember(CobblemonMemories.TARGETED_BATTLE_POKEMON, id) } }
}
}
}
}
@@ -0,0 +1,35 @@
/*
* 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.entity.LivingEntity
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.task.SingleTickTask
import net.minecraft.entity.ai.brain.task.TaskRunnable
import net.minecraft.entity.ai.brain.task.TaskTriggerer
object AttackAngryAtTask {
fun create(): SingleTickTask<LivingEntity> = TaskTriggerer.task {
it.group(
it.queryMemoryValue(MemoryModuleType.ANGRY_AT),
it.queryMemoryAbsent(MemoryModuleType.ATTACK_TARGET)
).apply(it) { angryAt, attackTarget ->
TaskRunnable { world, entity, _ ->
val angryAt = it.getValue(angryAt)
val livingEntity = world.getEntity(angryAt) as? LivingEntity
if (livingEntity != null) {
entity.brain.remember(MemoryModuleType.ATTACK_TARGET, livingEntity)
} else {
entity.brain.forget(MemoryModuleType.ANGRY_AT)
}
return@TaskRunnable true
}
}
}
}
@@ -0,0 +1,37 @@
/*
* 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.entity.ai.FuzzyTargeting
import net.minecraft.entity.ai.brain.BlockPosLookTarget
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.WalkTarget
import net.minecraft.entity.ai.brain.task.SingleTickTask
import net.minecraft.entity.ai.brain.task.TaskRunnable
import net.minecraft.entity.ai.brain.task.TaskTriggerer
import net.minecraft.entity.mob.PathAwareEntity
object ChooseLandWanderTargetTask {
fun create(chance: Int, horizontalRange: Int, verticalRange: Int, walkSpeed: Float, completionRange: Int): SingleTickTask<PathAwareEntity> {
return TaskTriggerer.task {
it.group(
it.queryMemoryAbsent(MemoryModuleType.WALK_TARGET),
it.queryMemoryOptional(MemoryModuleType.LOOK_TARGET)
).apply(it) { walkTarget, lookTarget ->
TaskRunnable { world, entity, time ->
if (world.random.nextInt(chance) != 0) return@TaskRunnable false
val targetVec = FuzzyTargeting.find(entity, horizontalRange, verticalRange) ?: return@TaskRunnable false
walkTarget.remember(WalkTarget(targetVec, walkSpeed, completionRange))
lookTarget.remember(BlockPosLookTarget(targetVec.add(0.0, 1.5, 0.0)))
return@TaskRunnable true
}
}
}
}
}
@@ -0,0 +1,155 @@
/*
* 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.entity.ai.NoPenaltyTargeting
import net.minecraft.entity.ai.brain.EntityLookTarget
import net.minecraft.entity.ai.brain.MemoryModuleState
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.WalkTarget
import net.minecraft.entity.ai.brain.task.MultiTickTask
import net.minecraft.entity.ai.pathing.Path
import net.minecraft.entity.mob.PathAwareEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
class FollowWalkTargetTask(
minRunTime: Int = 150,
maxRunTime: Int = 250
) : MultiTickTask<PathAwareEntity>(
mapOf(
MemoryModuleType.WALK_TARGET to MemoryModuleState.VALUE_PRESENT,
MemoryModuleType.PATH to MemoryModuleState.VALUE_ABSENT,
MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE to MemoryModuleState.REGISTERED
), minRunTime, maxRunTime
) {
private var pathUpdateCountdownTicks = 0
private var path: Path? = null
private var lookTargetPos: BlockPos? = null
private var speed = 0f
override fun shouldRun(arg: ServerWorld, arg2: PathAwareEntity): Boolean {
if (this.pathUpdateCountdownTicks > 0) {
--this.pathUpdateCountdownTicks
return false
} else {
val brain = arg2.brain
val walkTarget = brain.getOptionalRegisteredMemory(MemoryModuleType.WALK_TARGET).get()
val hasReached = this.hasReached(arg2, walkTarget)
if (!hasReached && this.hasFinishedPath(arg2, walkTarget, arg.time)) {
this.lookTargetPos = walkTarget.lookTarget.blockPos
return true
} else {
brain.forget(MemoryModuleType.WALK_TARGET)
if (hasReached) {
brain.forget(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE)
}
return false
}
}
}
override fun shouldKeepRunning(arg: ServerWorld, arg2: PathAwareEntity, l: Long): Boolean {
if (this.path != null && this.lookTargetPos != null) {
val optional = arg2.brain.getOptionalRegisteredMemory(MemoryModuleType.WALK_TARGET)
val isTargetSpectator = optional.map(::isTargetSpectator).orElse(false)
val entityNavigation = arg2.navigation
return !entityNavigation.isIdle && optional.isPresent && !this.hasReached(arg2, optional.get()) && !isTargetSpectator
} else {
return false
}
}
override fun finishRunning(world: ServerWorld, entity: PathAwareEntity, l: Long) {
val walkTarget = entity.brain.getOptionalRegisteredMemory(MemoryModuleType.WALK_TARGET).orElse(null)
if (walkTarget != null && !this.hasReached(entity, walkTarget) && entity.navigation.isNearPathStartPos) {
this.pathUpdateCountdownTicks = world.getRandom().nextInt(40)
}
entity.navigation.stop()
entity.brain.forget(MemoryModuleType.WALK_TARGET)
entity.brain.forget(MemoryModuleType.PATH)
this.path = null
}
override fun run(arg: ServerWorld, entity: PathAwareEntity, time: Long) {
entity.brain.remember(MemoryModuleType.PATH, this.path)
entity.navigation.startMovingAlong(this.path, speed.toDouble())
}
override fun keepRunning(world: ServerWorld, entity: PathAwareEntity, l: Long) {
val path = entity.navigation.currentPath
val brain = entity.brain
if (this.path !== path) {
this.path = path
brain.remember(MemoryModuleType.PATH, path)
}
if (path != null && lookTargetPos != null) {
val walkTarget = brain.getOptionalRegisteredMemory(MemoryModuleType.WALK_TARGET).get()
if (walkTarget.lookTarget.blockPos.getSquaredDistance(lookTargetPos) > 4.0 && hasFinishedPath(entity, walkTarget, world.time)) {
lookTargetPos = walkTarget.lookTarget.blockPos
run(world, entity, l)
}
}
}
private fun hasFinishedPath(entity: PathAwareEntity, walkTarget: WalkTarget, time: Long): Boolean {
val blockPos = walkTarget.lookTarget.blockPos
this.path = entity.navigation.findPathTo(blockPos, 0)
this.speed = walkTarget.speed
val brain = entity.brain
if (hasReached(entity, walkTarget)) {
brain.forget(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE)
} else {
val bl = this.path != null && path!!.reachesTarget()
if (bl) {
brain.forget(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE)
} else if (!brain.hasMemoryModule(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE)) {
brain.remember(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, time)
}
if (this.path != null) {
return true
}
val vec3d = NoPenaltyTargeting.findTo(
entity,
10,
7,
Vec3d.ofBottomCenter(blockPos),
1.5707963705062866
)
if (vec3d != null) {
this.path = entity.navigation.findPathTo(vec3d.x, vec3d.y, vec3d.z, 0)
return this.path != null
}
}
return false
}
private fun hasReached(entity: PathAwareEntity, walkTarget: WalkTarget): Boolean {
return walkTarget.lookTarget.blockPos.getManhattanDistance(entity.blockPos) <= walkTarget.completionRange
}
companion object {
private fun isTargetSpectator(target: WalkTarget): Boolean {
val lookTarget = target.lookTarget
return if (lookTarget is EntityLookTarget) {
lookTarget.entity.isSpectator
} else {
false
}
}
}
}
@@ -0,0 +1,43 @@
/*
* 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 com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.toDF
import com.mojang.datafixers.util.Pair
import net.minecraft.entity.ai.brain.MemoryModuleState
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.tslat.smartbrainlib.api.core.behaviour.ExtendedBehaviour
import net.tslat.smartbrainlib.util.BrainUtils
//todo: make sure it's the same behaviour
class GetAngryAtAttackerTask : ExtendedBehaviour<PokemonEntity>() {
init {
whenStarting { entity ->
val hurtByEntity = requireNotNull(
BrainUtils.getMemory(entity, MemoryModuleType.HURT_BY_ENTITY)
)
val angryAt = BrainUtils.getMemory(entity, MemoryModuleType.ANGRY_AT)
if (angryAt != null && angryAt == hurtByEntity.uuid) {
return@whenStarting
}
entity.brain.remember(MemoryModuleType.ANGRY_AT, hurtByEntity.uuid)
}
}
override fun getMemoryRequirements(): List<Pair<MemoryModuleType<*>, MemoryModuleState>> {
//todo: not sure what state these should be
return listOf(
MemoryModuleType.HURT_BY_ENTITY toDF MemoryModuleState.REGISTERED,
MemoryModuleType.ANGRY_AT toDF MemoryModuleState.REGISTERED,
)
}
}
@@ -0,0 +1,37 @@
/*
* 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.entity.LivingEntity
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.WalkTarget
import net.minecraft.entity.ai.brain.task.SingleTickTask
import net.minecraft.entity.ai.brain.task.TaskRunnable
import net.minecraft.entity.ai.brain.task.TaskTriggerer
object MoveToAttackTargetTask {
fun create(): SingleTickTask<LivingEntity> = TaskTriggerer.task {
it.group(
it.queryMemoryValue(MemoryModuleType.ATTACK_TARGET),
it.queryMemoryOptional(MemoryModuleType.WALK_TARGET)
).apply(it) { attackTarget, walkTarget ->
TaskRunnable { world, entity, _ ->
val attackTarget = it.getValue(attackTarget)
val position = attackTarget.pos
val walkTarget = it.getOptionalValue(walkTarget).orElse(null)
if (walkTarget == null || walkTarget.lookTarget.pos.squaredDistanceTo(position) > 2.0) {
entity.brain.remember(MemoryModuleType.WALK_TARGET, WalkTarget(attackTarget, 0.5F, 1))
true
} else {
false
}
}
}
}
}
@@ -0,0 +1,30 @@
/*
* 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.entity.ai.brain.task.MultiTickTask
import net.minecraft.entity.mob.PathAwareEntity
import net.minecraft.registry.tag.FluidTags
import net.minecraft.server.world.ServerWorld
class StayAfloatTask(private val chance: Float = 0F) : MultiTickTask<PathAwareEntity>(emptyMap()) {
override fun shouldRun(world: ServerWorld, entity: PathAwareEntity): Boolean {
return entity.isTouchingWater && entity.getFluidHeight(FluidTags.WATER) > entity.swimHeight || entity.isInLava
}
override fun shouldKeepRunning(world: ServerWorld, entity: PathAwareEntity, l: Long): Boolean {
return this.shouldRun(world, entity)
}
override fun keepRunning(world: ServerWorld, entity: PathAwareEntity, l: Long) {
if (entity.random.nextFloat() < this.chance) {
entity.jumpControl.setActive()
}
}
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.entity.pokemon
import com.cobblemon.mod.common.*
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.entity.Despawner
import com.cobblemon.mod.common.api.events.CobblemonEvents
@@ -20,9 +21,7 @@ import com.cobblemon.mod.common.api.events.pokemon.ShoulderMountEvent
import com.cobblemon.mod.common.api.interaction.PokemonEntityInteraction
import com.cobblemon.mod.common.api.net.serializers.PoseTypeDataSerializer
import com.cobblemon.mod.common.api.net.serializers.StringSetDataSerializer
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.scheduling.Schedulable
@@ -36,11 +35,14 @@ import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.Poseable
import com.cobblemon.mod.common.entity.ai.*
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonNavigation
import com.cobblemon.mod.common.entity.pokemon.ai.goals.*
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.DrowsySensor
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.*
import com.cobblemon.mod.common.entity.pokemon.effects.EffectTracker
import com.cobblemon.mod.common.entity.pokemon.effects.IllusionEffect
import com.cobblemon.mod.common.net.messages.client.animation.PlayPoseableAnimationPacket
@@ -59,15 +61,22 @@ import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolut
import com.cobblemon.mod.common.pokemon.misc.GimmighoulStashHandler
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import net.minecraft.block.Blocks
import java.util.EnumSet
import java.util.Optional
import java.util.UUID
import java.util.concurrent.CompletableFuture
import com.google.common.collect.ImmutableList
import com.mojang.serialization.Dynamic
import net.minecraft.entity.*
import net.minecraft.entity.ai.brain.Activity
import net.minecraft.entity.ai.brain.Brain
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.sensor.NearestLivingEntitiesSensor
import net.minecraft.entity.ai.brain.sensor.NearestPlayersSensor
import net.minecraft.entity.ai.brain.sensor.Sensor
import net.minecraft.entity.ai.brain.sensor.SensorType
import net.minecraft.entity.ai.brain.task.ForgetAngryAtTargetTask
import net.minecraft.entity.ai.brain.task.LookAroundTask
import net.minecraft.entity.ai.brain.task.LookAtMobTask
import net.minecraft.entity.ai.brain.task.MultiTickTask
import net.minecraft.entity.ai.brain.task.Task
import net.minecraft.entity.ai.control.MoveControl
import net.minecraft.entity.ai.goal.EatGrassGoal
import net.minecraft.entity.ai.goal.Goal
import net.minecraft.entity.ai.pathing.PathNodeType
import net.minecraft.entity.attribute.DefaultAttributeContainer
import net.minecraft.entity.attribute.EntityAttributes
@@ -108,16 +117,27 @@ import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Box
import net.minecraft.util.math.Vec3d
import net.minecraft.world.EntityView
import net.minecraft.world.LightType
import net.minecraft.world.World
import net.minecraft.world.event.GameEvent
import net.tslat.smartbrainlib.api.SmartBrainOwner
import net.tslat.smartbrainlib.api.core.BrainActivityGroup
import net.tslat.smartbrainlib.api.core.SmartBrainProvider
import net.tslat.smartbrainlib.api.core.sensor.ExtendedSensor
import net.tslat.smartbrainlib.api.core.sensor.vanilla.HurtBySensor
import net.tslat.smartbrainlib.api.core.sensor.vanilla.NearbyLivingEntitySensor
import net.tslat.smartbrainlib.api.core.sensor.vanilla.NearbyPlayersSensor
import java.util.*
import java.util.concurrent.CompletableFuture
import kotlin.math.ceil
@Suppress("unused")
open class PokemonEntity(
world: World,
pokemon: Pokemon = Pokemon(),
type: EntityType<out PokemonEntity> = CobblemonEntities.POKEMON,
) : TameableShoulderEntity(type, world), Poseable, Shearable, Schedulable {
) : TameableShoulderEntity(type, world), Poseable, Shearable, Schedulable, SmartBrainOwner<PokemonEntity> {
companion object {
@JvmStatic val SPECIES = DataTracker.registerData(PokemonEntity::class.java, TrackedDataHandlerRegistry.STRING)
@JvmStatic val NICKNAME = DataTracker.registerData(PokemonEntity::class.java, TrackedDataHandlerRegistry.TEXT_COMPONENT)
@@ -142,6 +162,39 @@ open class PokemonEntity(
fun createAttributes(): DefaultAttributeContainer.Builder = LivingEntity.createLivingAttributes()
.add(EntityAttributes.GENERIC_FOLLOW_RANGE)
.add(EntityAttributes.GENERIC_ATTACK_KNOCKBACK)
val SENSORS: Collection<SensorType<out Sensor<in PokemonEntity>>> = listOf(
SensorType.NEAREST_LIVING_ENTITIES,
SensorType.HURT_BY,
SensorType.NEAREST_PLAYERS,
CobblemonSensors.POKEMON_DROWSY
// CobblemonSensors.BATTLING_POKEMON,
// CobblemonSensors.NPC_BATTLING
)
val MEMORY_MODULES: List<MemoryModuleType<*>> = ImmutableList.of(
MemoryModuleType.LOOK_TARGET,
MemoryModuleType.WALK_TARGET,
MemoryModuleType.ATTACK_TARGET,
MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE,
MemoryModuleType.PATH,
MemoryModuleType.IS_PANICKING,
MemoryModuleType.VISIBLE_MOBS,
CobblemonMemories.POKEMON_FLYING,
// CobblemonMemories.NPC_BATTLING,
// CobblemonMemories.BATTLING_POKEMON,
MemoryModuleType.HURT_BY,
MemoryModuleType.HURT_BY_ENTITY,
MemoryModuleType.NEAREST_VISIBLE_PLAYER,
MemoryModuleType.ANGRY_AT,
MemoryModuleType.ATTACK_COOLING_DOWN,
CobblemonMemories.POKEMON_DROWSY,
CobblemonMemories.POKEMON_BATTLE,
MemoryModuleType.HOME,
CobblemonMemories.REST_PATH_COOLDOWN,
CobblemonMemories.TARGETED_BATTLE_POKEMON
)
}
val removalObservable = SimpleObservable<RemovalReason?>()
/** A list of observable subscriptions related to this entity that need to be cleaned up when the entity is removed. */
@@ -161,6 +214,7 @@ open class PokemonEntity(
stepHeight = behaviour.moving.stepHeight
// We need to update this value every time the Pokémon changes, other eye height related things will be dynamic.
this.updateEyeHeight()
setupTasks()
}
var despawner: Despawner<PokemonEntity> = Cobblemon.bestSpawner.defaultPokemonDespawner
@@ -179,6 +233,8 @@ open class PokemonEntity(
var battleId: UUID?
get() = dataTracker.get(BATTLE_ID).orElse(null)
set(value) = dataTracker.set(BATTLE_ID, Optional.ofNullable(value))
val battle: PokemonBattle?
get() = battleId?.let { BattleRegistry.getBattle(it) }
val isBattling: Boolean
get() = dataTracker.get(BATTLE_ID).isPresent
@@ -227,6 +283,7 @@ open class PokemonEntity(
delegate.initialize(this)
delegate.changePokemon(pokemon)
calculateDimensions()
setupTasks()
}
override fun initDataTracker() {
@@ -270,8 +327,10 @@ open class PokemonEntity(
if (battleId != null) {
busyLocks.remove(BATTLE_LOCK) // Remove in case it's hopped across to another battle, don't want extra battle locks
busyLocks.add(BATTLE_LOCK)
brain.remember(CobblemonMemories.POKEMON_BATTLE, battleId)
} else {
busyLocks.remove(BATTLE_LOCK)
brain.forget(CobblemonMemories.POKEMON_BATTLE)
}
}
}
@@ -330,6 +389,11 @@ open class PokemonEntity(
schedulingTracker.update(1/20F)
}
override fun mobTick() {
super.mobTick()
tickBrain(this)
}
fun setMoveControl(moveControl: MoveControl) {
this.moveControl = moveControl
}
@@ -527,46 +591,131 @@ open class PokemonEntity(
override fun getNavigation() = navigation as PokemonNavigation
override fun createNavigation(world: World) = PokemonNavigation(world, this)
@Suppress("SENSELESS_COMPARISON")
public override fun initGoals() {
// DO NOT REMOVE
// LivingEntity#getActiveEyeHeight is called in the constructor of Entity
// Pokémon param is not available yet
if (this.pokemon == null) {
return
}
moveControl = PokemonMoveControl(this)
goalSelector.clear { true }
goalSelector.add(0, PokemonInBattleMovementGoal(this, 10))
goalSelector.add(0, object : Goal() {
override fun canStart() = this@PokemonEntity.dataTracker.get(PHASING_TARGET_ID) != -1 || pokemon.status?.status == Statuses.SLEEP || dataTracker.get(DYING_EFFECTS_STARTED) || evolutionEntity != null
override fun shouldContinue(): Boolean {
if (pokemon.status?.status == Statuses.SLEEP && !canSleep() && !isBusy) {
return false
} else if (pokemon.status?.status == Statuses.SLEEP || isBusy) {
return true
}
return false
}
override fun getControls() = EnumSet.allOf(Control::class.java)
})
@Suppress("KotlinConstantConditions")
// Brain.Profile is final and Kotlin error's, but this cast is safe.
override fun createBrainProfile() = SmartBrainProvider(this) as Brain.Profile<*>
goalSelector.add(1, PokemonBreatheAirGoal(this))
goalSelector.add(2, PokemonFloatToSurfaceGoal(this))
goalSelector.add(3, PokemonFollowOwnerGoal(this, 1.0, 8F, 2F, false))
goalSelector.add(4, PokemonMoveIntoFluidGoal(this))
goalSelector.add(5, SleepOnTrainerGoal(this))
goalSelector.add(5, WildRestGoal(this))
if (pokemon.getFeature<FlagSpeciesFeature>(DataKeys.HAS_BEEN_SHEARED) != null) {
goalSelector.add(5, EatGrassGoal(this))
}
goalSelector.add(6, PokemonWanderAroundGoal(this))
goalSelector.add(7, PokemonLookAtEntityGoal(this, ServerPlayerEntity::class.java, 5F))
goalSelector.add(8, PokemonPointAtSpawnGoal(this))
override fun getSensors(): List<ExtendedSensor<PokemonEntity>> {
return listOf(
NearbyLivingEntitySensor(),
HurtBySensor(),
NearbyPlayersSensor(),
DrowsySensor()
)
}
override fun initGoals() {
super.initGoals()
moveControl = PokemonMoveControl(this)
}
override fun onFinishPathfinding() {
super.onFinishPathfinding()
(moveControl as PokemonMoveControl).stop()
}
override fun getCoreTasks(): BrainActivityGroup<PokemonEntity> {
val tasks: MutableList<MultiTickTask<in PokemonEntity>> = mutableListOf()
if (!pokemon.form.behaviour.moving.swim.canBreatheUnderwater) {
tasks.add(StayAfloatTask(0.8F))
}
tasks.add(GetAngryAtAttackerTask())
tasks.add(ForgetAngryAtTargetTask.create())
tasks.add(HandleBattleActivityGoal.create())
tasks.add(FollowWalkTargetTask())
return BrainActivityGroup.coreTasks(
tasks
)
}
fun getIdleTasks(): List<com.mojang.datafixers.util.Pair<Int, Task<in PokemonEntity>>> {
val tasks: MutableList<com.mojang.datafixers.util.Pair<Int, Task<in PokemonEntity>>> = mutableListOf()
if (pokemon.form.behaviour.moving.canLook) {
if (pokemon.form.behaviour.moving.looksAtEntities) {
tasks.add(0 toDF LookAtMobTask.create(15F))
}
tasks.add(0 toDF LookAroundTask(45, 90))
}
tasks.add(0 toDF ChooseLandWanderTargetTask.create(pokemon.form.behaviour.moving.wanderChance, horizontalRange = 10, verticalRange = 5, walkSpeed = 0.33F, completionRange = 1))
tasks.add(0 toDF GoToSleepTask.create())
tasks.add(0 toDF FindRestingPlaceTask.create(16, 8))
tasks.add(0 toDF EatGrassTask())
tasks.add(0 toDF AttackAngryAtTask.create())
tasks.add(0 toDF MoveToAttackTargetTask.create())
tasks.add(0 toDF MoveToOwnerTask.create(completionRange = 4, maxDistance = 14F, teleportDistance = 24F))
return tasks
}
fun getBattlingTasks(): List<com.mojang.datafixers.util.Pair<Int, Task<in PokemonEntity>>> {
val tasks: MutableList<com.mojang.datafixers.util.Pair<Int, Task<in PokemonEntity>>> = mutableListOf()
tasks.add(0 toDF LookAtTargetedBattlePokemonTask.create())
tasks.add(0 toDF LookAroundTask(Int.MAX_VALUE - 1, Int.MAX_VALUE - 1))
return tasks
}
fun getSleepingTasks(): List<com.mojang.datafixers.util.Pair<Int, Task<in PokemonEntity>>> {
val tasks: MutableList<com.mojang.datafixers.util.Pair<Int, Task<in PokemonEntity>>> = mutableListOf()
tasks.add(1 toDF WakeUpTask.create())
return tasks
}
fun setupTasks() {
val brain = this.getBrain()
brain.setTaskList(Activity.CORE, ImmutableList.copyOf(getCoreTasks()))
brain.setTaskList(Activity.IDLE, ImmutableList.copyOf(getIdleTasks()))
brain.setTaskList(CobblemonActivities.BATTLING_ACTIVITY, ImmutableList.copyOf(getBattlingTasks()))
brain.setTaskList(CobblemonActivities.POKEMON_SLEEPING_ACTIVITY, ImmutableList.copyOf(getSleepingTasks()))
brain.setCoreActivities(setOf(Activity.CORE))
brain.setDefaultActivity(Activity.IDLE)
brain.resetPossibleActivities()
}
//
// @Suppress("SENSELESS_COMPARISON")
// public override fun initGoals() {
// // DO NOT REMOVE
// // LivingEntity#getActiveEyeHeight is called in the constructor of Entity
// // Pokémon param is not available yet
// if (this.pokemon == null) {
// return
// }
// moveControl = PokemonMoveControl(this)
// goalSelector.clear { true }
// goalSelector.add(0, PokemonInBattleMovementGoal(this, 10))
// goalSelector.add(0, object : Goal() {
// override fun canStart() = this@PokemonEntity.dataTracker.get(PHASING_TARGET_ID) != -1 || pokemon.status?.status == Statuses.SLEEP || dataTracker.get(DYING_EFFECTS_STARTED) || evolutionEntity != null
// override fun shouldContinue(): Boolean {
// if (pokemon.status?.status == Statuses.SLEEP && !canSleep() && !isBusy) {
// return false
// } else if (pokemon.status?.status == Statuses.SLEEP || isBusy) {
// return true
// }
// return false
// }
// override fun getControls() = EnumSet.allOf(Control::class.java)
// })
//
// goalSelector.add(1, PokemonBreatheAirGoal(this))
// goalSelector.add(2, PokemonFloatToSurfaceGoal(this))
// goalSelector.add(3, PokemonFollowOwnerGoal(this, 1.0, 8F, 2F, false))
// goalSelector.add(4, PokemonMoveIntoFluidGoal(this))
// goalSelector.add(5, SleepOnTrainerGoal(this))
// goalSelector.add(5, WildRestGoal(this))
//
// if (pokemon.getFeature<FlagSpeciesFeature>(DataKeys.HAS_BEEN_SHEARED) != null) {
// goalSelector.add(5, EatGrassGoal(this))
// }
//
// goalSelector.add(6, PokemonWanderAroundGoal(this))
// goalSelector.add(7, PokemonLookAtEntityGoal(this, ServerPlayerEntity::class.java, 5F))
// goalSelector.add(8, PokemonPointAtSpawnGoal(this))
// }
fun canSleep(): Boolean {
val rest = behaviour.resting
val worldTime = (world.timeOfDay % 24000).toInt()
@@ -582,6 +731,25 @@ open class PokemonEntity(
(rest.biomes.isEmpty() || rest.biomes.any { it.fits(biome, this.world.registryManager.get(RegistryKeys.BIOME)) })
}
fun canSleepAt(pos: BlockPos): Boolean {
val rest = behaviour.resting
val light = world.getLightLevel(pos)
val blockState = world.getBlockState(pos)
val block = blockState.block
val biome = world.getBiome(pos).value()
val seesSky = world.isSkyVisible(pos.up())
val fits = true
val canStayAt = world.canEntityStayAt(pos, ceil(width).toInt(), ceil(height).toInt(), PositionType.LAND)
return light in rest.light &&
(rest.skyLight == null || world.lightingProvider.get(LightType.SKY).getLightLevel(pos) in rest.skyLight) &&
(rest.blocks.isEmpty() || rest.blocks.any { it.fits(block, this.world.registryManager.get(RegistryKeys.BLOCK)) }) &&
(rest.biomes.isEmpty() || rest.biomes.any { it.fits(biome, this.world.registryManager.get(RegistryKeys.BIOME)) }) &&
(rest.canSeeSky == null || rest.canSeeSky == seesSky) &&
fits &&
canStayAt
}
override fun createChild(level: ServerWorld, partner: PassiveEntity) = null
override fun isReadyToSitOnPlayer(): Boolean {
@@ -633,7 +801,10 @@ open class PokemonEntity(
} else if (pokemon.lastFlowerFed.isOf(Items.POPPY) || pokemon.lastFlowerFed.isOf(Items.TORCHFLOWER)) {
effect = StatusEffect.byRawId(16) // Night Vision
duration = 100 // 5 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.PINK_TULIP) || pokemon.lastFlowerFed.isOf(Items.RED_TULIP) || pokemon.lastFlowerFed.isOf(Items.WHITE_TULIP) || pokemon.lastFlowerFed.isOf(Items.ORANGE_TULIP)) {
} else if (pokemon.lastFlowerFed.isOf(Items.PINK_TULIP) || pokemon.lastFlowerFed.isOf(Items.RED_TULIP) || pokemon.lastFlowerFed.isOf(
Items.WHITE_TULIP
) || pokemon.lastFlowerFed.isOf(Items.ORANGE_TULIP)
) {
effect = StatusEffect.byRawId(18) // Weakness
duration = 180 // 9 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.WITHER_ROSE)) {
@@ -654,9 +825,7 @@ open class PokemonEntity(
// reset the flower fed state
pokemon.lastFlowerFed = ItemStack.EMPTY
return ActionResult.success(world.isClient)
}
else {
} else {
val mushroomStew = ItemUsage.exchangeStack(itemStack, player, Items.MUSHROOM_STEW.defaultStack)
player.setStackInHand(hand, mushroomStew)
return ActionResult.success(world.isClient)
@@ -43,7 +43,7 @@ class PokemonServerDelegate : PokemonSideDelegate {
override fun changePokemon(pokemon: Pokemon) {
updatePathfindingPenalties(pokemon)
entity.initGoals()
// entity.initGoals()
updateMaxHealth()
}
@@ -34,7 +34,7 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
}
override fun tick() {
if (pokemonEntity.pokemon.status?.status == Statuses.SLEEP || pokemonEntity.isDead) {
if (pokemonEntity.isDead) {
pokemonEntity.movementSpeed = 0F
pokemonEntity.upwardSpeed = 0F
return
@@ -47,7 +47,7 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
behaviour.moving.swim.swimSpeed
} else {
behaviour.moving.walk.walkSpeed
}
} * 2.5F
val baseSpeed = entity.getAttributeValue(EntityAttributes.GENERIC_MOVEMENT_SPEED).toFloat() * this.speed.toFloat()
val adjustedSpeed = baseSpeed * mediumSpeed
@@ -203,4 +203,10 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
}
return true
}
fun stop() {
state = State.WAIT
forwardMovement = 0.0f
sidewaysMovement = 0.0f
}
}
@@ -0,0 +1,46 @@
/*
* 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.pokemon.ai.sensors
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.sensor.SensorType
import net.minecraft.server.world.ServerWorld
import net.tslat.smartbrainlib.api.core.sensor.ExtendedSensor
/**
* Senses when the Pokémon is in a situation that would make it ready to sleep.
*
* @author Hiroku
* @since March 23rd, 2024
*/
class DrowsySensor : ExtendedSensor<PokemonEntity>() {
override fun memoriesUsed(): List<MemoryModuleType<*>> {
return listOf(
CobblemonMemories.POKEMON_DROWSY
)
}
override fun type(): SensorType<out ExtendedSensor<*>> {
return CobblemonSensors.POKEMON_DROWSY
}
override fun sense(world: ServerWorld, entity: PokemonEntity) {
val rest = entity.behaviour.resting
val isDrowsy = entity.brain.getOptionalRegisteredMemory(CobblemonMemories.POKEMON_DROWSY).orElse(false)
val shouldBeDrowsy = rest.canSleep && world.timeOfDay.toInt() in rest.times
if (!isDrowsy && shouldBeDrowsy) {
entity.brain.remember(CobblemonMemories.POKEMON_DROWSY, true)
} else if (isDrowsy && !shouldBeDrowsy) {
entity.brain.forget(CobblemonMemories.POKEMON_DROWSY)
}
}
}
@@ -0,0 +1,88 @@
/*
* 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.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeature
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.DataKeys
import com.google.common.collect.ImmutableMap
import net.minecraft.block.Block
import net.minecraft.block.Blocks
import net.minecraft.entity.ai.brain.MemoryModuleState
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.task.MultiTickTask
import net.minecraft.predicate.block.BlockStatePredicate
import net.minecraft.server.world.ServerWorld
import net.minecraft.world.GameRules
/**
* The baa's eat the graa's
*
* @author Hiroku
* @since April 6th, 2024
*/
class EatGrassTask : MultiTickTask<PokemonEntity>(
ImmutableMap.of(
MemoryModuleType.ANGRY_AT, MemoryModuleState.VALUE_ABSENT,
MemoryModuleType.ATTACK_TARGET, MemoryModuleState.VALUE_ABSENT,
MemoryModuleType.WALK_TARGET, MemoryModuleState.VALUE_ABSENT,
CobblemonMemories.POKEMON_BATTLE, MemoryModuleState.VALUE_ABSENT
)
) {
val grassPredicate = BlockStatePredicate.forBlock(Blocks.GRASS)
var timer = -1
override fun shouldRun(world: ServerWorld, entity: PokemonEntity): Boolean {
if (entity.random.nextInt(500) != 0) {
return false
} else {
entity.pokemon.getFeature<FlagSpeciesFeature>(DataKeys.HAS_BEEN_SHEARED)?.enabled?.takeIf { it } ?: run {
return false
}
val blockPos = entity.blockPos
if (grassPredicate.test(world.getBlockState(blockPos)) ) {
return true
} else if (world.getBlockState(blockPos.down()).isOf(Blocks.GRASS_BLOCK)) {
return true
}
return false
}
}
override fun run(world: ServerWorld, entity: PokemonEntity, time: Long) {
timer = 40
world.sendEntityStatus(entity, 10.toByte())
}
override fun shouldKeepRunning(world: ServerWorld, entity: PokemonEntity, time: Long): Boolean {
timer--
if (timer < 0) {
val blockPos = entity.blockPos
if (grassPredicate.test(world.getBlockState(blockPos)) ) {
if (world.gameRules.getBoolean(GameRules.DO_MOB_GRIEFING)) {
world.breakBlock(blockPos, false)
}
entity.onEatingGrass()
} else if (world.getBlockState(blockPos.down()).isOf(Blocks.GRASS_BLOCK)) {
val blockPos2 = blockPos.down()
if (world.getBlockState(blockPos2).isOf(Blocks.GRASS_BLOCK)) {
if (world.gameRules.getBoolean(GameRules.DO_MOB_GRIEFING)) {
world.syncWorldEvent(2001, blockPos2, Block.getRawIdFromState(Blocks.GRASS_BLOCK.defaultState))
world.setBlockState(blockPos2, Blocks.DIRT.defaultState, 2)
}
entity.onEatingGrass()
}
}
return false
}
return true
}
}
@@ -0,0 +1,53 @@
/*
* 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.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.getBlockPositions
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.WalkTarget
import net.minecraft.entity.ai.brain.task.SingleTickTask
import net.minecraft.entity.ai.brain.task.TaskRunnable
import net.minecraft.entity.ai.brain.task.TaskTriggerer
import net.minecraft.util.math.Box
object FindRestingPlaceTask {
fun create(horizontalSearchDistance: Int, verticalSearchDistance: Int): SingleTickTask<PokemonEntity> {
return TaskTriggerer.task {
it.group(
it.queryMemoryAbsent(MemoryModuleType.ANGRY_AT),
it.queryMemoryAbsent(MemoryModuleType.ATTACK_TARGET),
it.queryMemoryAbsent(MemoryModuleType.WALK_TARGET),
it.queryMemoryAbsent(CobblemonMemories.POKEMON_BATTLE),
it.queryMemoryValue(CobblemonMemories.POKEMON_DROWSY),
it.queryMemoryAbsent(CobblemonMemories.REST_PATH_COOLDOWN)
).apply(it) { _, _, walkTarget, _, pokemonDrowsy, restPathCooldown ->
TaskRunnable { world, entity, _ ->
return@TaskRunnable if (it.getValue(pokemonDrowsy) && entity.pokemon.status?.status != Statuses.SLEEP && entity.pokemon.storeCoordinates.get()?.store !is PartyStore) {
entity.brain.remember(CobblemonMemories.REST_PATH_COOLDOWN, true, 40)
val position = entity.world
.getBlockPositions(Box.of(entity.pos, horizontalSearchDistance.toDouble(), verticalSearchDistance.toDouble(), horizontalSearchDistance.toDouble()))
.filter(entity::canSleepAt)
// .randomOrNull()
.minByOrNull { it.getSquaredDistance(entity.pos) }
if (position != null) {
walkTarget.remember(WalkTarget(position.down(), 0.3F, 1))
}
true
} else {
false
}
}
}
}
}
}
@@ -0,0 +1,47 @@
/*
* 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.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.task.SingleTickTask
import net.minecraft.entity.ai.brain.task.TaskRunnable
import net.minecraft.entity.ai.brain.task.TaskTriggerer
object GoToSleepTask {
fun create(): SingleTickTask<PokemonEntity> {
return TaskTriggerer.task {
it.group(
it.queryMemoryAbsent(MemoryModuleType.ANGRY_AT),
it.queryMemoryAbsent(MemoryModuleType.ATTACK_TARGET),
it.queryMemoryAbsent(MemoryModuleType.WALK_TARGET),
it.queryMemoryAbsent(CobblemonMemories.POKEMON_BATTLE),
it.queryMemoryOptional(CobblemonMemories.POKEMON_DROWSY)
).apply(it) { _, _, _, _, pokemonDrowsy ->
TaskRunnable { world, entity, _ ->
val hasSleepStatus = entity.pokemon.status?.status === Statuses.SLEEP
if (entity.behaviour.resting.canSleep && ((it.getOptionalValue(pokemonDrowsy).orElse(false) && entity.canSleepAt(entity.blockPos.down())) || hasSleepStatus) && entity.pokemon.storeCoordinates.get()?.store !is PartyStore) {
if (!hasSleepStatus) {
entity.pokemon.status = PersistentStatusContainer(Statuses.SLEEP)
}
entity.brain.resetPossibleActivities(listOf(CobblemonActivities.POKEMON_SLEEPING_ACTIVITY))
return@TaskRunnable true
} else {
return@TaskRunnable false
}
}
}
}
}
}
@@ -0,0 +1,53 @@
/*
* 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.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.entity.ai.brain.Activity
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.task.SingleTickTask
import net.minecraft.entity.ai.brain.task.TaskRunnable
import net.minecraft.entity.ai.brain.task.TaskTriggerer
/**
* Moves Pokémon in and out of the battling activity.
*
* @author Hiroku
* @since April 8th, 2024
*/
object HandleBattleActivityGoal {
fun create(): SingleTickTask<PokemonEntity> {
return TaskTriggerer.task {
it.group(
it.queryMemoryOptional(CobblemonMemories.POKEMON_BATTLE),
it.queryMemoryOptional(MemoryModuleType.WALK_TARGET),
it.queryMemoryOptional(MemoryModuleType.LOOK_TARGET),
).apply(it) { battle, walkTarget, lookTarget ->
TaskRunnable { world, entity, _ ->
val battleUUID = it.getOptionalValue(battle).orElse(null)
if (battleUUID != null && !entity.brain.hasActivity(CobblemonActivities.BATTLING_ACTIVITY)) {
entity.brain.resetPossibleActivities(listOf(CobblemonActivities.BATTLING_ACTIVITY))
entity.brain.forget(MemoryModuleType.WALK_TARGET)
entity.brain.forget(MemoryModuleType.LOOK_TARGET)
entity.navigation.stop()
return@TaskRunnable true
} else if (battleUUID == null && entity.brain.hasActivity(CobblemonActivities.BATTLING_ACTIVITY)) {
entity.brain.resetPossibleActivities(listOf(Activity.IDLE))
entity.brain.forget(MemoryModuleType.WALK_TARGET)
entity.brain.forget(MemoryModuleType.LOOK_TARGET)
return@TaskRunnable true
}
return@TaskRunnable false
}
}
}
}
}
@@ -0,0 +1,60 @@
/*
* 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.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.entity.ai.brain.EntityLookTarget
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.task.SingleTickTask
import net.minecraft.entity.ai.brain.task.TaskRunnable
import net.minecraft.entity.ai.brain.task.TaskTriggerer
/**
* Manages the look target of a Pokémon in battle.
*
* @author Hiroku
* @since April 8th, 2024
*/
object LookAtTargetedBattlePokemonTask {
fun create(): SingleTickTask<PokemonEntity> {
return TaskTriggerer.task {
it.group(
it.queryMemoryValue(CobblemonMemories.POKEMON_BATTLE),
it.queryMemoryOptional(CobblemonMemories.TARGETED_BATTLE_POKEMON),
it.queryMemoryOptional(MemoryModuleType.LOOK_TARGET),
).apply(it) { _, targetedBattlePokemon, lookTarget ->
TaskRunnable { world, entity, _ ->
val targeted = it.getOptionalValue(targetedBattlePokemon).orElse(null)
val targetedEntity = targeted?.let { world.getEntity(it) as? PokemonEntity }
val look = it.getOptionalValue(lookTarget).orElse(null) as? EntityLookTarget
if (targeted != null && targetedEntity == null) {
entity.brain.forget(CobblemonMemories.TARGETED_BATTLE_POKEMON)
return@TaskRunnable false
} else if (targetedEntity != null && look?.entity != targetedEntity) {
entity.brain.remember(MemoryModuleType.LOOK_TARGET, EntityLookTarget(targetedEntity, true))
return@TaskRunnable true
} else if (targetedEntity == null) {
val battle = entity.battle ?: return@TaskRunnable false
val nearestOpposingPokemon = battle.sides
.find { entity in it.actors.flatMap { it.pokemonList.map { it.entity } } }
?.getOppositeSide()?.actors
?.flatMap { it.pokemonList.mapNotNull { it.entity } }
?.minByOrNull { it.distanceTo(entity) }
if (nearestOpposingPokemon != null) {
entity.brain.remember(MemoryModuleType.LOOK_TARGET, EntityLookTarget(nearestOpposingPokemon, true))
return@TaskRunnable true
}
}
return@TaskRunnable false
}
}
}
}
}
@@ -0,0 +1,96 @@
/*
* 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.pokemon.ai.tasks
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import kotlin.math.abs
import net.minecraft.entity.Entity
import net.minecraft.entity.ai.brain.EntityLookTarget
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.WalkTarget
import net.minecraft.entity.ai.brain.task.SingleTickTask
import net.minecraft.entity.ai.brain.task.TaskRunnable
import net.minecraft.entity.ai.brain.task.TaskTriggerer
import net.minecraft.entity.ai.pathing.LandPathNodeMaker
import net.minecraft.entity.ai.pathing.PathNodeType
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.random.Random
object MoveToOwnerTask {
fun create(completionRange: Int, maxDistance: Float, teleportDistance: Float): SingleTickTask<PokemonEntity> = TaskTriggerer.task {
it.group(
it.queryMemoryOptional(MemoryModuleType.WALK_TARGET),
it.queryMemoryAbsent(MemoryModuleType.ANGRY_AT)
).apply(it) { walkTarget, _ ->
TaskRunnable { _, entity, _ ->
val owner = entity.owner ?: return@TaskRunnable false
if (entity.pokemon.storeCoordinates.get()?.store !is PartyStore) {
return@TaskRunnable false
}
if (entity.distanceTo(owner) > teleportDistance) {
if (tryTeleport(entity, owner)) {
entity.brain.forget(MemoryModuleType.LOOK_TARGET)
entity.brain.forget(MemoryModuleType.WALK_TARGET)
}
return@TaskRunnable true
} else if (entity.distanceTo(owner) > maxDistance && it.getOptionalValue(walkTarget).isEmpty) {
entity.brain.remember(MemoryModuleType.LOOK_TARGET, EntityLookTarget(owner, true))
entity.brain.remember(MemoryModuleType.WALK_TARGET, WalkTarget(owner, 0.4F, completionRange))
return@TaskRunnable true
}
return@TaskRunnable false
}
}
}
private fun tryTeleport(entity: PokemonEntity, owner: Entity): Boolean {
val blockPos = owner.blockPos
for (i in 0..9) {
val j = this.getRandomInt(entity.random, -3, 3)
val k = this.getRandomInt(entity.random, -1, 1)
val l = this.getRandomInt(entity.random, -3, 3)
val succeeded = this.tryTeleportTo(entity, owner, blockPos.x + j, blockPos.y + k, blockPos.z + l)
if (succeeded) {
return true
}
}
return false
}
private fun tryTeleportTo(entity: PokemonEntity, owner: Entity, x: Int, y: Int, z: Int): Boolean {
if (abs(x.toDouble() - owner.x) < 2.0 && abs(z - owner.z) < 2.0) {
return false
} else if (!this.canTeleportTo(entity, BlockPos(x, y, z))) {
return false
} else {
entity.refreshPositionAndAngles(
x.toDouble() + 0.5, y.toDouble(), z.toDouble() + 0.5,
entity.yaw,
entity.pitch
)
entity.navigation.stop()
return true
}
}
private fun canTeleportTo(entity: PokemonEntity, pos: BlockPos): Boolean {
val pathNodeType = LandPathNodeMaker.getLandNodeType(entity.world, pos.mutableCopy())
if (pathNodeType != PathNodeType.WALKABLE) {
return false
} else {
val blockPos = pos.subtract(entity.blockPos)
return entity.world.isSpaceEmpty(entity, entity.boundingBox.offset(blockPos))
}
}
private fun getRandomInt(random: Random, min: Int, max: Int): Int {
return random.nextInt(max - min + 1) + min
}
}
@@ -0,0 +1,38 @@
/*
* 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.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.entity.ai.brain.task.SingleTickTask
import net.minecraft.entity.ai.brain.task.TaskRunnable
import net.minecraft.entity.ai.brain.task.TaskTriggerer
object WakeUpTask {
fun create(): SingleTickTask<PokemonEntity> {
return TaskTriggerer.task {
it.group(
it.queryMemoryAbsent(CobblemonMemories.POKEMON_BATTLE),
it.queryMemoryAbsent(CobblemonMemories.POKEMON_DROWSY)
).apply(it) { _, _ ->
TaskRunnable { world, entity, _ ->
if (entity.pokemon.status?.status == Statuses.SLEEP && entity.pokemon.storeCoordinates.get()?.store !is PartyStore) {
entity.pokemon.status = null
entity.brain.resetPossibleActivities()
return@TaskRunnable true
} else {
return@TaskRunnable false
}
}
}
}
}
}
@@ -28,6 +28,8 @@ class RestBehaviour {
val biomes = mutableListOf<RegistryLikeCondition<Biome>>()
val light = IntRange(0, 15)
val depth = SleepDepth.normal
val canSeeSky: Boolean? = null
val skyLight: IntRange? = null
val willSleepOnBed = false
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.util
import net.minecraft.block.BlockState
import net.minecraft.entity.Entity
import net.minecraft.entity.ai.pathing.NavigationType
import net.minecraft.item.Item
import net.minecraft.particle.ParticleEffect
import net.minecraft.registry.Registry
@@ -97,6 +98,19 @@ fun BlockView.getBlockStatesWithPos(box: Box): Iterable<Pair<BlockState, BlockPo
return states
}
fun BlockView.getBlockPositions(box: Box): Iterable<BlockPos> {
val positions = mutableListOf<BlockPos>()
val (xRange, yRange, zRange) = box.getRanges()
for (x in xRange) {
for (y in yRange) {
for (z in zRange) {
positions.add(BlockPos(x, y, z))
}
}
}
return positions
}
fun BlockView.getWaterAndLavaIn(box: Box): Pair<Boolean, Boolean> {
var hasWater = false
var hasLava = false
@@ -120,6 +134,63 @@ fun Entity.canFit(vec: Vec3d): Boolean {
return world.isSpaceEmpty(box)
}
enum class PositionType {
LAND,
WATER,
SEAFLOOR,
LAVAFLOOR
}
fun World.canEntityStayAt(position: BlockPos, width: Int = 1, height: Int = 1, positionType: PositionType): Boolean {
val minX = kotlin.math.floor(position.x + 0.5 - (width - 1) / 2F).toInt()
val maxX = kotlin.math.ceil(position.x + 0.5 + (width - 1) / 2F).toInt()
val maxY = position.y + height
val minZ = kotlin.math.floor(position.z + 0.5 - (width - 1) / 2F).toInt()
val maxZ = kotlin.math.ceil(position.z + 0.5 + (width - 1) / 2F).toInt()
val mutable = BlockPos.Mutable()
for (x in minX until maxX) {
for (y in position.y..maxY) {
for (z in minZ until maxZ) {
val state = getBlockState(mutable.set(x, y, z))
// If it's a floor check, we likely need to do something different than surrounding blocks
if (y == position.y) {
if (positionType == PositionType.LAND) {
// Land entities need to be standing on solid ground
if (state.canPathfindThrough(this, mutable, NavigationType.LAND)) {
return false
}
}
} else {
when (positionType) {
PositionType.LAND -> {
if (!state.canPathfindThrough(this, mutable, NavigationType.LAND)) {
return false
}
}
PositionType.WATER, PositionType.SEAFLOOR -> {
if (!state.fluidState.isIn(FluidTags.WATER)) {
return false
}
}
PositionType.LAVAFLOOR -> {
if (!state.fluidState.isIn(FluidTags.LAVA)) {
return false
}
}
}
}
}
}
}
return true
}
val World.itemRegistry: Registry<Item>
get() = registryManager.get(RegistryKeys.ITEM)
val World.biomeRegistry: Registry<Biome>
@@ -96,6 +96,7 @@ transitive-accessible field net/minecraft/client/model/ModelPart$Cuboid sides [L
transitive-accessible class net/minecraft/client/model/ModelPart$Quad
transitive-accessible method net/minecraft/entity/ai/brain/Activity <init> (Ljava/lang/String;)V
transitive-accessible method net/minecraft/entity/ai/brain/sensor/SensorType <init> (Ljava/util/function/Supplier;)V
#accessible method net/minecraft/client/render/RenderLayer$MultiPhase (Ljava/lang/String;Lnet/minecraft/client/render/VertexFormat;Lnet/minecraft/client/render/VertexFormat$DrawMode;IZZLnet/minecraft/client/render/RenderLayer$MultiPhaseParameters;)Lnet/minecraft/client/render/RenderLayer$MultiPhase;
#String name, VertexFormat vertexFormat, VertexFormat.DrawMode drawMode, int expectedBufferSize, boolean hasCrumbling, boolean translucent, MultiPhaseParameters phases
@@ -185,7 +185,8 @@
"canSleep": true,
"willSleepOnBed": true,
"depth": "normal",
"light": "0-4"
"light": "0-2",
"canSeeSky": false
}
},
"moves": [
+1
View File
@@ -60,6 +60,7 @@ dependencies {
isTransitive = false
}
modRuntimeOnly(libs.jei.fabric)
modImplementation(libs.sbl.fabric)
// modImplementation(libs.flywheelFabric)
// include(libs.flywheelFabric)
@@ -100,6 +100,9 @@ object CobblemonFabric : CobblemonImplementation {
CobblemonPlacementModifierTypes.touch()
CobblemonProcessorTypes.touch()
CobblemonActivities.activities.forEach { Registry.register(Registries.ACTIVITY, cobblemonResource(it.id), it) }
CobblemonSensors.sensors.forEach { (key, sensorType) -> Registry.register(Registries.SENSOR_TYPE, cobblemonResource(key), sensorType) }
CobblemonMemories.memories.forEach { (key, memoryModuleType) -> Registry.register(Registries.MEMORY_MODULE_TYPE, cobblemonResource(key), memoryModuleType) }
EntitySleepEvents.STOP_SLEEPING.register { playerEntity, _ ->
if (playerEntity !is ServerPlayerEntity) {
return@register
@@ -162,6 +162,18 @@ class CobblemonForge : CobblemonImplementation {
ForgeRegistries.ACTIVITIES.register(cobblemonResource(it.id), it)
}
}
event.register(RegistryKeys.SENSOR_TYPE) {
CobblemonSensors.sensors.forEach { (key, sensorType) ->
ForgeRegistries.SENSOR_TYPES.register(cobblemonResource(key), sensorType)
}
}
event.register(RegistryKeys.MEMORY_MODULE_TYPE) {
CobblemonMemories.memories.forEach { (key, memoryModuleType) ->
ForgeRegistries.MEMORY_MODULE_TYPES.register(cobblemonResource(key), memoryModuleType)
}
}
}
fun onDataPackSync(event: OnDatapackSyncEvent) {
@@ -39,6 +39,8 @@ repositories {
//JEI
maven("https://maven.blamejared.com/")
maven("https://maven.tterrag.com/")
// SBL
maven("https://dl.cloudsmith.io/public/tslat/sbl/maven/")
}
license {
+3
View File
@@ -20,6 +20,7 @@ molang = "1.1.11"
graal = "22.3.0"
icu4j = "71.1"
jei-api = "15.2.0.25"
sbl = "1.14"
# Fabric
fabric-loader = "0.15.10"
@@ -65,6 +66,8 @@ jei-api = { group = "local.mezz.jei", name = "common-api", version.ref = "jei-ap
molang = { group = "local.com.bedrockk", name = "molang", version.ref = "molang" }
graal = { group = "org.graalvm.js", name = "js", version.ref = "graal" }
icu4j = { group = "com.ibm.icu", name = "icu4j", version.ref = "icu4j" }
sbl-common = { group = "net.tslat.smartbrainlib", name = "SmartBrainLib-common-1.20.1", version.ref = "sbl" }
sbl-fabric = { group = "net.tslat.smartbrainlib", name = "SmartBrainLib-fabric-1.20.1", version.ref = "sbl" }
# Fabric
fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" }