Compare commits

...
Author SHA1 Message Date
Polymeta b79e73928a add multibattle NPC test command for debugging
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-21 15:35:59 +01:00
Polymeta c89ea759a6 fix invalidActionExceptions if the gimmickMove's targetType is different from it's normal move
good example is earthquake (allAdjacent) vs maxquake (adjacentFoe)

Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-08 17:00:58 +01:00
Polymeta c6479525f9 double oop docs
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-07 16:07:55 +01:00
Polymeta 2a7dababab oop licenses
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-07 15:58:21 +01:00
Polymeta 7ec6e61d4a first approach with resetting forms pre-battle
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-07 15:16:06 +01:00
Polymeta 7735af5c11 fixed itempredicates not working when used in species definition
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-07 11:39:51 +01:00
Polymeta 18d25dcf04 small reshuffle for requirements and introduce keyitem requirement (also add aerodactyl as example)
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-07 11:39:18 +01:00
Polymeta e259dc18a2 fix doubles not properly working with gimmicks
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-07 11:26:30 +01:00
Polymeta 7439436e93 fix trailing comma in schema, and check pokemon entity for registryAccess before doing the rest
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-06 10:49:37 +01:00
Polymeta 1c51d5ca98 prevent shiftbutton from being triggered or clicked when its not actually visible
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-06 10:42:59 +01:00
24 changed files with 242 additions and 64 deletions
@@ -6,9 +6,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.transformation.requirements.template
package com.cobblemon.mod.common.api.pokemon.transformation.requirement
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.world.entity.LivingEntity
@@ -32,7 +31,7 @@ interface EntityQueryRequirement : TransformationRequirement {
* Checks if the given [Pokemon] & [LivingEntity] satisfies the requirement.
*
* @param pokemon The [Pokemon] being queried.
* @param queriedEntity The [LivingEntity] that can be compared in context of a [Level].
* @param queriedEntity The [LivingEntity] that can be compared in context of a [net.minecraft.world.level.Level].
* @return If the requirement was satisfied.
*/
fun check(pokemon: Pokemon, queriedEntity: LivingEntity): Boolean
@@ -0,0 +1,57 @@
/*
* 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.pokemon.transformation.requirement
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
/**
* A [TransformationRequirement] that expects the [Pokemon] to have an owner.
* It can be the [ServerPlayer] or the [NPCEntity] that owns it.
*
* Whenever an owner can't be resolved [TransformationRequirement.check] will never succeed.
*
* @author Polymeta
* @since May 6th, 2025
*/
interface OwnerQueryRequirement : TransformationRequirement {
override fun check(pokemon: Pokemon): Boolean {
val owner = pokemon.getOwnerEntity() ?: return false
return when (owner) {
is ServerPlayer -> {
this.checkPlayer(pokemon, owner)
}
is NPCEntity -> {
this.checkNPC(pokemon, owner)
}
else -> false
}
}
/**
* Checks if the given [Pokemon] & [ServerPlayer] satisfies the requirement.
*
* @param pokemon The [Pokemon] being queried.
* @param owner The [ServerPlayer] that owns this [Pokemon].
* @return If the requirement was satisfied.
*/
fun checkPlayer(pokemon: Pokemon, owner: ServerPlayer): Boolean
/**
* Checks if the given [Pokemon] & [NPCEntity] satisfies the requirement.
* This method mainly exists as NPCs are different to players in a lot of ways (e.g., lack of advancements),
* so they need to be handled differently, either by checking an NPC config or always returning true/false
*
* @param pokemon The [Pokemon] being queried.
* @param owner The [NPCEntity] that owns this [Pokemon].
* @return If the requirement was satisfied.
*/
fun checkNPC(pokemon: Pokemon, owner: NPCEntity): Boolean
}
@@ -127,7 +127,8 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
// No PP or disabled or something
return false
}
val availableTargets = (gimmickMove?.target ?: move.target).targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: return true
val availableTargets = (if (gimmickID != null && validGimmickMove) gimmickMove.target else move.target)
.targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: return true
val pnx = targetPnx ?: return false // If the targets list is non-null then they need to have specified a target
val (_, targetPokemon) = activeBattlePokemon.actor.battle.getActorAndActiveSlotFromPNX(pnx)
@@ -27,6 +27,7 @@ import com.cobblemon.mod.common.pokemon.Nature
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.transformation.form.TemporaryForm
import com.cobblemon.mod.common.util.battleLang
import java.util.UUID
import net.minecraft.network.chat.MutableComponent
@@ -44,6 +45,9 @@ open class BattlePokemon(
val effectedPokemon = pokemon.clone()
BattleCloneProperty.isBattleClone().apply(effectedPokemon)
UncatchableProperty.uncatchable().apply(effectedPokemon)
val shouldRevertForm = (effectedPokemon.form as? TemporaryForm)?.revertBeforeBattle ?: (effectedPokemon.form as? TemporaryForm)?.battleOnly ?: false
if (shouldRevertForm)
effectedPokemon.formProxy.server().revert()
return BattlePokemon(
originalPokemon = pokemon,
effectedPokemon = effectedPokemon,
@@ -51,13 +55,18 @@ open class BattlePokemon(
)
}
fun playerOwned(pokemon: Pokemon): BattlePokemon = BattlePokemon(
originalPokemon = pokemon,
effectedPokemon = pokemon,
postBattleEntityOperation = { entity ->
entity.effects.wipe()
}
)
fun playerOwned(pokemon: Pokemon): BattlePokemon {
val shouldRevertForm = (pokemon.form as? TemporaryForm)?.revertBeforeBattle ?: (pokemon.form as? TemporaryForm)?.battleOnly ?: false
if (shouldRevertForm)
pokemon.formProxy.server().revert()
return BattlePokemon(
originalPokemon = pokemon,
effectedPokemon = pokemon,
postBattleEntityOperation = { entity ->
entity.effects.wipe()
}
)
}
}
val struct = QueryStruct(
@@ -14,7 +14,9 @@ import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.ForcePassActionResponse
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.ShowdownMoveset
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import java.util.UUID
@@ -80,6 +82,10 @@ class ClientBattle(
}
}
fun pendingGimmick(gimmick: ShowdownMoveset.Gimmick): Boolean {
return pendingActionRequests.none { (it.response as? MoveActionResponse)?.gimmickID == gimmick.id }
}
fun getPokemonFromPNX(pnx: String): Pair<ClientBattleActor, ActiveClientBattlePokemon> {
val actor = sides.flatMap { it.actors }.find { it.showdownId == pnx.substring(0, 2) }
?: throw IllegalStateException("Invalid pnx: $pnx - unknown actor")
@@ -13,11 +13,11 @@ import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleGimmickButton.GimmickTile
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.toRGB
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.sounds.SoundEvents
@@ -30,7 +30,7 @@ import net.minecraft.sounds.SoundEvents
* @author Segfault Guy
* @since July 8th, 2023
*/
abstract class BattleGimmickButton(gimmick: ShowdownMoveset.Gimmick, val x: Float, val y: Float) {
abstract class BattleGimmickButton(val gimmick: ShowdownMoveset.Gimmick, val x: Float, val y: Float) {
companion object {
const val WIDTH = 36
@@ -61,24 +61,32 @@ abstract class BattleGimmickButton(gimmick: ShowdownMoveset.Gimmick, val x: Floa
var toggled = false
private val sfx = SimpleSoundInstance.forUI(SoundEvents.ANVIL_LAND, 1.0F)
private val texture = gimmick.id
val selectable: Boolean
get() {
val battle = CobblemonClient.battle ?: return false
return battle.pendingGimmick(gimmick)
}
fun render(matrices: PoseStack, mouseX: Int, mouseY: Int, delta: Float) {
fun render(graphics: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = matrices,
matrixStack = graphics.pose(),
texture = cobblemonResource("textures/gui/battle/battle_gimmick_${texture}.png"),
x = x * 2,
y = y * 2,
height = HEIGHT,
width = WIDTH,
vOffset = if (toggled || isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
vOffset = if ((toggled || isHovered(mouseX.toDouble(), mouseY.toDouble()) && selectable)) HEIGHT else 0,
textureHeight = HEIGHT * 2,
scale = SCALE
scale = SCALE,
alpha = if (selectable) 1.0F else 0.5F
)
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX >= x && mouseX <= x + XOFF && mouseY >= y && mouseY <= y + YOFF
fun toggle(): Boolean {
if (!selectable)
return toggled
toggled = !toggled
Minecraft.getInstance().soundManager.play(sfx)
return toggled
@@ -74,7 +74,8 @@ class BattleMoveSelection(
BattleGimmickButton.create(gimmick, this, backButton.x + xOff, backButton.y)
}
val shiftButton = BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F )
val shiftButton = if (request.activePokemon.getFormat().battleType.slotsPerActor == 3 && (request.activePokemon.getPNX()[2] == 'a' || request.activePokemon.getPNX()[2] == 'c'))
BattleShiftButton(x + 22.5F, Minecraft.getInstance().window.guiScaledHeight - 22F ) else null
open class MoveTile(
val moveSelection: BattleMoveSelection,
@@ -190,31 +191,30 @@ class BattleMoveSelection(
}
backButton.render(context, mouseX, mouseY, delta)
gimmickButtons.forEach {
it.render(context.pose(), mouseX, mouseY, delta)
}
if(this.request.activePokemon.getFormat().battleType.slotsPerActor == 3 && (request.activePokemon.getPNX()[2] == 'a' || request.activePokemon.getPNX()[2] == 'c')) {
shiftButton.render(context, mouseX, mouseY, delta)
it.render(context, mouseX, mouseY, delta)
}
shiftButton?.render(context, mouseX, mouseY, delta)
}
override fun mousePrimaryClicked(mouseX: Double, mouseY: Double): Boolean {
val move = moveTiles.find { it.isHovered(mouseX, mouseY) }
val gimmick = gimmickButtons.find { it.isHovered(mouseX, mouseY) }
if (move != null) {
if(this.request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
if (this.request.activePokemon.getFormat().battleType.pokemonPerSide == 1) {
move.onClick()
} else {
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move))
return true
} else if (move.selectable) {
battleGUI.changeActionSelection(BattleTargetSelection(battleGUI, request, move.move, move.response.gimmickID, move.move.gimmickMove))
playDownSound(Minecraft.getInstance().soundManager)
return true
}
return true
} else if (backButton.isHovered(mouseX, mouseY)) {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.changeActionSelection(null)
} else if (gimmick != null) {
} else if (gimmick != null && gimmick.selectable) {
gimmickButtons.filter { it != gimmick }.forEach { it.toggled = false }
moveTiles = if (gimmick.toggle()) gimmick.tiles else baseTiles
} else if(shiftButton.isHovered(mouseX,mouseY)) {
} else if (shiftButton?.isHovered(mouseX,mouseY) == true) {
playDownSound(Minecraft.getInstance().soundManager)
battleGUI.selectAction(request, ShiftActionResponse())
}
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.font
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.InBattleGimmickMove
import com.cobblemon.mod.common.battles.InBattleMove
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.client.CobblemonClient
@@ -22,7 +23,6 @@ import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleSwitchPokemonSelection.Companion
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -41,9 +41,11 @@ import kotlin.math.floor
import kotlin.math.sin
class BattleTargetSelection(
battleGUI: BattleGUI,
request: SingleActionRequest,
val move: InBattleMove
battleGUI: BattleGUI,
request: SingleActionRequest,
val move: InBattleMove,
val gimmickID: String?,
gimmickMove: InBattleGimmickMove?
) : BattleActionSelection(
battleGUI = battleGUI,
request = request,
@@ -80,10 +82,10 @@ class BattleTargetSelection(
}
val targets = request.activePokemon.getAllActivePokemon()
val targetType = if (gimmickID != null && gimmickMove != null) gimmickMove.target else move.target
val backButton = BattleBackButton(x + 9F, Minecraft.getInstance().window.guiScaledHeight - 22F)
val selectableTargetList = move.target.targetList(request.activePokemon)
val multiTargetList = if(selectableTargetList == null) request.activePokemon.getMultiTargetList(move.target) else null
val selectableTargetList = targetType.targetList(request.activePokemon)
val multiTargetList = if(selectableTargetList == null) request.activePokemon.getMultiTargetList(targetType) else null
val baseTiles = targets.mapIndexed { index, target ->
val isAlly = target.isAllied(request.activePokemon)
@@ -123,7 +125,7 @@ class BattleTargetSelection(
private val isMultiTarget = multiTargetList?.firstOrNull { it.getPNX() == target.getPNX() } != null
private val isCurrentPokemon = request.activePokemon.battlePokemon?.uuid == target.battlePokemon!!.uuid
open val response: MoveActionResponse get() = MoveActionResponse(targetSelection.move.id, responseTarget)
open val response: MoveActionResponse get() = MoveActionResponse(targetSelection.move.id, responseTarget, targetSelection.gimmickID)
val state = FloatingState()
open val selectable: Boolean get() = isMultiTarget || (responseTarget != null)
@@ -26,6 +26,7 @@ import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -67,12 +68,12 @@ object TestCommand {
try {
//this.testCodecOutput(context)
val player = context.source.entity as ServerPlayer
player.party().forEach { it.currentHealth = it.hp / 2 }
val npc = NPCEntity(player.level())
npc.setPos(player.x, player.y, player.z)
npc.npc = NPCClasses.getByName("standard")!!
npc.initialize(50)
player.level().addFreshEntity(npc)
//player.party().forEach { it.currentHealth = it.hp / 2 }
//val npc = NPCEntity(player.level())
//npc.setPos(player.x, player.y, player.z)
//npc.npc = NPCClasses.getByName("standard")!!
//npc.initialize(50)
//player.level().addFreshEntity(npc)
// val evolutionEntity = GenericBedrockEntity(world = player.level())
// evolutionEntity.apply {
// category = cobblemonResource("evolution")
@@ -126,6 +127,20 @@ object TestCommand {
// val player = context.source.entity as ServerPlayerEntity
// player.giveItemStack(PokemonItem.from(PokemonSpecies.random(), "alolan"))
val npcs = player.level().getEntities(player, player.boundingBox.inflate(20.0)) { it is NPCEntity }
.map { NPCBattleActor(it as NPCEntity, it.getPartyForChallenge(listOf(player))!!, 0) }
.sortedBy { player.distanceToSqr(it.entity) }
val playerTeam = player.party().toBattleTeam()
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
BattleRegistry.startBattle(
battleFormat = BattleFormat.GEN_9_MULTI,
side1 = BattleSide(playerActor, npcs[0]),
side2 = BattleSide(npcs[1], npcs[2])
).ifSuccessful {
it.battlePartyStores.add(player.party())
}
} catch (e: Exception) {
Cobblemon.LOGGER.error(e)
}
@@ -1146,7 +1146,8 @@ open class Pokemon : ShowdownIdentifiable {
/** Revolves the [RegistryAccess] associated with this pokemon, if one exists. */
val registryAccess: RegistryAccess
@Throws(IllegalStateException::class)
get() = this.getOwnerEntity()?.registryAccess()
get() = this.entity?.registryAccess()
?: this.getOwnerEntity()?.registryAccess()
?: server()?.registryAccess()
?: throw IllegalStateException("No registry access available")
@@ -56,8 +56,9 @@ object CobblemonRequirementAdapter : RequirementAdapter {
this.registerType(StructureRequirement.ADAPTER_VARIANT, StructureRequirement::class)
this.registerType(AnyRequirement.ADAPTER_VARIANT, AnyRequirement::class)
this.registerType(PropertyRangeRequirement.ADAPTER_VARIANT, PropertyRangeRequirement::class)
this.registerType(PlayerHasAdvancementRequirement.ADAPTER_VARIANT, PlayerHasAdvancementRequirement::class)
this.registerType(AdvancementRequirement.ADAPTER_VARIANT, AdvancementRequirement::class)
this.registerType(HealthRequirement.ADAPTER_VARIANT, HealthRequirement::class)
this.registerType(KeyItemRequirement.ADAPTER_VARIANT, KeyItemRequirement::class)
}
override fun <T : TransformationRequirement> registerType(id: String, type: KClass<T>) {
@@ -39,6 +39,8 @@ import net.minecraft.resources.ResourceLocation
open class TemporaryForm (
/** Whether this form is triggered during a battle. */
val battleOnly: Boolean = false,
/** Whether this form has to be reverted before a battle begins. If null, [battleOnly] is used **/
val revertBeforeBattle: Boolean? = null,
/** Whether this form can be freely toggled after requirements are initially met. */
val toggleable: Boolean = false,
/** */
@@ -83,7 +85,7 @@ open class TemporaryForm (
*/
override fun start(pokemon: Pokemon, context: ContextTrigger.Context?): Boolean {
if (this.test(pokemon, context)) {
return if (this.optional)
return if (this.optional || this.toggleable)
pokemon.formProxy.server().add(this)
else {
this.forceStart(pokemon)
@@ -10,22 +10,22 @@ package com.cobblemon.mod.common.pokemon.transformation.requirements
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.requirements.template.EntityQueryRequirement
import net.minecraft.world.entity.LivingEntity
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.OwnerQueryRequirement
import com.cobblemon.mod.common.entity.npc.NPCEntity
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
import kotlin.collections.iterator
/**
* A [TransformationRequirement] that checks if the player has a certain [Advancement]
* A [TransformationRequirement] that checks if the player has a certain [net.minecraft.advancements.Advancement]
*
* @param requiredAdvancement The [ResourceLocation] of the required advancement
*
* @author whatsy
* @author whatsy, Polymeta
*/
class PlayerHasAdvancementRequirement(val requiredAdvancement: ResourceLocation) : EntityQueryRequirement {
override fun check(pokemon: Pokemon, queriedEntity: LivingEntity): Boolean {
val player = queriedEntity as? ServerPlayer ?: return false
for (entry in player.advancements.progress) {
class AdvancementRequirement(val requiredAdvancement: ResourceLocation) : OwnerQueryRequirement {
override fun checkPlayer(pokemon: Pokemon, owner: ServerPlayer): Boolean {
for (entry in owner.advancements.progress) {
if (entry.key.id == requiredAdvancement && entry.value.isDone) {
return true
}
@@ -33,6 +33,11 @@ class PlayerHasAdvancementRequirement(val requiredAdvancement: ResourceLocation)
return false
}
override fun checkNPC(pokemon: Pokemon, owner: NPCEntity): Boolean {
//TODO some sort of config potentially on the NPC itself
return true
}
companion object {
val ADAPTER_VARIANT = "advancement"
}
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.pokemon.transformation.requirements
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.requirements.template.EntityQueryRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.EntityQueryRequirement
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.pokemon.transformation.requirements
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.requirements.template.EntityQueryRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.EntityQueryRequirement
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.LivingEntity
@@ -0,0 +1,40 @@
/*
* 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.pokemon.transformation.requirements
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.OwnerQueryRequirement
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.hasKeyItem
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
/**
* A [OwnerQueryRequirement] that checks if the player has a certain "key item". Key items are not real items,
* but can be awarded (and removed) at free will of cobblemon or 3rd party mods.
*
* @property requiredKeyItem The key item the player needs to have, for example `cobblemon:z_ring` or `cobblemon:key_stone` for Z-Moves and Mega evolutions respectively
*
* @author Polymeta
* @since May 6th, 2025
*/
class KeyItemRequirement(val requiredKeyItem: ResourceLocation) : OwnerQueryRequirement {
override fun checkPlayer(pokemon: Pokemon, owner: ServerPlayer): Boolean {
return owner.hasKeyItem(requiredKeyItem)
}
override fun checkNPC(pokemon: Pokemon, owner: NPCEntity): Boolean {
//TODO some sort of config potentially on the NPC itself
return true
}
companion object {
const val ADAPTER_VARIANT = "key_item"
}
}
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.pokemon.transformation.requirements
import com.cobblemon.mod.common.api.spawning.condition.MoonPhase
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.requirements.template.EntityQueryRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.EntityQueryRequirement
import net.minecraft.world.entity.LivingEntity
/**
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.pokemon.transformation.requirements
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.requirements.template.EntityQueryRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.EntityQueryRequirement
import net.minecraft.core.registries.Registries
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.level.levelgen.structure.Structure
@@ -11,7 +11,7 @@ package com.cobblemon.mod.common.pokemon.transformation.requirements
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.requirements.template.EntityQueryRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.EntityQueryRequirement
import net.minecraft.world.entity.LivingEntity
/**
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.pokemon.transformation.requirements
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.requirements.template.EntityQueryRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.EntityQueryRequirement
import net.minecraft.world.entity.LivingEntity
/**
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.pokemon.transformation.requirements
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.transformation.requirements.template.EntityQueryRequirement
import com.cobblemon.mod.common.api.pokemon.transformation.requirement.EntityQueryRequirement
import net.minecraft.world.entity.LivingEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.level.Level
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.util.server
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
@@ -15,15 +16,27 @@ import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.mojang.serialization.Codec
import com.mojang.serialization.JsonOps
import net.minecraft.core.HolderLookup
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.RegistryOps
import java.lang.reflect.Type
import java.util.stream.Stream
class CodecBackedAdapter<T>(val codec: Codec<T>) : JsonDeserializer<T>, JsonSerializer<T> {
val fallbackRegistry: RegistryOps<JsonElement> = RegistryOps.create(JsonOps.INSTANCE,
HolderLookup.Provider.create(Stream.of(BuiltInRegistries.ITEM.asLookup())))
override fun deserialize(
jElement: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): T {
return JsonOps.INSTANCE.withDecoder(codec).apply(jElement).result().get().first as T
val ops = if (server() != null) {
server()!!.registryAccess().createSerializationContext(JsonOps.INSTANCE)
} else {
fallbackRegistry
}
return ops.withDecoder(codec).apply(jElement).result().get().first as T
}
override fun serialize(
@@ -31,6 +44,11 @@ class CodecBackedAdapter<T>(val codec: Codec<T>) : JsonDeserializer<T>, JsonSeri
typeOfSrc: Type?,
context: JsonSerializationContext?
): JsonElement {
return JsonOps.INSTANCE.withEncoder(codec).apply(src).result().get()
val ops = if (server() != null) {
server()!!.registryAccess().createSerializationContext(JsonOps.INSTANCE)
} else {
fallbackRegistry
}
return ops.withEncoder(codec).apply(src).result().get()
}
}
@@ -520,7 +520,7 @@
"description": "The form's nested temporary forms",
"type": "array",
"items": {
"type": "object",
"type": "object"
},
"_permanentOnly": "This field is applicable to permanent forms only"
},
@@ -322,6 +322,8 @@
{
"name": "Mega",
"primaryType": "rock",
"variant": "temporary",
"toggleable": true,
"secondaryType": "flying",
"maleRatio": 0.875,
"height": 21,
@@ -329,6 +331,18 @@
"pokedex": [
"cobblemon.species.aerodactyl-mega.desc"
],
"requirements": [
{
"variant": "held_item",
"itemCondition": {
"items": ["minecraft:glowstone"]
}
},
{
"variant": "key_item",
"requiredKeyItem": "cobblemon:key_stone"
}
],
"labels": [
"gen6",
"mega",