forked from mirrors/cobblemon
Showdown Form and Species registration rework.
This commit is contained in:
committed by
Segfault Guy
parent
641f529de3
commit
7fb873dc2c
@@ -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<Species> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<String, String> = 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<Species> {
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
+8
-4
@@ -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)
|
||||
}
|
||||
|
||||
+10
-6
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Stat, Int>? = null,
|
||||
@@ -73,16 +78,12 @@ class FormData(
|
||||
private val _shoulderEffects: MutableList<ShoulderEffect>? = null,
|
||||
@SerializedName("moves")
|
||||
private var _moves: Learnset? = null,
|
||||
@SerializedName("evolutions")
|
||||
private val _evolutions: MutableSet<Evolution>? = null,
|
||||
@SerializedName("abilities")
|
||||
private var _abilities: AbilityPool? = null,
|
||||
@SerializedName("drops")
|
||||
private var _drops: DropTable? = null,
|
||||
@SerializedName("pokedex")
|
||||
private var _pokedex: MutableList<String>? = 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<String>? = 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<String>()
|
||||
|
||||
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<Evolution>
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -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<StoreCoordinates<*>?>(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<Evolution> get() = this.form.evolutions.sortedBy { evolution -> evolution.optional }
|
||||
val evolutions: Iterable<Evolution> get() {
|
||||
val form = this.form
|
||||
return if (form is PermanentForm) form.evolutions.sortedBy { evolution -> evolution.optional } else this.species.evolutions
|
||||
}
|
||||
val lockedEvolutions: Iterable<Evolution>
|
||||
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.
|
||||
|
||||
@@ -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<Species>, ShowdownIdentifiable {
|
||||
var weight = 1F
|
||||
private set
|
||||
|
||||
var forms = mutableListOf<FormData>()
|
||||
var permanentForms = mutableListOf<PermanentForm>()
|
||||
private set
|
||||
|
||||
val standardForm by lazy { FormData(_evolutions = this.evolutions).initialize(this) }
|
||||
var temporaryForms = mutableListOf<TemporaryForm>()
|
||||
private set
|
||||
|
||||
val standardForm by lazy {
|
||||
StandardForm(
|
||||
_evolutions = this.evolutions,
|
||||
_permanentForms = this.permanentForms,
|
||||
_temporaryForms = this.temporaryForms
|
||||
).initialize(this) as StandardForm
|
||||
}
|
||||
|
||||
val forms: Collection<FormData> get() = permanentForms + temporaryForms
|
||||
|
||||
val transformations: Collection<Transformation> get() = evolutions + temporaryForms
|
||||
|
||||
var labels = hashSetOf<String>()
|
||||
private set
|
||||
@@ -164,15 +180,13 @@ class Species : ClientDataSynchronizer<Species>, 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<Species>, 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<Species>, 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<Species>, 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<Species>, 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<Species>, 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 {
|
||||
|
||||
+72
@@ -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<Evolution>? = null,
|
||||
@SerializedName("preEvolution")
|
||||
private val _preEvolution: PreEvolution? = null,
|
||||
@SerializedName("temporaryForms")
|
||||
private val _temporaryForms: MutableList<TemporaryForm>? = 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<Evolution>
|
||||
get() = _evolutions ?: mutableSetOf()
|
||||
|
||||
val preEvolution: PreEvolution?
|
||||
get() = _preEvolution ?: species.preEvolution
|
||||
|
||||
val temporaryForms: MutableList<TemporaryForm>
|
||||
get() = _temporaryForms ?: mutableListOf()
|
||||
|
||||
open val forms: List<FormData> get() = temporaryForms
|
||||
|
||||
open val transformations: List<Transformation> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -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<Evolution>? = null,
|
||||
@SerializedName("temporaryForms")
|
||||
private val _temporaryForms: MutableList<TemporaryForm>? = null,
|
||||
@SerializedName("regularForms")
|
||||
private val _permanentForms: MutableList<PermanentForm>? = null,
|
||||
) : PermanentForm(_evolutions = _evolutions, _temporaryForms = _temporaryForms) {
|
||||
|
||||
val permanentForms: MutableList<PermanentForm>
|
||||
get() = _permanentForms ?: mutableListOf()
|
||||
|
||||
override val forms: List<FormData> = this.temporaryForms + this.permanentForms
|
||||
|
||||
/** Aggregates child forms and their nested forms. */
|
||||
fun flattenForms(): List<FormData> = this.forms + this.permanentForms.filter { it != this }.flatMap { it.temporaryForms }
|
||||
}
|
||||
+38
@@ -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<String> = 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<TransformationRequirement> = mutableSetOf()
|
||||
) : FormData(), Transformation
|
||||
+5
-2
@@ -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<DamageTakenTran
|
||||
}
|
||||
|
||||
fun supports(pokemon: Pokemon): Boolean {
|
||||
return pokemon.form.evolutions.any { evolution ->
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -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<DefeatTransformation
|
||||
}
|
||||
|
||||
override fun shouldKeep(pokemon: Pokemon): Boolean {
|
||||
return pokemon.form.evolutions.any { evolution ->
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -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<Last
|
||||
}
|
||||
|
||||
fun supports(pokemon: Pokemon): Boolean {
|
||||
return pokemon.form.evolutions.any { evolution ->
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -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<RecoilTransformation
|
||||
}
|
||||
|
||||
fun supports(pokemon: Pokemon): Boolean {
|
||||
return pokemon.form.evolutions.any { evolution ->
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -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<UseMoveTransformati
|
||||
}
|
||||
|
||||
fun supports(pokemon: Pokemon, move: MoveTemplate): Boolean {
|
||||
return pokemon.form.evolutions.any { evolution ->
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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 {
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user