Compare commits

...
Author SHA1 Message Date
Guilherme Pereira 880f92751c add poke mine task 2025-11-21 01:23:43 -03:00
Guilherme Pereira fba40387bb add teleport poke skill 2025-11-21 01:22:20 -03:00
Guilherme Pereira 1217e6d101 Merge branch 'main' into poke-skills 2025-11-20 16:27:39 -03:00
Paul 905c410aee Added 'enabled' property to poke skills 2025-10-04 11:29:07 +10:00
Paul cea0c826c6 Merge remote-tracking branch 'origin/main' into poke-skills
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt
2025-10-03 15:50:05 +10:00
Paul dc3b17df3b conflicto fixo 2025-09-21 13:01:01 +10:00
Paul 5602dee9ac Merge remote-tracking branch 'origin/main' into poke-skills
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/keybind/keybinds/PartySendBinding.kt
2025-09-21 12:47:03 +10:00
Paul 9e4137cc20 Tighten poke skill keybind time 2025-09-01 21:19:58 +10:00
Paul c92248f6e9 Added poke skill system with example quick_ride.json skill, some GUI improvements needed 2025-09-01 12:13:07 +10:00
46 changed files with 955 additions and 45 deletions
@@ -10,13 +10,13 @@ package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.ai.CobblemonAttackTargetData
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.ai.config.CobblemonMineTargetData
import com.cobblemon.mod.common.api.dialogue.ActiveDialogue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.PrimitiveCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.GlobalPos
import net.minecraft.core.UUIDUtil
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
@@ -92,6 +92,8 @@ object CobblemonMemories {
val DISABLE_WALK_TO_WANTED_ITEM = register<Boolean>("disable_walk_to_wanted_item")
@JvmField
val TIME_TRYING_TO_REACH_WANTED_ITEM = register<Int>("time_trying_to_wanted_item")
@JvmField
val MINE_TARGET = register<CobblemonMineTargetData>("poke_skill_mine_target")
@JvmStatic
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
@@ -147,6 +147,7 @@ import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
import com.cobblemon.mod.common.net.messages.server.RequestPlayerInteractionsPacket
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
import com.cobblemon.mod.common.net.messages.server.SendOutPokemonPacket
import com.cobblemon.mod.common.net.messages.server.UsePokeSkillPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamLeavePacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamRequestPacket
@@ -177,6 +178,7 @@ import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPack
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.skills.PokeSkillMinePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHiddenPacket
@@ -201,6 +203,7 @@ import com.cobblemon.mod.common.net.messages.server.trade.UpdateTradeOfferPacket
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
import com.cobblemon.mod.common.net.serverhandling.ChallengeResponseHandler
import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
import com.cobblemon.mod.common.net.serverhandling.UsePokeSkillHandler
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
@@ -232,6 +235,7 @@ import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandl
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.skills.PokeSkillMinePacketHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetItemHiddenHandler
@@ -325,6 +329,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(MarkingsUpdatePacket.ID, MarkingsUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RideBoostsUpdatePacket.ID, RideBoostsUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RideStaminaUpdatePacket.ID, RideStaminaUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(SkillCooldownsUpdatePacket.ID, SkillCooldownsUpdatePacket::decode, PokemonUpdatePacketHandler()))
// Evolution start
list.add(PacketRegisterInfo(AddEvolutionPacket.ID, AddEvolutionPacket::decode, PokemonUpdatePacketHandler()))
@@ -422,6 +427,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(BehaviourSyncPacket.ID, BehaviourSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MarkRegistrySyncPacket.ID, MarkRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(RideSettingsSyncPacket.ID, RideSettingsSyncPacket::decode, RideSettingsSyncHandler))
list.add(PacketRegisterInfo(PokeSkillsRegistrySyncPacket.ID, PokeSkillsRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
// Effects
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
@@ -511,6 +517,7 @@ object CobblemonNetwork {
// Interaction Packets
list.add(PacketRegisterInfo(InteractPokemonPacket.ID, InteractPokemonPacket::decode, InteractPokemonHandler))
list.add(PacketRegisterInfo(RequestPlayerInteractionsPacket.ID, RequestPlayerInteractionsPacket::decode, RequestInteractionsHandler))
list.add(PacketRegisterInfo(UsePokeSkillPacket.ID, UsePokeSkillPacket::decode, UsePokeSkillHandler))
// Storage Packets
list.add(PacketRegisterInfo(SendOutPokemonPacket.ID, SendOutPokemonPacket::decode, SendOutPokemonHandler))
@@ -610,6 +617,9 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SetEntityBehaviourPacket.ID, SetEntityBehaviourPacket::decode, SetEntityBehaviourHandler))
list.add(PacketRegisterInfo(DamageOnCollisionPacket.ID, DamageOnCollisionPacket::decode, DamageOnCollisionPacketHandler))
// Poké Skills
list.add(PacketRegisterInfo(PokeSkillMinePacket.ID, PokeSkillMinePacket::decode, PokeSkillMinePacketHandler))
// Debug
list.add(PacketRegisterInfo(RequestOpenRidingStatsDebugGUIPacket.ID, RequestOpenRidingStatsDebugGUIPacket::decode, RequestOpenRidingStatsDebugGUIHandler))
@@ -0,0 +1,68 @@
/*
* 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.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.entity.pokemon.PokeSkill
import com.cobblemon.mod.common.net.messages.client.data.PokeSkillsRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.adapters.ActionEffectTimelineAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.TextAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.world.entity.player.Player
/**
* The JSON data registry for all the possible [PokeSkill]s.
*
* @author Hiroku
* @since August 31st, 2025
*/
object CobblemonPokeSkills : JsonDataRegistry<PokeSkill> {
override val id = cobblemonResource("poke_skills")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<CobblemonPokeSkills>()
override val typeToken = TypeToken.get(PokeSkill::class.java)
override val resourcePath = "poke_skills"
override val gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(ActionEffectTimeline::class.java, ActionEffectTimelineAdapter)
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(Component::class.java, TextAdapter)
.create()
var skills = mapOf<ResourceLocation, PokeSkill>()
fun getAccessibleSkills(player: Player, pokemon: Pokemon): List<PokeSkill> {
return skills.values.filter { it.canAccess(player, pokemon) }
}
override fun reload(data: Map<ResourceLocation, PokeSkill>) {
data.forEach { (id, skill) -> skill.id = id }
skills = data
observable.emit(this)
Cobblemon.LOGGER.info("Loaded ${skills.size} PokéSkills")
}
override fun sync(player: ServerPlayer) {
player.sendPacket(PokeSkillsRegistrySyncPacket(skills.values.toList()))
}
}
@@ -0,0 +1,9 @@
package com.cobblemon.mod.common.api.ai.config
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
class CobblemonMineTargetData(
val blockPos: BlockPos,
val direction: Direction,
)
@@ -0,0 +1,36 @@
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.Companion.wrapped
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.entity.ai.PokeSkillMineTask
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.memory.MemoryModuleType
class PokeSkillMineTaskConfig : SingleTaskConfig {
override fun getVariables(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): List<MoLangConfigVariable> {
return listOf()
}
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
behaviourConfigurationContext.addMemories(
CobblemonMemories.MINE_TARGET,
MemoryModuleType.WALK_TARGET,
)
return PokeSkillMineTask().wrapped<PokemonEntity>()
}
}
@@ -123,6 +123,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("poke_skill_mine") to PokeSkillMineTaskConfig::class.java,
)
}
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonPokeSkills
import com.cobblemon.mod.common.CobblemonUnlockableWallpapers
import com.cobblemon.mod.common.Environment
import com.cobblemon.mod.common.api.ai.CobblemonBlockPosTracker
@@ -76,6 +77,7 @@ import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import com.cobblemon.mod.common.entity.PosableEntity
@@ -94,6 +96,9 @@ import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.activestate.ActivePokemonState
import com.cobblemon.mod.common.pokemon.activestate.SentOutState
import com.cobblemon.mod.common.pokemon.activestate.ShoulderedState
import com.cobblemon.mod.common.pokemon.ai.ObtainableItem
import com.cobblemon.mod.common.pokemon.ai.ObtainableItemCondition
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
@@ -104,6 +109,7 @@ import com.mojang.datafixers.util.Either
import java.util.UUID
import kotlin.math.sqrt
import kotlin.random.Random
import net.minecraft.client.player.AbstractClientPlayer
import net.minecraft.commands.arguments.EntityAnchorArgument
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
@@ -606,7 +612,33 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
if (player is AbstractClientPlayer) {
map.put("is_battling") {
DoubleValue(CobblemonClient.battle?.spectating == false) // Checks not-null and not-spectating at the same time, trust
}
}
if (player is ServerPlayer) {
map.put("ride") { params ->
val entity = (params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? Entity ?: return@put DoubleValue.ZERO
player.startRiding(entity)
}
map.put("has_respawn_position") {
DoubleValue(player.respawnPosition != null)
}
map.put("respawn_position") {
player.respawnPosition?.toArrayStruct() ?: DoubleValue.ZERO
}
map.put("respawn_dimension") {
StringValue(player.respawnDimension.location().toString())
}
map.put("teleport_to_respawn") {
val respawnPosition = player.respawnPosition?.center ?: return@put DoubleValue.ZERO
player.teleportTo(respawnPosition.x, respawnPosition.y, respawnPosition.z)
DoubleValue.ONE
}
map.put("is_battling") {
DoubleValue(player.isInBattle())
}
map.put("seen_credits") { _ ->
DoubleValue(player.seenCredits)
}
@@ -834,6 +866,13 @@ object MoLangFunctions {
map.put("delta_movement") {
return@put listOf(entity.deltaMovement.x, entity.deltaMovement.y, entity.deltaMovement.z).asArrayValue(::DoubleValue)
}
map.put("set_position") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
entity.setPos(x, y, z)
return@put DoubleValue.ONE
}
map.put("tags") {
val tags = entity.tags
val array = ArrayStruct(hashMapOf())
@@ -970,6 +1009,7 @@ object MoLangFunctions {
map.put("vertical_velocity") { DoubleValue(entity.deltaMovement.y) }
map.put("is_on_ground") { _ -> DoubleValue(entity.onGround()) }
map.put("world") { _ -> entity.level().worldRegistry.wrapAsHolder(entity.level()).asWorldMoLangValue() }
map.put("dimension") { _ -> StringValue(entity.level().dimension().location().toString()) }
map.put("biome") { _ -> entity.level().getBiome(entity.blockPosition()).asBiomeMoLangValue() }
map.put("is_passenger") { DoubleValue(entity.isPassenger) }
map.put("find_nearby_block") { params ->
@@ -1236,6 +1276,10 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
map.put("is_passenger") { params ->
val passengerEntity = (params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? LivingEntity ?: return@put DoubleValue.ZERO
return@put DoubleValue(passengerEntity in entity.passengers)
}
if (entity is PathfinderMob) {
map.put("walk_to") { params ->
val x = params.getDouble(0)
@@ -1700,7 +1744,7 @@ object MoLangFunctions {
return@put aspects.asArrayValue { StringValue(it) }
}
map.put("form_name") {
StringValue(pokemon.form.name.toString())
StringValue(pokemon.form.name)
}
// Yes, this is the lazy call for a single hardcoded pre-evolution.
// Lol. Lmao, even.
@@ -1893,6 +1937,29 @@ object MoLangFunctions {
evolution?.forceEvolve(pokemon)
StringValue(evolution.toString())
}
map.put("is_sent_out") {
DoubleValue(pokemon.state is SentOutState)
}
map.put("is_shouldered") {
DoubleValue(pokemon.state is ShoulderedState)
}
map.put("entity") {
(pokemon.state as? ActivePokemonState)?.entity?.struct ?: DoubleValue.ZERO
}
map.put("is_rideable") {
return@put DoubleValue(!pokemon.riding.behaviours.isNullOrEmpty())
}
map.put("has_status") { params ->
val status = params.getString(0).asIdentifierDefaultingNamespace()
return@put DoubleValue(pokemon.status?.status?.name == status)
}
map.put("add_skill_cooldown") { params ->
val skillId = params.getString(0).asIdentifierDefaultingNamespace()
val cooldownTicks = params.getIntOrNull(1)
val skill = CobblemonPokeSkills.skills[skillId] ?: return@put DoubleValue.ZERO
pokemon.addSkillCooldown(skillId, cooldownTicks ?: skill.cooldownTicks)
return@put DoubleValue.ONE
}
map
}
)
@@ -2297,7 +2364,7 @@ object MoLangFunctions {
map.put("max_selectable_times") { DoubleValue(dropEntry.maxSelectableTimes.toDouble()) }
map.put("can_drop") { params ->
val pokemon = params.getOrNull<ObjectValue<Pokemon>>(0)?.obj
DoubleValue(if (dropEntry.canDrop(pokemon)) 1.0 else 0.0)
DoubleValue(dropEntry.canDrop(pokemon))
}
map
}
@@ -2559,11 +2626,11 @@ object MoLangFunctions {
)
value.functions.put("is_in") {
val tag = TagKey.create(key, ResourceLocation.parse(it.getString(0).replace("#", "")))
return@put DoubleValue(if (value.obj.`is`(tag)) 1.0 else 0.0)
return@put DoubleValue(value.obj.`is`(tag))
}
value.functions.put("is_of") {
val identifier = ResourceLocation.parse(it.getString(0))
return@put DoubleValue(if (value.obj.`is`(identifier)) 1.0 else 0.0)
return@put DoubleValue(value.obj.`is`(identifier))
}
return value
}
@@ -50,7 +50,7 @@ class ActionEffectTimeline(
}
fun chainKeyframes(context: ActionEffectContext, iterator: Iterator<ActionEffectKeyframe>, finalFuture: CompletableFuture<Unit>) {
if (!iterator.hasNext()) {
if (!iterator.hasNext() || context.cancelled) {
finalFuture.complete(Unit)
} else {
val keyframe = iterator.next()
@@ -70,6 +70,7 @@ class ActionEffectContext(
val runtime: MoLangRuntime,
var canBeInterrupted: Boolean = false,
var interrupted: Boolean = false,
var cancelled: Boolean = false,
var currentKeyframes: MutableList<ActionEffectKeyframe> = mutableListOf(),
var level: Level? = null
) {
@@ -57,6 +57,7 @@ object ActionEffects : JsonDataRegistry<ActionEffectTimeline> {
ActionEffectKeyframe.register<RunActionEffectKeyframe>("run_action_effect")
ActionEffectKeyframe.register<EntityParticlesActionEffectKeyframe>("entity_particles")
ActionEffectKeyframe.register<EntitySoundActionEffectKeyframe>("entity_sound")
ActionEffectKeyframe.register<CancelActionEffectKeyframe>("cancel")
}
override val gson = GsonBuilder()
@@ -0,0 +1,19 @@
/*
* 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.api.moves.animations.keyframes
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import java.util.concurrent.CompletableFuture
class CancelActionEffectKeyframe : ConditionalActionEffectKeyframe() {
override fun playWhenTrue(context: ActionEffectContext): CompletableFuture<Unit> {
context.cancelled = true
return CompletableFuture.completedFuture(Unit)
}
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.api.moves.animations.keyframes
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.scheduling.delayedFuture
@@ -102,7 +102,7 @@ open class PlayerPartyStore(
for (pokemon in this) {
// Awake from fainted
if (pokemon.isFainted()) {
//Skip awaken logic check if config value is 0
// Skip awaken logic check if config value is 0
if (Cobblemon.config.faintAwakenHealthPercent > 0) {
pokemon.faintedTimer -= 1
if (pokemon.faintedTimer <= -1) {
@@ -119,7 +119,7 @@ open class PlayerPartyStore(
}
// Passive healing while less than full health
else if (pokemon.currentHealth < pokemon.maxHealth) {
//Skip passive healing logic check if config value is 0
// Skip passive healing logic check if config value is 0
if (Cobblemon.config.healPercent > 0) {
pokemon.healTimer--
if (pokemon.healTimer <= -1) {
@@ -159,7 +159,7 @@ open class PlayerPartyStore(
// Interaction Cooldown
if (pokemon.interactionCooldowns.any()) {
pokemon.tickInteractionCooldown()
pokemon.tickInteractionCooldowns()
}
pokemon.features.filterIsInstance<TickingSpeciesFeature>().forEach { it.onSecondPassed(player.serverLevel(), pokemon, null) }
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.pasture.PastureLinkManager
import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.api.text.italicise
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.textClickHandlers
import com.cobblemon.mod.common.block.PastureBlock
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
@@ -113,7 +112,7 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
pokemon.tickMetabolism()
}
if (pokemon.interactionCooldowns.any()) {
pokemon.tickInteractionCooldown()
pokemon.tickInteractionCooldowns()
}
}
blockEntity.togglePastureOn(blockEntity.getInRangeViewerCount(world, blockEntity.blockPos) > 0)
@@ -165,7 +165,7 @@ object CobblemonClient {
PlatformEvents.CLIENT_TICK_POST.subscribe { event ->
val player = event.client.player
if (player != null) {
var selectedItem = player.inventory.getItem(player.inventory.selected)
val selectedItem = player.inventory.getItem(player.inventory.selected)
if (pokedexUsageContext.scanningGuiOpen &&
!(selectedItem.`is`(CobblemonItemTags.POKEDEX)) &&
!(player.offhandItem.`is`(CobblemonItemTags.POKEDEX) &&
@@ -180,6 +180,8 @@ object CobblemonClient {
return@subscribe
}
storage.party.forEach { it?.tickSkillCooldowns() }
val nearbyPokemon = player.level().getEntities(
player,
AABB.ofSize(player.position(), 16.0, 16.0, 16.0)
@@ -28,7 +28,7 @@ class InteractWheelButton(
private val tooltipText: String?,
x: Int,
y: Int,
private val isEnabled: Boolean,
private val isEnabled: () -> Boolean,
private val colour: () -> Vector3f?,
onPress: OnPress,
private val canHover: (Double, Double) -> Boolean
@@ -74,12 +74,12 @@ class InteractWheelButton(
y = y,
width = width,
height = height,
vOffset = if (isHovered(mouseX.toFloat(), mouseY.toFloat()) && isEnabled) height else 0,
vOffset = if (isHovered(mouseX.toFloat(), mouseY.toFloat()) && isEnabled()) height else 0,
textureHeight = height * 2,
alpha = if (isEnabled) 1f else 0.4F
alpha = if (isEnabled()) 1f else 0.4F
)
if(isEnabled && isHovered(mouseX.toFloat(), mouseY.toFloat())){
if (isEnabled() && isHovered(mouseX.toFloat(), mouseY.toFloat())){
tooltipText?.let {
context.renderTooltip(Minecraft.getInstance().font, Component.translatable(it), mouseX, mouseY)
}
@@ -95,7 +95,7 @@ class InteractWheelButton(
y = iconY,
width = ICON_SIZE,
height = ICON_SIZE,
alpha = if (isEnabled) 1F else 0.2F,
alpha = if (isEnabled()) 1F else 0.2F,
red = colour.x,
green = colour.y,
blue = colour.z,
@@ -114,7 +114,7 @@ class InteractWheelButton(
y = iconY - ICON_SIZE,
width = ICON_SIZE,
height = ICON_SIZE,
alpha = if (isEnabled) 1F else 0.4F,
alpha = if (isEnabled()) 1F else 0.4F,
red = colour.x,
green = colour.y,
blue = colour.z,
@@ -95,7 +95,7 @@ class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelO
tooltipText = option?.tooltipText,
x = x,
y = y,
isEnabled = option != null && option.enabled,
isEnabled = { option != null && option.enabled() },
colour = option?.colour ?: { null },
onPress = { option?.onPress?.invoke() },
canHover = { a: Double, b: Double -> !isMouseInCenter(a, b)}
@@ -9,20 +9,24 @@
package com.cobblemon.mod.common.client.gui.interact.wheel
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonPokeSkills
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.interaction.PokemonInteractionGUICreationEvent
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.interact.battleRequest.BattleConfigureGUI
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.server.UsePokeSkillPacket
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
import com.cobblemon.mod.common.net.messages.server.trade.AcceptTradeRequestPacket
import com.cobblemon.mod.common.net.messages.server.trade.OfferTradePacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.common.collect.ArrayListMultimap
import com.google.common.collect.Multimap
import java.util.UUID
import net.minecraft.client.Minecraft
import net.minecraft.client.player.AbstractClientPlayer
import net.minecraft.network.chat.Component
import org.joml.Vector3f
@@ -30,7 +34,7 @@ fun createPokemonInteractGui(pokemonID: UUID, canMountShoulder: Boolean, canGive
val mountShoulder = InteractWheelOption(
iconResource = cobblemonResource("textures/gui/interact/interact_wheel_icon_shoulder.png"),
tooltipText = "cobblemon.ui.interact.mount.shoulder",
enabled = canMountShoulder,
enabled = { canMountShoulder },
onPress = {
if (canMountShoulder) {
InteractPokemonPacket(pokemonID, InteractTypePokemon.SHOULDER).sendToServer()
@@ -41,7 +45,7 @@ fun createPokemonInteractGui(pokemonID: UUID, canMountShoulder: Boolean, canGive
val giveHeldItem = InteractWheelOption(
iconResource = cobblemonResource("textures/gui/interact/interact_wheel_icon_held_item.png"),
tooltipText = "cobblemon.ui.interact.give.item",
enabled = canGiveHeld,
enabled = { canGiveHeld },
onPress = {
if (canGiveHeld) {
InteractPokemonPacket(pokemonID, InteractTypePokemon.HELD_ITEM).sendToServer()
@@ -52,7 +56,7 @@ fun createPokemonInteractGui(pokemonID: UUID, canMountShoulder: Boolean, canGive
val giveCosmeticItem = InteractWheelOption(
iconResource = cobblemonResource("textures/gui/interact/interact_wheel_icon_cosmetic_item.png"),
tooltipText = "cobblemon.ui.interact.give.cosmetic_item",
enabled = canGiveCosmetic,
enabled = { canGiveCosmetic },
onPress = {
if (canGiveCosmetic) {
InteractPokemonPacket(pokemonID, InteractTypePokemon.COSMETIC_ITEM).sendToServer()
@@ -64,7 +68,7 @@ fun createPokemonInteractGui(pokemonID: UUID, canMountShoulder: Boolean, canGive
val ride = InteractWheelOption(
iconResource = cobblemonResource("textures/gui/interact/interact_wheel_icon_ride.png"),
tooltipText = "cobblemon.ui.interact.ride",
enabled = canRide,
enabled = { canRide },
onPress = {
if (canRide) {
InteractPokemonPacket(pokemonID, InteractTypePokemon.RIDE).sendToServer()
@@ -177,6 +181,35 @@ fun createPlayerInteractGui(optionsPacket: PlayerInteractOptionsPacket): Interac
return InteractWheelGUI(options, Component.translatable("cobblemon.ui.interact.player"))
}
fun createPokeSkillGui(player: AbstractClientPlayer, pokemon: Pokemon): InteractWheelGUI {
val accessibleSkills = CobblemonPokeSkills.getAccessibleSkills(player, pokemon)
val options: Multimap<Orientation, InteractWheelOption> = ArrayListMultimap.create()
for (skill in accessibleSkills) {
val skillOption = InteractWheelOption(
iconResource = skill.icon,
enabled = { skill.id !in pokemon.skillCooldownsTicks },
tooltipText = skill.name,
onPress = {
CobblemonNetwork.sendToServer(UsePokeSkillPacket(skillId = skill.id, pokemonId = pokemon.uuid))
closeGUI()
}
)
when (options.size()) {
0 -> options.put(Orientation.NORTH, skillOption)
1 -> options.put(Orientation.NORTHEAST, skillOption)
2 -> options.put(Orientation.SOUTHEAST, skillOption)
3 -> options.put(Orientation.SOUTH, skillOption)
4 -> options.put(Orientation.SOUTHWEST, skillOption)
5 -> options.put(Orientation.NORTHWEST, skillOption)
6 -> options.put(Orientation.WEST, skillOption)
7 -> options.put(Orientation.EAST, skillOption)
else -> break // We only have 8 slots
}
}
return InteractWheelGUI(options, Component.translatable("cobblemon.ui.interact.poke_skill"))
}
private fun getLangKey(status: PlayerInteractOptionsPacket.OptionStatus) : String {
return when (status) {
PlayerInteractOptionsPacket.OptionStatus.TOO_FAR -> "cobblemon.ui.interact.too_far"
@@ -14,7 +14,7 @@ import org.joml.Vector3f
data class InteractWheelOption(
val iconResource: ResourceLocation,
val secondaryIconResource: ResourceLocation? = null,
val enabled: Boolean = true,
val enabled: () -> Boolean = { true },
val tooltipText: String?,
val colour: () -> Vector3f? = { null },
val onPress: () -> Unit
@@ -38,6 +38,7 @@ object CobblemonKeyBinds {
val PARTY_OVERLAY_DOWN = this.queue(DownShiftPartyBinding)
val PARTY_OVERLAY_UP = this.queue(UpShiftPartyBinding)
val SEND_OUT_POKEMON = this.queue(PartySendBinding)
val OPEN_POKE_SKILLS_WHEEL = this.queue(OpenPokeSkillsWheelBinding)
fun register(registrar: (KeyMapping) -> Unit) {
this.keyBinds.forEach(registrar::invoke)
@@ -0,0 +1,17 @@
package com.cobblemon.mod.common.client.keybind.keybinds
import com.cobblemon.mod.common.client.keybind.CobblemonKeyBinding
import com.cobblemon.mod.common.client.keybind.KeybindCategories
import com.cobblemon.mod.common.net.messages.server.pokemon.skills.PokeSkillMinePacket
import com.mojang.blaze3d.platform.InputConstants
object OpenPokeSkillsWheelBinding : CobblemonKeyBinding(
"key.cobblemon.open_poke_skills_wheel",
InputConstants.Type.KEYSYM,
InputConstants.KEY_B,
KeybindCategories.COBBLEMON_CATEGORY
) {
override fun onPress() {
PokeSkillMinePacket().sendToServer()
}
}
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.battle.ClientBattle
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.interact.wheel.createPokeSkillGui
import com.cobblemon.mod.common.client.keybind.CobblemonBlockingKeyBinding
import com.cobblemon.mod.common.client.keybind.KeybindCategories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -26,12 +27,12 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.isUsingPokedex
import com.cobblemon.mod.common.util.traceFirstEntityCollision
import com.mojang.blaze3d.platform.InputConstants
import kotlin.math.pow
import net.minecraft.client.Minecraft
import net.minecraft.client.player.LocalPlayer
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.ClipContext
import kotlin.math.pow
object PartySendBinding : CobblemonBlockingKeyBinding(
"key.cobblemon.throwpartypokemon",
@@ -39,6 +40,7 @@ object PartySendBinding : CobblemonBlockingKeyBinding(
InputConstants.KEY_R,
KeybindCategories.COBBLEMON_CATEGORY
) {
const val POKE_SKILL_TIME = 0.7F
var canApplyChange = true
var heldDownSeconds = 0F
@@ -53,7 +55,19 @@ object PartySendBinding : CobblemonBlockingKeyBinding(
override fun onTick() {
if (wasDown) {
if (heldDownSeconds < 100) {
val couldRunSkill = heldDownSeconds < POKE_SKILL_TIME
heldDownSeconds += Minecraft.getInstance().timer.getGameTimeDeltaPartialTick(false)
if (couldRunSkill && heldDownSeconds >= POKE_SKILL_TIME && canApplyChange) {
val player = Minecraft.getInstance().player
val selectedPokemon = CobblemonClient.storage.party.get(CobblemonClient.storage.selectedSlot)
if (selectedPokemon != null && player != null) {
canApplyChange = false
val gui = createPokeSkillGui(player, selectedPokemon)
Minecraft.getInstance().setScreen(gui)
}
}
}
} else {
heldDownSeconds = 0F
@@ -105,7 +119,8 @@ object PartySendBinding : CobblemonBlockingKeyBinding(
entityClass = LivingEntity::class.java,
ignoreEntity = player,
maxDistance = Cobblemon.config.battleSpectateMaxDistance,
collideBlock = ClipContext.Fluid.NONE)
collideBlock = ClipContext.Fluid.NONE
)
if (canSendOutPokemon(player, targetEntity)) {
sendToServer(SendOutPokemonPacket(CobblemonClient.storage.selectedSlot))
} else {
@@ -127,10 +142,13 @@ object PartySendBinding : CobblemonBlockingKeyBinding(
//The server sends a packet back that opens the player interaction menu with the proper options
sendToServer(RequestPlayerInteractionsPacket(entity.uuid, entity.id, pokemon.uuid))
}
is PokemonEntity -> {
if (!entity.canBattle(player) || entity.position().distanceToSqr(player.position()) > Cobblemon.config.battleWildMaxDistance.pow(2)) return
sendToServer(BattleChallengePacket(entity.id, pokemon.uuid, BattleFormat.GEN_9_SINGLES))
}
if (!entity.canBattle(player) || entity.position()
.distanceToSqr(player.position()) > Cobblemon.config.battleWildMaxDistance.pow(2)
) return
sendToServer(BattleChallengePacket(entity.id, pokemon.uuid, BattleFormat.GEN_9_SINGLES))
}
}
}
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.CobblemonBehaviours
import com.cobblemon.mod.common.CobblemonCallbacks
import com.cobblemon.mod.common.CobblemonCosmeticItems
import com.cobblemon.mod.common.CobblemonMechanics
import com.cobblemon.mod.common.CobblemonPokeSkills
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.CobblemonUnlockableWallpapers
import com.cobblemon.mod.common.api.abilities.Abilities
@@ -77,6 +78,7 @@ object CobblemonDataProvider : DataProvider {
this.register(GlobalSpeciesFeatures, reloadable = false)
this.register(SpeciesFeatureAssignments, reloadable = false)
this.register(ActionEffects, reloadable = true)
this.register(CobblemonPokeSkills, reloadable = true)
this.register(Moves, reloadable = false)
this.register(Abilities, reloadable = false)
this.register(CobblemonBehaviours, reloadable = false)
@@ -0,0 +1,91 @@
package com.cobblemon.mod.common.entity.ai
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.google.common.collect.ImmutableMap
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
class PokeSkillMineTask : Behavior<PokemonEntity>(
ImmutableMap.of(
CobblemonMemories.MINE_TARGET, MemoryStatus.VALUE_PRESENT,
MemoryModuleType.WALK_TARGET, MemoryStatus.REGISTERED,
),
Int.MAX_VALUE,
Int.MAX_VALUE,
) {
override fun checkExtraStartConditions(level: ServerLevel, entity: PokemonEntity): Boolean {
val mineTarget = entity.brain.getMemory(CobblemonMemories.MINE_TARGET).orElse(null) ?: return false
val blockPos = mineTarget.blockPos
return !level.isEmptyBlock(blockPos)
}
override fun canStillUse(level: ServerLevel, entity: PokemonEntity, gameTime: Long): Boolean {
val mineTarget = entity.brain.getMemory(CobblemonMemories.MINE_TARGET).orElse(null) ?: return false
val blockPos = mineTarget.blockPos
return !level.isEmptyBlock(blockPos)
}
override fun tick(level: ServerLevel, entity: PokemonEntity, gameTime: Long) {
val mineTarget = entity.brain.getMemory(CobblemonMemories.MINE_TARGET).orElse(null) ?: return
val blockPos = mineTarget.blockPos
val direction = mineTarget.direction
val walkPos = blockPos.relative(direction)
entity.brain.setMemory(
MemoryModuleType.WALK_TARGET,
WalkTarget(BlockPosTracker(walkPos), 1f, 0)
)
val distSq = entity.distanceToSqr(blockPos.center)
if (distSq <= 5.0) {
val blocksToMine = get3x3AreaAround(blockPos, direction)
for (blockToMine in blocksToMine ) {
val state = level.getBlockState(blockToMine)
if (!state.isAir) {
level.destroyBlock(blockToMine, true, entity)
}
}
entity.brain.eraseMemory(CobblemonMemories.MINE_TARGET)
}
}
fun get3x3AreaAround(pos: BlockPos, face: Direction): List<BlockPos> {
val list = mutableListOf<BlockPos>()
when (face) {
Direction.UP, Direction.DOWN -> {
// Horizontal 3×3 (x/z)
for (dx in -1..1)
for (dz in -1..1)
list += pos.offset(dx, 0, dz)
}
Direction.NORTH, Direction.SOUTH -> {
// Vertical 3×3 (x/y)
for (dx in -1..1)
for (dy in -1..1)
list += pos.offset(dx, dy, 0)
}
Direction.EAST, Direction.WEST -> {
// Vertical 3×3 (z/y)
for (dz in -1..1)
for (dy in -1..1)
list += pos.offset(0, dy, dz)
}
}
return list
}
}
@@ -0,0 +1,118 @@
/*
* 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
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.codec.EXPRESSION_LIKE_CODEC
import com.cobblemon.mod.common.util.isInBattle
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
/**
* The reference object for a skill that a Pokémon can use in the overworld.
*
* @author Hiroku
* @since August 31st, 2025
*/
class PokeSkill(
val name: String,
val enabled: Boolean = true,
val actionEffect: ActionEffectTimeline,
val icon: ResourceLocation,
val cooldownTicks: Int,
val accessCondition: ExpressionLike = "true".asExpressionLike(),
val useCondition: ExpressionLike = "true".asExpressionLike()
) {
companion object {
val runtime = MoLangRuntime().setup()
val CODEC = RecordCodecBuilder.create<PokeSkill> { instance ->
instance.group(
Codec.STRING.fieldOf("name").forGetter { it.name },
Codec.BOOL.fieldOf("enabled").forGetter { it.enabled },
Codec.unit<ActionEffectTimeline> { ActionEffectTimeline.NONE }.fieldOf("actionEffect").forGetter { it.actionEffect },
ResourceLocation.CODEC.fieldOf("icon").forGetter { it.icon },
Codec.INT.fieldOf("cooldownTicks").forGetter { it.cooldownTicks },
EXPRESSION_LIKE_CODEC.fieldOf("accessCondition").forGetter { it.accessCondition },
EXPRESSION_LIKE_CODEC.fieldOf("useCondition").forGetter { it.useCondition }
).apply(instance, ::PokeSkill)
}
val PACKET_CODEC = ByteBufCodecs.fromCodecWithRegistries(CODEC)
}
@Transient
lateinit var id: ResourceLocation
fun canAccess(player: Player, pokemon: Pokemon): Boolean {
if (!enabled) {
return false
}
runtime.withQueryValue("player", player.asMostSpecificMoLangValue())
runtime.withQueryValue("pokemon", pokemon.struct)
val hasAccess = runtime.resolveBoolean(accessCondition)
return hasAccess
}
fun canUse(player: Player, pokemon: Pokemon): Boolean {
val status = pokemon.status?.status
if (id in pokemon.skillCooldownsTicks || player.isInBattle() || status == Statuses.SLEEP || status == Statuses.FROZEN) {
return false
}
runtime.withQueryValue("player", player.asMostSpecificMoLangValue())
runtime.withQueryValue("pokemon", pokemon.struct)
val canUse = runtime.resolveBoolean(useCondition)
return canUse
}
fun use(
player: ServerPlayer,
pokemon: Pokemon,
// context information!!!
): Boolean {
if (!canUse(player, pokemon)) return false
val runtime = MoLangRuntime().setup()
runtime.withQueryValue("player", player.asMostSpecificMoLangValue())
runtime.withQueryValue("pokemon", pokemon.struct)
runtime.withQueryValue("skill_id", StringValue(id.toString()))
// put the context in the coconut
// blablabalbalbal doing that now
val providers = mutableListOf<Any>(TargetsProvider(player))
pokemon.entity?.let { providers.add(UsersProvider(it)) }
// build the context
val actionEffectContext = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers,
)
// run the thing. Store the context on the pokemon so we know we have one running? prevent it from doing another? idk man
val future = actionEffect.run(actionEffectContext)
return true
}
}
@@ -20,12 +20,13 @@ object ServerTickHandler {
secondsTick++
if (secondsTick == 20) {
secondsTick = 0
// Party tick
for (player in server.playerList.players) {
player.party().onSecondPassed(player)
// Party tick
for (player in server.playerList.players) {
val party = player.party()
party.forEach { it.tickSkillCooldowns() }
if (secondsTick == 20) {
secondsTick = 0
party.onSecondPassed(player)
}
}
}
@@ -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.net.messages.client.data
import com.cobblemon.mod.common.CobblemonPokeSkills
import com.cobblemon.mod.common.entity.pokemon.PokeSkill
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class PokeSkillsRegistrySyncPacket(
pokeSkills: Collection<PokeSkill>
) : DataRegistrySyncPacket<PokeSkill, PokeSkillsRegistrySyncPacket>(pokeSkills) {
override val id = ID
override fun encodeEntry(buffer: RegistryFriendlyByteBuf, entry: PokeSkill) {
PokeSkill.PACKET_CODEC.encode(buffer, entry)
}
override fun decodeEntry(buffer: RegistryFriendlyByteBuf): PokeSkill? {
return PokeSkill.PACKET_CODEC.decode(buffer)
}
override fun synchronizeDecoded(entries: Collection<PokeSkill>) {
CobblemonPokeSkills.reload(entries.associateBy { it.id })
}
companion object {
val ID = cobblemonResource("poke_skill_sync")
fun decode(buffer: RegistryFriendlyByteBuf) = PokeSkillsRegistrySyncPacket(emptyList()).apply { decodeBuffer(buffer) }
}
}
@@ -0,0 +1,51 @@
/*
* 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.net.messages.client.pokemon.update
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readSizedInt
import com.cobblemon.mod.common.util.writeSizedInt
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* Packet that adds a single skill cooldown to a Pokémon's skill cooldowns map.
*
* @author Hiroku
* @since August 31st, 2025
*/
class SkillCooldownsUpdatePacket(
pokemon: () -> Pokemon?,
val skillId: ResourceLocation,
val cooldownTicks: Int
) : SingleUpdatePacket<Pair<ResourceLocation, Int>, SkillCooldownsUpdatePacket>(pokemon, skillId to cooldownTicks) {
override val id: ResourceLocation = ID
override fun encodeValue(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(skillId)
buffer.writeSizedInt(IntSize.U_SHORT, cooldownTicks)
}
override fun set(pokemon: Pokemon, value: Pair<ResourceLocation, Int>) {
val (skillId, cooldownTicks) = value
pokemon.skillCooldownsTicks[skillId] = cooldownTicks
}
companion object {
val ID = cobblemonResource("skill_cooldowns_update")
fun decode(buffer: RegistryFriendlyByteBuf): SkillCooldownsUpdatePacket {
val pokemon = decodePokemon(buffer)
val skillId = buffer.readResourceLocation()
val cooldownTicks = buffer.readSizedInt(IntSize.U_SHORT)
return SkillCooldownsUpdatePacket(pokemon, skillId, cooldownTicks)
}
}
}
@@ -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.net.messages.server
import com.cobblemon.mod.common.api.net.NetworkPacket
import java.util.UUID
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* Packet sent by the client to the server to indicate that it wants to use a Poké Skill.
*
* The server will validate if the skill can be used, and if so, will execute it.
*
* @param skillId The ID of the skill to use.
* @param pokemonId The UUID of the Pokémon using the skill.
*
* @author Hiroku
* @since August 31st, 2025
*/
class UsePokeSkillPacket(
val skillId: ResourceLocation,
val pokemonId: UUID,
) : NetworkPacket<UsePokeSkillPacket> {
override val id: ResourceLocation = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(skillId)
buffer.writeUUID(pokemonId)
}
companion object {
val ID = ResourceLocation("cobblemon", "use_poke_skill")
fun decode(buffer: RegistryFriendlyByteBuf): UsePokeSkillPacket {
return UsePokeSkillPacket(
buffer.readResourceLocation(),
buffer.readUUID()
)
}
}
}
@@ -0,0 +1,16 @@
package com.cobblemon.mod.common.net.messages.server.pokemon.skills
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
class PokeSkillMinePacket() : NetworkPacket<PokeSkillMinePacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
}
companion object {
val ID = cobblemonResource("poke_skill_mine")
fun decode(buffer: RegistryFriendlyByteBuf) = PokeSkillMinePacket()
}
}
@@ -0,0 +1,27 @@
/*
* 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.net.serverhandling
import com.cobblemon.mod.common.CobblemonPokeSkills
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.server.UsePokeSkillPacket
import com.cobblemon.mod.common.util.party
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
object UsePokeSkillHandler : ServerNetworkPacketHandler<UsePokeSkillPacket> {
override fun handle(packet: UsePokeSkillPacket, server: MinecraftServer, player: ServerPlayer) {
val party = player.party()
val pokemon = party[packet.pokemonId] ?: return // Who?
val skill = CobblemonPokeSkills.getAccessibleSkills(player, pokemon).firstOrNull { it.id == packet.skillId } ?: return // What skill?
if (skill.canUse(player, pokemon)) {
skill.use(player, pokemon)
}
}
}
@@ -0,0 +1,43 @@
package com.cobblemon.mod.common.net.serverhandling.pokemon.skills
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.config.CobblemonMineTargetData
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.server.pokemon.skills.PokeSkillMinePacket
import com.cobblemon.mod.common.pokemon.activestate.ActivePokemonState
import com.cobblemon.mod.common.pokemon.activestate.ShoulderedState
import com.cobblemon.mod.common.util.raycast
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.HitResult
object PokeSkillMinePacketHandler : ServerNetworkPacketHandler<PokeSkillMinePacket> {
override fun handle(
packet: PokeSkillMinePacket,
server: MinecraftServer,
player: ServerPlayer
) {
val rayCastHitResult = player.raycast(5f, ClipContext.Fluid.NONE)
if (rayCastHitResult.type != HitResult.Type.BLOCK) {
return
}
val party = Cobblemon.storage.getParty(player)
val pokemon = party.get(0) ?: return
val state = pokemon.state
if (state is ShoulderedState || state !is ActivePokemonState) {
return
}
val pokemonEntity = state.entity
if (pokemonEntity == null) {
return
}
val mineTargetData = CobblemonMineTargetData(rayCastHitResult.blockPos, rayCastHitResult.direction)
pokemonEntity.brain.setMemory(CobblemonMemories.MINE_TARGET, mineTargetData)
}
}
@@ -678,6 +678,8 @@ open class Pokemon : ShowdownIdentifiable {
val riding: RidingProperties
get() = this.form.riding
var skillCooldownsTicks: MutableMap<ResourceLocation, Int> = mutableMapOf()
init {
storeCoordinates.subscribe { if (it != null && it.store !is PCStore && this.tetheringId != null) afterOnServer(seconds = 0.05F) { this.tetheringId = null } }
storeCoordinates.subscribe {
@@ -1037,13 +1039,23 @@ open class Pokemon : ShowdownIdentifiable {
this.interactionCooldowns.remove(group)
}
open fun tickInteractionCooldown() {
open fun tickInteractionCooldowns() {
this.interactionCooldowns.entries.forEach { (key, value) ->
val newValue = value - 1
if(newValue <= 0)
this.interactionCooldowns.remove(key)
if (newValue <= 0)
this.interactionCooldowns -= key
else
this.interactionCooldowns.put(key, newValue)
this.interactionCooldowns[key] = newValue
}
}
fun tickSkillCooldowns() {
this.skillCooldownsTicks.entries.forEach { (key, value) ->
val newValue = value - 1
if (newValue <= 0)
this.skillCooldownsTicks -= key
else
this.skillCooldownsTicks[key] = newValue
}
}
@@ -2096,6 +2108,11 @@ open class Pokemon : ShowdownIdentifiable {
return true
}
fun addSkillCooldown(skillId: ResourceLocation, cooldownTicks: Int) {
skillCooldownsTicks[skillId] = cooldownTicks
onChange(SkillCooldownsUpdatePacket({ this }, skillId, cooldownTicks))
}
fun notify(packet: PokemonUpdatePacket<*>) {
storeCoordinates.get()?.run { sendPacketToPlayers(store.getObservingPlayers(), packet) }
}
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.entity.ai.FollowWalkTargetTask
import com.cobblemon.mod.common.entity.ai.GetAngryAtAttackerTask
import com.cobblemon.mod.common.entity.ai.LookAroundTaskWrapper
import com.cobblemon.mod.common.entity.ai.MoveToAttackTargetTask
import com.cobblemon.mod.common.entity.ai.PokeSkillMineTask
import com.cobblemon.mod.common.entity.ai.StayAfloatTask
import com.cobblemon.mod.common.entity.ai.SwapActivityTask
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -201,7 +202,8 @@ object PokemonBrain {
CobblemonMemories.HERD_LEADER,
CobblemonMemories.HERD_SIZE,
CobblemonMemories.ATTACK_TARGET_DATA,
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH
CobblemonMemories.NEARBY_SWEET_BERRY_BUSH,
CobblemonMemories.MINE_TARGET,
)
private fun coreTasks(pokemon: Pokemon) = buildList<Pair<Int, BehaviorControl<in PokemonEntity>>> {
@@ -253,6 +255,7 @@ object PokemonBrain {
}
add(0 toDF SwapActivityTask.possessing(MemoryModuleType.ATTACK_TARGET, Activity.FIGHT))
add(0 toDF SwapActivityTask.possessing(MemoryModuleType.AVOID_TARGET, Activity.AVOID))
add(0 toDF PokeSkillMineTask())
// add(0 toDF HuntPlayerTask()) // commenting this out to test other things
add(1 toDF FertilizerTask())
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.pokemon.status.statuses.persistent
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.util.cobblemonResource
class FrozenStatus : PersistentStatus(
name = cobblemonResource("frozen"),
showdownName = "frz",
@@ -20,6 +20,7 @@ object DataKeys {
const val POKEMON_FRIENDSHIP = "Friendship"
const val POKEMON_FULLNESS = "Fullness"
const val POKEMON_INTERACTION_COOLDOWN = "InteractionCooldown"
const val POKEMON_SKILL_COOLDOWNS = "SkillCooldowns"
const val POKEMON_IVS = "IVs"
const val POKEMON_IVS_BASE = "Base"
@@ -99,10 +99,16 @@ fun ServerPlayer.didSleep(): Boolean {
return true
}
fun ServerPlayer.isInBattle() = BattleRegistry.getBattleByParticipatingPlayer(this) != null
fun Player.isInBattle() = if (this is ServerPlayer) {
BattleRegistry.getBattleByParticipatingPlayer(this) != null
} else {
CobblemonClient.battle?.spectating == false
}
var ServerPlayer.spawner: PlayerSpawner
get() = (this as PlayerSpawnerAccessor).getPlayerSpawner()
set(value) = (this as PlayerSpawnerAccessor).setPlayerSpawner(value)
fun ServerPlayer.getBattleState(): Pair<PokemonBattle, BattleActor>? {
val battle = BattleRegistry.getBattleByParticipatingPlayer(this)
if (battle != null) {
@@ -0,0 +1,31 @@
/*
* 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.util.adapters
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import java.lang.reflect.Type
/**
* Adapter for deserializing a reference to an [ActionEffectTimeline] from its identifier.
*
* @author Hiroku
* @since August 31st, 2025
*/
object ActionEffectTimelineAdapter : JsonDeserializer<ActionEffectTimeline> {
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): ActionEffectTimeline {
val identifier = json.asString.asIdentifierDefaultingNamespace()
val timeline = ActionEffects.actionEffects[identifier]
return timeline ?: throw IllegalArgumentException("No ActionEffectTimeline found for id $identifier")
}
}
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.util.codec
import com.bedrockk.molang.Expression
import com.bedrockk.molang.MoLang
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.getString
import com.mojang.serialization.Codec
import com.mojang.serialization.Codec.optionalField
@@ -29,6 +31,17 @@ val EXPRESSION_CODEC = object : PrimitiveCodec<Expression> {
}
}
val EXPRESSION_LIKE_CODEC = object : PrimitiveCodec<ExpressionLike> {
override fun <T> read(ops: DynamicOps<T>, input: T): DataResult<ExpressionLike> {
return ops.getStringValue(input).map { it.asExpressionLike() }
}
override fun <T> write(ops: DynamicOps<T>, value: ExpressionLike): T {
return ops.createString(value.getString())
}
}
// special codec that always writes default value
// vanilla changed the behaviour of Codec#optionalFieldOf(String, A) so that it OMITS default values when encoding
// later on decoding it assumes no value -> default, but we want some default being encoded anyways so it can be targeted by commands
@@ -35,6 +35,7 @@ internal data class ClientPokemonP3(
val rideBoosts: Map<String, Float>,
val currentFullness: Int,
val interactionCooldowns: Map<ResourceLocation, Int>,
val skillCooldownTicks: Map<ResourceLocation, Int>,
) : Partial<Pokemon> {
override fun into(other: Pokemon): Pokemon {
@@ -53,6 +54,7 @@ internal data class ClientPokemonP3(
this.rideBoosts.let { other.setRideBoosts(it.mapKeys { RidingStat.valueOf(it.key) }) }
other.currentFullness = this.currentFullness
other.interactionCooldowns = this.interactionCooldowns.toMutableMap()
other.skillCooldownsTicks = this.skillCooldownTicks.toMutableMap()
return other
}
@@ -75,7 +77,8 @@ internal data class ClientPokemonP3(
Codec.unboundedMap(Codec.STRING, Codec.FLOAT).fieldOf(DataKeys.POKEMON_RIDE_BOOSTS).forGetter(ClientPokemonP3::rideBoosts),
Codec.intRange(0, 100).fieldOf(DataKeys.POKEMON_FULLNESS).forGetter(ClientPokemonP3::currentFullness),
Codec.unboundedMap(ResourceLocation.CODEC, Codec.INT).fieldOf(DataKeys.POKEMON_INTERACTION_COOLDOWN).forGetter(ClientPokemonP3::interactionCooldowns),
).apply(instance, ::ClientPokemonP3)
Codec.unboundedMap(ResourceLocation.CODEC, Codec.INT).fieldOf(DataKeys.POKEMON_SKILL_COOLDOWNS).forGetter(ClientPokemonP3::skillCooldownTicks)
).apply(instance, ::ClientPokemonP3)
}
internal fun from(pokemon: Pokemon): ClientPokemonP3 = ClientPokemonP3(
@@ -92,6 +95,7 @@ internal data class ClientPokemonP3(
pokemon.getRideBoosts().mapKeys { it.key.name },
pokemon.currentFullness,
pokemon.interactionCooldowns,
pokemon.skillCooldownsTicks,
)
}
}
@@ -20,6 +20,7 @@ import com.cobblemon.mod.common.util.codec.optionalFieldOfWithDefault
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.Optional
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
@@ -43,6 +44,7 @@ internal data class PokemonP3(
val rideStamina: Float,
val currentFullness: Int,
val interactionCooldowns: Map<ResourceLocation, Int>,
val skillCooldownTicks: Map<ResourceLocation, Int>,
) : Partial<Pokemon> {
override fun into(other: Pokemon): Pokemon {
@@ -79,6 +81,7 @@ internal data class PokemonP3(
other.rideStamina = this.rideStamina
other.currentFullness = this.currentFullness
other.interactionCooldowns = this.interactionCooldowns.toMutableMap()
other.skillCooldownsTicks = this.skillCooldownTicks.toMutableMap()
return other
}
@@ -100,6 +103,7 @@ internal data class PokemonP3(
Codec.FLOAT.optionalFieldOf(DataKeys.POKEMON_RIDE_STAMINA, 1F).forGetter(PokemonP3::rideStamina),
Codec.intRange(0, 100).optionalFieldOf(DataKeys.POKEMON_FULLNESS, 0).forGetter(PokemonP3::currentFullness),
Codec.unboundedMap(ResourceLocation.CODEC, Codec.INT).optionalFieldOf(DataKeys.POKEMON_INTERACTION_COOLDOWN, emptyMap<ResourceLocation, Int>()).forGetter(PokemonP3::interactionCooldowns),
Codec.unboundedMap(ResourceLocation.CODEC, Codec.INT).optionalFieldOf(DataKeys.POKEMON_SKILL_COOLDOWNS, emptyMap<ResourceLocation, Int>()).forGetter { it.skillCooldownTicks },
).apply(instance) {
originalTrainerType,
originalTrainer,
@@ -115,7 +119,8 @@ internal data class PokemonP3(
rideBoosts,
rideStamina,
currentFullness,
interactionCooldown -> PokemonP3(
interactionCooldown,
skillCooldownTicks -> PokemonP3(
originalTrainerType,
originalTrainer,
forcedAspects.toSet(),
@@ -131,6 +136,7 @@ internal data class PokemonP3(
rideStamina,
currentFullness,
interactionCooldown,
skillCooldownTicks,
)
}
}
@@ -155,6 +161,7 @@ internal data class PokemonP3(
pokemon.rideStamina,
pokemon.currentFullness,
pokemon.interactionCooldowns,
pokemon.skillCooldownsTicks,
)
}
}
@@ -842,6 +842,8 @@
"cobblemon.fossilmachine.protected": "This fossil belongs to %s!",
"cobblemon.poke_skill.quick_ride.name": "Quick Ride",
"itemGroup.cobblemon.blocks": "Cobblemon: Blocks",
"itemGroup.cobblemon.utility_item": "Cobblemon: Utility Items",
"itemGroup.cobblemon.agriculture": "Cobblemon: Agriculture",
@@ -0,0 +1,12 @@
{
"timeline": [
{
"type": "molang",
"expressions": [
"q.pokemon.entity.set_position(q.player.x, q.player.y, q.player.z);",
"q.player.ride(q.pokemon.entity);",
"q.pokemon.add_skill_cooldown(q.skill_id);"
]
}
]
}
@@ -0,0 +1,63 @@
{
"timeline": [
{
"type": "add_holds",
"holds": ["effects"]
},
{
"type": "animation",
"animation": ["psybeam", "beam", "special"],
"delay": 0.8
},
{
"type": "entity_particles",
"effect": "cobblemon:psybeam_charge",
"locators": ["special", "target"]
},
{
"type": "pause",
"pause": 0.53
},
{
"type": "entity_particles",
"effect": "cobblemon:psybeam_actorpilot",
"locators": ["special", "target"],
"targetLocators": ["target"]
},
{
"type": "pause",
"pause": 0.1
},
{
"type": "entity_particles",
"entityCondition": "q.entity.is_user == false",
"effect": "cobblemon:psybeam_target",
"locators": ["target"]
},
{
"type": "pause",
"pause": 0.5
},
{
"type": "molang",
"expressions": [
"q.player.teleport_to_respawn();",
"q.pokemon.add_skill_cooldown(q.skill_id);"
]
},
{
"type": "entity_particles",
"entityCondition": "q.entity.is_user == false",
"effect": "cobblemon:psybeam_targetburst",
"locators": ["target"]
},
{
"type": "remove_holds",
"holds": ["effects"]
},
{
"type": "pause",
"pause": 1.7
}
]
}
@@ -0,0 +1,16 @@
{
"visible": false,
"configurations": [
{
"type": "add_tasks_to_activity",
"activity": "minecraft:idle",
"tasksByPriority": {
"0": [ "poke_skill_mine" ]
}
},
{
"type": "cobblemon:add_memories_and_sensors",
"memories": ["cobblemon:poke_skill_mine_target"]
}
]
}
@@ -0,0 +1,9 @@
{
"name": "cobblemon.poke_skill.quick_ride.name",
"enabled": true,
"actionEffect": "cobblemon:begin_riding",
"icon": "cobblemon:textures/gui/interact/interact_wheel_icon_ride.png",
"cooldownTicks": 40,
"accessCondition": "q.pokemon.is_rideable",
"useCondition": "q.pokemon.is_sent_out && !q.pokemon.entity.is_passenger(q.player)"
}
@@ -0,0 +1,9 @@
{
"name": "cobblemon.poke_skill.teleport.name",
"enabled": true,
"actionEffect": "cobblemon:teleport_to_spawn",
"icon": "cobblemon:textures/gui/interact/interact_wheel_icon_ride.png",
"cooldownTicks": 20,
"accessCondition": "q.pokemon.has_learned('teleport')",
"useCondition": "q.player.has_respawn_position && q.player.dimension == q.player.respawn_dimension"
}