Compare commits

...
Author SHA1 Message Date
Nadja Reitzenstein 9318be9a17 Aggron has some hands 2025-09-20 21:20:07 +02:00
9 changed files with 207 additions and 38 deletions
@@ -20,6 +20,7 @@ import net.minecraft.world.entity.ai.memory.MemoryModuleType
import java.util.Optional
import java.util.UUID
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.LivingEntity
object CobblemonMemories {
@@ -60,6 +61,7 @@ object CobblemonMemories {
val WANDER_CONTROL = register<CobblemonWanderControl>("wander_control")
val DISABLE_WALK_TO_WANTED_ITEM = register<Boolean>("disable_walk_to_wanted_item")
val TIME_TRYING_TO_REACH_WANTED_ITEM = register<Int>("time_trying_to_wanted_item")
val RECENT_CATCH_ATTEMPT = register<UUID>("recent_catch_attempt")
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
val memoryModule = MemoryModuleType(Optional.of(codec))
@@ -0,0 +1,31 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.config.task.EatGrassTaskConfig.Companion.EATING_GRASS
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.FiteMeSoyBoyTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class FiteThrowerTaskConfig : SingleTaskConfig {
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
behaviourConfigurationContext.addMemories(CobblemonMemories.RECENT_CATCH_ATTEMPT)
return WrapperLivingEntityTask(
FiteMeSoyBoyTask(),
PokemonEntity::class.java,
)
}
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext): List<MoLangConfigVariable> = emptyList()
}
@@ -120,6 +120,7 @@ interface TaskConfig {
cobblemonResource("target_entity") to TargetEntityTaskConfig::class.java,
cobblemonResource("memory_aspect") to MemoryAspectTaskConfig::class.java,
cobblemonResource("activity_change") to ActivityChangeTaskConfig::class.java,
cobblemonResource("fite_me_soy_boy") to FiteThrowerTaskConfig::class.java,
)
}
@@ -13,6 +13,7 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonEntities.EMPTY_POKEBALL
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokeball.PokeBallCaptureCalculatedEvent
@@ -72,8 +73,8 @@ import net.minecraft.world.item.Item
import net.minecraft.world.level.Level
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.EntityHitResult
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
import java.util.Optional
import java.util.concurrent.CompletableFuture
class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragModifier, Schedulable {
@@ -178,21 +179,30 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
var pokeBall: PokeBall = PokeBalls.POKE_BALL
override fun onHitBlock(hitResult: BlockHitResult) {
if (captureState == CaptureState.NOT) {
if (level().isServerSide()) {
super.onHitBlock(hitResult)
level().sendParticlesServer(ParticleTypes.CLOUD, hitResult.location, 2, hitResult.location.subtract(position()).normalize().scale(-0.1), 0.0)
level().playSoundServer(position(), SoundEvents.WOOD_PLACE, pitch = 2.5F)
discard()
val player = this.owner as? ServerPlayer
if (player?.isCreative == false) {
spawnAtLocation(defaultItem)
when (captureState) {
CaptureState.FALL -> {
if (level().isServerSide()) {
beginCapture()
}
}
} else {
setNoGravity(false)
deltaMovement = Vec3.ZERO
CaptureState.NOT -> {
if (level().isServerSide()) {
super.onHitBlock(hitResult)
level().sendParticlesServer(ParticleTypes.CLOUD, hitResult.location, 2, hitResult.location.subtract(position()).normalize().scale(-0.1), 0.0)
level().playSoundServer(position(), SoundEvents.WOOD_PLACE, pitch = 2.5F)
discard()
val player = this.owner as? ServerPlayer
if (player?.isCreative == false) {
spawnAtLocation(defaultItem)
}
}
}
else -> {
isNoGravity = false
deltaMovement = Vec3.ZERO
}
}
}
@@ -215,15 +225,14 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
}
if (battle != null && owner != null && owner is LivingEntity) {
val throwerActor = battle.getActor(owner.uuid)
val throwerActor = battle.getActor(owner.uuid) ?: run {
owner.sendSystemMessage(lang("capture.in_battle", pokemonEntity.exposedSpecies.translatedName).red())
return onHitEntity@drop()
}
val hitActor = battle.actors.find { it.isForPokemon(pokemonEntity) }
val hitBattlePokemon = hitActor?.activePokemon?.find { it.battlePokemon?.effectedPokemon?.entity == pokemonEntity }
if (throwerActor == null) {
owner.sendSystemMessage(lang("capture.in_battle", pokemonEntity.exposedSpecies.translatedName).red())
return drop()
}
if (hitActor == null || hitBattlePokemon == null) {
return drop() // Weird, shouldn't be possible
}
@@ -254,6 +263,9 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
} else if (pokemonEntity.isBusy) {
owner?.sendSystemMessage(lang("capture.busy", pokemonEntity.exposedSpecies.translatedName).red())
return drop()
} else if (battle == null && pokemonEntity.brain.hasMemoryValue(CobblemonMemories.RECENT_CATCH_ATTEMPT)) {
pokemonEntity.playAnimation("cry")
return drop()
} else if (owner is ServerPlayer && BattleRegistry.getBattleByParticipatingPlayer(owner) != null) {
owner.sendSystemMessage(lang("you_in_battle").red())
return drop()
@@ -456,7 +468,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
after(seconds = 0.7F) {
// Start beaming them up.
deltaMovement = Vec3.ZERO
setNoGravity(true)
isNoGravity = true
level().playSoundServer(position(), CobblemonSounds.POKE_BALL_RECALL, volume = 0.6F)
pokemonEntity.beamMode = 3
}
@@ -471,7 +483,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
// If it was still falling after a second and a half, just assume it's landed because we can't wait all day.
if (captureState == CaptureState.FALL) {
deltaMovement = Vec3.ZERO
setNoGravity(true)
isNoGravity = true
setOnGround(true)
beginCapture()
}
@@ -479,21 +491,14 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
}
}
override fun onHit(hitResult: HitResult) {
super.onHit(hitResult)
if (captureState == CaptureState.FALL && hitResult.type == HitResult.Type.BLOCK) {
if (level().isServerSide()) {
beginCapture()
}
}
}
// We have hit the ground, time to stop falling and start shaking! Calculate capture.
fun beginCapture() {
// We have hit the ground, time to stop falling and start shaking! Calculate capture.
capturingPokemon?.setPositionSafely(position())
val capturingPokemon = this.capturingPokemon ?: return
capturingPokemon.setPositionSafely(position())
val thrower = owner as LivingEntity
val captureResult = Cobblemon.config.captureCalculator.processCapture(thrower, this, capturingPokemon!!).let {
val event = PokeBallCaptureCalculatedEvent(thrower = thrower, pokemonEntity = capturingPokemon!!, pokeBallEntity = this, captureResult = it)
val captureResult = Cobblemon.config.captureCalculator.processCapture(thrower, this, capturingPokemon).let {
val event = PokeBallCaptureCalculatedEvent(thrower = thrower, pokemonEntity = capturingPokemon, pokeBallEntity = this, captureResult = it)
CobblemonEvents.POKE_BALL_CAPTURE_CALCULATED.post(event)
event.captureResult
}
@@ -530,11 +535,12 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
}
.build()
}
override fun getCurrentPoseType(): PoseType {
return PoseType.NONE
}
override fun canUsePortal(allowVehicles: Boolean) = false
override fun canUsePortal(allowVehicles: Boolean) = captureState == CaptureState.NOT
override fun getAddEntityPacket(entityTrackerEntry: ServerEntity): Packet<ClientGamePacketListener> =
ClientboundCustomPayloadPacket(
@@ -0,0 +1,76 @@
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.google.common.collect.ImmutableMap
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.EntityTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import java.util.Optional
import java.util.UUID
class FiteMeSoyBoyTask : Behavior<PokemonEntity>(
ImmutableMap.of(
CobblemonMemories.RECENT_CATCH_ATTEMPT, MemoryStatus.VALUE_PRESENT,
)
) {
companion object {
private const val ATTACK_RANGE = 6.0
private const val SPEED = 1.0f
}
private var targetEntity: Optional<ServerPlayer> = Optional.empty()
override fun checkExtraStartConditions(level: ServerLevel, entity: PokemonEntity): Boolean {
this.targetEntity = findSoyBoy(level, entity)
return targetEntity.isPresent
}
override fun canStillUse(level: ServerLevel, entity: PokemonEntity, gameTime: Long): Boolean
= targetEntity.map { it.isAlive }.orElse(false)
&& entity.brain.hasMemoryValue(CobblemonMemories.RECENT_CATCH_ATTEMPT)
private fun findSoyBoy(level: ServerLevel, entity: PokemonEntity): Optional<ServerPlayer> {
val soyBoyUuid = entity.brain.getMemory(CobblemonMemories.RECENT_CATCH_ATTEMPT)?.get() ?: return Optional.empty()
return level.players()
.find { it.uuid == soyBoyUuid }
?.let { Optional.of(it) } ?: Optional.empty()
}
override fun start(level: ServerLevel, entity: PokemonEntity, gameTime: Long) {
addLookWalkTargets(entity)
super.start(level, entity, gameTime)
}
private fun addLookWalkTargets(entity: LivingEntity) {
targetEntity.ifPresent {
val lookTarget = EntityTracker(it, true)
entity.brain.setMemory(MemoryModuleType.LOOK_TARGET, lookTarget)
entity.brain.setMemory(MemoryModuleType.WALK_TARGET, WalkTarget(lookTarget, SPEED, 1))
}
}
override fun stop(level: ServerLevel, entity: PokemonEntity, gameTime: Long) {
targetEntity = Optional.empty()
}
override fun tick(level: ServerLevel, entity: PokemonEntity, gameTime: Long) {
if (targetEntity.isPresent && entity.hasLineOfSight(targetEntity.get())) {
val soyBoy = targetEntity.get()
if (soyBoy.isAlive) {
if (entity.distanceToSqr(soyBoy) <= ATTACK_RANGE * ATTACK_RANGE) {
if (entity.forceBattle(soyBoy)) {
entity.brain.eraseMemory(CobblemonMemories.RECENT_CATCH_ATTEMPT)
}
} else {
addLookWalkTargets(entity)
}
}
}
}
}
@@ -201,7 +201,8 @@ object PokemonBrain {
CobblemonMemories.HERD_LEADER,
CobblemonMemories.HERD_SIZE,
CobblemonMemories.ATTACK_TARGET_DATA,
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH,
CobblemonMemories.RECENT_CATCH_ATTEMPT
)
private fun coreTasks(pokemon: Pokemon) = buildList<Pair<Int, BehaviorControl<in PokemonEntity>>> {
@@ -209,10 +210,12 @@ object PokemonBrain {
add(0 toDF StayAfloatTask(0.8F))
}
add(0 toDF FiteMeSoyBoyTask())
if (pokemon.form.behaviour.combat.willDefendSelf) {
add(0 toDF GetAngryAtAttackerTask.create())
add(1 toDF GetAngryAtAttackerTask.create())
} else {
add(0 toDF FleeFromAttackerTask.create("600".asExpression()))
add(1 toDF FleeFromAttackerTask.create("600".asExpression()))
}
add(0 toDF StopBeingAngryIfTargetDead.create())
@@ -3757,6 +3757,9 @@
"cobblemon.behaviour.pokemon_picks_up_items.name": "Picks Up Items",
"cobblemon.behaviour.pokemon_picks_up_items.desc": "The Pokémon will pick up items that it cares about.",
"cobblemon.behaviour.pokemon_fites_thrower.name": "Fite Poké Ball Thrower",
"cobblemon.behaviour.pokemon_fites_thrower.desc": "Fite me, soy boy!",
"npc.sacchi.name": "Professor Sacchi",
"cobblemon.move.pound": "Pound",
@@ -0,0 +1,42 @@
{
"name": "cobblemon.behaviour.pokemon_fites_thrower.name",
"description": "cobblemon.behaviour.pokemon_fites_thrower.desc",
"entityType": "cobblemon:pokemon",
"onAdd": "q.entity.add_callback('cobblemon:hit_by_pokeball', 'cobblemon:fite_thrower');",
"onRemove": "q.entity.remove_callback('cobblemon:hit_by_pokeball', 'cobblemon:fite_thrower');",
"configurations": [
{
"type": "add_memories_and_sensors",
"memories": [
"cobblemon:recent_catch_attempt"
]
},
{
"type": "add_tasks_to_activity",
"activity": "core",
"tasksByPriority": {
"0": [
"cobblemon:fite_me_soy_boy"
]
}
},
{
"type": "add_tasks_to_activity",
"activity": "idle",
"tasksByPriority": {
"1": [
"switch_to_fight"
]
}
},
{
"type": "add_tasks_to_activity",
"activity": "fight",
"tasksByPriority": {
"3": [
"switch_from_fight"
]
}
}
]
}
@@ -0,0 +1,5 @@
t.thrower = q.pokeball.thrower;
t.thrower != 0 ? {
'Set the entity memory to try engaging the thrower in battle for the next 30 seconds';
q.entity.set_uuid_memory('cobblemon:recent_catch_attempt', t.thrower.uuid, 20 * 60);
};