diff --git a/alpha-pokemon-todo b/alpha-pokemon-todo new file mode 100644 index 0000000000..792d8e3395 --- /dev/null +++ b/alpha-pokemon-todo @@ -0,0 +1,9 @@ +- Some kind of particle effect for them? they're meant to have glowing eyes, so maybe some red particles on the eye locator +--- global posers? stacked posers? +--- red texture seems to be what some people wanted. + +/- shoulder mounts part of it +- omni boost +- roar when player comes within reach, adjust levels + +- Riding settings to control what the min scale is for a pokemon to be ridable. \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt index e9ef05f29d..70b55a6ed3 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt @@ -86,6 +86,7 @@ import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacke import com.cobblemon.mod.common.permission.LaxPermissionValidator import com.cobblemon.mod.common.platform.events.PlatformEvents import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.pokemon.aspects.ALPHA_ASPECT import com.cobblemon.mod.common.pokemon.aspects.CHARACTERISTIC_RAINBOW_ASPECT import com.cobblemon.mod.common.pokemon.aspects.COSMETIC_SLOT_ASPECT import com.cobblemon.mod.common.pokemon.aspects.GENDER_ASPECT @@ -302,6 +303,7 @@ object Cobblemon { GENDER_ASPECT.register() COSMETIC_SLOT_ASPECT.register() CHARACTERISTIC_RAINBOW_ASPECT.register() + ALPHA_ASPECT.register() SpeciesFeatures.types["choice"] = ChoiceSpeciesFeatureProvider::class.java SpeciesFeatures.types["flag"] = FlagSpeciesFeatureProvider::class.java diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt b/common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt index 839acd25e1..13e0ecfff6 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt @@ -325,6 +325,8 @@ object CobblemonNetwork { list.add(PacketRegisterInfo(MarkingsUpdatePacket.ID, MarkingsUpdatePacket::decode, PokemonUpdatePacketHandler())) list.add(PacketRegisterInfo(RideBoostsUpdatePacket.ID, RideBoostsUpdatePacket::decode, PokemonUpdatePacketHandler())) list.add(PacketRegisterInfo(RideStaminaUpdatePacket.ID, RideStaminaUpdatePacket::decode, PokemonUpdatePacketHandler())) + list.add(PacketRegisterInfo(AlphaUpdatePacket.ID, AlphaUpdatePacket::decode, PokemonUpdatePacketHandler())) + list.add(PacketRegisterInfo(ScaleModifierUpdatePacket.ID, ScaleModifierUpdatePacket::decode, PokemonUpdatePacketHandler())) // Evolution start list.add(PacketRegisterInfo(AddEvolutionPacket.ID, AddEvolutionPacket::decode, PokemonUpdatePacketHandler())) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/PokemonProperties.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/PokemonProperties.kt index 6a739eedc7..fa6528fc61 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/PokemonProperties.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/PokemonProperties.kt @@ -131,6 +131,7 @@ open class PokemonProperties { props.gender = Gender.entries.toList().parsePropertyOfCollection(keyPairs, listOf("gender"), labelsOptional = true) { it.name.lowercase() } props.level = parseIntProperty(keyPairs, listOf("level", "lvl", "l"))?.coerceIn(1, Cobblemon.config.maxPokemonLevel) props.shiny = parseBooleanProperty(keyPairs, listOf("shiny", "s")) + props.isAlpha = parseBooleanProperty(keyPairs, listOf("alpha", "is_alpha")) props.species = parseSpeciesIdentifier(keyPairs) props.form = parseForm(keyPairs) props.friendship = parseIntProperty(keyPairs, listOf("friendship"))?.coerceIn(0, Cobblemon.config.maxPokemonFriendship) @@ -359,6 +360,7 @@ open class PokemonProperties { var heldItem: String? = null var cosmeticItem: String? = null var scaleModifier: Float? = null + var isAlpha: Boolean? = null var minPerfectIVs: Int? = null var ivs: IVs? = null @@ -409,6 +411,7 @@ open class PokemonProperties { shiny?.let { pokemon.shiny = it } gender?.let { pokemon.gender = it } level?.let { pokemon.level = it } + isAlpha?.let { pokemon.isAlpha = it } friendship?.also { pokemon.setFriendship(it) } fullness?.let { pokemon.currentFullness = it } pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemon.caughtBall = pokeball } } @@ -528,6 +531,7 @@ open class PokemonProperties { pokeball?.takeIf { it != pokemon.caughtBall.name.toString() }?.let { return false } nature?.takeIf { it != pokemon.nature.name.toString() }?.let { return false } ability?.takeIf { it != pokemon.ability.name }?.let { return false } + isAlpha?.takeIf { it != pokemon.isAlpha }?.let { return false } status?.takeIf { it != pokemon.status?.status?.showdownName }?.let { return false } minPerfectIVs?.takeIf { pokemon.ivs.count { stat -> stat.value == IVs.MAX_VALUE } < it }?.let { return false } ivs?.forEach{ stat -> @@ -587,6 +591,7 @@ open class PokemonProperties { friendship?.takeIf { it != properties.friendship }?.let { return false } pokeball?.takeIf { it != properties.pokeball }?.let { return false } nature?.takeIf { it != properties.nature }?.let { return false } + isAlpha?.takeIf { it != properties.isAlpha }?.let { return false } ability?.takeIf { it != properties.ability }?.let { return false } status?.takeIf { it != properties.status }?.let { return false } minPerfectIVs?.takeIf { it != properties.minPerfectIVs }?.let { return false } @@ -630,13 +635,13 @@ open class PokemonProperties { @JvmOverloads fun roll(pokemon: Pokemon, player: ServerPlayer? = null) { val baseTypes = pokemon.form.types.toList() - var shinyRate = Cobblemon.config.shinyRate + var shinyRate = config.shinyRate CobblemonEvents.SHINY_CHANCE_CALCULATION.post(ShinyChanceCalculationEvent(shinyRate, pokemon)) { event -> shinyRate = event.calculate(player) } if (this.shiny == null) pokemon.shiny = shinyRate.checkRate() if (this.teraType == null) pokemon.teraType = - if (Cobblemon.config.teraTypeRate.checkRate()) { + if (config.teraTypeRate.checkRate()) { var picked = TeraTypes.random(true) while (picked is ElementalTypeTeraType && picked.type in pokemon.types) { picked = TeraTypes.random(true) @@ -644,6 +649,10 @@ open class PokemonProperties { picked } else TeraTypes.forElementalType(baseTypes.random()) + isAlpha?.let { pokemon.isAlpha = it } + if (this.scaleModifier == null) { + pokemon.initializeScale() + } } @JvmOverloads @@ -666,6 +675,7 @@ open class PokemonProperties { pokeball?.let { nbt.putString(DataKeys.POKEMON_CAUGHT_BALL, it) } nature?.let { nbt.putString(DataKeys.POKEMON_NATURE, it) } ability?.let { nbt.putString(DataKeys.POKEMON_ABILITY, it) } + isAlpha?.let { nbt.putBoolean(DataKeys.POKEMON_ALPHA, it) } status?.let { nbt.putString(DataKeys.POKEMON_STATUS_NAME, it) } minPerfectIVs?.let { nbt.putInt(DataKeys.POKEMON_MIN_PERFECT_IVS, it) } ivs?.let { nbt.put(DataKeys.POKEMON_IVS, IVs.CODEC.encodeStart(NbtOps.INSTANCE, it).result().get()) } @@ -700,6 +710,7 @@ open class PokemonProperties { pokeball = if (tag.contains(DataKeys.POKEMON_CAUGHT_BALL)) tag.getString(DataKeys.POKEMON_CAUGHT_BALL) else null nature = if (tag.contains(DataKeys.POKEMON_NATURE)) tag.getString(DataKeys.POKEMON_NATURE) else null ability = if (tag.contains(DataKeys.POKEMON_ABILITY)) tag.getString(DataKeys.POKEMON_ABILITY) else null + isAlpha = if (tag.contains(DataKeys.POKEMON_ALPHA)) tag.getBoolean(DataKeys.POKEMON_ALPHA) else null status = if (tag.contains(DataKeys.POKEMON_STATUS_NAME)) tag.getString(DataKeys.POKEMON_STATUS_NAME) else null minPerfectIVs = if (tag.contains(DataKeys.POKEMON_MIN_PERFECT_IVS)) tag.getInt(DataKeys.POKEMON_MIN_PERFECT_IVS) else null ivs = if (tag.contains(DataKeys.POKEMON_IVS)) IVs.CODEC.decode(NbtOps.INSTANCE, tag.getCompound(DataKeys.POKEMON_IVS)).result().getOrNull()?.first else null @@ -735,6 +746,7 @@ open class PokemonProperties { pokeball?.let { json.addProperty(DataKeys.POKEMON_CAUGHT_BALL, it) } nature?.let { json.addProperty(DataKeys.POKEMON_NATURE, it) } ability?.let { json.addProperty(DataKeys.POKEMON_ABILITY, it) } + isAlpha?.let { json.addProperty(DataKeys.POKEMON_ALPHA, it) } status?.let { json.addProperty(DataKeys.POKEMON_STATUS_NAME, it) } minPerfectIVs?.let { json.addProperty(DataKeys.POKEMON_MIN_PERFECT_IVS, it) } ivs?.let { json.add(DataKeys.POKEMON_IVS, IVs.CODEC.encodeStart(JsonOps.INSTANCE, it).result().get()) } @@ -769,6 +781,7 @@ open class PokemonProperties { pokeball = json.get(DataKeys.POKEMON_CAUGHT_BALL)?.asString nature = json.get(DataKeys.POKEMON_NATURE)?.asString ability = json.get(DataKeys.POKEMON_ABILITY)?.asString + isAlpha = json.get(DataKeys.POKEMON_ALPHA)?.asBoolean status = json.get(DataKeys.POKEMON_STATUS_NAME)?.asString minPerfectIVs = json.get(DataKeys.POKEMON_MIN_PERFECT_IVS)?.asInt ivs = json.getAsJsonObject(DataKeys.POKEMON_IVS)?.let { IVs.CODEC.decode(JsonOps.INSTANCE, it).result().getOrNull()?.first } @@ -803,6 +816,7 @@ open class PokemonProperties { pokeball?.let { pieces.add("pokeball=$it") } nature?.let { pieces.add("nature=$it") } ability?.let { pieces.add("ability=$it") } + isAlpha?.let { pieces.add("alpha=$it") } status?.let { pieces.add("status=$it") } minPerfectIVs?.let { pieces.add("min_perfect_ivs=$it") } ivs?.forEach{ stat -> diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/entity/PokemonClientDelegate.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/entity/PokemonClientDelegate.kt index bc2a74127c..9b66092863 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/entity/PokemonClientDelegate.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/entity/PokemonClientDelegate.kt @@ -20,8 +20,6 @@ import com.cobblemon.mod.common.api.pokeball.PokeBalls import com.cobblemon.mod.common.api.pokemon.aspect.aspectParticleMap import com.cobblemon.mod.common.api.pokemon.PokemonSpecies import com.cobblemon.mod.common.api.pokemon.aspect.ParticleData -import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState -import com.cobblemon.mod.common.api.riding.stats.RidingStat import com.cobblemon.mod.common.api.scheduling.ScheduledTask import com.cobblemon.mod.common.api.scheduling.SchedulingTracker import com.cobblemon.mod.common.api.scheduling.afterOnClient @@ -112,6 +110,8 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate { // force a model update - handles edge case where the PosableState's tracked PosableModel isn't updated until the LivingEntityRenderer render is run currentModel = VaryingModelRepository.getPoser(identifier, this) currentEntity.refreshRiding() + } else if (data == PokemonEntity.IS_ALPHA) { + currentEntity.pokemon.isAlpha = currentEntity.entityData.get(PokemonEntity.IS_ALPHA) } else if (data == PokemonEntity.ASPECTS) { currentAspects = currentEntity.entityData.get(PokemonEntity.ASPECTS) currentEntity.pokemon.shiny = currentAspects.contains("shiny") diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/summary/Summary.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/summary/Summary.kt index 6d8a37fb46..8d934b3684 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/summary/Summary.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/gui/summary/Summary.kt @@ -601,6 +601,20 @@ class Summary private constructor(party: Collection, private val edita shadow = true ) + val sizeCategory = selectedPokemon.getSizeCategory() + val isAlpha = selectedPokemon.isAlpha + + drawScaledText( + x = x + 62, + y = y + 32, + font = CobblemonResources.DEFAULT_LARGE, + context = context, + text = (if (isAlpha) "A".text() else sizeCategory.name.text()).bold(), + scale = 1F, + shadow = true, + centered = true + ) + // Shiny Icon if (selectedPokemon.shiny) { blitk( diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/PosableModel.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/PosableModel.kt index 71eeba4b2d..581e3069ee 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/PosableModel.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/PosableModel.kt @@ -113,7 +113,7 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame { open var properties = mutableMapOf() /* - * Hello future Hiro, this is past Hiro. You've gotten forgetful in your old age. + * Hello future Hiro, this is past Hiro. You've become forgetful in your old age. * * The profile translation is not actually necessary. The reason why you thought it was necessary * is that there is a 1.5 block offset applied by living-entity-renderer-intended models due to diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/client/render/pokemon/PokemonRenderer.kt b/common/src/main/kotlin/com/cobblemon/mod/common/client/render/pokemon/PokemonRenderer.kt index 6efc013695..be493b0700 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/client/render/pokemon/PokemonRenderer.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/client/render/pokemon/PokemonRenderer.kt @@ -292,7 +292,7 @@ class PokemonRenderer( } override fun scale(pEntity: PokemonEntity, pPoseStack: PoseStack, pPartialTickTime: Float) { - val scale = pEntity.pokemon.form.baseScale * pEntity.pokemon.scaleModifier * (pEntity.delegate as PokemonClientDelegate).entityScaleModifier + val scale = pEntity.pokemon.form.baseScale * pEntity.pokemon.effectiveScale * (pEntity.delegate as PokemonClientDelegate).entityScaleModifier pPoseStack.scale(scale, scale, scale) } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt b/common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt index 68e4b96b99..3de541f15e 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/config/CobblemonConfig.kt @@ -219,6 +219,18 @@ class CobblemonConfig { @CobblemonConfigField(Category.Pokemon, lang = "player_damage_pokemon", SERVER) var playerDamagePokemon = true + @CobblemonConfigField(Category.Pokemon, lang = "pokemon_size_variation", SERVER) + var pokemonSizeVariation = 0.05F + + @CobblemonConfigField(Category.Pokemon, lang = "alpha_pokemon_size_multiplier", SERVER) + var alphaPokemonSizeMultiplier = 1.2F + + @CobblemonConfigField(Category.Pokemon, lang = "baby_pokemon_level_duration", SERVER) + var babyPokemonLevelDuration = 9 + + @CobblemonConfigField(Category.Pokemon, lang = "baby_pokemon_size_multiplier", SERVER) + var babyPokemonSizeMultiplier = 0.8F + @CobblemonConfigField(Category.World, lang = "apple_leftovers_chance", SERVER) var appleLeftoversChance = 0.025 diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt b/common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt index 5f8348f889..0ee9fb015c 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt @@ -213,6 +213,7 @@ open class PokemonEntity( @JvmStatic var RIDE_BOOSTS = SynchedEntityData.defineId(PokemonEntity::class.java, RideBoostsDataSerializer) @JvmStatic var RIDE_STAMINA = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.FLOAT) @JvmStatic var SCALE_MODIFIER = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.FLOAT) + @JvmStatic var IS_ALPHA = SynchedEntityData.defineId(PokemonEntity::class.java, EntityDataSerializers.BOOLEAN) const val BATTLE_LOCK = "battle" const val EVOLUTION_LOCK = "evolving" @@ -460,6 +461,7 @@ open class PokemonEntity( builder.define(RIDE_BOOSTS, emptyMap()) builder.define(RIDE_STAMINA, 1F) builder.define(SCALE_MODIFIER, 1F) + builder.define(IS_ALPHA, false) } override fun onSyncedDataUpdated(data: EntityDataAccessor<*>) { @@ -1095,7 +1097,7 @@ open class PokemonEntity( override fun getBreedOffspring(serverLevel: ServerLevel, ageableMob: AgeableMob) = null override fun canSitOnShoulder(): Boolean { - return pokemon.form.shoulderMountable + return pokemon.form.shoulderMountable && !pokemon.isAlpha } override fun wantsToPickUp(stack: ItemStack): Boolean { @@ -1253,7 +1255,7 @@ open class PokemonEntity( } override fun getDimensions(pose: Pose): EntityDimensions { - val scale = effects.mockEffect?.scale ?: (form.baseScale * pokemon.scaleModifier) + val scale = effects.mockEffect?.scale ?: (form.baseScale * pokemon.effectiveScale) var result = this.exposedForm.hitbox.scale(scale) result = result.withEyeHeight(this.exposedForm.eyeHeight(this) * result.height) result = result.scale(this.scale) @@ -2610,11 +2612,15 @@ open class PokemonEntity( * represents a kind of 'responsibility' that the leader feels towards their followers - they believe in this * leader with some amount of fervor, and this gets used to ensure that the leader doesn't choose to follow a * different Pokémon that is of an equal or lower tier than this Pokémon is to its followers. + * - Alpha Pokémon automatically have the maximum possible tier. */ fun getHerdTier(): Int { val world = level() as? ServerLevel ?: return 0 val herdLeader = this.brain.getMemorySafely(CobblemonMemories.HERD_LEADER).orElse(null)?.let(UUID::fromString)?.let(world::getEntity) as? PokemonEntity return if (herdLeader == null) { + if (pokemon.isAlpha) { + return Int.MAX_VALUE + } if (!brain.hasMemoryValue(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)) { return 0 } @@ -2626,6 +2632,9 @@ open class PokemonEntity( it.behaviour.herd.bestMatchLeader(follower = it, possibleLeader = this)?.tier ?: 0 } ?: 0 } else { + if (herdLeader.pokemon.isAlpha) { + return Int.MAX_VALUE + } herdLeader.behaviour.herd.bestMatchLeader(follower = this, possibleLeader = herdLeader)?.tier ?: 0 } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonServerDelegate.kt b/common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonServerDelegate.kt index d4e681cf8e..059e2bf8d8 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonServerDelegate.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonServerDelegate.kt @@ -243,6 +243,9 @@ class PokemonServerDelegate : PokemonSideDelegate { if (entity.entityData.get(PokemonEntity.MARK) !=trackedMark) { entity.entityData.set(PokemonEntity.MARK, trackedMark) } + if (entity.entityData.get(PokemonEntity.IS_ALPHA) != entity.pokemon.isAlpha) { + entity.entityData.set(PokemonEntity.IS_ALPHA, entity.pokemon.isAlpha) + } entity.entityData.set(PokemonEntity.ASPECTS, trackedAspects) entity.entityData.set(PokemonEntity.LABEL_LEVEL, entity.pokemon.level) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/pokemon/update/AlphaUpdatePacket.kt b/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/pokemon/update/AlphaUpdatePacket.kt new file mode 100644 index 0000000000..c8034382dd --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/pokemon/update/AlphaUpdatePacket.kt @@ -0,0 +1,41 @@ +/* + * 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.pokemon.update + +import com.cobblemon.mod.common.api.pokeball.PokeBalls +import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.util.cobblemonResource +import com.cobblemon.mod.common.util.readIdentifier +import net.minecraft.network.RegistryFriendlyByteBuf + +/** + * Update packet for the alpha (as in the big scary pack leader) status of a Pokémon. + * + * @author Hiroku + * @since December 1st, 2025 + */ +class AlphaUpdatePacket(pokemon: () -> Pokemon?, value: Boolean): SingleUpdatePacket(pokemon, value) { + override val id = ID + override fun encodeValue(buffer: RegistryFriendlyByteBuf) { + buffer.writeBoolean(this.value) + } + + override fun set(pokemon: Pokemon, value: Boolean) { + pokemon.isAlpha = value + } + + companion object { + val ID = cobblemonResource("alpha_update") + fun decode(buffer: RegistryFriendlyByteBuf): CaughtBallUpdatePacket { + val pokemon = decodePokemon(buffer) + val pokeBall = PokeBalls.getPokeBall(buffer.readIdentifier()) ?: PokeBalls.POKE_BALL + return CaughtBallUpdatePacket(pokemon, pokeBall) + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/pokemon/update/ScaleModifierUpdatePacket.kt b/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/pokemon/update/ScaleModifierUpdatePacket.kt new file mode 100644 index 0000000000..7ac4368332 --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/pokemon/update/ScaleModifierUpdatePacket.kt @@ -0,0 +1,39 @@ +/* + * 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.pokemon.update + +import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.util.cobblemonResource +import net.minecraft.network.RegistryFriendlyByteBuf + +/** + * Update packet for a Pokémon's scale modifier. + * + * @author Hiroku + * @since December 1st, 2025 + */ +class ScaleModifierUpdatePacket(pokemon: () -> Pokemon?, value: Float): SingleUpdatePacket(pokemon, value) { + override val id = ID + override fun encodeValue(buffer: RegistryFriendlyByteBuf) { + buffer.writeFloat(this.value) + } + + override fun set(pokemon: Pokemon, value: Float) { + pokemon.scaleModifier = value + } + + companion object { + val ID = cobblemonResource("scale_modifier_update") + fun decode(buffer: RegistryFriendlyByteBuf): ScaleModifierUpdatePacket { + val pokemon = decodePokemon(buffer) + val scaleModifier = buffer.readFloat() + return ScaleModifierUpdatePacket(pokemon, scaleModifier) + } + } +} diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt b/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt index efc8c8bf82..88de28dae3 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/net/messages/client/spawn/SpawnPokemonPacket.kt @@ -61,6 +61,7 @@ class SpawnPokemonPacket( var tickSpawned: Int, var rideBoosts: Map, var rideStamina: Float, + var isAlpha: Boolean, vanillaSpawnPacket: ClientboundAddEntityPacket, ) : SpawnExtraDataEntityPacket(vanillaSpawnPacket) { @@ -93,6 +94,7 @@ class SpawnPokemonPacket( entity.tickCount, entity.entityData.get(PokemonEntity.RIDE_BOOSTS), entity.entityData.get(PokemonEntity.RIDE_STAMINA), + entity.entityData.get(PokemonEntity.IS_ALPHA), vanillaSpawnPacket ) @@ -127,6 +129,7 @@ class SpawnPokemonPacket( { _, value -> buffer.writeFloat(value) } ) buffer.writeFloat(rideStamina) + buffer.writeBoolean(isAlpha) } override fun applyData(entity: PokemonEntity, level: ClientLevel) { @@ -162,6 +165,7 @@ class SpawnPokemonPacket( entity.entityData.set(PokemonEntity.RIDE_BOOSTS, rideBoosts) entity.entityData.set(PokemonEntity.RIDE_STAMINA, rideStamina) entity.entityData.set(PokemonEntity.SCALE_MODIFIER, scaleModifier) + entity.entityData.set(PokemonEntity.IS_ALPHA, isAlpha) entity.ejectPassengers() passengers.forEach { @@ -206,6 +210,7 @@ class SpawnPokemonPacket( { buffer.readFloat() } ) val rideStamina = buffer.readFloat() + val isAlpha = buffer.readBoolean() val vanillaPacket = decodeVanillaPacket(buffer) return SpawnPokemonPacket( @@ -235,6 +240,7 @@ class SpawnPokemonPacket( tickSpawned, rideBoosts, rideStamina, + isAlpha, vanillaPacket ) } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt index 76ddd0cebc..49daa78af4 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt @@ -110,6 +110,7 @@ import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP3 import com.cobblemon.mod.common.util.codec.internal.PokemonP1 import com.cobblemon.mod.common.util.codec.internal.PokemonP2 import com.cobblemon.mod.common.util.codec.internal.PokemonP3 +import com.cobblemon.mod.common.util.nextBetween import com.cobblemon.mod.common.util.playSoundServer import com.cobblemon.mod.common.util.server import com.cobblemon.mod.common.util.setPositionSafely @@ -238,6 +239,15 @@ open class Pokemon : ShowdownIdentifiable { var characteristic: Characteristic = Characteristic.calculate(ivs, uuid) private set + var isAlpha: Boolean = false + set(value) { + if (field != value) { + field = value + updateAspects() + onChange(AlphaUpdatePacket({ this }, value)) + } + } + fun setIV(stat : Stat, value : Int) { val quotient = clamp(currentHealth / maxHealth.toFloat(), 0F, 1F) ivs[stat] = value @@ -246,6 +256,11 @@ open class Pokemon : ShowdownIdentifiable { } } + fun initializeScale() { + val variation = Cobblemon.config.pokemonSizeVariation + scaleModifier = Random.nextBetween(1 - variation, 1 + variation) + } + fun hyperTrainIV(stat: Stat, value: Int) { val quotient = clamp(currentHealth / maxHealth.toFloat(), 0F, 1F) CobblemonEvents.HYPER_TRAINED_IV_PRE.postThen( @@ -550,7 +565,23 @@ open class Pokemon : ShowdownIdentifiable { val speed: Int get() = getStat(Stats.SPEED) - var scaleModifier = 1F + var scaleModifier: Float = 1F + set(value) { + if (field != value) { + field = value + onChange(ScaleModifierUpdatePacket({ this }, value)) + } + } + + val effectiveScale: Float + get() { + val babyPokemonMultiplier = if (level - Cobblemon.config.babyPokemonLevelDuration < 1) { + Cobblemon.config.babyPokemonSizeMultiplier + } else { + 1F + } + return babyPokemonMultiplier * if (this.isAlpha) Cobblemon.config.alphaPokemonSizeMultiplier else scaleModifier + } var caughtBall: PokeBall = PokeBalls.POKE_BALL set(value) { @@ -2001,6 +2032,10 @@ open class Pokemon : ShowdownIdentifiable { return createPokemonProperties(*extractors.toTypedArray()) } + fun getSizeCategory(): PokemonSizeCategory { + return PokemonSizeCategory.fromScale(scaleModifier) + } + fun addExperience(source: ExperienceSource, xp: Int): AddExperienceResult { if (xp < 0 || !this.canLevelUpFurther()) { return AddExperienceResult(level, level, emptySet(), 0) // no negatives! diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/PokemonSizeCategory.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/PokemonSizeCategory.kt new file mode 100644 index 0000000000..6bc0947cea --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/PokemonSizeCategory.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 Cobblemon Contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.cobblemon.mod.common.pokemon + +import com.cobblemon.mod.common.Cobblemon + +enum class PokemonSizeCategory { + XS, + S, + M, + L, + XL; + + companion object { + // Find which size category corresponds to the given scale modifier. + // The scale modifier should be between 1 - Cobblemon.config.pokemonSizeVariation and 1 + Cobblemon.config.pokemonSizeVariation + // First 20% -> XS, etc. + fun fromScale(scaleModifier: Float): PokemonSizeCategory { + val range = Cobblemon.config.pokemonSizeVariation * 2 + val segmentSize = range / PokemonSizeCategory.entries.size + val adjustedScale = scaleModifier - (1 - Cobblemon.config.pokemonSizeVariation) + val index = (adjustedScale / segmentSize).toInt().coerceIn(0, PokemonSizeCategory.entries.size - 1) + return PokemonSizeCategory.entries[index] + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/aspects/PokemonAspects.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/aspects/PokemonAspects.kt index 9ae14f1603..10e2b83119 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/aspects/PokemonAspects.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/aspects/PokemonAspects.kt @@ -53,6 +53,26 @@ val COSMETIC_SLOT_ASPECT = object : AspectProvider { override fun provide(properties: PokemonProperties) = emptySet() } +val ALPHA_ASPECT = object : AspectProvider { + val ASPECT = "alpha" + + override fun provide(pokemon: Pokemon): Set { + return if (pokemon.isAlpha) { + setOf(ASPECT) + } else { + emptySet() + } + } + + override fun provide(properties: PokemonProperties): Set { + return if (properties.isAlpha == true) { + setOf(ASPECT) + } else { + emptySet() + } + } +} + val CHARACTERISTIC_RAINBOW_ASPECT = object : AspectProvider { fun calculateColourAspect(nature: Nature, characteristic: Characteristic): String? { return when (nature.increasedStat to characteristic.relevantStat) { diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt index 1b99ca21ca..b42cbd6aea 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt @@ -20,6 +20,7 @@ object DataKeys { const val POKEMON_FRIENDSHIP = "Friendship" const val POKEMON_FULLNESS = "Fullness" const val POKEMON_INTERACTION_COOLDOWN = "InteractionCooldown" + const val POKEMON_ALPHA = "Alpha" const val POKEMON_IVS = "IVs" const val POKEMON_IVS_BASE = "Base" diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP3.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP3.kt index 6b4c26c17d..9563955b82 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP3.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP3.kt @@ -35,6 +35,7 @@ internal data class ClientPokemonP3( val rideBoosts: Map, val currentFullness: Int, val interactionCooldowns: Map, + val isAlpha: Boolean, ) : Partial { override fun into(other: Pokemon): Pokemon { @@ -53,6 +54,7 @@ internal data class ClientPokemonP3( this.rideBoosts.let { other.setRideBoosts(it.mapKeys { RidingStat.valueOf(it.key) }) } other.currentFullness = this.currentFullness other.interactionCooldowns = this.interactionCooldowns.toMutableMap() + other.isAlpha = isAlpha return other } @@ -75,6 +77,7 @@ internal data class ClientPokemonP3( Codec.unboundedMap(Codec.STRING, Codec.FLOAT).fieldOf(DataKeys.POKEMON_RIDE_BOOSTS).forGetter(ClientPokemonP3::rideBoosts), Codec.intRange(0, 100).fieldOf(DataKeys.POKEMON_FULLNESS).forGetter(ClientPokemonP3::currentFullness), Codec.unboundedMap(ResourceLocation.CODEC, Codec.INT).fieldOf(DataKeys.POKEMON_INTERACTION_COOLDOWN).forGetter(ClientPokemonP3::interactionCooldowns), + Codec.BOOL.optionalFieldOf(DataKeys.POKEMON_ALPHA, false).forGetter(ClientPokemonP3::isAlpha) ).apply(instance, ::ClientPokemonP3) } @@ -92,6 +95,7 @@ internal data class ClientPokemonP3( pokemon.getRideBoosts().mapKeys { it.key.name }, pokemon.currentFullness, pokemon.interactionCooldowns, + pokemon.isAlpha, ) } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt index ddd9fbbcd2..84184514b0 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt @@ -43,6 +43,7 @@ internal data class PokemonP3( val rideStamina: Float, val currentFullness: Int, val interactionCooldowns: Map, + val isAlpha: Boolean ) : Partial { override fun into(other: Pokemon): Pokemon { @@ -79,6 +80,7 @@ internal data class PokemonP3( other.rideStamina = this.rideStamina other.currentFullness = this.currentFullness other.interactionCooldowns = this.interactionCooldowns.toMutableMap() + other.isAlpha = isAlpha other.recalculateCharacteristic() other.updateAspects() return other @@ -102,6 +104,7 @@ internal data class PokemonP3( Codec.FLOAT.optionalFieldOf(DataKeys.POKEMON_RIDE_STAMINA, 1F).forGetter(PokemonP3::rideStamina), Codec.intRange(0, 100).optionalFieldOf(DataKeys.POKEMON_FULLNESS, 0).forGetter(PokemonP3::currentFullness), Codec.unboundedMap(ResourceLocation.CODEC, Codec.INT).optionalFieldOf(DataKeys.POKEMON_INTERACTION_COOLDOWN, emptyMap()).forGetter(PokemonP3::interactionCooldowns), + Codec.BOOL.optionalFieldOf(DataKeys.POKEMON_ALPHA, false).forGetter(PokemonP3::isAlpha) ).apply(instance) { originalTrainerType, originalTrainer, @@ -117,7 +120,8 @@ internal data class PokemonP3( rideBoosts, rideStamina, currentFullness, - interactionCooldown -> PokemonP3( + interactionCooldown, + isAlpha -> PokemonP3( originalTrainerType, originalTrainer, forcedAspects.toSet(), @@ -133,6 +137,7 @@ internal data class PokemonP3( rideStamina, currentFullness, interactionCooldown, + isAlpha ) } } @@ -157,6 +162,7 @@ internal data class PokemonP3( pokemon.rideStamina, pokemon.currentFullness, pokemon.interactionCooldowns, + pokemon.isAlpha ) } } diff --git a/common/src/main/resources/assets/cobblemon/lang/en_us.json b/common/src/main/resources/assets/cobblemon/lang/en_us.json index d05a290dfb..6ce5904253 100644 --- a/common/src/main/resources/assets/cobblemon/lang/en_us.json +++ b/common/src/main/resources/assets/cobblemon/lang/en_us.json @@ -2522,6 +2522,14 @@ "cobblemon.config.ui.capture_calculator.tooltip": "The ID of the capture calculator. Can be added by another mod. If the ID doesn't exist it uses the default capture calculator.", "cobblemon.config.ui.player_damage_pokemon": "Player Damage Pokémon", "cobblemon.config.ui.player_damage_pokemon.tooltip": "Whether Players can attack Pokémon like normal Minecraft entities.", + "cobblemon.config.ui.pokemon_size_variation": "Pokémon Size Variation", + "cobblemon.config.ui.pokemon_size_variation.tooltip": "How much variation in size is possible for spawned Pokémon.", + "cobblemon.config.ui.alpha_pokemon_size_multiplier": "Alpha Pokémon Size Multiplier", + "cobblemon.config.ui.alpha_pokemon_size_multiplier.tooltip": "The size multiplier applied to alpha Pokémon.", + "cobblemon.config.ui.baby_pokemon_level_duration": "Baby Pokémon Level Duration", + "cobblemon.config.ui.baby_pokemon_level_duration.tooltip": "The number of levels that a Pokémon is considered a baby for the purposes of appearing smaller than usual.", + "cobblemon.config.ui.baby_pokemon_size_multiplier": "Baby Pokémon Minimum Size", + "cobblemon.config.ui.baby_pokemon_size_multiplier.tooltip": "The size multiplier applied to a level 1 Pokémon.", "cobblemon.config.ui.apple_leftovers_chance": "Apple Leftovers Chance", "cobblemon.config.ui.apple_leftovers_chance.tooltip": "The chance to drop Leftovers when a Player eats an item that has #cobblemon:held/leaves_leftovers.", "cobblemon.config.ui.max_roots_in_area": "Max Roots in Area",