Merge branch 'fix-evo-tree-crash' into 'main'

Fix grotle evolution crashing evo tree and species features not synching to the client

See merge request cable-mc/cobblemon!2456
This commit is contained in:
Polymeta
2026-10-10 12:51:04 +00:00
28 changed files with 979 additions and 941 deletions
@@ -425,7 +425,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesEvolutionSyncPacket.ID, SpeciesEvolutionSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(DexEvolutionDisplaySyncPacket.ID, DexEvolutionDisplaySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(PropertiesCompletionRegistrySyncPacket.ID, PropertiesCompletionRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(StandardSpeciesFeatureSyncPacket.ID, StandardSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(GlobalSpeciesFeatureSyncPacket.ID, GlobalSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
@@ -11,9 +11,6 @@ package com.cobblemon.mod.common.api.conditional
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
/**
@@ -24,19 +21,11 @@ import java.lang.reflect.Type
* @author Hiroku
* @since July 17th, 2022
*/
interface RegistryLikeAdapter<B> : JsonDeserializer<RegistryLikeCondition<B>>, JsonSerializer<RegistryLikeCondition<B>> {
interface RegistryLikeAdapter<B> : JsonDeserializer<RegistryLikeCondition<B>> {
val registryLikeConditions: MutableList<(JsonElement) -> RegistryLikeCondition<B>?>
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): RegistryLikeCondition<B> {
return registryLikeConditions.firstNotNullOfOrNull { it(json) }
?: throw IllegalArgumentException("Unable to deserialize $json")
}
override fun serialize(src: RegistryLikeCondition<B>, typeOfSrc: Type, ctx: JsonSerializationContext): JsonElement {
return when (src) {
is RegistryLikeTagCondition<B> -> JsonPrimitive(RegistryLikeTagCondition.PREFIX + src.tag.location())
is RegistryLikeIdentifierCondition<B> -> JsonPrimitive(src.identifier.toString())
else -> throw IllegalArgumentException("Unable to serialize $src")
}
}
}
@@ -72,30 +72,6 @@ object Dexes : JsonDataRegistry<PokedexDef> {
return findDexEntryBySpeciesId(cobblemonResource("national"), speciesId)
}
fun findRootDexEntryBySpeciesId(speciesId: ResourceLocation, formName: String): Pair<PokedexEntry, PokedexForm> {
var rootSpecies = PokemonSpecies.getByIdentifier(speciesId)!!
val initialFormEquivalents = getFormEquivalents(formName)
var rootForm = rootSpecies.forms.firstOrNull { initialFormEquivalents.contains(it.name.lowercase()) }
?: rootSpecies.standardForm
var preEvolution = rootForm.preEvolution
while (preEvolution != null) {
rootSpecies = preEvolution.species
rootForm = preEvolution.resolveForm(rootForm.aspects.toSet())
preEvolution = rootForm.preEvolution
}
val entry = findDexEntryBySpeciesId(rootSpecies.resourceIdentifier)
?: throw RuntimeException("Root dex entry not found")
val formEquivalents = getFormEquivalents(rootForm.name)
val form =
entry.forms.firstOrNull { formEquivalents.contains(it.displayForm.lowercase()) } ?:
entry.forms.first { it.displayForm.equals("Normal", ignoreCase = true) }
return entry to form
}
fun getFormEquivalents(formName: String): List<String> {
val key = equivalentFormsLookup[formName.lowercase()]
return equivalentForms[key] ?: listOf(formName.lowercase())
@@ -8,13 +8,19 @@
package com.cobblemon.mod.common.api.pokedex.entry
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.DexEntrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.DexEvolutionDisplaySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
@@ -38,6 +44,8 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
val entries = mutableMapOf<ResourceLocation, PokedexEntry>()
val evolutionDisplays = mutableListOf<DexEvolutionDisplay>()
override fun reload(data: Map<ResourceLocation, PokedexEntry>) {
data.forEach { _, entry ->
entries[entry.id] = entry
@@ -50,6 +58,7 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
}
}
}
computeEvolutionDisplays()
observable.emit(this)
}
@@ -57,5 +66,56 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
override fun sync(player: ServerPlayer) {
DexEntrySyncPacket(entries.values).sendToPlayer(player)
DexEvolutionDisplaySyncPacket(evolutionDisplays).sendToPlayer(player)
}
fun computeEvolutionDisplays() {
evolutionDisplays.clear()
PokemonSpecies.species.forEach {
evolutionDisplays.addAll(buildDisplays(it.standardForm))
evolutionDisplays.addAll(it.forms.flatMap { form -> buildDisplays(form) })
}
}
fun buildDisplays(form: FormData): List<DexEvolutionDisplay> {
val result = mutableListOf<DexEvolutionDisplay>()
(form.ownEvolutions ?: form.evolutions).forEach { evolution ->
val requirements = mutableListOf<DexEvolutionRequirementDisplay>()
getToolTipHeaderIcon(evolution)?.let { header ->
requirements.add(header)
}
evolution.requirements.mapNotNull { resolveRequirementData(it) }.forEach { requirement ->
requirements.add(requirement)
}
listOfNotNull(evolution.result, evolution.shedder).forEach resultLoop@{ resultProperties ->
val resultSpecies = resultProperties.species
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
?: return@resultLoop
if (!resultSpecies.implemented)
return@resultLoop
// Carry the source form's aspects over like a real evolution does (Alola-Bias Pichu -> Alola-Bias Pikachu)
val resultForm = resultSpecies.getForm(form.aspects.toSet() + resultProperties.aspects)
val toAspects = resultProperties.aspects + resultForm.aspects
val resultRequirements = if (resultProperties === evolution.shedder) {
requirements + CobblemonDexEvolutionRequirementDisplay(CobblemonItems.POKE_BALL, lang("ui.pokedex.evolution.shedder.tooltip"))
} else {
requirements
}
result.add(DexEvolutionDisplay(
fromSpecies = form.species.resourceIdentifier,
fromForm = form.name,
fromAspects = form.aspects.toSet(),
toSpecies = resultSpecies.resourceIdentifier,
toForm = resultForm.name,
toAspects = toAspects,
requirements = resultRequirements,
))
}
}
return result
}
}
@@ -0,0 +1,464 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokedex.entry
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeTagCondition
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.pokemon.requirements.*
import com.cobblemon.mod.common.util.adapters.RegistryAccessResolver
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.ExtraCodecs
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.levelgen.structure.Structure
import kotlin.jvm.optionals.getOrNull
data class DexEvolutionDisplay(
val fromSpecies: ResourceLocation,
val fromForm: String,
val fromAspects: Set<String>,
val toSpecies: ResourceLocation,
val toForm: String,
val toAspects: Set<String>,
val requirements: List<DexEvolutionRequirementDisplay>,
)
interface DexEvolutionRequirementDisplay {
val icon: Item
val tooltip: MutableComponent
}
data class CobblemonDexEvolutionRequirementDisplay(
override val icon: Item,
override val tooltip: MutableComponent,
) : DexEvolutionRequirementDisplay
data class CobblemonBiomeDexEvolutionRequirementDisplay(
override val icon: Item,
override val tooltip: MutableComponent,
val condition: ExtraCodecs.TagOrElementLocation? = null,
val antiCondition: ExtraCodecs.TagOrElementLocation? = null,
) : DexEvolutionRequirementDisplay
fun getToolTipHeaderIcon(evolution: Evolution): DexEvolutionRequirementDisplay? {
when (evolution) {
is ItemInteractionEvolution -> return getToolTipHeaderIcon(evolution)
is TradeEvolution -> return getToolTipHeaderIcon(evolution)
}
return null
}
fun getToolTipHeaderIcon(itemInteractionEvolution: ItemInteractionEvolution): CobblemonDexEvolutionRequirementDisplay {
val item = itemInteractionEvolution.requiredContext.items.get().first().value()
return CobblemonDexEvolutionRequirementDisplay(item, lang("ui.pokedex.evolution.item_interact.tooltip", item.description.plainCopy().green()))
}
fun getToolTipHeaderIcon(tradeEvolution: TradeEvolution): CobblemonDexEvolutionRequirementDisplay {
val species = tradeEvolution.requiredContext.species
val tooltip = if (species == null) {
lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
} else {
lang("ui.pokedex.evolution.trade.tooltip.with_species", PokemonSpecies.getByIdentifier(species.asIdentifierDefaultingNamespace())!!.translatedName.green())
}
return CobblemonDexEvolutionRequirementDisplay(CobblemonItems.LINK_CABLE, tooltip)
}
fun resolveRequirementData(requirement: Requirement): DexEvolutionRequirementDisplay? {
val icon = requirement.pokedexIcon?.let {
RegistryAccessResolver.getAccess().registryOrThrow(Registries.ITEM).get(it)
}
val tooltip = requirement.pokedexTooltip?.let {
Component.translatable(it)
}
if (icon != null && tooltip != null) {
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
return buildRequirementData(requirement)
}
fun buildRequirementData(requirement: Requirement): DexEvolutionRequirementDisplay? {
when (requirement) {
is AdvancementRequirement -> return buildAdvancementRequirement(requirement)
is BattleCriticalHitsRequirement -> return buildBattleCriticalHitsRequirement(requirement)
is BiomeRequirement -> return buildBiomeRequirement(requirement)
is BlocksTraveledRequirement -> return buildBlocksTraveledRequirement(requirement)
is DamageTakenRequirement -> return buildDamageTakenRequirement(requirement)
is DefeatRequirement -> return buildDefeatRequirement(requirement)
is FriendshipRequirement -> return buildFriendshipRequirement(requirement)
is HeldItemRequirement -> return buildHeldItemRequirement(requirement)
is LevelRequirement -> return buildLevelRequirement(requirement)
is MoonPhaseRequirement -> return buildMoonPhaseRequirement(requirement)
is MoveSetRequirement -> return buildMoveSetRequirement(requirement)
is MoveTypeRequirement -> return buildMoveTypeRequirement(requirement)
is NegateRequirement -> return buildNegateRequirement(requirement)
is OwnerHoldsItemRequirement -> return buildOwnerHoldsItemRequirement(requirement)
is PartyMemberRequirement -> return buildPartyMemberRequirement(requirement)
is PokemonPropertiesRequirement -> return buildPokemonPropertiesRequirement(requirement)
is PropertyRangeRequirement -> return buildPropertyRangeRequirement(requirement)
is RecoilRequirement -> return buildRecoilRequirement(requirement)
is StatCompareRequirement -> return buildStatCompareRequirement(requirement)
is StatEqualRequirement -> return buildStatEqualRequirement(requirement)
is StructureRequirement -> return buildStructureRequirement(requirement)
is TimeRangeRequirement -> return buildTimeRangeRequirement(requirement)
is UseMoveRequirement -> return buildUseMoveRequirement(requirement)
is WeatherRequirement -> return buildWeatherRequirement(requirement)
}
return null
}
fun buildAdvancementRequirement(advancementRequirement: AdvancementRequirement): DexEvolutionRequirementDisplay {
val icon = Items.BOOK
val identifier = advancementRequirement.requiredAdvancement
val advancementLanguageKey = "${identifier.namespace}.${identifier.path.substringAfterLast('/')}"
val advancementComponent = Component.translatable("advancements.$advancementLanguageKey").green()
val tooltip = lang("ui.pokedex.requirement.advancement.tooltip", advancementComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildBattleCriticalHitsRequirement(battleCriticalHitsRequirement: BattleCriticalHitsRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.MEDICINAL_LEEK
val criticalsComponent = Component.literal(battleCriticalHitsRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.battle_critical_hits.tooltip", criticalsComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
private fun tagDisplayName(registry: String, id: ResourceLocation): MutableComponent {
return Component.translatableWithFallback("tag.$registry.${id.namespace}.${id.path.replace('/', '.')}", prettifyPath(id.path))
}
private fun prettifyPath(path: String): String {
return path.substringAfterLast('/').removePrefix("is_").split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) }
}
fun buildNegateRequirement(negateRequirement: NegateRequirement): DexEvolutionRequirementDisplay? {
val inner = negateRequirement.inner
when (inner) {
is BiomeRequirement -> buildBiomeRequirement(inner, negated = true)?.let { return it }
is StructureRequirement -> buildStructureRequirement(inner, negated = true)?.let { return it }
}
val innerDisplay = resolveRequirementData(inner) ?: return null
return CobblemonDexEvolutionRequirementDisplay(innerDisplay.icon, lang("ui.pokedex.requirement.negate.tooltip", innerDisplay.tooltip))
}
fun buildBiomeRequirement(biomeRequirement: BiomeRequirement, negated: Boolean = false): DexEvolutionRequirementDisplay? {
// Negating "in X" is "not in X"; negating both a condition and an anticondition isn't expressible as one line
if (negated && biomeRequirement.biomeCondition != null && biomeRequirement.biomeAnticondition != null) return null
val condition = if (negated) biomeRequirement.biomeAnticondition else biomeRequirement.biomeCondition
val antiCondition = if (negated) biomeRequirement.biomeCondition else biomeRequirement.biomeAnticondition
if (condition == null && antiCondition == null) {
return null
}
val icon = Items.COMPASS
val displayName: (RegistryLikeCondition<Biome>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> tagDisplayName("worldgen.biome", it.tag.location())
is RegistryLikeIdentifierCondition -> Component.translatable("biome.${it.identifier.namespace}.${it.identifier.path}")
else -> Component.literal(it.toString())
}
}
val tooltip = if (condition != null && antiCondition != null) {
lang("ui.pokedex.requirement.biome.both.tooltip", displayName(condition).green(), displayName(antiCondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.biome.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.biome.anticondition.tooltip", displayName(antiCondition!!).green())
}
var conditionConverted: ExtraCodecs.TagOrElementLocation? = null
if (condition != null) {
val conditionIdentifier = if (condition is RegistryLikeIdentifierCondition) condition.identifier else (condition as RegistryLikeTagCondition).tag.location
conditionConverted = ExtraCodecs.TagOrElementLocation(conditionIdentifier, condition is RegistryLikeTagCondition)
}
var antiConditionConverted: ExtraCodecs.TagOrElementLocation? = null
if (antiCondition != null) {
val conditionIdentifier = if (antiCondition is RegistryLikeIdentifierCondition) antiCondition.identifier else (antiCondition as RegistryLikeTagCondition).tag.location
antiConditionConverted = ExtraCodecs.TagOrElementLocation(conditionIdentifier, antiCondition is RegistryLikeTagCondition)
}
return CobblemonBiomeDexEvolutionRequirementDisplay(icon, tooltip, conditionConverted, antiConditionConverted)
}
fun buildBlocksTraveledRequirement(blocksTraveledRequirement: BlocksTraveledRequirement): DexEvolutionRequirementDisplay {
val icon = Items.LEATHER_BOOTS
val blocksComponent = Component.literal(blocksTraveledRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.blocks_traveled.tooltip", blocksComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildDamageTakenRequirement(damageTakenRequirement: DamageTakenRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.PUNCHING_GLOVE
val blocksComponent = Component.literal(damageTakenRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.damage_taken.tooltip", blocksComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildDefeatRequirement(defeatRequirement: DefeatRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(defeatRequirement.target) ?: return null
val icon = CobblemonItems.PUNCHING_GLOVE
val defeatedAmount = defeatRequirement.amount
val descriptionComponent = Component.literal(defeatedAmount.toString()).green().add(" ").add(description)
val tooltip = lang("ui.pokedex.requirement.defeat.tooltip", descriptionComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildFriendshipRequirement(friendshipRequirement: FriendshipRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.SOOTHE_BELL
val friendshipAmountComponent = Component.literal(friendshipRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.friendship.tooltip", friendshipAmountComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildHeldItemRequirement(heldItemRequirement: HeldItemRequirement): DexEvolutionRequirementDisplay? {
val items = heldItemRequirement.itemCondition.items.getOrNull() ?: return null
val item = items.firstOrNull()?.value() ?: return null
val itemComponent = items.unwrapKey().map { tag ->
val id = tag.location()
val fallback = id.path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) }
Component.translatableWithFallback("tag.item.${id.namespace}.${id.path.replace('/', '.')}", fallback)
}.orElseGet { item.description.plainCopy() }
val tooltip = lang("ui.pokedex.requirement.held_item.tooltip", itemComponent.green())
return CobblemonDexEvolutionRequirementDisplay(item, tooltip)
}
fun buildLevelRequirement(levelRequirement: LevelRequirement): DexEvolutionRequirementDisplay {
val minLevel = levelRequirement.minLevel
val maxLevel = levelRequirement.maxLevel
val icon = CobblemonItems.RARE_CANDY
val tooltip =
if (minLevel == maxLevel || maxLevel == Int.MAX_VALUE)
lang("ui.pokedex.requirement.level.tooltip.min", Component.literal(minLevel.toString()).green())
else
lang("ui.pokedex.requirement.level.tooltip.between", Component.literal(minLevel.toString()).green(), Component.literal(maxLevel.toString()).green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): DexEvolutionRequirementDisplay {
val icon = Items.CLOCK
val moonPhaseComponent = moonPhaseRequirement.moonPhase.displayName().green()
val tooltip = lang("ui.pokedex.requirement.moon_phase.tooltip", moonPhaseComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoveSetRequirement(moveSetRequirement: MoveSetRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.TECHNICAL_MACHINE
val tooltip = lang("ui.pokedex.requirement.has_move.tooltip", moveSetRequirement.move.displayName.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoveTypeRequirement(moveTypeRequirement: MoveTypeRequirement): DexEvolutionRequirementDisplay {
val type = ElementalTypes.get(moveTypeRequirement.type.name)
val icon = when (type) {
ElementalTypes.NORMAL -> CobblemonItems.NORMAL_GEM
ElementalTypes.FIRE -> CobblemonItems.FIRE_GEM
ElementalTypes.WATER -> CobblemonItems.WATER_GEM
ElementalTypes.GRASS -> CobblemonItems.GRASS_GEM
ElementalTypes.ELECTRIC -> CobblemonItems.ELECTRIC_GEM
ElementalTypes.ICE -> CobblemonItems.ICE_GEM
ElementalTypes.FIGHTING -> CobblemonItems.FIGHTING_GEM
ElementalTypes.POISON -> CobblemonItems.POISON_GEM
ElementalTypes.GROUND -> CobblemonItems.GROUND_GEM
ElementalTypes.FLYING -> CobblemonItems.FLYING_GEM
ElementalTypes.PSYCHIC -> CobblemonItems.PSYCHIC_GEM
ElementalTypes.BUG -> CobblemonItems.BUG_GEM
ElementalTypes.ROCK -> CobblemonItems.ROCK_GEM
ElementalTypes.GHOST -> CobblemonItems.GHOST_GEM
ElementalTypes.DRAGON -> CobblemonItems.DRAGON_GEM
ElementalTypes.DARK -> CobblemonItems.DARK_GEM
ElementalTypes.STEEL -> CobblemonItems.STEEL_GEM
ElementalTypes.FAIRY -> CobblemonItems.FAIRY_GEM
else -> CobblemonItems.NORMAL_GEM
}
val tooltip = lang("ui.pokedex.requirement.has_move_type.tooltip", moveTypeRequirement.type.displayName.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildOwnerHoldsItemRequirement(ownerHoldsItemRequirement: OwnerHoldsItemRequirement): DexEvolutionRequirementDisplay {
val item = ownerHoldsItemRequirement.itemCondition.items.get().first().value()
val tooltip = lang("ui.pokedex.requirement.owner_held_item.tooltip", item.description.plainCopy().green())
return CobblemonDexEvolutionRequirementDisplay(item, tooltip)
}
fun buildPartyMemberRequirement(partyMemberRequirement: PartyMemberRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(partyMemberRequirement.target) ?: return null
val icon = CobblemonItems.POKE_BALL
val tooltip = if (partyMemberRequirement.contains) {
lang("ui.pokedex.requirement.party_member.tooltip", description)
} else {
lang("ui.pokedex.requirement.party_member.anticondition.tooltip", description)
}
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildPokemonPropertiesRequirement(pokemonPropertiesRequirement: PokemonPropertiesRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(pokemonPropertiesRequirement.target) ?: return null
val icon = Items.NAME_TAG
val tooltip = lang("ui.pokedex.requirement.pokemon_properties.tooltip", description)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildPropertyRangeRequirement(propertyRangeRequirement: PropertyRangeRequirement): DexEvolutionRequirementDisplay? {
val provider = SpeciesFeatures.getFeature(propertyRangeRequirement.feature) as? IntSpeciesFeatureProvider ?: return null
val displayName = provider.display?.name?.takeIf { it.isNotBlank() }?.asTranslated()?.green() ?: return null
val icon = provider.itemPoints.keys.firstOrNull()?.let { BuiltInRegistries.ITEM.get(it) } ?: Items.NAME_TAG
val range = propertyRangeRequirement.range
val tooltip = lang(
"ui.pokedex.requirement.property_range.tooltip",
displayName,
Component.literal(range.first.toString()).green(),
Component.literal(range.last.toString()).green()
)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildRecoilRequirement(recoilRequirement: RecoilRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.LIFE_ORB
val recoilComponent = Component.literal(recoilRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.recoil.tooltip", recoilComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStatCompareRequirement(statCompareRequirement: StatCompareRequirement): DexEvolutionRequirementDisplay {
val highStat = Stats.getStat(statCompareRequirement.highStat)
val lowStat = Stats.getStat(statCompareRequirement.lowStat)
val icon = when (highStat) {
Stats.HP, Stats.EVASION, Stats.ACCURACY -> CobblemonItems.HEALTH_MOCHI
Stats.ATTACK -> CobblemonItems.MUSCLE_MOCHI
Stats.DEFENCE -> CobblemonItems.RESIST_MOCHI
Stats.SPECIAL_ATTACK -> CobblemonItems.GENIUS_MOCHI
Stats.SPECIAL_DEFENCE -> CobblemonItems.CLEVER_MOCHI
Stats.SPEED -> CobblemonItems.SWIFT_MOCHI
}
val highStatComponent = highStat.displayName.plainCopy().green()
val lowStatComponent = lowStat.displayName.plainCopy().green()
val tooltip = lang("ui.pokedex.requirement.stat_compare.tooltip", highStatComponent, lowStatComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStatEqualRequirement(statEqualRequirement: StatEqualRequirement): DexEvolutionRequirementDisplay {
val statOne = Stats.getStat(statEqualRequirement.statOne)
val statTwo = Stats.getStat(statEqualRequirement.statTwo)
val statOneComponent = statOne.displayName.plainCopy().green()
val statTwoComponent = statTwo.displayName.plainCopy().green()
val icon = CobblemonItems.FRESH_START_MOCHI
val tooltip = lang("ui.pokedex.requirement.stat_equal.tooltip", statOneComponent, statTwoComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStructureRequirement(structureRequirement: StructureRequirement, negated: Boolean = false): DexEvolutionRequirementDisplay? {
if (negated && structureRequirement.structureCondition != null && structureRequirement.structureAnticondition != null) return null
val condition = if (negated) structureRequirement.structureAnticondition else structureRequirement.structureCondition
val anticondition = if (negated) structureRequirement.structureCondition else structureRequirement.structureAnticondition
if (condition == null && anticondition == null) {
return null
}
val icon = Items.STRUCTURE_BLOCK
val displayName: (RegistryLikeCondition<Structure>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> tagDisplayName("worldgen.structure", it.tag.location())
is RegistryLikeIdentifierCondition -> Component.literal(prettifyPath(it.identifier.path))
else -> Component.literal(it.toString())
}
}
val tooltip = if (condition != null && anticondition != null) {
lang("ui.pokedex.requirement.structure.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.structure.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.structure.anticondition.tooltip", displayName(anticondition!!).green())
}
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): DexEvolutionRequirementDisplay? {
val icon = Items.CLOCK
val timeRangeComponent = timeRangeRequirement.range.displayName() ?: return null
val tooltip = lang("ui.pokedex.requirement.time_range.tooltip", timeRangeComponent.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildUseMoveRequirement(useMoveRequirement: UseMoveRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.CHOICE_BAND
val tooltip = lang("ui.pokedex.requirement.use_move.tooltip", useMoveRequirement.move.displayName.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildWeatherRequirement(weatherRequirement: WeatherRequirement): DexEvolutionRequirementDisplay {
val icon: Item
val tooltip: MutableComponent
if (weatherRequirement.isThundering == true) {
icon = Items.LIGHTNING_ROD
tooltip = lang("ui.pokedex.requirement.weather.tooltip.thundering")
} else {
icon = Items.WATER_BUCKET
tooltip = lang("ui.pokedex.requirement.weather.tooltip.raining")
}
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree
package com.cobblemon.mod.common.api.pokedex.entry
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
@@ -28,7 +28,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.Items
internal fun describePokemonProperties(properties: PokemonProperties): MutableComponent? {
fun describePokemonProperties(properties: PokemonProperties): MutableComponent? {
if (hasUndescribedProperty(properties)) {
return null
}
@@ -26,7 +26,6 @@ import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.MovesetBuilder
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies.getByIdentifier
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
import com.cobblemon.mod.common.api.pokemon.effect.adapter.ShoulderEffectAdapter
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
@@ -44,9 +43,6 @@ import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
import com.cobblemon.mod.common.net.messages.client.data.FormEvolutionOverride
import com.cobblemon.mod.common.net.messages.client.data.SpeciesEvolutionEntry
import com.cobblemon.mod.common.net.messages.client.data.SpeciesEvolutionSyncPacket
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
@@ -246,18 +242,6 @@ object PokemonSpecies : JsonDataRegistry<Species> {
override fun sync(player: ServerPlayer) {
SpeciesRegistrySyncPacket(species.toList()).sendToPlayer(player)
// Sent after the species themselves so every species is already registered by the time this is decoded.
val evolutionEntries = species.map { singleSpecies ->
val formOverrides = singleSpecies.forms.mapNotNull { form ->
if (form.ownEvolutions == null && form.ownPreEvolution == null) {
null
} else {
FormEvolutionOverride(form.name, form.ownEvolutions, form.ownPreEvolution)
}
}
SpeciesEvolutionEntry(singleSpecies.resourceIdentifier, singleSpecies.evolutions, singleSpecies.preEvolution, formOverrides)
}
SpeciesEvolutionSyncPacket(evolutionEntries).sendToPlayer(player)
}
/**
@@ -25,8 +25,6 @@ interface PreEvolution {
val form: FormData
fun resolveForm(extraAspects: Set<String> = emptySet()): FormData = form
companion object {
fun of(species: Species, form: FormData = species.standardForm): PreEvolution = StandardPreEvolution(species, form)
@@ -38,7 +38,6 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_HEIG
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HEADER_BAR_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.ICONS_ARROW_PADDING_AROUND
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.ICONS_PADDING_AROUND
@@ -52,8 +51,7 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_MOVES
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_SIZE
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.TAB_STATS
import com.cobblemon.mod.common.client.gui.pokedex.widgets.*
import com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree.EvolutionTreeWidget
import com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree.debugEvolutionRequirementRendering
import com.cobblemon.mod.common.client.gui.pokedex.widgets.EvolutionTreeWidget
import com.cobblemon.mod.common.client.pokedex.PokedexType
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
@@ -828,10 +826,6 @@ class PokedexGUI private constructor(
}
}
if (tabInfoIndex == TAB_EVOLUTION_TREE && keyCode == InputConstants.KEY_SEMICOLON) {
debugEvolutionRequirementRendering()
}
return super.keyPressed(keyCode, scanCode, modifiers)
}
@@ -16,13 +16,17 @@ import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.Style
import net.minecraft.resources.ResourceLocation
import java.util.Optional
private val tooltipEdge = cobblemonResource("textures/gui/pokedex/tooltip_edge.png")
private val tooltipBackground = cobblemonResource("textures/gui/pokedex/tooltip_background.png")
private const val TOOLTIP_SLICE_SIZE = 11
private const val TOOLTIP_MAX_TEXT_WIDTH = 160
fun renderTooltip(
context: GuiGraphics,
@@ -49,7 +53,12 @@ fun renderTooltip(
posY: Int,
delta: Float,
offsetY: Int = 0,
maxTextWidth: Int = TOOLTIP_MAX_TEXT_WIDTH,
) {
val text = text.flatMap { wrapTooltipLine(it, maxTextWidth) }
// Only a tooltip that fits on a single line is centered; wrapped text is left-aligned
val centered = text.size == 1
val poseStack = context.pose()
val textWidth = text.maxOf { Minecraft.getInstance().font.width(it.font(CobblemonResources.DEFAULT_LARGE)) }
val tooltipPadding = 6
@@ -78,8 +87,6 @@ fun renderTooltip(
blitSlicedVertical(poseStack, tooltipBackground, posX - (tooltipWidth / 2), tooltipTop, tooltipWidth, totalHeight)
blitSlicedVertical(poseStack, tooltipEdge, posX + (tooltipWidth / 2), tooltipTop, 1, totalHeight)
val centered = text.size == 1
text.forEachIndexed { index, it ->
drawScaledText(
context = context,
@@ -95,6 +102,20 @@ fun renderTooltip(
poseStack.popPose()
}
private fun wrapTooltipLine(line: MutableComponent, maxWidth: Int): List<MutableComponent> {
val styled = line.copy().font(CobblemonResources.DEFAULT_LARGE)
if (Minecraft.getInstance().font.width(styled) <= maxWidth) return listOf(line)
return Minecraft.getInstance().font.splitter.splitLines(styled, maxWidth, Style.EMPTY).map { wrapped ->
val component = Component.empty()
wrapped.visit({ style, string ->
component.append(Component.literal(string).withStyle(style))
Optional.empty<Unit>()
}, Style.EMPTY)
component
}
}
// Stretching the whole 11px image over an arbitrary height thickens the caps as more lines are added, so
// instead the caps are drawn 1:1 and only the flat fill row is stretched to cover the remaining height
private fun blitSlicedVertical(
@@ -6,13 +6,16 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree
package com.cobblemon.mod.common.client.gui.pokedex.widgets
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.gui.*
import com.cobblemon.mod.common.api.pokedex.Dexes
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.entry.CobblemonBiomeDexEvolutionRequirementDisplay
import com.cobblemon.mod.common.api.pokedex.entry.DexEntries
import com.cobblemon.mod.common.api.pokedex.entry.DexEvolutionDisplay
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
@@ -20,6 +23,8 @@ import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUI
@@ -29,18 +34,21 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_D
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCROLL_SLOT_SIZE
import com.cobblemon.mod.common.client.gui.pokedex.ScaledButton
import com.cobblemon.mod.common.client.gui.pokedex.renderTooltip
import com.cobblemon.mod.common.client.gui.pokedex.widgets.PokemonSlotWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.renderScaledGuiItemIcon
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.tags.TagKey
class EvolutionTreeWidget(
val parent: PokedexGUI,
@@ -92,6 +100,8 @@ class EvolutionTreeWidget(
private var hoveredTooltips: List<MutableComponent>? = null
private var viewedForm: FormData? = null
private val expandButton: ScaledButton = ScaledButton(
0f,
0f,
@@ -146,9 +156,28 @@ class EvolutionTreeWidget(
if (pokedexEntry == null || pokedexForm == null) return
updatePositioning()
var rootSpecies = pokedexEntry
var rootForm = pokedexForm
var preEvolution = findPreEvolution(rootSpecies, rootForm)
while (preEvolution != null) {
rootSpecies = Dexes.findDexEntryBySpeciesId(preEvolution.fromSpecies)!!
rootForm = rootSpecies.forms.first { it.displayForm.equals(preEvolution.fromForm, ignoreCase = true) }
preEvolution = findPreEvolution(rootSpecies, rootForm)
}
val (rootPokedexEntry, rootPokedexForm) = Dexes.findRootDexEntryBySpeciesId(pokedexEntry.speciesId, pokedexForm.displayForm)
val rootEvolutionNode = buildEvolutionTree(rootPokedexEntry, rootPokedexForm, null)
val entry = Dexes.findDexEntryBySpeciesId(rootSpecies.speciesId)
?: throw RuntimeException("Root dex entry not found")
val viewedFormEquivalents = Dexes.getFormEquivalents(pokedexForm.displayForm)
viewedForm = PokemonSpecies.getByIdentifier(pokedexEntry.speciesId)
?.forms?.firstOrNull { viewedFormEquivalents.contains(it.name.lowercase()) }
val formEquivalents = Dexes.getFormEquivalents(rootForm.displayForm)
val form =
entry.forms.firstOrNull { formEquivalents.contains(it.displayForm.lowercase()) } ?:
entry.forms.first { it.displayForm.equals("Normal", ignoreCase = true) }
val rootEvolutionNode = buildEvolutionTree(entry, form)
// val isSameEntry = previousRootPokedexEntry != null && pokedexEntry?.id == previousRootPokedexEntry?.id
// val isSameForm =
@@ -172,6 +201,13 @@ class EvolutionTreeWidget(
panningY = 0.0
}
/** The evolution leading to [form]; the standard form falls back to any form of the species (no evolution leads to regular Vivillon). */
private fun findPreEvolution(entry: PokedexEntry, form: PokedexForm): DexEvolutionDisplay? {
return DexEntries.evolutionDisplays.firstOrNull { it.toSpecies == entry.speciesId && it.toForm == form.displayForm }
?: DexEntries.evolutionDisplays.takeIf { form.displayForm.equals("Normal", ignoreCase = true) }
?.firstOrNull { it.toSpecies == entry.speciesId }
}
fun updatePositioning() {
x = pX + X_MARGIN
y = pY + if (parent.evolutionTreeExpanded) Y_MARGIN_EXPANDED else Y_MARGIN_MINIMIZED
@@ -187,61 +223,50 @@ class EvolutionTreeWidget(
private fun buildEvolutionTree(
pokedexEntry: PokedexEntry,
pokedexForm: PokedexForm,
evolution: Evolution? = null,
resultProperties: PokemonProperties? = evolution?.result,
renderData: RenderEvolutionData? = evolution?.let { renderEvolutionData(it) }
aspects: Set<String> = emptySet(),
): EvolutionNode {
val species = PokemonSpecies.getByIdentifier(pokedexEntry.speciesId)!!
val discoveryLevel = CobblemonClient.clientPokedexData.getHighestKnowledgeFor(pokedexEntry, pokedexForm, aspects.toMutableSet())
val formEquivalents = Dexes.getFormEquivalents(pokedexForm.displayForm)
val form = species.forms.firstOrNull { formEquivalents.contains(it.name.lowercase()) }
val parentSpecies = Dexes.findDexEntryBySpeciesId(species.resourceIdentifier)!!
val parentPokedexForm = parentSpecies.forms.first { formEquivalents.contains(it.displayForm.lowercase()) }
val evolutions = DexEntries.evolutionDisplays.filter { it.fromSpecies == pokedexEntry.speciesId && formEquivalents.contains(it.fromForm.lowercase()) }
val aspects = resultProperties?.aspects?.toMutableSet() ?: mutableSetOf()
form?.aspects?.let { aspects.addAll(it) }
val discoveryLevel = CobblemonClient.clientPokedexData.getHighestKnowledgeFor(pokedexEntry, pokedexForm, aspects)
val evolutions = form?.evolutions ?: species.evolutions
// Avoid evolution.result.create() here: it builds a full battle-ready Pokemon (initializeMovesetFromDefault
// etc.) which relies on server-only data like CobblemonMovesetBuilders and crashes on the client
val children = evolutions.flatMap { evolution ->
val baseRenderData = renderEvolutionData(evolution)
listOfNotNull(evolution.result, evolution.shedder).mapNotNull { resultProperties ->
val resultSpecies = resultProperties.species
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
?: return@mapNotNull null
val resultForm = resultSpecies.getForm(aspects + resultProperties.aspects)
val speciesId = resultSpecies.resourceIdentifier
val childPokedexEntry = Dexes.findDexEntryBySpeciesId(speciesId)
if (childPokedexEntry == null) {
Cobblemon.LOGGER.warn("Dex entry not found for species: $speciesId")
return@mapNotNull null
}
val formEquivalents = Dexes.getFormEquivalents(resultForm.name.lowercase())
val childPokedexForm = childPokedexEntry.forms.first { formEquivalents.contains(it.displayForm.lowercase()) }
val childRenderData = if (resultProperties === evolution.shedder) {
RenderEvolutionData(
icons = (baseRenderData?.icons ?: emptyList()) + CobblemonItems.POKE_BALL,
tooltips = (baseRenderData?.tooltips ?: emptyList()) + lang("ui.pokedex.evolution.shedder.tooltip"),
)
} else {
baseRenderData
}
return@mapNotNull buildEvolutionTree(childPokedexEntry, childPokedexForm, evolution, resultProperties, childRenderData)
val children = evolutions.mapNotNull {
val childPokedexEntry = Dexes.findDexEntryBySpeciesId(it.toSpecies)
if (childPokedexEntry == null) {
Cobblemon.LOGGER.warn("Dex entry not found for species: ${it.toSpecies}")
return@mapNotNull null
}
val formEquivalents = Dexes.getFormEquivalents(it.toForm.lowercase())
val childPokedexForm = childPokedexEntry.forms.first { formEquivalents.contains(it.displayForm.lowercase()) }
buildEvolutionTree(childPokedexEntry, childPokedexForm, it.toAspects)
}
return EvolutionNode(pokedexEntry, parentPokedexForm, aspects, discoveryLevel, children, renderData)
return EvolutionNode(
pokedexEntry = pokedexEntry,
pokedexForm = pokedexForm,
aspects = aspects,
pokedexEntryProgress = discoveryLevel,
children = mergeEquivalentChildren(children),
renderDataAlternatives = evolutions,
)
}
private fun mergeEquivalentChildren(children: List<EvolutionNode>): List<EvolutionNode> {
return children
.groupBy { Triple(it.pokedexEntry.speciesId, it.pokedexForm.displayForm.lowercase(), it.aspects) }
.values
.map { group ->
if (group.size == 1) return@map group.first()
val first = group.first()
EvolutionNode(
first.pokedexEntry,
first.pokedexForm,
first.aspects,
first.pokedexEntryProgress,
first.children,
group.flatMap { it.renderDataAlternatives }
)
}
}
private fun renderTreeDrawingNode(
@@ -258,15 +283,19 @@ class EvolutionTreeWidget(
val (childX, _) = getDrawingCoordinates(child)
val lineStartX = treeX + SCROLL_SLOT_SIZE
val lineEndX = childX
val lineY = treeY + SCROLL_SLOT_SIZE / 2
child.value.renderData?.let { renderData ->
val formEquivalents = Dexes.getFormEquivalents(child.value.pokedexForm.displayForm)
val alternatives = treeDrawingNode.value.renderDataAlternatives.filter {
it.toSpecies == child.value.pokedexEntry.speciesId && formEquivalents.contains(it.toForm.lowercase()) && it.toAspects == child.value.aspects
}
alternatives.takeIf { it.isNotEmpty() }?.let {
drawEvolutionInfo(
guiGraphics,
renderData,
it,
lineStartX,
lineEndX,
childX,
lineY,
mouseX,
mouseY,
@@ -298,16 +327,19 @@ class EvolutionTreeWidget(
treeDrawingNode.children.forEach { child ->
val (childX, childY) = getDrawingCoordinates(child)
val lineStartX = treeForkEnd
val lineEndX = childX
val lineY = childY + SCROLL_SLOT_SIZE / 2
child.value.renderData?.let { renderData ->
val formEquivalents = Dexes.getFormEquivalents(child.value.pokedexForm.displayForm)
val alternatives = treeDrawingNode.value.renderDataAlternatives.filter {
it.toSpecies == child.value.pokedexEntry.speciesId && formEquivalents.contains(it.toForm.lowercase()) && it.toAspects == child.value.aspects
}
alternatives.takeIf { it.isNotEmpty() }?.let {
drawEvolutionInfo(
guiGraphics,
renderData,
lineStartX,
lineEndX,
it,
treeForkEnd,
childX,
lineY,
mouseX,
mouseY,
@@ -346,7 +378,7 @@ class EvolutionTreeWidget(
if (formName == "normal") null
else lang("ui.pokedex.info.form.${species}-${formName.replace("-", "")}")
formComponent?.add(" ")?.add(speciesComponent) ?: speciesComponent
formComponent?.let { speciesComponent.copy().add(" (").add(it).add(")") } ?: speciesComponent
}
guiGraphics.disableScissor()
@@ -408,7 +440,7 @@ class EvolutionTreeWidget(
private fun drawEvolutionInfo(
guiGraphics: GuiGraphics,
renderData: RenderEvolutionData,
renderDataAlternatives: List<DexEvolutionDisplay>,
lineStartX: Int,
lineEndX: Int,
lineY: Int,
@@ -416,6 +448,26 @@ class EvolutionTreeWidget(
mouseY: Int,
delta: Float,
) {
val icons = renderDataAlternatives.flatMap { it.requirements.map { req -> req.icon } }.distinct()
val playerBiome = Minecraft.getInstance().player?.let { it.level().getBiome(it.blockPosition()) }
val alternativeTooltips = renderDataAlternatives.map { alternative ->
alternative.requirements.map { req ->
if (req is CobblemonBiomeDexEvolutionRequirementDisplay) {
val tooltip = req.tooltip.copy()
if (playerBiome != null) {
val conditionFits = req.condition?.let { if (it.tag) playerBiome.`is`(TagKey.create(Registries.BIOME, it.id)) else playerBiome.`is`(it.id) } ?: true
val antiConditionFits = req.antiCondition?.let { if (it.tag) playerBiome.`is`(TagKey.create(Registries.BIOME, it.id)) else playerBiome.`is`(it.id) } ?: false
val isMet = conditionFits && !antiConditionFits
tooltip.add(if (isMet) Component.literal(" ✔").green() else Component.literal(" ✘").red())
}
tooltip
} else {
req.tooltip
}
}
}
val tooltips = combineAlternativeTooltips(alternativeTooltips)
val lineXMiddle = (lineStartX + lineEndX) / 2
val iconX = (lineXMiddle + ICON_X_PADDING - ICON_SLOT_HALF_PADDING).toInt()
val iconY = (lineY + ICON_Y_PADDING - ICON_SLOT_HALF_PADDING).toInt()
@@ -431,7 +483,7 @@ class EvolutionTreeWidget(
return
}
if (renderData.icons.isEmpty()) {
if (icons.isEmpty()) {
guiGraphics.hLine(
lineStartX,
lineEndX,
@@ -462,8 +514,8 @@ class EvolutionTreeWidget(
height = iconSize,
)
val index = (System.currentTimeMillis() / MILLIS_BETWEEN_ICONS_CYCLE) % renderData.icons.size
val item = renderData.icons[index.toInt()]
val index = (System.currentTimeMillis() / MILLIS_BETWEEN_ICONS_CYCLE) % icons.size
val item = icons[index.toInt()]
renderScaledGuiItemIcon(
itemStack = item.defaultInstance,
@@ -478,17 +530,41 @@ class EvolutionTreeWidget(
val isIconHovered = isElementHovered(
mouseX,
mouseY,
iconX.toInt(),
iconX.toInt() + iconSize,
iconY.toInt(),
iconY.toInt() + iconSize,
iconX,
iconX + iconSize,
iconY,
iconY + iconSize,
)
if (renderData.tooltips.isNotEmpty() &&
if (tooltips.isNotEmpty() &&
(isLineHovered || isIconHovered) &&
this.isHovered(mouseX.toDouble(), mouseY.toDouble())
) {
hoveredTooltips = renderData.tooltips
hoveredTooltips = tooltips.map { it.copy() }
}
}
private fun combineAlternativeTooltips(alternatives: List<List<Component>>): List<Component> {
val groups = alternatives.filter { it.isNotEmpty() }
if (groups.size <= 1) return groups.firstOrNull() ?: emptyList()
val sharedStrings = groups.map { group -> group.map { it.string }.toSet() }.reduce { acc, strings -> acc intersect strings }
val shared = groups.first().filter { it.string in sharedStrings }.distinctBy { it.string }
val remainders = groups
.map { group -> group.filter { it.string !in sharedStrings } }
.distinctBy { group -> group.map { it.string } }
// An alternative needing nothing beyond the shared lines makes the others irrelevant
if (remainders.any { it.isEmpty() }) return shared
if (remainders.size == 1) return shared + remainders.first()
return if (remainders.size >= 3 && remainders.all { it.size == 1 }) {
shared + lang("ui.pokedex.evolution.one_of").bold() + remainders.map { it.first() }
} else {
// Show each alternative in full, otherwise it's unclear where the first one starts
groups.distinctBy { group -> group.map { it.string } }.flatMapIndexed { index, group ->
if (index == 0) group else listOf(lang("ui.pokedex.evolution.or").bold()) + group
}
}
}
@@ -566,6 +642,6 @@ class EvolutionTreeWidget(
val aspects: Set<String>,
val pokedexEntryProgress: PokedexEntryProgress,
override val children: List<EvolutionNode>,
val renderData: RenderEvolutionData?,
val renderDataAlternatives: List<DexEvolutionDisplay>,
) : TreeNode<EvolutionNode>
}
@@ -1,48 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import net.minecraft.client.Minecraft
import net.minecraft.network.chat.Component
fun debugEvolutionRequirementRendering() {
if (!Cobblemon.config.enableDebugKeys) {
return
}
val gaps = mutableListOf<Pair<String, String>>()
for (species in PokemonSpecies.implemented) {
val evolutions = species.evolutions + species.forms.flatMap { it.evolutions }
for (evolution in evolutions) {
if (renderEvolutionData(evolution) == null) {
gaps.add("Evolution: ${evolution::class.simpleName}" to species.showdownId())
}
for (requirement in evolution.requirements) {
if (resolveRequirementData(requirement) == null) {
gaps.add("Requirement: ${requirement::class.simpleName}" to species.showdownId())
}
}
}
}
val player = Minecraft.getInstance().player
if (gaps.isEmpty()) {
player?.sendSystemMessage(Component.literal("Evolution render debug: no gaps found."))
return
}
val byType = gaps.groupBy({ it.first }, { it.second })
println("Evolution render debug found ${gaps.size} gap(s) across ${byType.size} type(s):")
byType.forEach { (type, species) -> println(" $type: ${species.distinct().joinToString(", ")}") }
player?.sendSystemMessage(Component.literal("Evolution render debug: ${gaps.size} gap(s) across ${byType.size} type(s), see log for details."))
}
@@ -1,123 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.DummyEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.itemRegistry
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.Item
data class RenderEvolutionData(
val icons: List<Item>,
val tooltips: List<MutableComponent>,
)
internal fun resolveRequirementData(requirement: Requirement): RenderRequirementData? {
val icon = requirement.pokedexIcon?.let {
Minecraft.getInstance()?.player?.level()?.itemRegistry?.get(it)
}
val tooltip = requirement.pokedexTooltip?.let {
Component.translatable(it)
}
if (icon != null && tooltip != null) {
return RenderRequirementData(icon, tooltip)
}
return renderRequirementData(requirement)
}
fun renderEvolutionData(evolution: Evolution): RenderEvolutionData? {
when (evolution) {
is BlockClickEvolution -> return renderBlockClickEvolution(evolution)
is DummyEvolution -> return renderDummyEvolution(evolution)
is ItemInteractionEvolution -> return renderItemInteractionEvolution(evolution)
is LevelUpEvolution -> return renderLevelUpEvolution(evolution)
is TradeEvolution -> return renderTradeEvolution(evolution)
}
return null
}
fun renderBlockClickEvolution(blockClickEvolution: BlockClickEvolution): RenderEvolutionData? {
return null
}
// TODO: Use dummyEvolution?
fun renderDummyEvolution(evolution: Evolution): RenderEvolutionData? {
return null
}
fun renderItemInteractionEvolution(itemInteractionEvolution: ItemInteractionEvolution): RenderEvolutionData? {
val item = itemInteractionEvolution.requiredContext.items.get().first().value()
val icons = mutableListOf(item)
val tooltips =
mutableListOf(lang("ui.pokedex.evolution.item_interact.tooltip", item.description.plainCopy().green()))
val requirementsDataList = itemInteractionEvolution.requirements.mapNotNull { requirement ->
resolveRequirementData(requirement)
}
requirementsDataList.forEach { requirement ->
icons.add(requirement.icon)
tooltips.add(requirement.tooltip)
}
return RenderEvolutionData(icons, tooltips)
}
fun renderLevelUpEvolution(levelUpEvolution: LevelUpEvolution): RenderEvolutionData? {
val requirementsDataList = levelUpEvolution.requirements.map { requirement ->
resolveRequirementData(requirement)
}
val icons = requirementsDataList.mapNotNull { requirementData -> requirementData?.icon }
val tooltips = requirementsDataList.mapNotNull { requirementData -> requirementData?.tooltip }
return RenderEvolutionData(icons, tooltips)
}
fun renderTradeEvolution(tradeEvolution: TradeEvolution): RenderEvolutionData? {
val icons = mutableListOf<Item>(CobblemonItems.LINK_CABLE)
val tooltips = mutableListOf<MutableComponent>()
val species = tradeEvolution.requiredContext.species
if (species == null) {
tooltips.add(lang("ui.pokedex.evolution.trade.tooltip.with_any_species"))
} else {
PokemonSpecies.getByIdentifier(species.asIdentifierDefaultingNamespace())?.let {
tooltips.add(lang("ui.pokedex.evolution.trade.tooltip.with_species", it.translatedName.green()))
}
}
val requirementsDataList = tradeEvolution.requirements.mapNotNull { requirement ->
resolveRequirementData(requirement)
}
requirementsDataList.forEach { requirement ->
icons.add(requirement.icon)
tooltips.add(requirement.tooltip)
}
return RenderEvolutionData(icons, tooltips)
}
@@ -1,379 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pokedex.widgets.evolutiontree
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeTagCondition
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.pokemon.requirements.*
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.levelgen.structure.Structure
data class RenderRequirementData(
val icon: Item,
val tooltip: MutableComponent,
)
fun renderRequirementData(requirement: Requirement): RenderRequirementData? {
when (requirement) {
is AdvancementRequirement -> return renderAdvancementRequirement(requirement)
is AnyRequirement -> return renderAnyRequirement(requirement)
is AreaRequirement -> return renderAreaRequirement(requirement)
is AttackDefenceRatioRequirement -> return renderAttackDefenceRatioRequirement(requirement)
is BattleCriticalHitsRequirement -> return renderBattleCriticalHitsRequirement(requirement)
is BiomeRequirement -> return renderBiomeRequirement(requirement)
is BlocksTraveledRequirement -> return renderBlocksTraveledRequirement(requirement)
is DamageTakenRequirement -> return renderDamageTakenRequirement(requirement)
is DefeatRequirement -> return renderDefeatRequirement(requirement)
is FriendshipRequirement -> return renderFriendshipRequirement(requirement)
is HeldItemRequirement -> return renderHeldItemRequirement(requirement)
is LevelRequirement -> return renderLevelRequirement(requirement)
is MoonPhaseRequirement -> return renderMoonPhaseRequirement(requirement)
is MoveSetRequirement -> return renderMoveSetRequirement(requirement)
is MoveTypeRequirement -> return renderMoveTypeRequirement(requirement)
is OwnerHoldsItemRequirement -> return renderOwnerHoldsItemRequirement(requirement)
is PartyMemberRequirement -> return renderPartyMemberRequirement(requirement)
is PokemonPropertiesRequirement -> return renderPokemonPropertiesRequirement(requirement)
is PropertyRangeRequirement -> return renderPropertyRangeRequirement(requirement)
is RecoilRequirement -> return renderRecoilRequirement(requirement)
is StatCompareRequirement -> return renderStatCompareRequirement(requirement)
is StatEqualRequirement -> return renderStatEqualRequirement(requirement)
is StructureRequirement -> return renderStructureRequirement(requirement)
is TimeRangeRequirement -> return renderTimeRangeRequirement(requirement)
is UseMoveRequirement -> return renderUseMoveRequirement(requirement)
is WeatherRequirement -> return renderWeatherRequirement(requirement)
is WorldRequirement -> return renderWorldRequirement(requirement)
}
return null
}
fun renderAdvancementRequirement(advancementRequirement: AdvancementRequirement): RenderRequirementData? {
val icon = Items.BOOK
val identifier = advancementRequirement.requiredAdvancement
val advancementLanguageKey = "${identifier.namespace}.${identifier.path.substringAfterLast('/')}"
val advancementComponent = Component.translatable("advancements.$advancementLanguageKey").green()
val tooltip = lang("ui.pokedex.requirement.advancement.tooltip", advancementComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderAnyRequirement(anyRequirement: AnyRequirement): RenderRequirementData? {
return null
}
fun renderAreaRequirement(areaRequirement: AreaRequirement): RenderRequirementData? {
return null
}
fun renderAttackDefenceRatioRequirement(attackDefenceRatioRequirement: AttackDefenceRatioRequirement): RenderRequirementData? {
return null
}
fun renderBattleCriticalHitsRequirement(battleCriticalHitsRequirement: BattleCriticalHitsRequirement): RenderRequirementData? {
val icon = CobblemonItems.MEDICINAL_LEEK
val criticalsComponent = Component.literal(battleCriticalHitsRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.battle_critical_hits.tooltip", criticalsComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderBiomeRequirement(biomeRequirement: BiomeRequirement): RenderRequirementData? {
val condition = biomeRequirement.biomeCondition
val anticondition = biomeRequirement.biomeAnticondition
if (condition == null && anticondition == null) {
return null
}
val icon = Items.COMPASS
val displayName: (RegistryLikeCondition<Biome>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> Component.literal(it.tag.location().path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) })
is RegistryLikeIdentifierCondition -> Component.translatable("biome.${it.identifier.namespace}.${it.identifier.path}")
else -> Component.literal(it.toString())
}
}
val tooltip = if (condition != null && anticondition != null) {
lang("ui.pokedex.requirement.biome.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.biome.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.biome.anticondition.tooltip", displayName(anticondition!!).green())
}
val playerBiome = Minecraft.getInstance()?.player?.let { it.level().getBiome(it.blockPosition()) }
if (playerBiome != null) {
val isMet = (condition == null || condition.fits(playerBiome)) && (anticondition == null || !anticondition.fits(playerBiome))
tooltip.add(if (isMet) Component.literal(" ✔").green() else Component.literal(" ✘").red())
}
return RenderRequirementData(icon, tooltip)
}
fun renderBlocksTraveledRequirement(blocksTraveledRequirement: BlocksTraveledRequirement): RenderRequirementData? {
val icon = Items.LEATHER_BOOTS
val blocksComponent = Component.literal(blocksTraveledRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.blocks_traveled.tooltip", blocksComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderDamageTakenRequirement(damageTakenRequirement: DamageTakenRequirement): RenderRequirementData? {
val icon = CobblemonItems.PUNCHING_GLOVE
val blocksComponent = Component.literal(damageTakenRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.damage_taken.tooltip", blocksComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderDefeatRequirement(defeatRequirement: DefeatRequirement): RenderRequirementData? {
val description = describePokemonProperties(defeatRequirement.target) ?: return null
val icon = CobblemonItems.PUNCHING_GLOVE
val defeatedAmount = defeatRequirement.amount
val descriptionComponent = Component.literal(defeatedAmount.toString()).green().add(" ").add(description)
val tooltip = lang("ui.pokedex.requirement.defeat.tooltip", descriptionComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderFriendshipRequirement(friendshipRequirement: FriendshipRequirement): RenderRequirementData? {
val icon = CobblemonItems.SOOTHE_BELL
val friendshipAmountComponent = Component.literal(friendshipRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.friendship.tooltip", friendshipAmountComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderHeldItemRequirement(heldItemRequirement: HeldItemRequirement): RenderRequirementData? {
val item = heldItemRequirement.itemCondition.items.get().first().value()
val tooltip = lang("ui.pokedex.requirement.held_item.tooltip", item.description.plainCopy().green())
return RenderRequirementData(item, tooltip)
}
fun renderLevelRequirement(levelRequirement: LevelRequirement): RenderRequirementData? {
val minLevel = levelRequirement.minLevel
val maxLevel = levelRequirement.maxLevel
val icon = CobblemonItems.RARE_CANDY
val tooltip =
if (minLevel == maxLevel || maxLevel == Int.MAX_VALUE)
lang("ui.pokedex.requirement.level.tooltip.min", Component.literal(minLevel.toString()).green())
else
lang("ui.pokedex.requirement.level.tooltip.between", Component.literal(minLevel.toString()).green(), Component.literal(maxLevel.toString()).green())
return RenderRequirementData(icon, tooltip)
}
fun renderMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): RenderRequirementData? {
val icon = Items.CLOCK
val moonPhaseComponent = moonPhaseRequirement.moonPhase.displayName().green()
val tooltip = lang("ui.pokedex.requirement.moon_phase.tooltip", moonPhaseComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderMoveSetRequirement(moveSetRequirement: MoveSetRequirement): RenderRequirementData? {
val icon = CobblemonItems.TECHNICAL_MACHINE
val tooltip = lang("ui.pokedex.requirement.has_move.tooltip", moveSetRequirement.move.displayName.green())
return RenderRequirementData(icon, tooltip)
}
fun renderMoveTypeRequirement(moveTypeRequirement: MoveTypeRequirement): RenderRequirementData? {
val type = ElementalTypes.get(moveTypeRequirement.type.name)
val icon = when (type) {
ElementalTypes.NORMAL -> CobblemonItems.NORMAL_GEM
ElementalTypes.FIRE -> CobblemonItems.FIRE_GEM
ElementalTypes.WATER -> CobblemonItems.WATER_GEM
ElementalTypes.GRASS -> CobblemonItems.GRASS_GEM
ElementalTypes.ELECTRIC -> CobblemonItems.ELECTRIC_GEM
ElementalTypes.ICE -> CobblemonItems.ICE_GEM
ElementalTypes.FIGHTING -> CobblemonItems.FIGHTING_GEM
ElementalTypes.POISON -> CobblemonItems.POISON_GEM
ElementalTypes.GROUND -> CobblemonItems.GROUND_GEM
ElementalTypes.FLYING -> CobblemonItems.FLYING_GEM
ElementalTypes.PSYCHIC -> CobblemonItems.PSYCHIC_GEM
ElementalTypes.BUG -> CobblemonItems.BUG_GEM
ElementalTypes.ROCK -> CobblemonItems.ROCK_GEM
ElementalTypes.GHOST -> CobblemonItems.GHOST_GEM
ElementalTypes.DRAGON -> CobblemonItems.DRAGON_GEM
ElementalTypes.DARK -> CobblemonItems.DARK_GEM
ElementalTypes.STEEL -> CobblemonItems.STEEL_GEM
ElementalTypes.FAIRY -> CobblemonItems.FAIRY_GEM
else -> CobblemonItems.NORMAL_GEM
}
val tooltip = lang("ui.pokedex.requirement.has_move_type.tooltip", moveTypeRequirement.type.displayName.green())
return RenderRequirementData(icon, tooltip)
}
fun renderOwnerHoldsItemRequirement(ownerHoldsItemRequirement: OwnerHoldsItemRequirement): RenderRequirementData? {
val item = ownerHoldsItemRequirement.itemCondition.items.get().first().value()
val tooltip = lang("ui.pokedex.requirement.owner_held_item.tooltip", item.description.plainCopy().green())
return RenderRequirementData(item, tooltip)
}
fun renderPartyMemberRequirement(partyMemberRequirement: PartyMemberRequirement): RenderRequirementData? {
val description = describePokemonProperties(partyMemberRequirement.target) ?: return null
val icon = CobblemonItems.POKE_BALL
val tooltip = if (partyMemberRequirement.contains) {
lang("ui.pokedex.requirement.party_member.tooltip", description)
} else {
lang("ui.pokedex.requirement.party_member.anticondition.tooltip", description)
}
return RenderRequirementData(icon, tooltip)
}
fun renderPokemonPropertiesRequirement(pokemonPropertiesRequirement: PokemonPropertiesRequirement): RenderRequirementData? {
val description = describePokemonProperties(pokemonPropertiesRequirement.target) ?: return null
val icon = Items.NAME_TAG
val tooltip = lang("ui.pokedex.requirement.pokemon_properties.tooltip", description)
return RenderRequirementData(icon, tooltip)
}
fun renderPropertyRangeRequirement(propertyRangeRequirement: PropertyRangeRequirement): RenderRequirementData? {
val provider = SpeciesFeatures.getFeature(propertyRangeRequirement.feature) as? IntSpeciesFeatureProvider ?: return null
val displayName = provider.display?.name?.takeIf { it.isNotBlank() }?.asTranslated()?.green() ?: return null
val icon = provider.itemPoints.keys.firstOrNull()?.let { BuiltInRegistries.ITEM.get(it) } ?: Items.NAME_TAG
val range = propertyRangeRequirement.range
val tooltip = lang(
"ui.pokedex.requirement.property_range.tooltip",
displayName,
Component.literal(range.first.toString()).green(),
Component.literal(range.last.toString()).green()
)
return RenderRequirementData(icon, tooltip)
}
fun renderRecoilRequirement(recoilRequirement: RecoilRequirement): RenderRequirementData? {
val icon = CobblemonItems.LIFE_ORB
val recoilComponent = Component.literal(recoilRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.recoil.tooltip", recoilComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderStatCompareRequirement(statCompareRequirement: StatCompareRequirement): RenderRequirementData? {
val highStat = Stats.getStat(statCompareRequirement.highStat)
val lowStat = Stats.getStat(statCompareRequirement.lowStat)
val icon = when (highStat) {
Stats.HP, Stats.EVASION, Stats.ACCURACY -> CobblemonItems.HEALTH_MOCHI
Stats.ATTACK -> CobblemonItems.MUSCLE_MOCHI
Stats.DEFENCE -> CobblemonItems.RESIST_MOCHI
Stats.SPECIAL_ATTACK -> CobblemonItems.GENIUS_MOCHI
Stats.SPECIAL_DEFENCE -> CobblemonItems.CLEVER_MOCHI
Stats.SPEED -> CobblemonItems.SWIFT_MOCHI
}
val highStatComponent = highStat.displayName.plainCopy().green()
val lowStatComponent = lowStat.displayName.plainCopy().green()
val tooltip = lang("ui.pokedex.requirement.stat_compare.tooltip", highStatComponent, lowStatComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderStatEqualRequirement(statEqualRequirement: StatEqualRequirement): RenderRequirementData? {
val statOne = Stats.getStat(statEqualRequirement.statOne)
val statTwo = Stats.getStat(statEqualRequirement.statTwo)
val statOneComponent = statOne.displayName.plainCopy().green()
val statTwoComponent = statTwo.displayName.plainCopy().green()
val icon = CobblemonItems.FRESH_START_MOCHI
val tooltip = lang("ui.pokedex.requirement.stat_equal.tooltip", statOneComponent, statTwoComponent)
return RenderRequirementData(icon, tooltip)
}
fun renderStructureRequirement(structureRequirement: StructureRequirement): RenderRequirementData? {
val condition = structureRequirement.structureCondition
val anticondition = structureRequirement.structureAnticondition
if (condition == null && anticondition == null) {
return null
}
val icon = Items.STRUCTURE_BLOCK
val displayName: (RegistryLikeCondition<Structure>) -> MutableComponent = {
val path = when (it) {
is RegistryLikeTagCondition -> it.tag.location().path
is RegistryLikeIdentifierCondition -> it.identifier.path
else -> it.toString()
}
Component.literal(path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) })
}
val tooltip = if (condition != null && anticondition != null) {
lang("ui.pokedex.requirement.structure.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.structure.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.structure.anticondition.tooltip", displayName(anticondition!!).green())
}
return RenderRequirementData(icon, tooltip)
}
fun renderTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): RenderRequirementData? {
val icon = Items.CLOCK
val timeRangeComponent = timeRangeRequirement.range.displayName() ?: return null
val tooltip = lang("ui.pokedex.requirement.time_range.tooltip", timeRangeComponent.green())
return RenderRequirementData(icon, tooltip)
}
fun renderUseMoveRequirement(useMoveRequirement: UseMoveRequirement): RenderRequirementData? {
val icon = CobblemonItems.CHOICE_BAND
val tooltip = lang("ui.pokedex.requirement.use_move.tooltip", useMoveRequirement.move.displayName.green())
return RenderRequirementData(icon, tooltip)
}
fun renderWeatherRequirement(weatherRequirement: WeatherRequirement): RenderRequirementData? {
val icon: Item
val tooltip: MutableComponent
if (weatherRequirement.isThundering == true) {
icon = Items.LIGHTNING_ROD
tooltip = lang("ui.pokedex.requirement.weather.tooltip.thundering")
} else {
icon = Items.WATER_BUCKET
tooltip = lang("ui.pokedex.requirement.weather.tooltip.raining")
}
return RenderRequirementData(icon, tooltip)
}
fun renderWorldRequirement(worldRequirement: WorldRequirement): RenderRequirementData? {
return null
}
@@ -0,0 +1,107 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.data
import com.cobblemon.mod.common.api.pokedex.entry.CobblemonBiomeDexEvolutionRequirementDisplay
import com.cobblemon.mod.common.api.pokedex.entry.CobblemonDexEvolutionRequirementDisplay
import com.cobblemon.mod.common.api.pokedex.entry.DexEntries
import com.cobblemon.mod.common.api.pokedex.entry.DexEvolutionDisplay
import com.cobblemon.mod.common.api.pokedex.entry.DexEvolutionRequirementDisplay
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readNullable
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.readText
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeNullable
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.writeText
import com.google.common.collect.Lists
import com.google.common.collect.Sets
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.ExtraCodecs
class DexEvolutionDisplaySyncPacket(entries: MutableList<DexEvolutionDisplay>)
: DataRegistrySyncPacket<DexEvolutionDisplay, DexEvolutionDisplaySyncPacket>(entries) {
override val id = ID
override fun encodeEntry(buffer: RegistryFriendlyByteBuf, entry: DexEvolutionDisplay) {
buffer.writeIdentifier(entry.fromSpecies)
buffer.writeString(entry.fromForm)
buffer.writeCollection(entry.fromAspects) { _, aspect -> buffer.writeString(aspect) }
buffer.writeIdentifier(entry.toSpecies)
buffer.writeString(entry.toForm)
buffer.writeCollection(entry.toAspects) { _, aspect -> buffer.writeString(aspect) }
buffer.writeCollection(entry.requirements) { _, req ->
when (req) {
is CobblemonBiomeDexEvolutionRequirementDisplay -> {
buffer.writeByte(1)
buffer.writeIdentifier(BuiltInRegistries.ITEM.getKey(req.icon))
buffer.writeText(req.tooltip)
buffer.writeNullable(req.condition) { b, cond ->
b.writeIdentifier(cond.id)
b.writeBoolean(cond.tag)
}
buffer.writeNullable(req.antiCondition) { b, cond ->
b.writeIdentifier(cond.id)
b.writeBoolean(cond.tag)
}
}
else -> {
buffer.writeByte(0)
buffer.writeIdentifier(BuiltInRegistries.ITEM.getKey(req.icon))
buffer.writeText(req.tooltip)
}
}
}
}
override fun decodeEntry(buffer: RegistryFriendlyByteBuf): DexEvolutionDisplay? {
val fromSpecies = buffer.readIdentifier()
val fromForm = buffer.readString()
val fromAspects = buffer.readCollection(Sets::newHashSetWithExpectedSize) { buffer.readString() }
val toSpecies = buffer.readIdentifier()
val toForm = buffer.readString()
val toAspects = buffer.readCollection(Sets::newHashSetWithExpectedSize) { buffer.readString() }
val requirements = buffer.readCollection(Lists::newArrayListWithExpectedSize) {
val type = buffer.readByte().toInt()
val icon = BuiltInRegistries.ITEM.get(buffer.readIdentifier())
val tooltip = buffer.readText().copy()
if (type == 1) {
val condition = buffer.readNullable { b ->
val id = b.readIdentifier()
val tag = b.readBoolean()
ExtraCodecs.TagOrElementLocation(id, tag)
}
val antiCondition = buffer.readNullable { b ->
val id = b.readIdentifier()
val tag = b.readBoolean()
ExtraCodecs.TagOrElementLocation(id, tag)
}
CobblemonBiomeDexEvolutionRequirementDisplay(icon, tooltip, condition, antiCondition)
} else {
CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
}.toList()
return DexEvolutionDisplay(fromSpecies, fromForm, fromAspects, toSpecies, toForm, toAspects, requirements)
}
override fun synchronizeDecoded(entries: Collection<DexEvolutionDisplay>) {
DexEntries.evolutionDisplays.clear()
DexEntries.evolutionDisplays.addAll(entries)
}
companion object {
val ID = cobblemonResource("dex_evolution_display_sync")
fun decode(buffer: RegistryFriendlyByteBuf): DexEvolutionDisplaySyncPacket =
DexEvolutionDisplaySyncPacket(mutableListOf()).apply { decodeBuffer(buffer) }
}
}
@@ -1,100 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.data
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.evolution.PreEvolution
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeString
import com.google.gson.reflect.TypeToken
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* Carries the evolutions/preEvolution of every [Species] (and any [FormData] overrides), sent right after
* [SpeciesRegistrySyncPacket] so that every species is already registered by the time this is decoded. This avoids
* the cross-species lookup ordering hazards that come with resolving evolutions during the species sync itself.
*/
class SpeciesEvolutionSyncPacket(entries: Collection<SpeciesEvolutionEntry>) : DataRegistrySyncPacket<SpeciesEvolutionEntry, SpeciesEvolutionSyncPacket>(entries) {
override val id = ID
override fun encodeEntry(buffer: RegistryFriendlyByteBuf, entry: SpeciesEvolutionEntry) {
try {
buffer.writeIdentifier(entry.speciesIdentifier)
buffer.writeString(PokemonSpecies.gson.toJson(entry.evolutions, EVOLUTION_SET_TYPE))
buffer.writeNullable(entry.preEvolution) { _, preEvolution -> buffer.writeString(PokemonSpecies.gson.toJson(preEvolution, PreEvolution::class.java)) }
buffer.writeCollection(entry.formOverrides) { _, override ->
buffer.writeString(override.formName)
buffer.writeNullable(override.evolutions) { _, evolutions -> buffer.writeString(PokemonSpecies.gson.toJson(evolutions, EVOLUTION_SET_TYPE)) }
buffer.writeNullable(override.preEvolution) { _, preEvolution -> buffer.writeString(PokemonSpecies.gson.toJson(preEvolution, PreEvolution::class.java)) }
}
} catch (e: Exception) {
Cobblemon.LOGGER.error("Caught exception encoding evolution data for species {}", entry.speciesIdentifier, e)
}
}
override fun decodeEntry(buffer: RegistryFriendlyByteBuf): SpeciesEvolutionEntry? {
val identifier = buffer.readIdentifier()
return try {
val evolutions: MutableSet<Evolution> = PokemonSpecies.gson.fromJson(buffer.readString(), EVOLUTION_SET_TYPE)
val preEvolution = buffer.readNullable { _ -> PokemonSpecies.gson.fromJson(buffer.readString(), PreEvolution::class.java) }
val formOverrides = buffer.readList { _ ->
val formName = buffer.readString()
val formEvolutions = buffer.readNullable { _ -> PokemonSpecies.gson.fromJson<MutableSet<Evolution>>(buffer.readString(), EVOLUTION_SET_TYPE) }
val formPreEvolution = buffer.readNullable { _ -> PokemonSpecies.gson.fromJson(buffer.readString(), PreEvolution::class.java) }
FormEvolutionOverride(formName, formEvolutions, formPreEvolution)
}
SpeciesEvolutionEntry(identifier, evolutions, preEvolution, formOverrides)
} catch (e: Exception) {
Cobblemon.LOGGER.error("Caught exception decoding evolution data for species {}", identifier, e)
null
}
}
override fun synchronizeDecoded(entries: Collection<SpeciesEvolutionEntry>) {
entries.forEach { entry ->
val species = PokemonSpecies.getByIdentifier(entry.speciesIdentifier)
if (species == null) {
Cobblemon.LOGGER.error("Received evolution data for unknown species {}", entry.speciesIdentifier)
return@forEach
}
species.applyEvolutionData(entry.evolutions, entry.preEvolution)
entry.formOverrides.forEach { override ->
species.getFormByName(override.formName).applyEvolutionData(override.evolutions, override.preEvolution)
}
}
}
companion object {
val ID = cobblemonResource("species_evolution_sync")
private val EVOLUTION_SET_TYPE = TypeToken.getParameterized(MutableSet::class.java, Evolution::class.java).type
fun decode(buffer: RegistryFriendlyByteBuf): SpeciesEvolutionSyncPacket = SpeciesEvolutionSyncPacket(emptyList()).apply { decodeBuffer(buffer) }
}
}
class SpeciesEvolutionEntry(
val speciesIdentifier: ResourceLocation,
val evolutions: MutableSet<Evolution>,
val preEvolution: PreEvolution?,
val formOverrides: List<FormEvolutionOverride>
)
class FormEvolutionOverride(
val formName: String,
val evolutions: MutableSet<Evolution>?,
val preEvolution: PreEvolution?
)
@@ -76,7 +76,7 @@ class FormData(
@SerializedName("moves")
private var _moves: Learnset? = null,
@SerializedName("evolutions")
private var _evolutions: MutableSet<Evolution>? = null,
private val _evolutions: MutableSet<Evolution>? = null,
@SerializedName("abilities")
private var _abilities: AbilityPool? = null,
@SerializedName("drops")
@@ -84,7 +84,7 @@ class FormData(
@SerializedName("pokedex")
private var _pokedex: MutableList<String>? = null,
@SerializedName("preEvolution")
private var _preEvolution: PreEvolution? = null,
private val _preEvolution: PreEvolution? = null,
private var standingEyeHeight: Float? = null,
private var swimmingEyeHeight: Float? = null,
private var flyingEyeHeight: Float? = null,
@@ -269,22 +269,13 @@ class FormData(
this.species = species
this.behaviour.parent = species.behaviour
Cobblemon.statProvider.provide(this)
this._lightingData?.let { this._lightingData = it.copy(lightLevel = it.lightLevel.coerceIn(0, 15)) }
behaviour.herd.initialize()
return this
}
/**
* Applies decoded evolution data onto this form, received separately via [com.cobblemon.mod.common.net.messages.client.data.SpeciesEvolutionSyncPacket]
* after every species has already been registered.
*/
internal fun applyEvolutionData(evolutions: MutableSet<Evolution>?, preEvolution: PreEvolution?) {
this._evolutions = evolutions
this._preEvolution = preEvolution
// These properties are lazy, force resolution now that every species is guaranteed to be registered
// 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)) }
behaviour.herd.initialize()
return this
}
internal fun resolveEvolutionMoves() {
@@ -181,20 +181,11 @@ class Species : ShowdownIdentifiable {
this.forms.add(0, this.standardForm)
}
this.lightingData?.let { this.lightingData = it.copy(lightLevel = it.lightLevel.coerceIn(0, 15)) }
behaviour.herd.initialize()
}
/**
* Applies decoded evolution data onto this species, received separately via [com.cobblemon.mod.common.net.messages.client.data.SpeciesEvolutionSyncPacket]
* after every species has already been registered.
*/
internal fun applyEvolutionData(evolutions: MutableSet<Evolution>, preEvolution: PreEvolution?) {
// Mutate in place rather than reassign: standardForm may have already captured this.evolutions by reference.
this.evolutions.clear()
this.evolutions.addAll(evolutions)
this.preEvolution = preEvolution
// 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
behaviour.herd.initialize()
}
// 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
@@ -25,20 +25,12 @@ internal class CobblemonLazyPreEvolution(private val rawData: String) : PreEvolu
this.properties.species?.asIdentifierDefaultingNamespace()?.let { PokemonSpecies.getByIdentifier(it) } ?: throw IllegalArgumentException("A PreEvolution needs a valid species")
}
private fun computeForm(extraAspects: Set<String>): FormData {
val properties = this.properties
return properties.form?.let { formId -> this.species.forms.firstOrNull { it.formOnlyShowdownId().equals(formId, true) } }
?: this.species.getForm(properties.aspects + this.rawData.split(" ").map { it.lowercase() }.toSet() + extraAspects)
private val lazyForm: FormData by lazy {
this.properties.form?.let { formId -> this.species.forms.firstOrNull { it.formOnlyShowdownId().equals(formId, true) } } ?: this.species.standardForm
}
private val lazyForm: FormData by lazy { computeForm(emptySet()) }
override val species: Species
get() = this.lazySpecies
override val form: FormData
get() = this.lazyForm
override fun resolveForm(extraAspects: Set<String>): FormData {
return if (extraAspects.isEmpty()) this.form else computeForm(extraAspects)
}
}
@@ -12,12 +12,10 @@ import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
import net.minecraft.world.phys.AABB
object BoxAdapter : JsonDeserializer<AABB>, JsonSerializer<AABB> {
object BoxAdapter : JsonDeserializer<AABB> {
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): AABB {
json as JsonObject
return AABB(
@@ -29,15 +27,4 @@ object BoxAdapter : JsonDeserializer<AABB>, JsonSerializer<AABB> {
json.get("maxZ")?.asDouble ?: 9999999.0
)
}
override fun serialize(src: AABB, typeOfSrc: Type, ctx: JsonSerializationContext): JsonElement {
val json = JsonObject()
json.addProperty("minX", src.minX)
json.addProperty("minY", src.minY)
json.addProperty("minZ", src.minZ)
json.addProperty("maxX", src.maxX)
json.addProperty("maxY", src.maxY)
json.addProperty("maxZ", src.maxZ)
return json
}
}
@@ -13,8 +13,6 @@ import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
/**
@@ -24,7 +22,7 @@ import java.lang.reflect.Type
* @author Hiroku
* @since July 24th, 2022
*/
object DropEntryAdapter : JsonDeserializer<DropEntry>, JsonSerializer<DropEntry> {
object DropEntryAdapter : JsonDeserializer<DropEntry> {
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): DropEntry {
json as JsonObject
val entryClass = json.get("type")?.asString
@@ -32,12 +30,4 @@ object DropEntryAdapter : JsonDeserializer<DropEntry>, JsonSerializer<DropEntry>
?: DropEntry.defaultType
return ctx.deserialize(json, entryClass)
}
override fun serialize(src: DropEntry, typeOfSrc: Type, ctx: JsonSerializationContext): JsonElement {
val json = ctx.serialize(src, src::class.java).asJsonObject
val name = DropEntry.entryTypes.entries.find { it.value == src::class.java }?.key
?: throw IllegalArgumentException("Cannot resolve drop entry type for ${src::class.qualifiedName}")
json.addProperty("type", name)
return json
}
}
@@ -10,21 +10,13 @@ package com.cobblemon.mod.common.util.adapters
import com.bedrockk.molang.Expression
import com.bedrockk.molang.MoLang
import com.cobblemon.mod.common.util.getString
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
object ExpressionAdapter : JsonDeserializer<Expression>, JsonSerializer<Expression> {
object ExpressionAdapter : JsonDeserializer<Expression> {
override fun deserialize(json: JsonElement, typeOfT: Type, ctx: JsonDeserializationContext): Expression {
return MoLang.createParser(json.asString).parseExpression()
}
override fun serialize(src: Expression, typeOfSrc: Type, ctx: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.getString())
}
}
@@ -13,9 +13,6 @@ import com.cobblemon.mod.common.util.asExpressionLike
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
/**
@@ -25,7 +22,7 @@ import java.lang.reflect.Type
* @author Hiroku
* @since October 22nd, 2023
*/
object ExpressionLikeAdapter : JsonDeserializer<ExpressionLike>, JsonSerializer<ExpressionLike> {
object ExpressionLikeAdapter : JsonDeserializer<ExpressionLike> {
override fun deserialize(json: JsonElement, type: Type, ctx: JsonDeserializationContext): ExpressionLike {
return if (json.isJsonPrimitive) {
json.asString.asExpressionLike()
@@ -35,8 +32,4 @@ object ExpressionLikeAdapter : JsonDeserializer<ExpressionLike>, JsonSerializer<
throw IllegalArgumentException("Invalid expression JSON: $json")
}
}
override fun serialize(src: ExpressionLike, typeOfSrc: Type, ctx: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.getString())
}
}
@@ -11,23 +11,22 @@ package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Environment
import com.cobblemon.mod.common.util.server
import com.google.gson.JsonElement
import com.mojang.serialization.DynamicOps
import com.mojang.serialization.JsonOps
import net.minecraft.client.Minecraft
import net.minecraft.core.RegistryAccess
import net.minecraft.resources.RegistryOps
import net.minecraft.core.registries.BuiltInRegistries
/**
* Utility for getting RegistryAccess with the correct client/server registry access.
* Used when as a fallback when manually encoding/decoding registry-backed values (e.g., items, predicates).
*/
object RegistryAccessResolver {
private val builtInAccess: RegistryAccess by lazy { RegistryAccess.fromRegistryOfRegistries(BuiltInRegistries.REGISTRY) }
fun getAccess(): RegistryAccess {
try {
Cobblemon.implementation
} catch (e: UninitializedPropertyAccessException) {
return RegistryAccess.EMPTY
return builtInAccess
}
if (Cobblemon.implementation.environment() == Environment.CLIENT) {
val minecraft = Minecraft.getInstance()
@@ -35,13 +34,12 @@ object RegistryAccessResolver {
return minecraft.level!!.registryAccess()
}
if (Cobblemon.implementation.environment() == Environment.SERVER) {
val server = server()
if (server != null)
return server.registryAccess()
}
return RegistryAccess.EMPTY
return builtInAccess
}
}
@@ -1477,22 +1477,28 @@
"cobblemon.ui.pokedex.evolution.trade.tooltip.with_species": "Evolves when traded with %1$s",
"cobblemon.ui.pokedex.evolution.trade.tooltip.with_any_species": "Evolves when traded",
"cobblemon.ui.pokedex.evolution.shedder.tooltip": "Also requires a spare Poké Ball and an empty party slot",
"cobblemon.ui.pokedex.evolution.or": "OR",
"cobblemon.ui.pokedex.evolution.one_of": "One of:",
"cobblemon.ui.pokedex.requirement.advancement.tooltip": "Player must have %1$s advancement",
"cobblemon.ui.pokedex.requirement.battle_critical_hits.tooltip": "Must hit %1$s critical-hits in battle",
"cobblemon.ui.pokedex.requirement.biome.anticondition.tooltip": "Must not be in the %1$s biome to evolve",
"cobblemon.ui.pokedex.requirement.biome.both.tooltip": "Must be in the %1$s biome and not in the %2$s biome to evolve",
"cobblemon.ui.pokedex.requirement.biome.tooltip": "Must be in the %1$s biome to evolve",
"cobblemon.ui.pokedex.requirement.biome.anticondition.tooltip": "Must not be in the %1$s biome",
"cobblemon.ui.pokedex.requirement.biome.both.tooltip": "Must be in the %1$s biome and not in the %2$s biome",
"cobblemon.ui.pokedex.requirement.biome.tooltip": "Must be in the %1$s biome",
"cobblemon.ui.pokedex.requirement.blocks_traveled.tooltip": "Must travel %1$s blocks",
"cobblemon.ui.pokedex.requirement.damage_taken.tooltip": "Must take %1$s damage in battle without fainting",
"cobblemon.ui.pokedex.requirement.defeat.tooltip": "Must defeat %1$s in battle to evolve",
"cobblemon.ui.pokedex.requirement.friendship.tooltip": "Requires at least %1$s friendship",
"cobblemon.ui.pokedex.requirement.has_move.tooltip": "Must know move %1$s",
"cobblemon.ui.pokedex.requirement.has_move_type.tooltip": "Must know a move of type %1$s",
"cobblemon.ui.pokedex.requirement.has_move_type.tooltip": "Must know a move of %1$s type",
"cobblemon.ui.pokedex.requirement.held_item.tooltip": "Must be holding %1$s",
"cobblemon.ui.pokedex.requirement.level.tooltip.min": "Evolves starting at level %1$s",
"cobblemon.ui.pokedex.requirement.level.tooltip.between": "Evolves between levels %1$s and %2$s",
"cobblemon.ui.pokedex.requirement.moon_phase.tooltip": "Must evolve in %1$s moon phase",
"cobblemon.ui.pokedex.requirement.nature.toxtricity_amped": "Must have a §aHardy§r, §aBrave§r, §aAdamant§r, §aNaughty§r, §aDocile§r, §aImpish§r, §aLax§r, §aHasty§r, §aJolly§r, §aNaive§r, §aRash§r, §aSassy§r, or §aQuirky§r nature",
"cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key": "Must have a §aLonely§r, §aBold§r, §aRelaxed§r, §aTimid§r, §aSerious§r, §aModest§r, §aMild§r, §aQuiet§r, §aBashful§r, §aCalm§r, §aGentle§r, or §aCareful§r nature",
"cobblemon.ui.pokedex.requirement.negate.tooltip": "Not: %1$s",
"cobblemon.ui.pokedex.requirement.nickname.malamar": "Must be named §aDinnerbone§r or §aGrumm§r",
"cobblemon.ui.pokedex.requirement.party_member.anticondition.tooltip": "Party must not have a Pokémon with %1$s",
"cobblemon.ui.pokedex.requirement.party_member.tooltip": "Party must have a Pokémon with %1$s",
"cobblemon.ui.pokedex.requirement.pokemon_properties.tooltip": "Must be %1$s",
@@ -1507,9 +1513,9 @@
"cobblemon.ui.pokedex.requirement.recoil.tooltip": "Must suffer %1$s units of recoil in battle",
"cobblemon.ui.pokedex.requirement.stat_compare.tooltip": "Must have its %1$s higher than its %2$s",
"cobblemon.ui.pokedex.requirement.stat_equal.tooltip": "Must have its %1$s equals to its %2$s",
"cobblemon.ui.pokedex.requirement.structure.anticondition.tooltip": "Must not be in the %1$s structure to evolve",
"cobblemon.ui.pokedex.requirement.structure.both.tooltip": "Must be in the %1$s structure and not in the %2$s structure to evolve",
"cobblemon.ui.pokedex.requirement.structure.tooltip": "Must be in the %1$s structure to evolve",
"cobblemon.ui.pokedex.requirement.structure.anticondition.tooltip": "Must not be in the %1$s structure",
"cobblemon.ui.pokedex.requirement.structure.both.tooltip": "Must be in the %1$s structure and not in the %2$s structure",
"cobblemon.ui.pokedex.requirement.structure.tooltip": "Must be in the %1$s structure",
"cobblemon.ui.pokedex.requirement.time_range.tooltip": "Must evolve during the %1$s",
"cobblemon.ui.pokedex.requirement.use_move.tooltip": "Must use move %1$s in battle",
"cobblemon.ui.pokedex.requirement.weather.tooltip.raining": "Must be in rain to evolve",
@@ -3663,6 +3669,31 @@
"tag.item.cobblemon.tall_flowers": "Tall Flowers",
"tag.item.cobblemon.small_flowers": "Small Flowers",
"tag.worldgen.biome.cobblemon.evolution.regional.bergmite_hisuibiome": "Hisuian",
"tag.worldgen.biome.cobblemon.evolution.regional.bergmite_kalosbiome": "Kalosian",
"tag.worldgen.biome.cobblemon.evolution.regional.cubone_alolabiome": "Alolan",
"tag.worldgen.biome.cobblemon.evolution.regional.cubone_kantobiome": "Kantonian",
"tag.worldgen.biome.cobblemon.evolution.regional.dartrix_alolabiome": "Alolan",
"tag.worldgen.biome.cobblemon.evolution.regional.dartrix_hisuibiome": "Hisuian",
"tag.worldgen.biome.cobblemon.evolution.regional.dewott_hisuibiome": "Hisuian",
"tag.worldgen.biome.cobblemon.evolution.regional.dewott_unovabiome": "Unovan",
"tag.worldgen.biome.cobblemon.evolution.regional.exeggcute_alolabiome": "Alolan",
"tag.worldgen.biome.cobblemon.evolution.regional.exeggcute_kantobiome": "Kantonian",
"tag.worldgen.biome.cobblemon.evolution.regional.goomy_hisuibiome": "Hisuian",
"tag.worldgen.biome.cobblemon.evolution.regional.goomy_kalosbiome": "Kalosian",
"tag.worldgen.biome.cobblemon.evolution.regional.koffing_galarbiome": "Galarian",
"tag.worldgen.biome.cobblemon.evolution.regional.koffing_kantobiome": "Kantonian",
"tag.worldgen.biome.cobblemon.evolution.regional.mimejr_galarbiome": "Galarian",
"tag.worldgen.biome.cobblemon.evolution.regional.mimejr_sinnohbiome": "Sinnohan",
"tag.worldgen.biome.cobblemon.evolution.regional.petilil_hisuibiome": "Hisuian",
"tag.worldgen.biome.cobblemon.evolution.regional.petilil_unovabiome": "Unovan",
"tag.worldgen.biome.cobblemon.evolution.regional.pikachu_alolabiome": "Alolan",
"tag.worldgen.biome.cobblemon.evolution.regional.pikachu_kantobiome": "Kantonian",
"tag.worldgen.biome.cobblemon.evolution.regional.quilava_hisuibiome": "Hisuian",
"tag.worldgen.biome.cobblemon.evolution.regional.quilava_johtobiome": "Johtonian",
"tag.worldgen.biome.cobblemon.evolution.regional.rufflet_hisuibiome": "Hisuian",
"tag.worldgen.biome.cobblemon.evolution.regional.rufflet_unovabiome": "Unovan",
"cobblemon.entity.variable.challenge_cooldown.name": "Challenge Cooldown",
"cobblemon.entity.variable.challenge_cooldown.desc": "How long the NPC will be un-challenge-able from that player's last challenge.",
"cobblemon.entity.variable.player_win_command.name": "Player Win Command",
@@ -165,17 +165,6 @@
"special:sing"
],
"evolutions": [
{
"id": "eevee_jolteon",
"variant": "item_interact",
"result": "jolteon",
"consumeHeldItem": false,
"learnableMoves": [
"thundershock"
],
"requirements": [],
"requiredContext": "cobblemon:thunder_stone"
},
{
"id": "eevee_vaporeon",
"variant": "item_interact",
@@ -187,6 +176,17 @@
"requirements": [],
"requiredContext": "cobblemon:water_stone"
},
{
"id": "eevee_jolteon",
"variant": "item_interact",
"result": "jolteon",
"consumeHeldItem": false,
"learnableMoves": [
"thundershock"
],
"requirements": [],
"requiredContext": "cobblemon:thunder_stone"
},
{
"id": "eevee_flareon",
"variant": "item_interact",
@@ -237,26 +237,6 @@
}
]
},
{
"id": "eevee_sylveon",
"variant": "level_up",
"result": "sylveon",
"consumeHeldItem": false,
"learnableMoves": [
"fairywind",
"disarmingvoice"
],
"requirements": [
{
"variant": "friendship",
"amount": 160
},
{
"variant": "has_move_type",
"type": "fairy"
}
]
},
{
"id": "eevee_leafeon",
"variant": "item_interact",
@@ -278,6 +258,26 @@
],
"requirements": [],
"requiredContext": "cobblemon:ice_stone"
},
{
"id": "eevee_sylveon",
"variant": "level_up",
"result": "sylveon",
"consumeHeldItem": false,
"learnableMoves": [
"fairywind",
"disarmingvoice"
],
"requirements": [
{
"variant": "friendship",
"amount": 160
},
{
"variant": "has_move_type",
"type": "fairy"
}
]
}
],
"forms": [
@@ -258,7 +258,9 @@
},
{
"variant": "properties",
"target": "nickname=Dinnerbone"
"target": "nickname=Dinnerbone",
"pokedexIcon": "minecraft:name_tag",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nickname.malamar"
}
]
},
@@ -275,7 +277,9 @@
},
{
"variant": "properties",
"target": "nickname=Grumm"
"target": "nickname=Grumm",
"pokedexIcon": "minecraft:name_tag",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nickname.malamar"
}
]
}
@@ -101,7 +101,9 @@
},
{
"variant": "properties",
"target": "toxel nature=hardy"
"target": "toxel nature=hardy",
"pokedexIcon": "cobblemon:serious_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
],
"drops": {
@@ -129,7 +131,9 @@
},
{
"variant": "properties",
"target": "toxel nature=brave"
"target": "toxel nature=brave",
"pokedexIcon": "cobblemon:brave_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
],
"drops": {
@@ -157,7 +161,9 @@
},
{
"variant": "properties",
"target": "toxel nature=adamant"
"target": "toxel nature=adamant",
"pokedexIcon": "cobblemon:adamant_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
],
"drops": {
@@ -185,7 +191,9 @@
},
{
"variant": "properties",
"target": "toxel nature=naughty"
"target": "toxel nature=naughty",
"pokedexIcon": "cobblemon:naughty_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
],
"drops": {
@@ -213,7 +221,9 @@
},
{
"variant": "properties",
"target": "toxel nature=docile"
"target": "toxel nature=docile",
"pokedexIcon": "cobblemon:serious_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
],
"drops": {
@@ -241,7 +251,9 @@
},
{
"variant": "properties",
"target": "toxel nature=impish"
"target": "toxel nature=impish",
"pokedexIcon": "cobblemon:impish_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
],
"drops": {
@@ -269,7 +281,9 @@
},
{
"variant": "properties",
"target": "toxel nature=lax"
"target": "toxel nature=lax",
"pokedexIcon": "cobblemon:lax_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
],
"drops": {
@@ -297,7 +311,9 @@
},
{
"variant": "properties",
"target": "toxel nature=hasty"
"target": "toxel nature=hasty",
"pokedexIcon": "cobblemon:hasty_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
],
"drops": {
@@ -325,7 +341,9 @@
},
{
"variant": "properties",
"target": "toxel nature=jolly"
"target": "toxel nature=jolly",
"pokedexIcon": "cobblemon:jolly_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
],
"drops": {
@@ -353,7 +371,9 @@
},
{
"variant": "properties",
"target": "toxel nature=naive"
"target": "toxel nature=naive",
"pokedexIcon": "cobblemon:naive_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
],
"drops": {
@@ -381,7 +401,9 @@
},
{
"variant": "properties",
"target": "toxel nature=rash"
"target": "toxel nature=rash",
"pokedexIcon": "cobblemon:rash_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
]
},
@@ -398,7 +420,9 @@
},
{
"variant": "properties",
"target": "toxel nature=sassy"
"target": "toxel nature=sassy",
"pokedexIcon": "cobblemon:sassy_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
],
"drops": {
@@ -428,7 +452,9 @@
},
{
"variant": "properties",
"target": "toxel nature=quirky"
"target": "toxel nature=quirky",
"pokedexIcon": "cobblemon:serious_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_amped"
}
],
"drops": {
@@ -456,7 +482,9 @@
},
{
"variant": "properties",
"target": "toxel nature=lonely"
"target": "toxel nature=lonely",
"pokedexIcon": "cobblemon:lonely_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key"
}
],
"drops": {
@@ -484,7 +512,9 @@
},
{
"variant": "properties",
"target": "toxel nature=bold"
"target": "toxel nature=bold",
"pokedexIcon": "cobblemon:bold_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key"
}
]
},
@@ -501,7 +531,9 @@
},
{
"variant": "properties",
"target": "toxel nature=relaxed"
"target": "toxel nature=relaxed",
"pokedexIcon": "cobblemon:relaxed_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key"
}
],
"drops": {
@@ -529,7 +561,9 @@
},
{
"variant": "properties",
"target": "toxel nature=timid"
"target": "toxel nature=timid",
"pokedexIcon": "cobblemon:timid_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key"
}
],
"drops": {
@@ -557,7 +591,9 @@
},
{
"variant": "properties",
"target": "toxel nature=serious"
"target": "toxel nature=serious",
"pokedexIcon": "cobblemon:serious_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key"
}
],
"drops": {
@@ -585,7 +621,9 @@
},
{
"variant": "properties",
"target": "toxel nature=modest"
"target": "toxel nature=modest",
"pokedexIcon": "cobblemon:modest_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key"
}
],
"drops": {
@@ -613,7 +651,9 @@
},
{
"variant": "properties",
"target": "toxel nature=mild"
"target": "toxel nature=mild",
"pokedexIcon": "cobblemon:mild_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key"
}
],
"drops": {
@@ -641,7 +681,9 @@
},
{
"variant": "properties",
"target": "toxel nature=quiet"
"target": "toxel nature=quiet",
"pokedexIcon": "cobblemon:quiet_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key"
}
],
"drops": {
@@ -669,7 +711,9 @@
},
{
"variant": "properties",
"target": "toxel nature=bashful"
"target": "toxel nature=bashful",
"pokedexIcon": "cobblemon:serious_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key"
}
],
"drops": {
@@ -697,7 +741,9 @@
},
{
"variant": "properties",
"target": "toxel nature=calm"
"target": "toxel nature=calm",
"pokedexIcon": "cobblemon:calm_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key"
}
],
"drops": {
@@ -725,7 +771,9 @@
},
{
"variant": "properties",
"target": "toxel nature=gentle"
"target": "toxel nature=gentle",
"pokedexIcon": "cobblemon:gentle_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key"
}
],
"drops": {
@@ -755,7 +803,9 @@
},
{
"variant": "properties",
"target": "toxel nature=careful"
"target": "toxel nature=careful",
"pokedexIcon": "cobblemon:careful_mint",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.nature.toxtricity_low_key"
}
],
"drops": {