diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/PokemonSpecies.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/PokemonSpecies.kt index 36b18e5ff5..43bbde762e 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/PokemonSpecies.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/PokemonSpecies.kt @@ -28,6 +28,7 @@ import com.cobblemon.mod.common.api.pokemon.transformation.evolution.PreEvolutio import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroup import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroupAdapter +import com.cobblemon.mod.common.api.pokemon.labels.CobblemonPokemonLabels import com.cobblemon.mod.common.api.pokemon.moves.Learnset import com.cobblemon.mod.common.api.pokemon.stats.Stat import com.cobblemon.mod.common.api.pokemon.stats.Stats @@ -43,6 +44,9 @@ import com.cobblemon.mod.common.pokemon.SpeciesAdditions import com.cobblemon.mod.common.pokemon.transformation.predicate.NbtItemPredicate import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager import com.cobblemon.mod.common.pokemon.transformation.adapters.* +import com.cobblemon.mod.common.pokemon.transformation.form.PermanentForm +import com.cobblemon.mod.common.pokemon.transformation.form.StandardForm +import com.cobblemon.mod.common.pokemon.transformation.form.TemporaryForm import com.cobblemon.mod.common.util.adapters.* import com.cobblemon.mod.common.util.cobblemonResource import com.google.common.collect.HashBasedTable @@ -206,34 +210,18 @@ object PokemonSpecies : JsonDataRegistry { } /** - * The representation of a [Species] and/or [FormData] in the context of showdown. + * The representation of [FormData] in the context of Showdown. * This is intended as a sort of DTO that can be easily converted between JSON and Java/JS objects. * - * @param species The [Species] being converted or the base species if the form is not null. - * @param form The [FormData] being converted int o species (Showdown considers them species) will be null when dealing with the base form. + * @param form The [FormData] being converted into a species (Showdown considers them species). */ @Suppress("unused") - internal class ShowdownSpecies(species: Species, form: FormData?) { - val num = species.nationalPokedexNumber - val name = if (form != null) "${createShowdownName(species)}-${form.name}" else createShowdownName(species) - val baseSpecies = if (form != null) createShowdownName(species) else this.name - val forme = form?.name - // ToDo baseForme - val otherFormes = if (form == null && species.forms.isNotEmpty()) species.forms.map { "${this.name}-${it.name}" } else emptyList() - val formeOrder = if (form == null && this.otherFormes.isNotEmpty()) arrayListOf(this.name, *this.otherFormes.toTypedArray()) else emptyList() - val abilities: Map = mapOf( - "0" to "No Ability", - "1" to "No Ability", - "H" to "No Ability", - "S" to "No Ability" - ) - val types = (form?.types ?: species.types).map { it.name.replaceFirstChar(Char::uppercase) } - val preevo: String? = (form?.preEvolution ?: species.preEvolution)?.let { if (it.form == it.species.standardForm) createShowdownName(it.species) else "${createShowdownName(it.species)}-${it.form.name}" } - // For the context of battles the content here doesn't matter whatsoever and due to how PokemonProperties work we can't guarantee an actual specific species is defined. - val evos = if ((form?.evolutions ?: species.evolutions).isEmpty()) emptyList() else arrayListOf("") - val nfe = this.evos.isNotEmpty() - val eggGroups = (form?.eggGroups ?: species.eggGroups).map { it.showdownID } - val gender: String? = when (form?.maleRatio ?: species.maleRatio) { + internal open class ShowdownSpecies(form: FormData) { + val num = form.species.nationalPokedexNumber + val name = form.showdownName() + val types = form.types.map { it.name.replaceFirstChar(Char::uppercase) } + val eggGroups = form.eggGroups.map { it.showdownID } + val gender: String? = when (form.maleRatio) { 0F -> "F" 1F -> "M" -1F, 1.125F -> "N" @@ -241,35 +229,66 @@ object PokemonSpecies : JsonDataRegistry { } val genderRatio = if (this.gender == null) mapOf( - "maleRatio" to (form?.maleRatio ?: species.maleRatio), - "femaleRation" to (1F - (form?.maleRatio ?: species.maleRatio)) + "maleRatio" to (form.maleRatio), + "femaleRation" to (1F - (form.maleRatio)) ) else null - val baseStats = mapOf( - "hp" to (form?.baseStats?.get(Stats.HP) ?: species.baseStats[Stats.HP] ?: 1), - "atk" to (form?.baseStats?.get(Stats.ATTACK) ?: species.baseStats[Stats.ATTACK] ?: 1), - "def" to (form?.baseStats?.get(Stats.DEFENCE) ?: species.baseStats[Stats.DEFENCE] ?: 1), - "spa" to (form?.baseStats?.get(Stats.SPECIAL_ATTACK) ?: species.baseStats[Stats.SPECIAL_ATTACK] ?: 1), - "spd" to (form?.baseStats?.get(Stats.SPECIAL_DEFENCE) ?: species.baseStats[Stats.SPECIAL_DEFENCE] ?: 1), - "spe" to (form?.baseStats?.get(Stats.SPEED) ?: species.baseStats[Stats.SPEED] ?: 1) - ) - val heightm = (form?.height ?: species.height) / 10 - val weightkg = (form?.weight ?: species.weight) / 10 + val baseStats = Stats.PERMANENT.associateBy({ it.showdownId }, { form.baseStats[it] ?: 1 }) + val heightm = form.height / 10 + val weightkg = form.weight / 10 // This is ugly, but we already have it hardcoded in the mod anyway - val maxHP = if (species.showdownId() == "shedinja") 1 else null - val canGigantamax: String? = if (form?.gigantamaxMove != null) form.gigantamaxMove.name else null - val cannotDynamax = form?.dynamaxBlocked ?: species.dynamaxBlocked - // ToDo battleOnly - // ToDo changesFrom - val requiredMove = form?.requiredMove - val requiredItem = form?.requiredItem - val requiredItems = form?.requiredItems + val maxHP = if (this.num == 292) 1 else null + val cannotDynamax = form.dynamaxBlocked } - private fun createShowdownName(species: Species): String { - if (species.resourceIdentifier.namespace == Cobblemon.MODID) { - return species.name - } - return "${species.resourceIdentifier.namespace}:${species.name}" + /** + * The representation of a [PermanentForm] in the context of Showdown. + * + * @param form The [PermanentForm] being converted into a species (Showdown considers them species). + */ + internal open class ShowdownPermanentForm(form: PermanentForm) : ShowdownSpecies(form) { + val forme = if (form !is StandardForm) form.name else null + val baseSpecies = if (form !is StandardForm) form.species.standardForm.showdownName() else null + val preevo = form.preEvolution?.form?.showdownName() + // For the context of battles the content here doesn't matter whatsoever and due to how PokemonProperties work we can't guarantee an actual specific species is defined. + val evos = if (form.evolutions.isEmpty()) emptyList() else arrayListOf("") + val nfe = this.evos.isNotEmpty() + val canGigantamax = form.temporaryForms.firstOrNull { it.gigantamaxMove != null }?.gigantamaxMove } + /** + * The representation of a [StandardForm] in the context of Showdown. + * + * @param form The [StandardForm] being converted into a species. + */ + internal class ShowdownStandardForm(form: StandardForm) : ShowdownPermanentForm(form) { + val otherFormes = form.flattenForms().map { it.showdownName() } + val formeOrder = if (this.otherFormes.isNotEmpty()) arrayListOf(this.name, *this.otherFormes.toTypedArray()) else null + } + + /** + * The representation of a [TemporaryForm] in the context of Showdown. + * + * @param form The [TemporaryForm] being converted into a species (Showdown considers them species). + * @param baseForm The [PermanentForm] that transitions into this [TemporaryForm]. + */ + internal class ShowdownTemporaryForm(form: TemporaryForm) : ShowdownSpecies(form) { + val forme = form.name + val baseSpecies = form.species.standardForm.showdownName() + // forms this form can change from during a battle + val battleOnly = if (form.battleOnly) form.showdownParentForm() else null + // forms this form can change from outside of battle (mutually exclusive with above) + val changesFrom = if (!form.battleOnly) form.showdownParentForm() else null + } + + /** Accommodate for Showdown bullshit. */ + private fun TemporaryForm.showdownParentForm(): Any? { + // megas and primal don't use battleOnly because... fuck you? + if (this.battleOnly && this.labels.any { it == CobblemonPokemonLabels.MEGA || it == CobblemonPokemonLabels.PRIMAL }) + return null + // Necrozma-Ultra and Zygarde-Complete are the only forms where parent form != the forms it can change from + else if (this.parentOverrides.isNotEmpty()) + return this.species.standardForm.flattenForms().filter { parentOverrides.contains(it.showdownId()) }.map { it.showdownName() } + else + return parentForm?.showdownName() + } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/battles/runner/ShowdownService.kt b/common/src/main/kotlin/com/cobblemon/mod/common/battles/runner/ShowdownService.kt index c422d72956..d712890dac 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/battles/runner/ShowdownService.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/battles/runner/ShowdownService.kt @@ -10,6 +10,7 @@ package com.cobblemon.mod.common.battles.runner import com.cobblemon.mod.common.api.battles.model.PokemonBattle import com.cobblemon.mod.common.battles.runner.graal.GraalShowdownService +import com.cobblemon.mod.common.battles.runner.socket.SocketShowdownService import com.google.gson.JsonArray import java.util.UUID diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/battles/runner/graal/GraalShowdownService.kt b/common/src/main/kotlin/com/cobblemon/mod/common/battles/runner/graal/GraalShowdownService.kt index 505df16798..cd1b25de64 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/battles/runner/graal/GraalShowdownService.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/battles/runner/graal/GraalShowdownService.kt @@ -157,12 +157,16 @@ class GraalShowdownService : ShowdownService { val jsArray = this.context.eval("js", "new Array();") var index = 0L PokemonSpecies.species.forEach { species -> - jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownSpecies(species, null))) - species.forms.forEach { form -> - if (form != species.standardForm) { - jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownSpecies(species, form))) + jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownStandardForm(species.standardForm))) + species.permanentForms.forEach { form -> + jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownPermanentForm(form))) + form.temporaryForms.forEach { tempForm -> + jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownTemporaryForm(tempForm))) } } + species.temporaryForms.forEach { tempForm -> + jsArray.setArrayElement(index++, this.gson.toJson(PokemonSpecies.ShowdownTemporaryForm(tempForm))) + } } receiveSpeciesDataFn.execute(jsArray) } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/battles/runner/socket/SocketShowdownService.kt b/common/src/main/kotlin/com/cobblemon/mod/common/battles/runner/socket/SocketShowdownService.kt index dec987e6f2..5a18bbc92f 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/battles/runner/socket/SocketShowdownService.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/battles/runner/socket/SocketShowdownService.kt @@ -123,8 +123,8 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846 return gson.fromJson(response, JsonArray::class.java) } - private fun sendSpeciesData(species: Species, form: FormData?) { - writer.write(">receiveSpeciesData ${gson.toJson(PokemonSpecies.ShowdownSpecies(species, form))}\n") + private fun sendSpeciesData(species: PokemonSpecies.ShowdownSpecies) { + writer.write(">receiveSpeciesData ${gson.toJson(species)}\n") acknowledge() } @@ -137,12 +137,16 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846 writer.write(">resetSpeciesData\n") acknowledge() PokemonSpecies.species.forEach { species -> - sendSpeciesData(species, null) - species.forms.forEach { form -> - if (form != species.standardForm) { - sendSpeciesData(species, form) + sendSpeciesData(PokemonSpecies.ShowdownStandardForm(species.standardForm)) + species.permanentForms.forEach { form -> + sendSpeciesData(PokemonSpecies.ShowdownPermanentForm(form)) + form.temporaryForms.forEach { tempForm -> + sendSpeciesData(PokemonSpecies.ShowdownTemporaryForm(tempForm)) } } + species.temporaryForms.forEach { form -> + sendSpeciesData(PokemonSpecies.ShowdownTemporaryForm(form)) + } } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/command/ChangeEyeHeight.kt b/common/src/main/kotlin/com/cobblemon/mod/common/command/ChangeEyeHeight.kt index 43f1836750..6d36ffa675 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/command/ChangeEyeHeight.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/command/ChangeEyeHeight.kt @@ -46,8 +46,7 @@ object ChangeEyeHeight { val height = FloatArgumentType.getFloat(context, "height") applicator.invoke(pkm, height) - pkm.forms.clear() - pkm.forms.add(FormData().also { it.initialize(pkm) }) + // TODO refresh form return Command.SINGLE_SUCCESS } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/command/ChangeScaleAndSize.kt b/common/src/main/kotlin/com/cobblemon/mod/common/command/ChangeScaleAndSize.kt index 09c350517a..bb41652363 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/command/ChangeScaleAndSize.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/command/ChangeScaleAndSize.kt @@ -45,8 +45,7 @@ object ChangeScaleAndSize { pkm.baseScale = scale pkm.hitbox = EntityDimensions.scalable(width, height) - pkm.forms.clear() - pkm.forms.add(FormData().also { it.initialize(pkm) }) + // TODO refresh form return Command.SINGLE_SUCCESS } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/command/ChangeWalkSpeed.kt b/common/src/main/kotlin/com/cobblemon/mod/common/command/ChangeWalkSpeed.kt index 54c3207c2f..00a5d66702 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/command/ChangeWalkSpeed.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/command/ChangeWalkSpeed.kt @@ -41,8 +41,7 @@ object ChangeWalkSpeed { val walkSpeed = FloatArgumentType.getFloat(context, "walkSpeed") pkm.behaviour.moving.walk.walkSpeed = walkSpeed.toString().asExpressionLike() - pkm.forms.clear() - pkm.forms.add(FormData().also { it.initialize(pkm)}) + // TODO refresh form return Command.SINGLE_SUCCESS } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/pokemon/update/FormUpdatePacket.kt b/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/pokemon/update/FormUpdatePacket.kt index 4f712f8672..2517376340 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/pokemon/update/FormUpdatePacket.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/pokemon/update/FormUpdatePacket.kt @@ -10,6 +10,7 @@ package com.cobblemon.mod.common.net.messages.client.pokemon.update import com.cobblemon.mod.common.pokemon.FormData import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.pokemon.transformation.form.TemporaryForm import com.cobblemon.mod.common.util.cobblemonResource import net.minecraft.network.RegistryFriendlyByteBuf import net.minecraft.resources.ResourceLocation @@ -29,10 +30,12 @@ class FormUpdatePacket(pokemon: () -> Pokemon, form: FormData) : SingleUpdatePac val ID: ResourceLocation = cobblemonResource("packets/form-update") fun decode(buffer: RegistryFriendlyByteBuf): FormUpdatePacket { val pokemon = decodePokemon(buffer) + /* val form = FormData() form.species = pokemon().species form.decode(buffer) - return FormUpdatePacket(pokemon, form) + */ + return FormUpdatePacket(pokemon, TemporaryForm()) // TODO fix } } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/FormData.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/FormData.kt index 51341b19ae..8d7cd93d6c 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/FormData.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/FormData.kt @@ -16,15 +16,13 @@ import com.cobblemon.mod.common.api.abilities.CommonAbility import com.cobblemon.mod.common.api.abilities.PotentialAbility import com.cobblemon.mod.common.api.data.ShowdownIdentifiable import com.cobblemon.mod.common.api.drop.DropTable -import com.cobblemon.mod.common.api.moves.MoveTemplate import com.cobblemon.mod.common.api.net.Decodable import com.cobblemon.mod.common.api.net.Encodable -import com.cobblemon.mod.common.api.pokemon.PokemonSpecies import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect import com.cobblemon.mod.common.api.pokemon.egg.EggGroup -import com.cobblemon.mod.common.api.pokemon.transformation.evolution.PreEvolution import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroup import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroups +import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeature import com.cobblemon.mod.common.api.pokemon.moves.Learnset import com.cobblemon.mod.common.api.pokemon.stats.Stat import com.cobblemon.mod.common.api.types.ElementalType @@ -35,15 +33,22 @@ import com.cobblemon.mod.common.net.IntSize import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility import com.cobblemon.mod.common.pokemon.ai.FormPokemonBehaviour import com.cobblemon.mod.common.pokemon.lighthing.LightingData -import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution import com.cobblemon.mod.common.util.* import com.google.gson.annotations.SerializedName import net.minecraft.network.RegistryFriendlyByteBuf import net.minecraft.world.entity.EntityDimensions import net.minecraft.resources.ResourceLocation -class FormData( - name: String = "Normal", +/** + * Represents an alternate form of a [Pokemon]. + * + * These variations include changes to the base [Species]' stats. For strictly visual variations, see [SpeciesFeature]. + * + * @author Hiroku + * @since Dec 30th, 2021 + */ +abstract class FormData( + name: String = "Standard", // Internal for the sake of the base stat provider @SerializedName("baseStats") internal var _baseStats: MutableMap? = null, @@ -73,16 +78,12 @@ class FormData( private val _shoulderEffects: MutableList? = null, @SerializedName("moves") private var _moves: Learnset? = null, - @SerializedName("evolutions") - private val _evolutions: MutableSet? = null, @SerializedName("abilities") private var _abilities: AbilityPool? = null, @SerializedName("drops") private var _drops: DropTable? = null, @SerializedName("pokedex") private var _pokedex: MutableList? = null, - @SerializedName("preEvolution") - private val _preEvolution: PreEvolution? = null, private var standingEyeHeight: Float? = null, private var swimmingEyeHeight: Float? = null, private var flyingEyeHeight: Float? = null, @@ -96,15 +97,6 @@ class FormData( private var _height: Float? = null, @SerializedName("weight") private var _weight: Float? = null, - val requiredMove: String? = null, - val requiredItem: String? = null, - /** For forms that can accept different items (e.g. Arceus-Grass: Meadow Plate or Grassium-Z). */ - val requiredItems: List? = null, - /** - * The [MoveTemplate] of the signature attack of the G-Max form. - * This is always null on any form aside G-Max. - */ - val gigantamaxMove: MoveTemplate? = null, @SerializedName("battleTheme") private var _battleTheme: ResourceLocation? = null, @SerializedName("lightingData") @@ -162,9 +154,6 @@ class FormData( var aspects = mutableListOf() - val preEvolution: PreEvolution? - get() = _preEvolution ?: species.preEvolution - val behaviour = FormPokemonBehaviour() val dynamaxBlocked: Boolean @@ -189,12 +178,23 @@ class FormData( get() = _labels ?: species.labels /** - * Contains the evolutions of this form. - * Do not access this property immediately after a species is loaded, it requires all species in the game to be loaded. - * To be aware of this gamestage subscribe to [PokemonSpecies.observable]. + * The [FormData] that precedes this form. Null if a parent form does not exist. + * + * For example the parent of Toxtricity's 'Gmax' form can be either its 'Low-Key' or 'Standard' form. + * The parent of Toxtricity's 'Low-Key' form is its 'Standard' form. + * And the parent of Toxtricity's 'Standard' form is null (does not exist). */ - val evolutions: MutableSet - get() = _evolutions ?: mutableSetOf() + @delegate:Transient + val parentForm: FormData? by lazy { parentFormInitializer } + + @delegate:Transient + val species: Species by lazy { speciesInitializer } + + @Transient + private var parentFormInitializer: FormData? = null + + @Transient + private lateinit var speciesInitializer: Species val battleTheme: ResourceLocation get() = _battleTheme ?: species.battleTheme @@ -218,30 +218,17 @@ class FormData( else -> this.standingEyeHeight } - @Transient - lateinit var species: Species - - fun initialize(species: Species): FormData { - this.species = species + open fun initialize(species: Species, parent: FormData? = null): FormData { + this.speciesInitializer = species + this.species + this.parentFormInitializer = parent + this.parentForm this.behaviour.parent = species.behaviour Cobblemon.statProvider.provide(this) - // These properties are lazy, these need all species to be reloaded but SpeciesAdditions is what will eventually trigger this after all species have been loaded - this.preEvolution?.species - this.preEvolution?.form - this.evolutions.size this._lightingData?.let { this._lightingData = it.copy(lightLevel = it.lightLevel.coerceIn(0, 15)) } return this } - internal fun resolveEvolutionMoves() { - this.evolutions.forEach { evolution -> - if (evolution.learnableMoves.isNotEmpty() && evolution.result.species != null) { - val pokemon = evolution.result.create() - pokemon.form.moves.evolutionMoves += evolution.learnableMoves - } - } - } - override fun equals(other: Any?): Boolean = other is FormData && other.showdownId() == this.showdownId() override fun hashCode(): Int = this.showdownId().hashCode() @@ -280,6 +267,7 @@ class FormData( pb.writeString(ability.template.name) } } + //TODO encode/decode Transformation properties of TemporaryForms } override fun decode(buffer: RegistryFriendlyByteBuf) { @@ -324,7 +312,7 @@ class FormData( * * @return The literal Showdown ID of this species and form. */ - override fun showdownId(): String = this.species.showdownId() + this.formOnlyShowdownId() + override fun showdownId(): String = this.parentForm?.let { parent -> parent.showdownId() + this.formOnlyShowdownId() } ?: this.species.showdownId() /** * The literal Showdown ID of this form [name]. @@ -335,4 +323,11 @@ class FormData( */ fun formOnlyShowdownId(): String = ShowdownIdentifiable.REGEX.replace(this.name.lowercase(), "") + /** + * The Showdown name of this form appended to [Species.showdownName]. + * For example form 'Gmax' of parent form 'Low-Key' of species 'Toxtricity' becomes 'Toxtricity-Low-Key-Gmax'. + * + * @return The Showdown name of this species and form. + */ + open fun showdownName(): String = this.parentForm?.let { parent -> parent.showdownName() + "-" + this.name } ?: this.species.showdownName() } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt index 39ef904e7f..93784a04b9 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt @@ -101,6 +101,7 @@ import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty import com.cobblemon.mod.common.pokemon.status.PersistentStatus import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer +import com.cobblemon.mod.common.pokemon.transformation.form.PermanentForm import com.cobblemon.mod.common.util.DataKeys import com.cobblemon.mod.common.util.cobblemonResource import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP1 @@ -191,7 +192,7 @@ open class Pokemon : ShowdownIdentifiable { _species.emit(value) } - var form = species.standardForm + var form: FormData = species.standardForm set(value) { val old = field // Species updates already update HP but just a form change may require it @@ -515,11 +516,17 @@ open class Pokemon : ShowdownIdentifiable { val storeCoordinates = SettableObservable?>(null) // We want non-optional evolutions to trigger first to avoid unnecessary packets and any cost associate with an optional one that would just be lost - val evolutions: Iterable get() = this.form.evolutions.sortedBy { evolution -> evolution.optional } + val evolutions: Iterable get() { + val form = this.form + return if (form is PermanentForm) form.evolutions.sortedBy { evolution -> evolution.optional } else this.species.evolutions + } val lockedEvolutions: Iterable get() = evolutions.filter { it !in evolutionProxy.current() } - val preEvolution: PreEvolution? get() = this.form.preEvolution + val preEvolution: PreEvolution? get() { + val form = this.form + return if (form is PermanentForm) form.preEvolution else this.species.preEvolution + } /** * Provides the sided [EvolutionController]s, these operations can be done safely with a simple side check. diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Species.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Species.kt index 6b8aa7c332..9edfc31681 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Species.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Species.kt @@ -22,12 +22,12 @@ import com.cobblemon.mod.common.api.pokemon.PokemonProperties import com.cobblemon.mod.common.api.pokemon.PokemonSpecies import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect import com.cobblemon.mod.common.api.pokemon.egg.EggGroup -import com.cobblemon.mod.common.pokemon.FormData import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution import com.cobblemon.mod.common.api.pokemon.transformation.evolution.PreEvolution import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroups import com.cobblemon.mod.common.api.pokemon.moves.Learnset import com.cobblemon.mod.common.api.pokemon.stats.Stat +import com.cobblemon.mod.common.api.pokemon.transformation.Transformation import com.cobblemon.mod.common.api.types.ElementalType import com.cobblemon.mod.common.api.types.ElementalTypes import com.cobblemon.mod.common.entity.PoseType.Companion.FLYING_POSES @@ -37,6 +37,9 @@ import com.cobblemon.mod.common.net.IntSize import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility import com.cobblemon.mod.common.pokemon.ai.PokemonBehaviour import com.cobblemon.mod.common.pokemon.lighthing.LightingData +import com.cobblemon.mod.common.pokemon.transformation.form.PermanentForm +import com.cobblemon.mod.common.pokemon.transformation.form.StandardForm +import com.cobblemon.mod.common.pokemon.transformation.form.TemporaryForm import com.cobblemon.mod.common.util.* import com.cobblemon.mod.common.util.codec.CodecUtils import com.mojang.serialization.Codec @@ -109,10 +112,23 @@ class Species : ClientDataSynchronizer, ShowdownIdentifiable { var weight = 1F private set - var forms = mutableListOf() + var permanentForms = mutableListOf() private set - val standardForm by lazy { FormData(_evolutions = this.evolutions).initialize(this) } + var temporaryForms = mutableListOf() + private set + + val standardForm by lazy { + StandardForm( + _evolutions = this.evolutions, + _permanentForms = this.permanentForms, + _temporaryForms = this.temporaryForms + ).initialize(this) as StandardForm + } + + val forms: Collection get() = permanentForms + temporaryForms + + val transformations: Collection get() = evolutions + temporaryForms var labels = hashSetOf() private set @@ -164,15 +180,13 @@ class Species : ClientDataSynchronizer, ShowdownIdentifiable { fun initialize() { Cobblemon.statProvider.provide(this) - this.forms.forEach { it.initialize(this) } - if (this.forms.isNotEmpty() && this.forms.none { it == this.standardForm }) { - this.forms.add(0, this.standardForm) - } this.lightingData?.let { this.lightingData = it.copy(lightLevel = it.lightLevel.coerceIn(0, 15)) } // These properties are lazy, these need all species to be reloaded but SpeciesAdditions is what will eventually trigger this after all species have been loaded this.preEvolution?.species this.preEvolution?.form this.evolutions.size + // init all forms + this.standardForm } // Ran after initialize due to us creating a Pokémon here which requires all the properties in #initialize to be present for both this and the results, this is the easiest way to quickly resolve species + form @@ -183,7 +197,7 @@ class Species : ClientDataSynchronizer, ShowdownIdentifiable { pokemon.form.moves.evolutionMoves += evolution.learnableMoves } } - this.forms.forEach(FormData::resolveEvolutionMoves) + this.permanentForms.forEach(PermanentForm::resolveEvolutionMoves) } fun create(level: Int = 10) = PokemonProperties.parse("species=\"${this.name}\" level=${level}").create() @@ -227,8 +241,9 @@ class Species : ClientDataSynchronizer, ShowdownIdentifiable { // Hitbox end this.moves.encode(buffer) buffer.writeCollection(this.pokedex) { pb, line -> pb.writeString(line) } - buffer.writeCollection(this.forms) { _, form -> form.encode(buffer) } buffer.writeIdentifier(this.battleTheme) + buffer.writeCollection(this.permanentForms) { _, form -> form.encode(buffer) } + buffer.writeCollection(this.temporaryForms) { _, form -> form.encode(buffer) } buffer.writeCollection(this.features) { pb, feature -> pb.writeString(feature) } buffer.writeNullable(this.lightingData) { pb, data -> pb.writeInt(data.lightLevel) @@ -261,9 +276,11 @@ class Species : ClientDataSynchronizer, ShowdownIdentifiable { this.moves.decode(buffer) this.pokedex.clear() this.pokedex += buffer.readList { pb -> pb.readString() } - this.forms.clear() - this.forms += buffer.readList{ FormData().apply { decode(buffer) } }.filterNotNull() this.battleTheme = buffer.readIdentifier() + this.permanentForms.clear() + this.permanentForms += buffer.readList{ PermanentForm().apply { decode(buffer) } }.filterNotNull() + this.temporaryForms.clear() + this.temporaryForms += buffer.readList{ TemporaryForm().apply { decode(buffer) } }.filterNotNull() this.features.clear() this.features += buffer.readList { pb -> pb.readString() } this.lightingData = buffer.readNullable { pb -> LightingData(pb.readInt(), pb.readEnumConstant(LightingData.LiquidGlowMode::class.java)) } @@ -308,7 +325,7 @@ class Species : ClientDataSynchronizer, ShowdownIdentifiable { /** * The literal Showdown ID of this species. * This will be a lowercase version of the [name] with all the non-alphanumeric characters removed. For example, "Mr. Mime" becomes "mrmime". - * If a Species is not a part of the Cobblemon mon the [resourceIdentifier] will have the namespace appended at the start of the ID resulting in something such as 'somemodaspecies' + * If a Species is not a part of the Cobblemon mod, the [resourceIdentifier] will have the namespace appended at the start of the ID resulting in something such as 'somemodaspecies' * * @return The literal Showdown ID of this species. */ @@ -330,6 +347,19 @@ class Species : ClientDataSynchronizer, ShowdownIdentifiable { */ private fun unformattedShowdownId(): String = ShowdownIdentifiable.REGEX.replace(this.name.lowercase(), "") + /** + * The Showdown name of this species. + * If a species is not a part of the Cobblemon mod, the [resourceIdentifier] will have the namespace appended at + * the start of the name. 'Namespace:Species'. + * + * @return The Showdown name of this species. + */ + fun showdownName(): String { + val namespace = this.resourceIdentifier.namespace + val name = this.name + return if (namespace == Cobblemon.MODID) name else "$namespace:$name" + } + override fun toString() = this.showdownId() companion object { diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/form/PermanentForm.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/form/PermanentForm.kt new file mode 100644 index 0000000000..781c0fb539 --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/form/PermanentForm.kt @@ -0,0 +1,72 @@ +package com.cobblemon.mod.common.pokemon.transformation.form + +import com.cobblemon.mod.common.Cobblemon +import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeature +import com.cobblemon.mod.common.api.pokemon.PokemonSpecies +import com.cobblemon.mod.common.api.pokemon.transformation.Transformation +import com.cobblemon.mod.common.api.pokemon.transformation.evolution.PreEvolution +import com.cobblemon.mod.common.pokemon.FormData +import com.cobblemon.mod.common.pokemon.Species +import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution +import com.google.gson.annotations.SerializedName + +/** + * A form of a [Species] that is noninterchangeable with the [StandardForm]. These forms behave like a different species, + * and can have a unique set of interchangeable [TemporaryForm]s, but share the same dex number. They are a variation + * determined by [Evolution] or [SpeciesFeature]. + * + * For example: + * https://bulbapedia.bulbagarden.net/wiki/Regional_form + * https://bulbapedia.bulbagarden.net/wiki/Lycanroc#Forms + * + * @author Segfault Guy + * @since October 21st, 2023 + */ +open class PermanentForm ( + @SerializedName("evolutions") + private val _evolutions: MutableSet? = null, + @SerializedName("preEvolution") + private val _preEvolution: PreEvolution? = null, + @SerializedName("temporaryForms") + private val _temporaryForms: MutableList? = null, +) : FormData() { + + /** + * Contains the evolutions of this form. + * Do not access this property immediately after a species is loaded, it requires all species in the game to be loaded. + * To be aware of this gamestage subscribe to [PokemonSpecies.observable]. + */ + val evolutions: MutableSet + get() = _evolutions ?: mutableSetOf() + + val preEvolution: PreEvolution? + get() = _preEvolution ?: species.preEvolution + + val temporaryForms: MutableList + get() = _temporaryForms ?: mutableListOf() + + open val forms: List get() = temporaryForms + + open val transformations: List get() = temporaryForms + evolutions + + override fun initialize(species: Species, parent: FormData?): FormData { + super.initialize(species, parent) + // These properties are lazy, these need all species to be reloaded but SpeciesAdditions is what will eventually trigger this after all species have been loaded + this.preEvolution?.species + this.preEvolution?.form + this.evolutions.size + // init nested forms + this.forms.forEach { it.initialize(species, this) } + return this + } + + internal fun resolveEvolutionMoves() { + this.evolutions.forEach { evolution -> + if (evolution.learnableMoves.isNotEmpty() && evolution.result.species != null) { + val pokemon = evolution.result.create() + pokemon.form.moves.evolutionMoves += evolution.learnableMoves + } + } + } + +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/form/StandardForm.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/form/StandardForm.kt new file mode 100644 index 0000000000..90020bcc9a --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/form/StandardForm.kt @@ -0,0 +1,30 @@ +package com.cobblemon.mod.common.pokemon.transformation.form + +import com.cobblemon.mod.common.pokemon.FormData +import com.cobblemon.mod.common.pokemon.Species +import com.cobblemon.mod.common.pokemon.transformation.evolution.Evolution +import com.google.gson.annotations.SerializedName + +/** + * The [FormData] representation of a [Species]. + * + * @author Segfault Guy + * @since December 10th, 2023 + */ +class StandardForm( + @SerializedName("evolutions") + private val _evolutions: MutableSet? = null, + @SerializedName("temporaryForms") + private val _temporaryForms: MutableList? = null, + @SerializedName("regularForms") + private val _permanentForms: MutableList? = null, +) : PermanentForm(_evolutions = _evolutions, _temporaryForms = _temporaryForms) { + + val permanentForms: MutableList + get() = _permanentForms ?: mutableListOf() + + override val forms: List = this.temporaryForms + this.permanentForms + + /** Aggregates child forms and their nested forms. */ + fun flattenForms(): List = this.forms + this.permanentForms.filter { it != this }.flatMap { it.temporaryForms } +} diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/form/TemporaryForm.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/form/TemporaryForm.kt new file mode 100644 index 0000000000..f6afcbb48a --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/form/TemporaryForm.kt @@ -0,0 +1,38 @@ +package com.cobblemon.mod.common.pokemon.transformation.form + +import com.cobblemon.mod.common.api.moves.MoveTemplate +import com.cobblemon.mod.common.api.pokemon.transformation.Transformation +import com.cobblemon.mod.common.api.pokemon.transformation.requirement.TransformationRequirement +import com.cobblemon.mod.common.api.pokemon.transformation.trigger.TransformationTrigger +import com.cobblemon.mod.common.pokemon.FormData +import com.cobblemon.mod.common.pokemon.Species +import com.cobblemon.mod.common.pokemon.transformation.triggers.ItemInteractionTrigger + +/** + * A form of a [Species] that is interchangeable with a parent [PermanentForm]. These forms are temporary and may be toggled + * OUTSIDE of battle by its [TransformationTrigger] when the [TransformationRequirement]s are satisfied. + * + * For example: + * https://bulbapedia.bulbagarden.net/wiki/Mega_Evolution + * https://bulbapedia.bulbagarden.net/wiki/Rotom#Forms + * + * Note: triggering of forms INSIDE a battle is forced by Showdown. + * + * @author Segfault Guy + * @since October 21st, 2023 + */ +class TemporaryForm ( + /** The [MoveTemplate] of the signature attack of the G-Max form. This is always null on any form aside G-Max. */ + val gigantamaxMove: MoveTemplate? = null, + /** Whether this form is triggered during a battle. */ + val battleOnly: Boolean = false, + /** + * When populated by form name(s) in [showdownId] format, overrides the [parentForm] that this form may transform from + * (e.g. Necrozma's 'Ultra' from is special can transform from its 'Dawn-Wings' or 'Dusk-Mane forms'). + */ + val parentOverrides: List = emptyList(), + /** The [TransformationTrigger] to attempt this transformation OUTSIDE of battle. */ + override val trigger: TransformationTrigger = ItemInteractionTrigger(), + /** The [TransformationRequirement]s that need to be satisfied for this transformation OUTSIDE of battle. */ + override val requirements: Set = mutableSetOf() +) : FormData(), Transformation \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/DamageTakenTransformationProgress.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/DamageTakenTransformationProgress.kt index 769a7cb1fc..9c28f8b240 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/DamageTakenTransformationProgress.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/DamageTakenTransformationProgress.kt @@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.pokemon.transformation.progress.Transformati import com.cobblemon.mod.common.api.pokemon.transformation.progress.TransformationProgressType import com.cobblemon.mod.common.api.pokemon.transformation.progress.TransformationProgressTypes import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.pokemon.transformation.form.PermanentForm import com.cobblemon.mod.common.pokemon.transformation.requirements.DamageTakenRequirement import com.cobblemon.mod.common.util.cobblemonResource import net.minecraft.resources.ResourceLocation @@ -60,8 +61,10 @@ class DamageTakenTransformationProgress : TransformationProgress - evolution.requirements.any { requirement -> + val form = pokemon.form + if (form !is PermanentForm) return false + return form.transformations.any { transformation -> + transformation.requirements.any { requirement -> requirement is DamageTakenRequirement } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/DefeatTransformationProgress.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/DefeatTransformationProgress.kt index 4751a1a1ac..95afdc1263 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/DefeatTransformationProgress.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/DefeatTransformationProgress.kt @@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.pokemon.transformation.progress.Transformati import com.cobblemon.mod.common.api.pokemon.transformation.progress.TransformationProgressType import com.cobblemon.mod.common.api.pokemon.transformation.progress.TransformationProgressTypes import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.pokemon.transformation.form.PermanentForm import com.cobblemon.mod.common.pokemon.transformation.requirements.DefeatRequirement import com.cobblemon.mod.common.util.cobblemonResource import net.minecraft.resources.ResourceLocation @@ -43,8 +44,10 @@ class DefeatTransformationProgress : TransformationProgress - evolution.requirements.any { requirement -> + val form = pokemon.form + if (form !is PermanentForm) return false + return form.transformations.any { transformation -> + transformation.requirements.any { requirement -> requirement is DefeatRequirement && requirement.target.originalString.equals(this.progress.target.originalString, true) } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/LastBattleCriticalHitsTransformationProgress.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/LastBattleCriticalHitsTransformationProgress.kt index bf1495e181..8189221286 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/LastBattleCriticalHitsTransformationProgress.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/LastBattleCriticalHitsTransformationProgress.kt @@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.pokemon.transformation.progress.Transformati import com.cobblemon.mod.common.api.pokemon.transformation.progress.TransformationProgressType import com.cobblemon.mod.common.api.pokemon.transformation.progress.TransformationProgressTypes import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.pokemon.transformation.form.PermanentForm import com.cobblemon.mod.common.pokemon.transformation.requirements.BattleCriticalHitsRequirement import com.cobblemon.mod.common.util.cobblemonResource import net.minecraft.resources.ResourceLocation @@ -54,8 +55,10 @@ class LastBattleCriticalHitsTransformationProgress : TransformationProgress - evolution.requirements.any { requirement -> + val form = pokemon.form + if (form !is PermanentForm) return false + return form.transformations.any { transformation -> + transformation.requirements.any { requirement -> requirement is BattleCriticalHitsRequirement } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/RecoilTransformationProgress.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/RecoilTransformationProgress.kt index d09dbea543..2459e9567e 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/RecoilTransformationProgress.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/RecoilTransformationProgress.kt @@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.pokemon.transformation.progress.Transformati import com.cobblemon.mod.common.api.pokemon.transformation.progress.TransformationProgressType import com.cobblemon.mod.common.api.pokemon.transformation.progress.TransformationProgressTypes import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.pokemon.transformation.form.PermanentForm import com.cobblemon.mod.common.pokemon.transformation.requirements.RecoilRequirement import com.cobblemon.mod.common.util.cobblemonResource import com.mojang.serialization.Codec @@ -60,8 +61,10 @@ class RecoilTransformationProgress : TransformationProgress - evolution.requirements.any { requirement -> + val form = pokemon.form + if (form !is PermanentForm) return false + return form.transformations.any { transformation -> + transformation.requirements.any { requirement -> requirement is RecoilRequirement } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/UseMoveTransformationProgress.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/UseMoveTransformationProgress.kt index d6fd6ebabf..9327471caa 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/UseMoveTransformationProgress.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/progress/UseMoveTransformationProgress.kt @@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.pokemon.transformation.progress.Transformati import com.cobblemon.mod.common.api.pokemon.transformation.progress.TransformationProgressType import com.cobblemon.mod.common.api.pokemon.transformation.progress.TransformationProgressTypes import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.pokemon.transformation.form.PermanentForm import com.cobblemon.mod.common.pokemon.transformation.requirements.UseMoveRequirement import com.cobblemon.mod.common.util.cobblemonResource import net.minecraft.resources.ResourceLocation @@ -66,8 +67,10 @@ class UseMoveTransformationProgress : TransformationProgress - evolution.requirements.any { requirement -> + val form = pokemon.form + if (form !is PermanentForm) return false + return form.transformations.any { transformation -> + transformation.requirements.any { requirement -> requirement is UseMoveRequirement && requirement.move == move } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/requirements/HeldItemRequirement.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/requirements/HeldItemRequirement.kt index 14d6bc00ed..254b873dae 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/requirements/HeldItemRequirement.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/transformation/requirements/HeldItemRequirement.kt @@ -26,7 +26,8 @@ import net.minecraft.world.item.ItemStack * @since March 21st, 2022 */ class HeldItemRequirement( - val itemCondition: NbtItemPredicate = NbtItemPredicate(ItemIdentifierCondition(ResourceLocation.parse("air"))), + val itemCondition: NbtItemPredicate = + NbtItemPredicate(ItemIdentifierCondition(ResourceLocation.fromNamespaceAndPath("minecraft", "air"))), val consumeHeldItem: Boolean? = null ) : TransformationRequirement { diff --git a/common/src/main/resources/assets/cobblemon/species.schema.json b/common/src/main/resources/assets/cobblemon/species.schema.json index 670b990938..87d4841fb6 100644 --- a/common/src/main/resources/assets/cobblemon/species.schema.json +++ b/common/src/main/resources/assets/cobblemon/species.schema.json @@ -5,6 +5,10 @@ "description": "A Pokémon species", "type": "object", "properties": { + "implemented": { + "type": "boolean", + "default": false + }, "name": { "description": "The Pokémon's name", "type": "string" @@ -21,9 +25,12 @@ "description": "The Pokémon secondary type", "type": "string" }, - "maleRatio": { - "description": "The Pokémon's gender ratio", - "type": ["number", "null"] + "abilities": { + "description": "The Pokémon's possible abilities", + "type": "array", + "items": { + "type": "string" + } }, "baseStats": { "description": "The Pokémon's base stats", @@ -57,6 +64,110 @@ "description": "The Pokémon's catch rate", "type": "integer" }, + "maleRatio": { + "description": "The Pokémon's gender ratio", + "type": "number" + }, + "baseFriendship": { + "description": "The Pokémon's base friendship", + "type": "integer" + }, + "experienceGroup": { + "description": "The Pokémon's experience group", + "type": "string" + }, + "eggCycles": { + "description": "The Pokémon's egg cycles", + "type": "integer" + }, + "eggGroups": { + "description": "The Pokémon's egg group", + "type": "string" + }, + "height": { + "description": "The Pokémon's Pokédex height in decimeters", + "type": "number" + }, + "weight": { + "description": "The Pokémon's Pokédex weight in hectograms", + "type": "number" + }, + "labels": { + "description": "The Pokémon's descriptors", + "type": "array", + "items": { + "type": "string" + } + }, + "features": { + "description": "The Pokémon's features", + "type": "array", + "items": { + "type": "string" + } + }, + "aspects": { + "description": "The Pokémon's aspects", + "type": "array", + "items": { + "type": "string" + } + }, + "baseExperienceYield": { + "description": "The Pokémon's base stats", + "type": "object" + }, + "evYield": { + "description": "The Pokémon's base stats", + "type": "object", + "hp": { + "description": "The Pokémon's base health stat", + "type": "integer" + }, + "attack": { + "description": "The Pokémon's base attack stat", + "type": "integer" + }, + "defence": { + "description": "The Pokémon's base defence stat", + "type": "integer" + }, + "specialAttack": { + "description": "The Pokémon's base special attack stat", + "type": "integer" + }, + "specialDefence": { + "description": "The Pokémon's base special defence stat", + "type": "integer" + }, + "speed": { + "description": "The Pokémon's base speed stat", + "type": "integer" + } + }, + "drops": { + "description": "The items this Pokémon drops on defeat", + "type": "object", + "amount": { + "description": "The maximum number of items this Pokémon may drop", + "type": "integer" + }, + "entries": { + "description": "The different items that this Pokémon may drop", + "type": "array", + "items": { + "type": "object", + "item": { + "description": "The ID of the item that is dropped", + "type": "string" + }, + "percentage": { + "description": "The chance that this item is one of the items dropped", + "type": "number" + } + } + } + }, "baseScale": { "description": "The Pokémon's base scale", "type": "number" @@ -73,38 +184,233 @@ "type": "number" }, "fixed": { - "description": "idk", - "type": "boolean" + "description": "Whether the hitbox is fixed", + "type": "boolean", + "default": false } }, + "battleTheme": { + "description": "The ID of the SoundEvent that's played as the Pokémon's encounter theme", + "type": "string", + "default": "cobblemon:battle.pvw.default" + }, + "dynamaxBlocked": { + "description": "Whether the Pokémon is restricted from dynamaxing", + "type": "boolean", + "default": false + }, + "shoulderMountable": { + "description": "The Pokémon's gender ratio", + "type": "boolean", + "default": false + }, + "behaviour": { + "description": "The Pokémon's hitbox", + "type": "object", + "moving": { + "type": "object", + "walk": { + "description": "The Pokémon's behaviour when on land", + "type": "object", + "canWalk": { + "description": "Whether the Pokémon can move on land", + "type": "boolean", + "default": true + }, + "avoidsLand": { + "description": "Whether the Pokémon seeks the nearest source of water", + "type": "boolean", + "default": false + }, + "walkSpeed": { + "description": "The Pokémon's speed while on land", + "type": "number", + "default": 0.35 + } + }, + "swim": { + "description": "The Pokémon's behaviour when in liquid", + "type": "object", + "avoidsWater": { + "description": "Whether the Pokémon seeks the nearest source of land", + "type": "boolean", + "default": false + }, + "hurtByLava": { + "description": "Whether the Pokémon is damaged in lava", + "type": "boolean", + "default": true + }, + "canSwimInWater": { + "description": "Whether the Pokémon can swim in water", + "type": "boolean", + "default": true + }, + "canSwimInLava": { + "description": "Whether the Pokémon can swim in lava", + "type": "boolean", + "default": true + }, + "canBreatheUnderwater": { + "description": "Whether the Pokémon can breathe in water", + "type": "boolean", + "default": false + }, + "canBreatheUnderlava": { + "description": "Whether the Pokémon can breathe in lava", + "type": "boolean", + "default": false + }, + "canWalkOnWater": { + "description": "Whether the Pokémon can walk on water", + "type": "boolean", + "default": false + }, + "canWalkOnLava": { + "description": "Whether the Pokémon can walk on lava", + "type": "boolean", + "default": false + }, + "swimSpeed": { + "description": "The Pokémon's speed while in liquid", + "type": "number", + "default": 0.3 + } + }, + "fly": { + "description": "The Pokémon's behaviour when in air", + "type": "object", + "canFly": { + "description": "Whether the Pokémon can move in ar", + "type": "boolean", + "default": false + }, + "flySpeedHorizontal": { + "description": "The Pokémon's speed while in air", + "type": "number", + "default": 0.3 + } + }, + "canLook": { + "description": "Whether the Pokémon can look at targets", + "type": "boolean", + "default": true + }, + "looksAtEntities": { + "description": "Whether the Pokémon can look at other entities", + "type": "boolean", + "default": true + }, + "wanderChance": { + "description": "The chance that the Pokémon breaks idle state and wanders", + "type": "integer", + "default": 120 + }, + "wanderSpeed": { + "description": "The speed that the Pokémon wanders", + "type": "number", + "default": 1.0 + } + }, + "resting": { + "type": "object", + "canSleep": { + "description": "Whether the Pokémon can enter a sleeping state", + "type": "boolean", + "default": false + }, + "times": { + "description": "The time of day that the Pokémon can sleep", + "type": "string", + "enum": ["any", "day", "night", "noon", "midnight", "dawn", "dusk", "twilight", "morning", "afternoon"], + "default": "night" + }, + "sleepChance": { + "description": "The chance that the Pokémon enters a sleeping state", + "type": "number", + "default": 0.0016 + }, + "blocks": { + "description": "The blocks that the Pokémon can sleep on", + "type": "array", + "items": { + "type": "string" + } + }, + "biomes": { + "description": "The biome that the Pokémon can sleep in", + "type": "array", + "items": { + "type": "string" + } + }, + "light": { + "description": "The light level that the Pokémon can sleep in", + "type": "string", + "default": "0-15" + }, + "depth": { + "description": "How deep the Pokémon sleeps", + "type": "string", + "enum": ["comatose", "normal"], + "default": "normal" + }, + "willSleepOnBed": { + "description": "Whether the Pokémon can sleep on a bed", + "type": "boolean" + } + }, + "idle": { + "type": "object", + "pointsAtSpawn": { + "description": "Whether the Pokémon looks towards world spawn", + "type": "boolean", + "default": false + } + } + }, + "moves": { + "description": "The Pokémon's learnset", + "type": "array", + "items": { + "type": "string" + } + }, + "preEvolution": { + "description": "The Pokémon's pre-evolved form", + "type": "string" + }, "evolutions": { "description": "The Pokémon's evolutions", "type": "array", "items": { "type": "object", - "variant": { - "description": "The ID of the event that attempts to start an evolution", - "type": "string", - "enum": ["level_up", "trade", "item_interact"] - }, "id": { - "description": "An unique ID for this evolution, IE pikachu_level", + "description": "A unique ID for this evolution (i.e. nosepass_probopass)", "type": "string" }, "result": { "description": "The PokemonProperties that represents the result of this evolution", "type": "string" }, - "optional": { - "description": "If this evolution will allow the user to choose when to start it or not", - "type": "boolean" + "learnableMoves": { + "description": "The moves learnt after evolution", + "type": "array", + "items": { + "type": "string" + } }, - "consumeHeldItem": { - "description": "If this evolution will consume the held item", - "type": "boolean" - }, - "data": { - "description": "The data that backs this event" + "trigger": { + "type": ["object", "string"], + "variant": { + "description": "The ID of the event that attempts to start this evolution", + "type": "string", + "enum": ["level_up", "trade", "item_interact", "block_click"] + }, + "requiredContext": { + "description": "The context required to kick-start this evolution", + "type": "string" + } }, "requirements": { "description": "The requirements behind this evolution", @@ -120,12 +426,27 @@ "description": "The data that backs this requirement" } } + }, + "optional": { + "description": "If this evolution will allow the user to choose when to start it or not", + "type": "boolean", + "default": true } } }, - "battleTheme": { - "description": "The SoundEvent ID for the theme to play when encountering a wild Pokémon of this species", - "type": "string" + "temporaryForms": { + "description": "The interchangeable forms of this Pokémon", + "type": "array", + "items": { + "type": "object" + } + }, + "permanentForms": { + "description": "The non-interchangeable forms of this Pokémon", + "type": "array", + "items": { + "type": "object" + } } } } \ No newline at end of file