mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-25 08:36:24 -04:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a091290eb8 | ||
|
|
a86ae09dc9 | ||
|
|
a5fbafe64d | ||
|
|
1de93b4933 | ||
|
|
f8dfc7c6cc | ||
|
|
accbed9c2b | ||
|
|
8b0e615777 | ||
|
|
746f12c724 | ||
|
|
a11d306a1b | ||
|
|
40d140e84d | ||
|
|
ffdb4842ac | ||
|
|
6baf60d07c | ||
|
|
cb3b7784b0 | ||
|
|
ee4f3b1629 | ||
|
|
518a7daa0e |
@@ -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
|
||||
}
|
||||
}
|
||||
+11
-2
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -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)
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ class PokemonServerDelegate : PokemonSideDelegate {
|
||||
|
||||
override fun changePokemon(pokemon: Pokemon) {
|
||||
updatePathfindingPenalties(pokemon)
|
||||
entity.initGoals()
|
||||
// entity.initGoals()
|
||||
updateMaxHealth()
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -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
|
||||
}
|
||||
}
|
||||
+46
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+88
@@ -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
|
||||
}
|
||||
}
|
||||
+53
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -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": [
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user