load features a lot sooner to avoid form switching

Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
This commit is contained in:
Polymeta
2026-09-21 10:13:59 +01:00
parent 2a0e8f6d47
commit 32b0699235
3 changed files with 68 additions and 39 deletions
+29
View File
@@ -50,6 +50,35 @@
### Particles
#### New Move Animations
- Fixed shiny pokemon under certain circumstances forgetting taught TMs
- Fixed a potential world generation stall caused by Type Gem clusters.
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
- Fixed NPCs getting their own TM progress file.
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
- Fixed Blastoise having an unintentional passenger seat.
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
- Fixed Ampharos' eyes not emitting particles.
- Fixed Alolan Raticate's cry not playing.
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
- Removed unused, placeholder habitat pools.
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
- Fixed Lucario's aura particles not emitting.
- Fixed Lycanroc's head movement.
- Added head movement to Archaludon.
- Fixed Duraludon line not having their sleep animations.
- Fixed Diglett line not having their faint animations.
- Fixed Kingambit moving its legs while walking.
### Localization
- Updated translations for:
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.moves.BenchedMoves
import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.pokemon.EVs
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Gender
@@ -21,20 +22,22 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.codec.CodecUtils
import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP1.Companion.FEATURES
import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP1.Companion.FEATURE_ID
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.Optional
import java.util.UUID
import net.minecraft.core.UUIDUtil
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.ComponentSerialization
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
internal class PokemonP1(
val uuid: UUID,
val species: Species,
val form: FormData,
val nickname: Optional<Component>,
val features: List<CompoundTag>,
val level: Int,
val experience: Int,
val friendship: Int,
@@ -55,7 +58,22 @@ internal class PokemonP1(
other.ability = this.ability
other.species = this.species
other.form = this.form
this.nickname.ifPresent { other.nickname = it.copy() }
this.features.forEach { featureNbt ->
val featureId = featureNbt.getString(FEATURE_ID)
if (featureId.isEmpty()) {
return@forEach
}
val speciesFeatureProviders = SpeciesFeatures.getFeaturesFor(other.species)
val feature = speciesFeatureProviders.firstNotNullOfOrNull { provider -> provider(featureNbt) } ?: return@forEach
if (
featureNbt.contains("keys", Tag.TAG_STRING.toInt()) &&
!featureNbt.getList("keys", Tag.TAG_STRING.toInt()).contains(StringTag.valueOf(featureId))
) {
return@forEach
}
other.features.removeIf { it.name == feature.name }
other.features.add(feature)
}
other.level = this.level
other.experience = this.experience
other.setFriendship(this.friendship)
@@ -91,7 +109,7 @@ internal class PokemonP1(
UUIDUtil.LENIENT_CODEC.fieldOf(DataKeys.POKEMON_UUID).forGetter(PokemonP1::uuid),
Species.BY_IDENTIFIER_CODEC.fieldOf(DataKeys.POKEMON_SPECIES_IDENTIFIER).forGetter(PokemonP1::species),
Codec.STRING.fieldOf(DataKeys.POKEMON_FORM_ID).forGetter { pokemon -> pokemon.form.formOnlyShowdownId() },
ComponentSerialization.CODEC.optionalFieldOf(DataKeys.POKEMON_NICKNAME).forGetter(PokemonP1::nickname),
Codec.list(CompoundTag.CODEC).optionalFieldOf(FEATURES, emptyList()).forGetter(PokemonP1::features),
CodecUtils.dynamicIntRange(1) { Cobblemon.config.maxPokemonLevel }.fieldOf(DataKeys.POKEMON_LEVEL).forGetter(PokemonP1::level),
Codec.intRange(0, Int.MAX_VALUE).fieldOf(DataKeys.POKEMON_EXPERIENCE).forGetter(PokemonP1::experience),
CodecUtils.dynamicIntRange(0) { Cobblemon.config.maxPokemonFriendship }.fieldOf(DataKeys.POKEMON_FRIENDSHIP).forGetter(PokemonP1::friendship),
@@ -104,9 +122,9 @@ internal class PokemonP1(
Codec.FLOAT.fieldOf(DataKeys.POKEMON_SCALE_MODIFIER).forGetter(PokemonP1::scaleModifier),
Codec.BOOL.fieldOf(DataKeys.POKEMON_SHINY).forGetter(PokemonP1::shiny),
Ability.CODEC.fieldOf(DataKeys.POKEMON_ABILITY).forGetter(PokemonP1::ability)
).apply(instance) { uuid, species, formId, nickname, level, experience, friendship, currentHealth, gender, ivs, evs, moveSet, benchedMoves, scaleModifier, shiny, ability ->
).apply(instance) { uuid, species, formId, features, level, experience, friendship, currentHealth, gender, ivs, evs, moveSet, benchedMoves, scaleModifier, shiny, ability ->
val form = species.forms.firstOrNull { it.formOnlyShowdownId() == formId } ?: species.standardForm
PokemonP1(uuid, species, form, nickname, level, experience, friendship, currentHealth, gender, ivs, evs, moveSet, benchedMoves, scaleModifier, shiny, ability)
PokemonP1(uuid, species, form, features, level, experience, friendship, currentHealth, gender, ivs, evs, moveSet, benchedMoves, scaleModifier, shiny, ability)
}
}
@@ -114,7 +132,11 @@ internal class PokemonP1(
pokemon.uuid,
pokemon.species,
pokemon.form,
Optional.ofNullable(pokemon.nickname),
pokemon.features.map { feature ->
val nbt = CompoundTag()
nbt.putString(FEATURE_ID, feature.name)
feature.saveToNBT(nbt)
},
pokemon.level,
pokemon.experience,
pokemon.friendship,
@@ -14,15 +14,12 @@ import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.pokemon.OriginalTrainerType
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP1.Companion.FEATURES
import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP1.Companion.FEATURE_ID
import com.cobblemon.mod.common.util.codec.optionalFieldOfWithDefault
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.ComponentSerialization
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import java.util.*
@@ -31,7 +28,7 @@ internal data class PokemonP3(
val originalTrainerType: OriginalTrainerType,
val originalTrainer: Optional<String>,
val forcedAspects: Set<String>,
val features: List<CompoundTag>,
val nickname: Optional<Component>,
val heldItemVisible: Optional<Boolean>,
val canDropHeldItem: Optional<Boolean>,
val cosmeticItem: ItemStack,
@@ -51,22 +48,7 @@ internal data class PokemonP3(
this.originalTrainer.ifPresent { other.originalTrainer = it }
other.refreshOriginalTrainer()
other.forcedAspects = this.forcedAspects
this.features.forEach { featureNbt ->
val featureId = featureNbt.getString(FEATURE_ID)
if (featureId.isEmpty()) {
return@forEach
}
val speciesFeatureProviders = SpeciesFeatures.getFeaturesFor(other.species)
val feature = speciesFeatureProviders.firstNotNullOfOrNull { provider -> provider(featureNbt) } ?: return@forEach
if (
featureNbt.contains("keys", Tag.TAG_STRING.toInt()) &&
!featureNbt.getList("keys", Tag.TAG_STRING.toInt()).contains(StringTag.valueOf(featureId))
) {
return@forEach
}
other.features.removeIf { it.name == feature.name }
other.features.add(feature)
}
this.nickname.ifPresent { other.nickname = it.copy() }
this.heldItemVisible.ifPresent { other.heldItemVisible = it }
this.canDropHeldItem.ifPresent { other.canDropHeldItem = it }
this.cosmeticItem.let { other.cosmeticItem = it }
@@ -92,7 +74,7 @@ internal data class PokemonP3(
OriginalTrainerType.CODEC.optionalFieldOfWithDefault(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, OriginalTrainerType.NONE).forGetter(PokemonP3::originalTrainerType),
Codec.STRING.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER).forGetter(PokemonP3::originalTrainer),
Codec.list(Codec.STRING).optionalFieldOf(DataKeys.POKEMON_FORCED_ASPECTS, emptyList()).forGetter { it.forcedAspects.toMutableList() },
Codec.list(CompoundTag.CODEC).optionalFieldOf(FEATURES, emptyList()).forGetter(PokemonP3::features),
ComponentSerialization.CODEC.optionalFieldOf(DataKeys.POKEMON_NICKNAME).forGetter(PokemonP3::nickname),
Codec.BOOL.optionalFieldOf(DataKeys.HELD_ITEM_VISIBLE).forGetter(PokemonP3::heldItemVisible),
Codec.BOOL.optionalFieldOf(DataKeys.HELD_ITEM_AI_DROPPABLE).forGetter(PokemonP3::canDropHeldItem),
ItemStack.CODEC.optionalFieldOf(DataKeys.POKEMON_COSMETIC_ITEM).forGetter { Optional.ofNullable(it.cosmeticItem.takeIf { !it.isEmpty }) },
@@ -109,7 +91,7 @@ internal data class PokemonP3(
originalTrainerType,
originalTrainer,
forcedAspects,
features,
nickname,
heldItemVisible,
canDropHeldItem,
cosmeticItem,
@@ -125,7 +107,7 @@ internal data class PokemonP3(
originalTrainerType,
originalTrainer,
forcedAspects.toSet(),
features,
nickname,
heldItemVisible,
canDropHeldItem,
cosmeticItem.orElse(ItemStack.EMPTY),
@@ -146,11 +128,7 @@ internal data class PokemonP3(
pokemon.originalTrainerType,
Optional.ofNullable(pokemon.originalTrainer),
pokemon.forcedAspects,
pokemon.features.map { feature ->
val nbt = CompoundTag()
nbt.putString(FEATURE_ID, feature.name)
feature.saveToNBT(nbt)
},
Optional.ofNullable(pokemon.nickname),
Optional.ofNullable(pokemon.heldItemVisible),
Optional.ofNullable(pokemon.canDropHeldItem),
pokemon.cosmeticItem,