Compare commits

...
Author SHA1 Message Date
Polymeta 4f5799e5df yet another seasoning codec tweak
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-30 00:39:13 +01:00
Polymeta 54ea4903ba Merge branch 'cooking-tooltip-overhaul' into 'main'
Cooking tooltip overhaul

See merge request cable-mc/cobblemon!1687
2025-05-29 22:07:13 +00:00
Plastered_Crab 28ad0cce83 Cooking tooltip overhaul 2025-05-29 22:07:13 +00:00
Polymeta 7fe1929609 Merge branch 'hyper_trained_ivs' into 'main'
Hyper Trained Stat IVs

See merge request cable-mc/cobblemon!1614
2025-05-29 15:53:12 +00:00
Konoha D e03082a75e Hyper Trained Stat IVs 2025-05-29 15:53:11 +00:00
Polymeta 8d0c27f0e4 [skip-ci] changelog
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-05-29 16:47:14 +01:00
Polymeta 91fc2dc896 Merge branch 'spawn-rules-npc-gisketch' into 'main'
Make Spawn Rules affect NPC spawns

See merge request cable-mc/cobblemon!1682
2025-05-29 15:43:12 +00:00
Ghegi Jimenez ab1ca295a3 Make Spawn Rules affect NPC spawns
- Update NPCSpawnDetail to have autoLabel() function similar to
  PokemonSpawnDetail to support spawn rules

Ref: https://gitlab.com/cable-mc/cobblemon/-/issues/1559
2025-05-29 09:36:37 +08:00
66 changed files with 973 additions and 449 deletions

No files matched your search

+4
View File
@@ -29,6 +29,7 @@
- Added Tatami blocks and Tatami Mat blocks, made from Hearty Grain, for decorating builds
- Added `/pcsearch <player> <pokemonProperties>` command that searches for a specific Pokémon within a player's PC.
- Added `/pctake <player> <box> <slot>` command that takes a specific Pokémon from a player's PC. Removes the pokemon if target is self or ran from the server.
- Added Hyper Training items (IV Modification) as well as some additional candy items to do so (Health Candy, Sickly Candy)
### Pokémon Added
@@ -222,6 +223,8 @@
}
}
`
- The IVs class has now been extended to include Hyper Trained values.
- Added `Pokemon#hyperTrainIV()` and `IVs#setHyperTrainedIV(Stat, Int)`
### MoLang & Datapacks
- The following usages for item predicates can now use item conditions like advancements do, you can learn about them in the [Minecraft wiki](https://minecraft.wiki/w/Advancement_definition#minecraft:filled_bucket)
@@ -248,6 +251,7 @@
- Added datapack-defined starter categories via `data/<namespace>/starters/*.json`, with built-in fallback and `useConfigStarters` merge option.
- The format of the `remedies.json` file has changed to allow for individual friendshipDrop amounts per remedy
- Fixed `entity.find_nearby_block` causing crashes when attempting to use a block tag
- Spawn Filters can now access `v.spawn.class` to get the identifier of an NPC class for when trying to influence NPC spawns
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -11,10 +11,12 @@ package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.PotComponent
import com.cobblemon.mod.common.item.components.*
import com.cobblemon.mod.common.platform.PlatformRegistry
import com.mojang.serialization.Codec
import net.minecraft.core.Registry
import net.minecraft.core.component.DataComponentType
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
@@ -70,6 +72,11 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.networkSynchronized(MobEffectsComponent.PACKET_CODEC)
.build())
val CRAFTED: DataComponentType<Boolean> = create("crafted", DataComponentType.builder<Boolean>()
.persistent(Codec.BOOL)
.networkSynchronized(ByteBufCodecs.BOOL)
.build())
fun register() {
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:pokemon_item"), POKEMON_ITEM)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:bait"), BAIT)
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.item.ability.AbilityChanger
import com.cobblemon.mod.common.api.mulch.MulchVariant
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.text.blue
@@ -40,6 +41,7 @@ import com.cobblemon.mod.common.item.interactive.*
import com.cobblemon.mod.common.item.interactive.ability.AbilityChangeItem
import com.cobblemon.mod.common.platform.PlatformRegistry
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.Registry
@@ -601,6 +603,33 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val GALARICA_NUTS = create("galarica_nuts", GalaricaNutItem())
// Hyper Training Items
val hyperTrainingItems = mutableListOf<HyperTrainingItem>()
@JvmField
val HEALTH_CANDY = hyperTrainingItem("health_candy", 1, setOf(Stats.HP), 0..IVs.MAX_VALUE)
@JvmField
val MIGHTY_CANDY = hyperTrainingItem("mighty_candy", 1, setOf(Stats.ATTACK), 0..IVs.MAX_VALUE)
@JvmField
val TOUGH_CANDY = hyperTrainingItem("tough_candy", 1, setOf(Stats.DEFENCE), 0..IVs.MAX_VALUE)
@JvmField
val SMART_CANDY = hyperTrainingItem("smart_candy", 1, setOf(Stats.SPECIAL_ATTACK), 0..IVs.MAX_VALUE)
@JvmField
val COURAGE_CANDY = hyperTrainingItem("courage_candy", 1, setOf(Stats.SPECIAL_DEFENCE), 0..IVs.MAX_VALUE)
@JvmField
val QUICK_CANDY = hyperTrainingItem("quick_candy", 1, setOf(Stats.SPEED), 0..IVs.MAX_VALUE)
@JvmField
val SICKLY_CANDY = hyperTrainingItem("sickly_candy", -1, setOf(Stats.HP), 0..IVs.MAX_VALUE)
@JvmField
val WEAK_CANDY = hyperTrainingItem("weak_candy", -1, setOf(Stats.ATTACK), 0..IVs.MAX_VALUE)
@JvmField
val BRITTLE_CANDY = hyperTrainingItem("brittle_candy", -1, setOf(Stats.DEFENCE), 0..IVs.MAX_VALUE)
@JvmField
val NUMB_CANDY = hyperTrainingItem("numb_candy", -1, setOf(Stats.SPECIAL_ATTACK), 0..IVs.MAX_VALUE)
@JvmField
val COWARD_CANDY = hyperTrainingItem("coward_candy", -1, setOf(Stats.SPECIAL_DEFENCE), 0..IVs.MAX_VALUE)
@JvmField
val SLOW_CANDY = hyperTrainingItem("slow_candy", -1, setOf(Stats.SPEED), 0..IVs.MAX_VALUE)
// Medicine
@JvmField
val RARE_CANDY = candyItem("rare_candy", Rarity.RARE) { _, pokemon -> pokemon.getExperienceToNextLevel() }
@@ -1585,6 +1614,12 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
return item
}
private fun hyperTrainingItem(name: String, increaseAmount: Int, targetStats: Set<Stat>, validRange: IntRange): HyperTrainingItem {
val item = this.create(name, HyperTrainingItem(increaseAmount, targetStats, validRange))
hyperTrainingItems.add(item)
return item
}
private fun apricornItem(name: String, apricornItem: ApricornItem): ApricornItem {
val finalName = "${name}_apricorn"
val item = this.create(finalName, apricornItem)
@@ -29,9 +29,12 @@ private val bubbleColourMap = mapOf(
fun getColourMixFromSeasonings(seasonings: List<ItemStack>, forBubbles: Boolean = false): Int? {
val flavors = seasonings
.flatMap { Seasonings.getFlavoursFromItemStack(it).entries }
.groupingBy { it.key }
.fold(0) { acc, entry -> acc + entry.value }
.mapNotNull { Seasonings.getFlavoursFromItemStack(it) }
.flatMap { it.entries }
.groupingBy { it.key }
.fold(0) { acc, entry -> acc + entry.value }
if (flavors.isEmpty()) return null
val maxFlavorValue = flavors.values.maxOrNull()
val dominantFlavors = flavors.filter { it.value == maxFlavorValue }.map { it.key }
@@ -52,21 +52,6 @@ object MobEffectUtils {
}
private fun mergeAmplifiers(amplifiers: List<Int>): Int {
if (amplifiers.isEmpty()) return 0
val sorted = amplifiers.sortedDescending()
var result = 0.0
for ((index, value) in sorted.withIndex()) {
val multiplier = when (index) {
0 -> 1.0
1 -> 0.75
2 -> 0.5
else -> 0.25
}
result += (value + 1) * multiplier
}
return ceil(result).toInt() - 1
return amplifiers.maxOrNull() ?: 0 // we do not want the power of the effects to combine, just take the highest one used of that type
}
}
@@ -21,6 +21,8 @@ import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.network.codec.StreamCodec
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.Item
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
data class Seasoning(
val ingredient: RegistryLikeCondition<Item>,
@@ -39,23 +41,23 @@ data class Seasoning(
val CODEC: Codec<Seasoning> = RecordCodecBuilder.create { builder ->
builder.group(
ITEM_REGISTRY_LIKE_CODEC.fieldOf("ingredient").forGetter { it.ingredient },
Codec.unboundedMap(Flavour.CODEC, Codec.INT).optionalFieldOf("flavours", null).forGetter { it.flavours },
Codec.unboundedMap(Flavour.CODEC, Codec.INT).optionalFieldOf("flavours").forGetter { Optional.ofNullable(it.flavours) },
DyeColor.CODEC.fieldOf("colour").forGetter { it.colour },
SpawnBait.Effect.CODEC.listOf().optionalFieldOf("baitEffects", null).forGetter { it.baitEffects },
Food.CODEC.optionalFieldOf("food", null).forGetter { it.food },
SerializableMobEffectInstance.CODEC.listOf().optionalFieldOf("mobEffects", null).forGetter { it.mobEffects }
).apply(builder, ::Seasoning)
SpawnBait.Effect.CODEC.listOf().optionalFieldOf("baitEffects").forGetter { Optional.ofNullable(it.baitEffects) },
Food.CODEC.optionalFieldOf("food").forGetter { Optional.ofNullable(it.food) },
SerializableMobEffectInstance.CODEC.listOf().optionalFieldOf("mobEffects").forGetter { Optional.ofNullable(it.mobEffects) }
).apply(builder) { item, flavours, colour, baitEffects, food, mobEffects ->
Seasoning(
item,
flavours.getOrNull(),
colour,
baitEffects.getOrNull(),
food.getOrNull(),
mobEffects.getOrNull()
)
}
}
val STREAM_CODEC: StreamCodec<ByteBuf, Seasoning> = ByteBufCodecs.fromCodec(CODEC)
val BLANK_SEASONING = Seasoning(
ingredient = RegistryLikeIdentifierCondition<Item>(cobblemonResource("blank")),
flavours = Flavour.entries.associateWith { 0 },
colour = DyeColor.WHITE,
baitEffects = emptyList(),
food = Food(),
mobEffects = emptyList()
)
}
}
@@ -12,10 +12,12 @@ import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
import com.cobblemon.mod.common.api.cooking.Seasoning.Companion.BLANK_SEASONING
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.fishing.SpawnBaitEffects
import com.cobblemon.mod.common.api.fishing.SpawnBaitUtils
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.item.components.FoodComponent
import com.cobblemon.mod.common.net.messages.client.cooking.SeasoningRegistrySyncPacket
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.ItemLikeConditionAdapter
@@ -69,19 +71,27 @@ object Seasonings : JsonDataRegistry<Seasoning> {
this.seasonings.addAll(seasonings)
}
fun getFlavoursFromItemStack(stack: ItemStack): Map<Flavour, Int> {
fun getFlavoursFromItemStack(stack: ItemStack): Map<Flavour, Int>? {
val holder = stack.itemHolder
val seasoning = seasonings.find { it.ingredient.fits(holder) } ?: BLANK_SEASONING
val inherentFlavours = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours ?: emptyMap()
val seasoningFlavours = seasoning.flavours ?: emptyMap()
val seasoning = seasonings.find { it.ingredient.fits(holder) }
val inherentFlavours = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours
val seasoningFlavours = seasoning?.flavours
return seasoningFlavours.mapValues { (flavour, value) ->
inherentFlavours.getOrDefault(flavour, 0) + value
if (seasoningFlavours.isNullOrEmpty() && inherentFlavours.isNullOrEmpty())
return null
return (seasoningFlavours ?: emptyMap()).mapValues { (flavour, value) ->
value + (inherentFlavours?.getOrDefault(flavour, 0) ?: 0)
}
}
fun getFoodFromItemStack(stack: ItemStack): Food? {
return getFromItemStack(stack)?.food
fun hasFlavors(stack: ItemStack): Boolean {
val flavors = getFlavoursFromItemStack(stack)
return !flavors.isNullOrEmpty() && flavors.any { it.value != 0 }
}
fun getFoodComponentFromItemStack(stack: ItemStack): FoodComponent? {
return getFromItemStack(stack)?.food?.toComponent()
}
fun hasFood(stack: ItemStack): Boolean {
@@ -98,7 +108,17 @@ object Seasonings : JsonDataRegistry<Seasoning> {
}
fun getBaitEffectsFromItemStack(stack: ItemStack): List<SpawnBait.Effect> {
return getFromItemStack(stack)?.baitEffects ?: emptyList()
val primaryEffects = SpawnBaitEffects.getEffectsFromItemStack(stack)
return if (primaryEffects.isNotEmpty()) {
primaryEffects
} else {
getFromItemStack(stack)?.baitEffects ?: emptyList()
}
}
fun hasBaitEffects(stack: ItemStack): Boolean {
return SpawnBaitEffects.getEffectsFromItemStack(stack).isNotEmpty() ||
!(getFromItemStack(stack)?.baitEffects.isNullOrEmpty())
}
fun getFromItemStack(stack: ItemStack): Seasoning? {
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.fishing
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
import com.cobblemon.mod.common.api.cooking.Seasonings
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.SpawnBaitRegistrySyncPacket
@@ -96,8 +97,9 @@ object SpawnBaitEffects : JsonDataRegistry<SpawnBait> {
return null
}
// if it has bait effects and is not listed as a seasoning then it is a bait
fun isFishingBait(stack: ItemStack): Boolean {
val holder = stack.itemHolder
return stack.has(CobblemonItemComponents.BAIT_EFFECTS) || effectsMap.values.any { it.item.fits(holder) }
return effectsMap.values.any { it.item.fits(holder) }
}
}
@@ -0,0 +1,56 @@
/*
* 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.moves
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.pokemon.Pokemon
object HiddenPowerUtil {
val hiddenPowerTable = arrayOf(
ElementalTypes.FIGHTING,
ElementalTypes.FLYING,
ElementalTypes.POISON,
ElementalTypes.GROUND,
ElementalTypes.ROCK,
ElementalTypes.BUG,
ElementalTypes.GHOST,
ElementalTypes.STEEL,
ElementalTypes.FIRE,
ElementalTypes.WATER,
ElementalTypes.GRASS,
ElementalTypes.ELECTRIC,
ElementalTypes.PSYCHIC,
ElementalTypes.ICE,
ElementalTypes.DRAGON,
ElementalTypes.DARK
)
fun getHiddenPowerType(pokemon: Pokemon?): ElementalType {
if (pokemon == null) {
return ElementalTypes.NORMAL
}
val ivs = pokemon.ivs
val ivArray = arrayOf(
ivs[Stats.HP],
ivs[Stats.ATTACK],
ivs[Stats.DEFENCE],
ivs[Stats.SPEED],
ivs[Stats.SPECIAL_ATTACK],
ivs[Stats.SPECIAL_DEFENCE]
).map { it ?: return@getHiddenPowerType ElementalTypes.NORMAL }
var tableIndex = 0
ivArray.forEachIndexed { index, it ->
tableIndex += (it % 2) shl index
}
tableIndex = tableIndex * 15 / 63
return hiddenPowerTable[tableIndex.coerceAtMost(hiddenPowerTable.size - 1)]
}
}
@@ -15,7 +15,6 @@ import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.MoveTarget
@@ -100,24 +99,6 @@ open class MoveTemplate(
Moves::getByName,
MoveTemplate::name
) { id -> "No MoveTemplate for ID $id" }
val hiddenPowerTable = arrayOf(
ElementalTypes.FIGHTING,
ElementalTypes.FLYING,
ElementalTypes.POISON,
ElementalTypes.GROUND,
ElementalTypes.ROCK,
ElementalTypes.BUG,
ElementalTypes.GHOST,
ElementalTypes.STEEL,
ElementalTypes.FIRE,
ElementalTypes.WATER,
ElementalTypes.GRASS,
ElementalTypes.ELECTRIC,
ElementalTypes.PSYCHIC,
ElementalTypes.ICE,
ElementalTypes.DRAGON,
ElementalTypes.DARK
)
}
/**
@@ -146,21 +127,7 @@ open class MoveTemplate(
return this.elementalType
}
if (name == "hiddenpower") {
val ivs = pokemon.ivs
val ivArray = arrayOf(
ivs[Stats.HP],
ivs[Stats.ATTACK],
ivs[Stats.DEFENCE],
ivs[Stats.SPEED],
ivs[Stats.SPECIAL_ATTACK],
ivs[Stats.SPECIAL_DEFENCE]
).map { it ?: return@getEffectiveElementalType ElementalTypes.NORMAL }
var tableIndex = 0
ivArray.forEachIndexed { index, it ->
tableIndex += (it % 2) shl index
}
tableIndex = tableIndex * 15 / 63
return hiddenPowerTable[tableIndex.coerceAtMost(hiddenPowerTable.size - 1)]
return HiddenPowerUtil.getHiddenPowerType(pokemon)
}
// TODO: Handle ability suppression: clientactivebattlepokemon needs data about volatiles
// TODO: Handle Liquid Voice: need to know what moves have the sound flag
@@ -43,6 +43,7 @@ import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.mojang.brigadier.StringReader
import com.mojang.serialization.Codec
import com.mojang.serialization.JsonOps
import java.util.UUID
import kotlin.math.min
import kotlin.random.Random
@@ -51,6 +52,7 @@ import net.minecraft.commands.arguments.item.ItemParser
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
import net.minecraft.network.chat.Component
@@ -59,6 +61,7 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import kotlin.jvm.optionals.getOrNull
/**
* A grouping of typical, selectable properties for a Pokémon. This is serializable
@@ -402,6 +405,9 @@ open class PokemonProperties {
ivs.forEach { stat ->
pokemon.setIV(stat.key, stat.value)
}
ivs.hyperTrainedIVs.forEach { stat ->
pokemon.hyperTrainIV(stat.key, stat.value)
}
}
evs?.let { evs ->
evs.forEach { stat ->
@@ -633,8 +639,8 @@ open class PokemonProperties {
nature?.let { nbt.putString(DataKeys.POKEMON_NATURE, it) }
ability?.let { nbt.putString(DataKeys.POKEMON_ABILITY, it) }
status?.let { nbt.putString(DataKeys.POKEMON_STATUS_NAME, it) }
ivs?.let { nbt.put(DataKeys.POKEMON_IVS, it.saveToNBT(CompoundTag())) }
evs?.let { nbt.put(DataKeys.POKEMON_EVS, it.saveToNBT(CompoundTag())) }
ivs?.let { nbt.put(DataKeys.POKEMON_IVS, IVs.CODEC.encodeStart(NbtOps.INSTANCE, it).result().get()) }
evs?.let { nbt.put(DataKeys.POKEMON_EVS, EVs.CODEC.encodeStart(NbtOps.INSTANCE, it).result().get()) }
type?.let { nbt.putString(DataKeys.ELEMENTAL_TYPE, it) }
teraType?.let { nbt.putString(DataKeys.POKEMON_TERA_TYPE, it) }
dmaxLevel?.let { nbt.putInt(DataKeys.POKEMON_DMAX_LEVEL, it) }
@@ -664,8 +670,8 @@ open class PokemonProperties {
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
status = if (tag.contains(DataKeys.POKEMON_STATUS_NAME)) tag.getString(DataKeys.POKEMON_STATUS_NAME) else null
ivs = if (tag.contains(DataKeys.POKEMON_IVS)) IVs().loadFromNBT(tag.getCompound(DataKeys.POKEMON_IVS)) as IVs else null
evs = if (tag.contains(DataKeys.POKEMON_EVS)) EVs().loadFromNBT(tag.getCompound(DataKeys.POKEMON_EVS)) as EVs else null
ivs = if (tag.contains(DataKeys.POKEMON_IVS)) IVs.CODEC.decode(NbtOps.INSTANCE, tag.getCompound(DataKeys.POKEMON_IVS)).result().getOrNull()?.first else null
evs = if (tag.contains(DataKeys.POKEMON_EVS)) EVs.CODEC.decode(NbtOps.INSTANCE, tag.getCompound(DataKeys.POKEMON_EVS)).result().getOrNull()?.first else null
type = if (tag.contains(DataKeys.ELEMENTAL_TYPE)) tag.getString(DataKeys.ELEMENTAL_TYPE) else null
teraType = if (tag.contains(DataKeys.POKEMON_TERA_TYPE)) tag.getString(DataKeys.POKEMON_TERA_TYPE) else null
dmaxLevel = if (tag.contains(DataKeys.POKEMON_DMAX_LEVEL)) tag.getInt(DataKeys.POKEMON_DMAX_LEVEL) else null
@@ -697,8 +703,8 @@ open class PokemonProperties {
nature?.let { json.addProperty(DataKeys.POKEMON_NATURE, it) }
ability?.let { json.addProperty(DataKeys.POKEMON_ABILITY, it) }
status?.let { json.addProperty(DataKeys.POKEMON_STATUS_NAME, it) }
ivs?.let { json.add(DataKeys.POKEMON_IVS, it.saveToJSON(JsonObject())) }
evs?.let { json.add(DataKeys.POKEMON_EVS, it.saveToJSON(JsonObject())) }
ivs?.let { json.add(DataKeys.POKEMON_IVS, IVs.CODEC.encodeStart(JsonOps.INSTANCE, it).result().get()) }
evs?.let { json.add(DataKeys.POKEMON_EVS, EVs.CODEC.encodeStart(JsonOps.INSTANCE, it).result().get()) }
type?.let { json.addProperty(DataKeys.ELEMENTAL_TYPE, it) }
teraType?.let { json.addProperty(DataKeys.POKEMON_TERA_TYPE, it) }
dmaxLevel?.let { json.addProperty(DataKeys.POKEMON_DMAX_LEVEL, it) }
@@ -729,8 +735,8 @@ open class PokemonProperties {
nature = json.get(DataKeys.POKEMON_NATURE)?.asString
ability = json.get(DataKeys.POKEMON_ABILITY)?.asString
status = json.get(DataKeys.POKEMON_STATUS_NAME)?.asString
ivs = json.getAsJsonObject(DataKeys.POKEMON_IVS)?.let { IVs().loadFromJSON(it) } as? IVs
evs = json.getAsJsonObject(DataKeys.POKEMON_EVS)?.let { EVs().loadFromJSON(it) } as? EVs
ivs = json.getAsJsonObject(DataKeys.POKEMON_IVS)?.let { IVs.CODEC.decode(JsonOps.INSTANCE, it).result().getOrNull()?.first }
evs = json.getAsJsonObject(DataKeys.POKEMON_EVS)?.let { EVs.CODEC.decode(JsonOps.INSTANCE, it).result().getOrNull()?.first }
type = json.get(DataKeys.ELEMENTAL_TYPE)?.asString
teraType = json.get(DataKeys.POKEMON_TERA_TYPE)?.asString
dmaxLevel = json.get(DataKeys.POKEMON_DMAX_LEVEL)?.asInt
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
@@ -16,6 +17,7 @@ import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asExpression
import com.google.gson.annotations.SerializedName
import kotlin.math.ceil
/**
* A [SpawnDetail] describing an [NPCEntity] spawn.
@@ -37,6 +39,16 @@ class NPCSpawnDetail : SpawnDetail() {
val minLevel: Expression = "1".asExpression()
val maxLevel: Expression = "100".asExpression()
override fun autoLabel() {
val npcClass = this.npcClass
struct.addFunction("class") { StringValue(npcClass.resourceIdentifier.toString()) }
labels.add(npcClass.resourceIdentifier.toString())
super.autoLabel()
}
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleStartedPostEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedPreEvent
import com.cobblemon.mod.common.api.moves.HiddenPowerUtil
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
@@ -96,7 +97,7 @@ object BattleRegistry {
// Gender
packedTeamBuilder.append("${pk.gender.showdownName}|")
// IVs
val ivsInOrder = Stats.PERMANENT.map { pk.ivs.getOrDefault(it) }.joinToString(separator = ",")
val ivsInOrder = Stats.PERMANENT.map { pk.ivs.getEffectiveBattleIV(it) }.joinToString(separator = ",")
packedTeamBuilder.append("$ivsInOrder|")
// Shiny
packedTeamBuilder.append("${if (pk.shiny) "S" else ""}|")
@@ -111,7 +112,9 @@ object BattleRegistry {
val pokeball = pokemon.effectedPokemon.caughtBall.name.path.replace("_", "")
packedTeamBuilder.append("$pokeball,")
// Hidden Power Type
packedTeamBuilder.append(",")
// This is empty if the pokemon is not Hyper Trained, and the provided type corresponds to base IVs and not battle IVs.
val hiddenPowerType = if (pk.ivs.hyperTrainedIVs.any()) HiddenPowerUtil.getHiddenPowerType(pk).name.replaceFirstChar { it.uppercase() } else ""
packedTeamBuilder.append("$hiddenPowerType,")
// Gigantamax
packedTeamBuilder.append("${if (pk.gmaxFactor) "G" else ""},")
// DynamaxLevel
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.CobblemonMenuType
import com.cobblemon.mod.common.CobblemonRecipeTypes
import com.cobblemon.mod.common.api.cooking.Seasonings
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity
import com.cobblemon.mod.common.client.tooltips.itemTakesSeasoningData
import com.cobblemon.mod.common.item.crafting.CookingPotRecipe
import com.cobblemon.mod.common.item.crafting.CookingPotRecipeBase
import net.minecraft.recipebook.ServerPlaceRecipe
@@ -109,11 +110,15 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
}
override fun handlePlacement(placeAll: Boolean, recipe: RecipeHolder<*>, player: ServerPlayer) {
// Check if the recipe value implements CookingPotRecipeBase
val recipeValue = recipe.value()
if (recipeValue is CookingPotRecipeBase) {
@Suppress("UNCHECKED_CAST")
val castedRecipe = recipe as RecipeHolder<CookingPotRecipeBase>
// Save seasoning contents in case the next recipe absorbs seasoning data
val seasoningSlots = CampfireBlockEntity.SEASONING_SLOTS
val preservedSeasonings = seasoningSlots.map { container.getItem(it).copy() }
this.beginPlacingRecipe()
try {
val serverPlaceRecipe = ServerPlaceRecipe(this)
@@ -121,6 +126,15 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
} finally {
this.finishPlacingRecipe(castedRecipe)
}
// Check if new result item supports seasoning
val result = castedRecipe.value().result
if (itemTakesSeasoningData(result)) {
seasoningSlots.forEachIndexed { index, slot ->
container.setItem(slot, preservedSeasonings[index])
}
}
} else {
throw IllegalArgumentException("Unsupported recipe type: ${recipeValue::class.java.name}")
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.block.campfirepot
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonSounds
import net.minecraft.sounds.SoundSource
import net.minecraft.world.entity.player.Player
@@ -23,8 +24,9 @@ class CookingPotResultSlot(
) : Slot(container, index, x, y) {
override fun onTake(player: Player, stack: ItemStack) {
val menu = player.containerMenu
stack.set(CobblemonItemComponents.CRAFTED, true)
val menu = player.containerMenu
if (menu is CookingPotMenu) {
menu.broadcastChanges()
player.playNotifySound(CobblemonSounds.CAMPFIRE_POT_USE, SoundSource.MASTER, 1.0f, 1.0f)
@@ -170,13 +170,12 @@ object CobblemonClient {
private fun registerTooltipManagers() {
TooltipManager.registerTooltipGenerator(CobblemonTooltipGenerator)
TooltipManager.registerTooltipGenerator(RecipeSeasoningAbsorbtionTooltipGenerator)
TooltipManager.registerTooltipGenerator(FishingBaitTooltipGenerator)
TooltipManager.registerTooltipGenerator(FishingRodTooltipGenerator)
TooltipManager.registerTooltipGenerator(SeasoningTooltipGenerator)
TooltipManager.registerTooltipGenerator(FishingRodTooltipGenerator)
TooltipManager.registerTooltipGenerator(AprijuiceTooltipGenerator)
TooltipManager.registerTooltipGenerator(PokePuffTooltipGenerator)
TooltipManager.registerTooltipGenerator(FoodTooltipGenerator)
TooltipManager.registerTooltipGenerator(MobEffectTooltipGenerator)
}
fun registerFlywheelRenderers() {
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.api.pokemon.feature.SynchronizedSpeciesFeaturePr
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.italicise
import com.cobblemon.mod.common.api.text.onHover
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
@@ -88,6 +90,10 @@ class StatWidget(
private val speedLabel = lang("ui.stats.speed")
}
var effectiveBattleIVs: Map<Stat, Int> = Stats.PERMANENT.associateWith { stat ->
pokemon.ivs.getEffectiveBattleIV(stat)
}
var statTabIndex = tabIndex
val renderableFeatures = SpeciesFeatures
.getFeaturesFor(pokemon.species)
@@ -351,7 +357,6 @@ class StatWidget(
scale = SCALE
)
}
when (statOptions.get(statTabIndex)) {
STATS -> drawStatHexagon(
mapOf(
@@ -366,7 +371,7 @@ class StatWidget(
maximum = 400
)
IV -> drawStatHexagon(
pokemon.ivs.associate { it.key to it.value },
effectiveBattleIVs,
colour = Vector3f(216F/255, 100F/255, 1F),
maximum = 31
)
@@ -494,9 +499,9 @@ class StatWidget(
}
private fun getStatValueAsText(stat: Stat): MutableComponent {
val value = when(statOptions.get(statTabIndex)) {
val value = when (statOptions.get(statTabIndex)) {
STATS -> if (stat == Stats.HP) "${pokemon.currentHealth} / ${pokemon.maxHealth}" else pokemon.getStat(stat).toString()
IV -> pokemon.ivs.getOrDefault(stat).toString()
IV -> if (pokemon.ivs.isHyperTrained(stat)) "${pokemon.ivs[stat]} (${pokemon.ivs.hyperTrainedIVs[stat]})" else pokemon.ivs[stat].toString()
EV -> pokemon.evs.getOrDefault(stat).toString()
else -> "0"
}
@@ -9,17 +9,14 @@
package com.cobblemon.mod.common.client.tooltips
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.text.blue
import com.cobblemon.mod.common.api.text.gray
import com.cobblemon.mod.common.item.AprijuiceItem
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
object AprijuiceTooltipGenerator : TooltipGenerator() {
override fun generateCategoryTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
if (stack.item !is AprijuiceItem) return null
return mutableListOf(seasoningHeader)
}
override fun generateAdditionalTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
if (stack.item !is AprijuiceItem) return null
@@ -30,7 +27,7 @@ object AprijuiceTooltipGenerator : TooltipGenerator() {
val quality = flavourComponent.getQuality()
resultLines.add(lang("cooking.cooking_quality", quality.getLang()))
resultLines.addAll(generateAdditionalFlavorTooltip(flavourComponent.flavours))
SeasoningTooltipGenerator.generateAdditionalTooltip(stack, lines)
return resultLines
}
@@ -28,15 +28,8 @@ import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
object FishingBaitTooltipGenerator : TooltipGenerator() {
private val fishingBaitHeader by lazy { lang("fishing_bait_effect_header").blue() }
private val fishingBaitItemClass by lazy { lang("item_class.fishing_bait").blue() }
private val Genders = mapOf<Gender, Component>(
Gender.MALE to lang("gender.male"),
Gender.FEMALE to lang("gender.female"),
Gender.GENDERLESS to lang("gender.genderless"),
)
override fun generateCategoryTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
if (stack.get(DataComponents.HIDE_ADDITIONAL_TOOLTIP) != null) {
return null
@@ -53,77 +46,7 @@ object FishingBaitTooltipGenerator : TooltipGenerator() {
}
val resultLines = mutableListOf<Component>()
// Determine the FishingBait or combined effects from poke_bait
val rawEffects = mutableListOf<SpawnBait.Effect>().apply {
if (stack.item is PokerodItem) {
addAll(SpawnBaitEffects.getEffectsFromRodItemStack(stack))
} else {
addAll(SpawnBaitEffects.getEffectsFromItemStack(stack))
}
// Check for Seasoning-based Bait effects
if (Seasonings.isSeasoning(stack)) {
val seasoningEffects = Seasonings.getBaitEffectsFromItemStack(stack)
if (seasoningEffects.isNotEmpty()) {
addAll(seasoningEffects)
}
}
}
val baitEffects = SpawnBaitUtils.mergeEffects(rawEffects)
if (baitEffects.isEmpty()) return null
resultLines.add(this.fishingBaitHeader)
val formatter = DecimalFormat("0.##")
baitEffects.forEach { effect ->
val effectType = effect.type.path.toString()
val effectSubcategory = effect.subcategory?.path
val effectChance = effect.chance * 100
var effectValue = when (effectType) {
"bite_time" -> (effect.value * 100).toInt()
else -> effect.value.toInt()
}
val subcategoryString: Component = if (effectSubcategory != null) {
when (effectType) {
"nature", "ev", "iv" -> com.cobblemon.mod.common.api.pokemon.stats.Stats.getStat(
effectSubcategory
).displayName
"gender_chance" -> Genders[Gender.valueOf(effectSubcategory.toUpperCase())]
"typing" -> ElementalTypes.get(effectSubcategory)?.displayName
"egg_group" -> {
val effectSubcategory = effect.subcategory.path
val eggGroup = effectSubcategory?.let { EggGroup.fromIdentifier(it) }
eggGroup?.let {
val langKey = "egg_group.${it.name.lowercase()}"
lang(langKey)
} ?: Component.literal(effectSubcategory ?: "Unknown").gold()
}
else -> Component.empty()
} ?: Component.literal("cursed").obfuscate()
} else Component.literal("cursed").obfuscate()
// Adjust shiny chance effectValue
if (effectType == "shiny_reroll") {
effectValue++
}
resultLines.add(
lang(
"fishing_bait_effects.$effectType.tooltip",
Component.literal(formatter.format(effectChance)).yellow(),
subcategoryString.copy().gold(),
Component.literal(formatter.format(effectValue)).green()
)
)
}
if (SpawnBaitEffects.isFishingBait(stack)) resultLines.add(this.fishingBaitItemClass)
return resultLines
}
@@ -1,36 +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.tooltips
import com.cobblemon.mod.common.api.cooking.Flavour
import com.cobblemon.mod.common.api.text.*
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.Component
val seasoningHeader by lazy { lang("item_class.seasoning").blue() }
private val flavorSubHeader by lazy { lang("seasoning_flavor_header").blue() }
fun generateAdditionalFlavorTooltip(flavours: Map<Flavour, Int>): MutableList<Component> {
val resultLines = mutableListOf<Component>()
resultLines.add(flavorSubHeader)
val combinedFlavorsLine = Component.literal("")
flavours.filter { it.key != Flavour.MILD }.forEach { (flavour, value) ->
var flavourText = lang("seasoning_flavor.${flavour.name.lowercase()}").withStyle(flavour.chatFormatting)
if (combinedFlavorsLine.string.isNotEmpty()) {
combinedFlavorsLine.append(" ")
}
combinedFlavorsLine.append(flavourText).append(" $value")
}
resultLines.add(combinedFlavorsLine)
return resultLines
}
@@ -1,35 +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.tooltips
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.text.blue
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
object FoodTooltipGenerator : TooltipGenerator() {
private val foodHeader by lazy { lang("item_class.food").blue() }
override fun generateCategoryTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
val food = stack.get(CobblemonItemComponents.FOOD) ?: return null
return mutableListOf(foodHeader)
}
override fun generateAdditionalTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
val food = stack.get(CobblemonItemComponents.FOOD) ?: return null
return mutableListOf(
lang("tooltip.food.hunger", Component.literal("${food.hunger}").yellow()),
lang("tooltip.food.saturation", Component.literal("%.2f".format(food.saturation)).green())
)
}
}
@@ -1,89 +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.tooltips
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.text.blue
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.Minecraft
import net.minecraft.network.chat.Component
import net.minecraft.world.effect.MobEffectInstance
import net.minecraft.world.item.ItemStack
import net.minecraft.world.effect.MobEffectUtil
object MobEffectTooltipGenerator : TooltipGenerator() {
private val effectHeader by lazy { lang("item_class.mob_effect").blue() }
override fun generateCategoryTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
val effects = stack.get(CobblemonItemComponents.MOB_EFFECTS)?.mobEffects ?: return null
return if (effects.isNotEmpty()) mutableListOf(effectHeader) else null
}
override fun generateAdditionalTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
val effects = stack.get(CobblemonItemComponents.MOB_EFFECTS)?.mobEffects ?: return null
if (effects.isEmpty()) return null
val tickRate = Minecraft.getInstance().level?.tickRateManager()?.tickrate() ?: 20.0f
val result = mutableListOf<Component>()
// Always add the header FIRST, even for additional tooltip
result.add(effectHeader)
for (instance in effects) {
val name = instance.effect.value().descriptionId
val duration = MobEffectUtil.formatDuration(instance, 1.0f, tickRate) // Already a Component
val amplifierRoman = getRomanNumeral(instance.amplifier + 1)
result.add(
lang(
"tooltip.mob_effect_entry",
Component.translatable(name).yellow(),
Component.literal(amplifierRoman).green(),
duration.string.green()
)
)
}
return result
}
private fun getRomanNumeral(number: Int): String {
if (number <= 0) return number.toString() // no Roman numeral for zero or negatives
val numerals = listOf(
1000 to "M",
900 to "CM",
500 to "D",
400 to "CD",
100 to "C",
90 to "XC",
50 to "L",
40 to "XL",
10 to "X",
9 to "IX",
5 to "V",
4 to "IV",
1 to "I"
)
var remaining = number
val result = StringBuilder()
for ((value, numeral) in numerals) {
while (remaining >= value) {
result.append(numeral)
remaining -= value
}
}
return result.toString()
}
}
@@ -28,11 +28,8 @@ object PokePuffTooltipGenerator : TooltipGenerator() {
override fun generateAdditionalTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
if (stack.item !is PokePuffItem) return null
// val flavourComponent = stack.get(CobblemonItemComponents.FLAVOUR) ?: return null
val resultLines = mutableListOf<Component>()
// resultLines.addAll(generateAdditionalFlavorTooltip(flavourComponent.flavours))
val dominantFlavour = PokePuffUtils.getDominantFlavour(stack)
if (dominantFlavour != null) {
val friendshipLiked = PokePuffUtils.getFriendshipChangeLiked(stack)
@@ -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.client.tooltips
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.text.blue
import com.cobblemon.mod.common.api.text.gray
import com.cobblemon.mod.common.item.AprijuiceItem
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
object RecipeSeasoningAbsorbtionTooltipGenerator : TooltipGenerator() {
override fun generateCategoryTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
// Skip if this is a crafting result preview. Vera wanted to be able to give extra context to things not fully crafted for added guidance in crafting UIs
if (stack.get(CobblemonItemComponents.CRAFTED) == true) {
return mutableListOf()
}
if (itemTakesSeasoningData(stack)) {
val accepted = mutableListOf<Component>()
if (recipeUsesFlavor(stack)) accepted.add(flavorHeader)
if (recipeUsesFood(stack)) accepted.add(foodHeader)
if (recipeUsesMobEffect(stack)) accepted.add(mobEffectHeader)
if (recipeUsesBaitEffect(stack)) accepted.add(baitEffectHeader)
if (accepted.isNotEmpty()) {
return mutableListOf(generateSeasoningAbsorbtionTooltip(accepted))
}
}
return null
}
}
@@ -8,21 +8,97 @@
package com.cobblemon.mod.common.client.tooltips
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.cooking.Seasonings
import com.cobblemon.mod.common.api.text.blue
import com.cobblemon.mod.common.api.text.gray
import com.cobblemon.mod.common.util.lang
import net.minecraft.core.component.DataComponents
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
object SeasoningTooltipGenerator : TooltipGenerator() {
override fun generateCategoryTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
if (!Seasonings.isSeasoning(stack)) return null
return mutableListOf(seasoningHeader)
val isSeasoningIngredient = Seasonings.isSeasoning(stack)
val flavors = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours
val food = stack.get(CobblemonItemComponents.FOOD)
val baitEffects = stack.get(CobblemonItemComponents.BAIT_EFFECTS)?.effects
val mobEffects = stack.get(CobblemonItemComponents.MOB_EFFECTS)?.mobEffects
val result = mutableListOf<Component>()
if (!isSeasoningIngredient && flavors == null && food == null && baitEffects == null && mobEffects == null) return null // exit early if there is nothing to show to save time
// This is where we label something as either an ingredient OR an item with Seasoning data. To help users know if something can be used IN the Pot or came FROM a pot at a glance
if (isSeasoningIngredient) {
// Flavor
if (Seasonings.hasFlavors(stack)) result.add(flavorSeasoningHeader)
// Food
if (Seasonings.hasFood(stack)) result.add(foodSeasoningHeader)
// Mob Effects
if (Seasonings.hasMobEffect(stack)) result.add(mobEffectSeasoningHeader)
// Bait Effects
if (Seasonings.hasBaitEffects(stack)) result.add(baitEffectSeasoningHeader)
} else { // these are output items that have Seasoning Component data on them
// Flavor
if (!flavors.isNullOrEmpty()) result.add(lang("seasoning_flavor_header").blue())
// Food
if (food != null) result.add(lang("seasoning_food_header").blue())
// Mob Effects
if (!mobEffects.isNullOrEmpty()) result.add(lang("seasoning_mob_effect_header").blue())
// Bait Effects
if (!baitEffects.isNullOrEmpty()) result.add(lang("seasoning_bait_effect_header").blue())
}
return if (result.isNotEmpty()) result else null
}
override fun generateAdditionalTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
val flavors = Seasonings.getFlavoursFromItemStack(stack)
if (flavors.all { it.value == 0 }) {
if (stack.get(DataComponents.HIDE_ADDITIONAL_TOOLTIP) != null) {
return null
}
return generateAdditionalFlavorTooltip(flavors)
val isSeasoningIngredient = Seasonings.isSeasoning(stack)
val flavors = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours
val food = stack.get(CobblemonItemComponents.FOOD)
val baitEffects = stack.get(CobblemonItemComponents.BAIT_EFFECTS)?.effects
val mobEffects = stack.get(CobblemonItemComponents.MOB_EFFECTS)?.mobEffects
if (!isSeasoningIngredient && flavors == null && food == null && baitEffects == null && mobEffects == null) return null // exit early if there is nothing to show to save time
val result = mutableListOf<Component>()
// this is where we want to replace the headers with the info and data for each seasoning category we want to show
// Flavor
val flavorData = Seasonings.getFlavoursFromItemStack(stack).takeIf { !it.isNullOrEmpty() } ?: flavors
if (flavorData != null && flavorData.any { it.value != 0 }) {
result.addAll(generateAdditionalFlavorTooltip(flavorData))
}
// Food
if (Seasonings.hasFood(stack) || food != null) {
result.addAll(generateAdditionalFoodTooltip(stack))
}
// Mob Effects
if (Seasonings.hasMobEffect(stack) || mobEffects != null) {
result.addAll(generateAdditionalMobEffectTooltip(stack))
}
// Bait Effects
if (Seasonings.hasBaitEffects(stack) || baitEffects != null) {
result.addAll(generateAdditionalBaitEffectTooltip(stack))
}
return if (result.isNotEmpty()) result else null
}
}
@@ -0,0 +1,301 @@
/*
* 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.tooltips
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonRecipeTypes
import com.cobblemon.mod.common.api.cooking.Flavour
import com.cobblemon.mod.common.api.cooking.Food
import com.cobblemon.mod.common.api.cooking.PokePuffUtils
import com.cobblemon.mod.common.api.cooking.Seasonings
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.fishing.SpawnBaitEffects
import com.cobblemon.mod.common.api.fishing.SpawnBaitUtils
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
import com.cobblemon.mod.common.api.text.*
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.item.components.FoodComponent
import com.cobblemon.mod.common.item.crafting.CookingPotRecipe
import com.cobblemon.mod.common.item.crafting.CookingPotRecipeBase
import com.cobblemon.mod.common.item.interactive.PokerodItem
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.util.lang
import net.minecraft.ChatFormatting
import net.minecraft.client.Minecraft
import net.minecraft.network.chat.Component
import net.minecraft.world.effect.MobEffectInstance
import net.minecraft.world.effect.MobEffectUtil
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.RecipeManager
import net.minecraft.world.level.Level
import java.text.DecimalFormat
val cookingPotRecipeAbsorbHeader by lazy { lang("item_class.cooking_pot_recipe_absorbs").gray() }
val seasoningsHeader by lazy { lang("item_class.seasonings").gray() }
val flavorSeasoningHeader by lazy { lang("item_class.flavor_seasoning").blue() }
val flavorHeader by lazy { lang("seasoning_flavor_header").gray() }
val flavorInfoSubHeader by lazy { lang("seasoning_flavor_info_header").blue() }
val baitEffectSeasoningHeader by lazy { lang("item_class.bait_effect_seasoning").blue() }
val baitEffectHeader by lazy { lang("seasoning_bait_effect_header").gray() }
val baitEffectInfoSubHeader by lazy { lang("seasoning_bait_effect_info_header").blue() }
val foodSeasoningHeader by lazy { lang("item_class.food_seasoning").blue() }
val foodHeader by lazy { lang("seasoning_food_header").gray() }
val foodInfoSubHeader by lazy { lang("seasoning_food_info_header").blue() }
val mobEffectSeasoningHeader by lazy { lang("item_class.mob_effect_seasoning").blue() }
val mobEffectHeader by lazy { lang("seasoning_mob_effect_header").gray() }
val mobEffectInfoSubHeader by lazy { lang("seasoning_mob_effect_info_header").blue() }
private fun recipeUsesProcessor(stack: ItemStack, processorType: String): Boolean {
val level = Minecraft.getInstance().level ?: return false
val recipeManager = level.recipeManager
val allCookingRecipes = recipeManager.getAllRecipesFor(CobblemonRecipeTypes.COOKING_POT_COOKING) +
recipeManager.getAllRecipesFor(CobblemonRecipeTypes.COOKING_POT_SHAPELESS)
return allCookingRecipes
.any { recipe ->
stack.item == recipe.value.result.item &&
recipe.value.seasoningProcessors.any { it.type == processorType }
}
}
fun itemTakesSeasoningData(stack: ItemStack): Boolean {
return recipeUsesFlavor(stack) || recipeUsesFood(stack) || recipeUsesMobEffect(stack) || recipeUsesBaitEffect(stack)
}
fun recipeUsesFlavor(stack: ItemStack): Boolean {
return recipeUsesProcessor(stack, "flavour")
}
fun recipeUsesFood(stack: ItemStack): Boolean {
return recipeUsesProcessor(stack, "food")
}
fun recipeUsesMobEffect(stack: ItemStack): Boolean {
return recipeUsesProcessor(stack, "mob_effects")
}
fun recipeUsesBaitEffect(stack: ItemStack): Boolean {
return recipeUsesProcessor(stack, "spawn_bait")
}
fun generateSeasoningAbsorbtionTooltip(accepted: List<Component>): Component {
if (accepted.isEmpty()) return Component.empty()
if (accepted.size == 1) return lang("item_class.cooking_pot_recipe_absorbs")
.append(" ").append(accepted[0]).append(" ")
.append(seasoningsHeader).gray()
val result = Component.literal("")
result.append(lang("item_class.cooking_pot_recipe_absorbs")).append(" ").gray()
for ((index, component) in accepted.withIndex()) {
when (index) {
accepted.lastIndex -> {
result.append("and ").append(component)
}
accepted.lastIndex - 1 -> {
result.append(component).append(", ")
}
else -> {
result.append(component).append(", ")
}
}
if (index == accepted.lastIndex) {
result.append(" ").append(seasoningsHeader).gray()
}
}
return result
}
fun generateAdditionalFlavorTooltip(flavours: Map<Flavour, Int>): MutableList<Component> {
val resultLines = mutableListOf<Component>()
resultLines.add(flavorInfoSubHeader)
val combinedFlavorsLine = Component.literal("")
flavours.filter { it.key != Flavour.MILD }.forEach { (flavour, value) ->
var flavourText = lang("seasoning_flavor.${flavour.name.lowercase()}").withStyle(flavour.chatFormatting)
if (combinedFlavorsLine.string.isNotEmpty()) {
combinedFlavorsLine.append(" ")
}
combinedFlavorsLine.append(flavourText).append(" $value")
}
resultLines.add(combinedFlavorsLine)
return resultLines
}
fun generateAdditionalMobEffectTooltip(stack: ItemStack): MutableList<Component> {
val effects: List<MobEffectInstance> =
Seasonings.getMobEffectsFromItemStack(stack)
.takeIf { it.isNotEmpty() }
?.map { it.toInstance() }
?: stack.get(CobblemonItemComponents.MOB_EFFECTS)?.mobEffects
?: return mutableListOf()
val tickRate = Minecraft.getInstance().level?.tickRateManager()?.tickrate() ?: 20.0f
val resultLines = mutableListOf<Component>()
resultLines.add(mobEffectInfoSubHeader)
for (instance in effects) {
val effect = instance.effect.value()
val name = Component.translatable(effect.descriptionId)
val color = if (effect.isBeneficial) ChatFormatting.AQUA else ChatFormatting.RED
val duration = MobEffectUtil.formatDuration(instance, 1.0f, tickRate)
val amplifierRoman = getRomanNumeral(instance.amplifier + 1)
resultLines.add(
lang(
"tooltip.mob_effect_entry",
name.copy().withStyle(color),
Component.literal(amplifierRoman).withStyle(color),
duration.string.green()
)
)
}
return resultLines
}
fun generateAdditionalFoodTooltip(stack: ItemStack): MutableList<Component> {
val food = Seasonings.getFoodComponentFromItemStack(stack) ?: stack.get(CobblemonItemComponents.FOOD)
val resultLines = mutableListOf<Component>()
if (food != null) {
resultLines.add(foodInfoSubHeader)
resultLines.add(
lang("tooltip.food.hunger", Component.literal("${food.hunger}").yellow())
)
resultLines.add(
lang("tooltip.food.saturation", Component.literal("%.2f".format(food.saturation)).green())
)
}
return resultLines
}
fun generateAdditionalBaitEffectTooltip(stack: ItemStack): MutableList<Component> {
val resultLines = mutableListOf<Component>()
val rawEffects = mutableListOf<SpawnBait.Effect>().apply {
if (stack.item is PokerodItem) {
addAll(SpawnBaitEffects.getEffectsFromRodItemStack(stack))
} else {
addAll(SpawnBaitEffects.getEffectsFromItemStack(stack))
}
if (Seasonings.isSeasoning(stack)) {
val seasoningEffects = Seasonings.getBaitEffectsFromItemStack(stack)
if (seasoningEffects.isNotEmpty()) {
addAll(seasoningEffects)
}
}
}
val baitEffects = SpawnBaitUtils.mergeEffects(rawEffects)
if (baitEffects.isNotEmpty()) {
resultLines.add(baitEffectInfoSubHeader)
val formatter = DecimalFormat("0.##")
val genders = mapOf(
Gender.MALE to lang("gender.male"),
Gender.FEMALE to lang("gender.female"),
Gender.GENDERLESS to lang("gender.genderless")
)
for (effect in baitEffects) {
val effectType = effect.type.path
val effectSubcategory = effect.subcategory?.path
val effectChance = effect.chance * 100
var effectValue = when (effectType) {
"bite_time" -> (effect.value * 100).toInt()
else -> effect.value.toInt()
}
val subcategoryString: Component = if (effectSubcategory != null) {
when (effectType) {
"nature", "ev", "iv" ->
com.cobblemon.mod.common.api.pokemon.stats.Stats.getStat(effectSubcategory).displayName
"gender_chance" ->
genders[Gender.valueOf(effectSubcategory.uppercase())]
"typing" ->
ElementalTypes.get(effectSubcategory)?.displayName
"egg_group" -> {
val eggGroup = EggGroup.fromIdentifier(effectSubcategory)
eggGroup?.let {
lang("egg_group.${it.name.lowercase()}")
} ?: Component.literal(effectSubcategory).gold()
}
else -> Component.empty()
} ?: Component.literal("cursed").obfuscate()
} else Component.literal("cursed").obfuscate()
if (effectType == "shiny_reroll") {
effectValue++
}
resultLines.add(
lang(
"fishing_bait_effects.$effectType.tooltip",
Component.literal(formatter.format(effectChance)).yellow(),
subcategoryString.copy().gold(),
Component.literal(formatter.format(effectValue)).green()
)
)
}
}
return resultLines
}
private fun getRomanNumeral(number: Int): String {
if (number <= 0) return number.toString()
val numerals = listOf(
1000 to "M",
900 to "CM",
500 to "D",
400 to "CD",
100 to "C",
90 to "XC",
50 to "L",
40 to "XL",
10 to "X",
9 to "IX",
5 to "V",
4 to "IV",
1 to "I"
)
var remaining = number
val result = StringBuilder()
for ((value, numeral) in numerals) {
while (remaining >= value) {
result.append(numeral)
remaining -= value
}
}
return result.toString()
}
@@ -16,6 +16,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder
import com.mojang.datafixers.DSL.TypeReference
import com.mojang.datafixers.DataFixer
import com.mojang.datafixers.DataFixerBuilder
import com.mojang.datafixers.schemas.Schema
import com.mojang.datafixers.util.Pair
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
@@ -26,10 +27,10 @@ import java.util.concurrent.Executors
@Suppress("MemberVisibilityCanBePrivate", "unused")
object CobblemonSchemas {
private val SAME: (Int, Schema) -> Schema = ::Schema
private val RESULT: DataFixerBuilder.Result = this.create()
const val DATA_VERSION = 1
const val DATA_VERSION = 2
/**
* The Cobblemon [DataFixer].
@@ -82,6 +83,8 @@ object CobblemonSchemas {
builder.addFixer(ShoulderStateJsonFix(schema1))
builder.addFixer(NicknameFix(schema1))
builder.addFixer(RaisedPPStagesFix(schema1))
val schema2 = builder.addSchema(2, SAME)
builder.addFixer(HypertrainIVFix(schema2))
}
private class CobblemonDataFixerCodec<R>(private val baseCodec: Codec<R>, private val typeReference: TypeReference) : Codec<R> {
@@ -0,0 +1,27 @@
/*
* 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.datafixer.fix
import com.cobblemon.mod.common.util.DataKeys
import com.mojang.datafixers.schemas.Schema
import com.mojang.serialization.Dynamic
import kotlin.jvm.optionals.getOrNull
class HypertrainIVFix(outputSchema: Schema) : PokemonFix(outputSchema) {
override fun fixPokemonData(dynamic: Dynamic<*>): Dynamic<*> {
var baseDynamic = dynamic
val oldIVs = baseDynamic.get(DataKeys.POKEMON_IVS).get().result().getOrNull() ?: return baseDynamic
baseDynamic = baseDynamic.remove(DataKeys.POKEMON_IVS)
val newIVs = baseDynamic.createMap(mapOf(baseDynamic.createString(DataKeys.POKEMON_IVS_BASE) to oldIVs,
baseDynamic.createString(DataKeys.POKEMON_IVS_HYPERTRAINED) to baseDynamic.createMap(emptyMap())))
baseDynamic = baseDynamic.set(DataKeys.POKEMON_IVS, newIVs)
return baseDynamic
}
}
@@ -483,6 +483,19 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.EXPERIENCE_CANDY_XL)
entries.accept(CobblemonItems.RARE_CANDY)
entries.accept(CobblemonItems.HEALTH_CANDY)
entries.accept(CobblemonItems.MIGHTY_CANDY)
entries.accept(CobblemonItems.TOUGH_CANDY)
entries.accept(CobblemonItems.SMART_CANDY)
entries.accept(CobblemonItems.COURAGE_CANDY)
entries.accept(CobblemonItems.QUICK_CANDY)
entries.accept(CobblemonItems.SICKLY_CANDY)
entries.accept(CobblemonItems.WEAK_CANDY)
entries.accept(CobblemonItems.BRITTLE_CANDY)
entries.accept(CobblemonItems.NUMB_CANDY)
entries.accept(CobblemonItems.COWARD_CANDY)
entries.accept(CobblemonItems.SLOW_CANDY)
entries.accept(CobblemonItems.LONELY_MINT)
entries.accept(CobblemonItems.ADAMANT_MINT)
entries.accept(CobblemonItems.NAUGHTY_MINT)
@@ -0,0 +1,73 @@
/*
* 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.item.interactive
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.item.PokemonSelectingItem
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.lang
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.Mth.clamp
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.level.Level
import kotlin.math.roundToInt
// An item that modifies a Pokémon's IVs according to a given modifier and valid range.
class HyperTrainingItem(val ivIncreaseAmount: Int, val targetStats: Set<Stat>, val validRange: IntRange) : CobblemonItem(Properties()), PokemonSelectingItem {
override val bagItem = null
// Helper to ensure prospective IVs are within the valid range
private fun canChangeIV(stat: Stat, pokemon: Pokemon): Boolean {
val effectiveIV = pokemon.ivs.getEffectiveBattleIV(stat)
return effectiveIV in validRange && effectiveIV + ivIncreaseAmount in 0..IVs.MAX_VALUE
}
override fun canUseOnPokemon(stack: ItemStack, pokemon: Pokemon): Boolean {
// Check if at least one stat's effective IV can be modified
return targetStats.any { stat -> canChangeIV(stat, pokemon) }
}
override fun applyToPokemon(
player: ServerPlayer,
stack: ItemStack,
pokemon: Pokemon
): InteractionResultHolder<ItemStack> {
if(!canUseOnPokemon(stack, pokemon)) {
return InteractionResultHolder.fail(stack)
}
// Modify the effective IVs for the target stats
targetStats.forEach { stat ->
if (canChangeIV(stat, pokemon)) {
val effectiveIV = pokemon.ivs.getEffectiveBattleIV(stat)
pokemon.hyperTrainIV(stat, effectiveIV + ivIncreaseAmount)
}
}
if (!player.isCreative) {
stack.shrink(1)
}
pokemon.entity?.playSound(CobblemonSounds.MEDICINE_PILLS_USE, 1F, 1F)
return InteractionResultHolder.success(stack)
}
override fun use(world: Level, user: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
if (user is ServerPlayer) {
return use(user, user.getItemInHand(hand))
}
return InteractionResultHolder.success(user.getItemInHand(hand))
}
}
@@ -23,7 +23,7 @@ import net.minecraft.network.RegistryFriendlyByteBuf
class EVsUpdatePacket(pokemon: () -> Pokemon?, eVs: EVs) : SingleUpdatePacket<EVs, EVsUpdatePacket>(pokemon, eVs) {
override val id = ID
override fun encodeValue(buffer: RegistryFriendlyByteBuf) {
this.value.saveToBuffer(buffer)
EVs.STREAM_CODEC.encode(buffer, this.value)
}
override fun set(pokemon: Pokemon, value: EVs) {
value.forEach { (stat, value) ->
@@ -32,7 +32,7 @@ class EVsUpdatePacket(pokemon: () -> Pokemon?, eVs: EVs) : SingleUpdatePacket<EV
}
companion object {
val ID = cobblemonResource("ev_update")
fun decode(buffer: RegistryFriendlyByteBuf) = EVsUpdatePacket(decodePokemon(buffer), EVs().apply { loadFromBuffer(buffer) })
fun decode(buffer: RegistryFriendlyByteBuf) = EVsUpdatePacket(decodePokemon(buffer), EVs.STREAM_CODEC.decode(buffer))
}
}
@@ -45,15 +45,19 @@ class EVsUpdatePacket(pokemon: () -> Pokemon?, eVs: EVs) : SingleUpdatePacket<EV
class IVsUpdatePacket(pokemon: () -> Pokemon?, iVs: IVs) : SingleUpdatePacket<IVs, IVsUpdatePacket>(pokemon, iVs) {
override val id = ID
override fun encodeValue(buffer: RegistryFriendlyByteBuf) {
this.value.saveToBuffer(buffer)
IVs.STREAM_CODEC.encode(buffer, this.value)
}
override fun set(pokemon: Pokemon, value: IVs) {
value.forEach { (stat, value) ->
pokemon.ivs[stat] = value
}
pokemon.ivs.hyperTrainedIVs.clear()
value.hyperTrainedIVs.forEach { (stat, value) ->
pokemon.ivs.setHyperTrainedIV(stat, value)
}
}
companion object {
val ID = cobblemonResource("iv_update")
fun decode(buffer: RegistryFriendlyByteBuf) = IVsUpdatePacket(decodePokemon(buffer), IVs().apply { loadFromBuffer(buffer) })
fun decode(buffer: RegistryFriendlyByteBuf) = IVsUpdatePacket(decodePokemon(buffer), IVs.STREAM_CODEC.decode(buffer))
}
}
@@ -15,6 +15,9 @@ import com.cobblemon.mod.common.api.pokemon.stats.EvSource
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import io.netty.buffer.ByteBuf
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.network.codec.StreamCodec
import kotlin.math.min
class EVs : PokemonStats() {
@@ -111,5 +114,8 @@ class EVs : PokemonStats() {
return@comapFlatMap map
}
)
@JvmStatic
val STREAM_CODEC: StreamCodec<ByteBuf, EVs> = ByteBufCodecs.fromCodec(CODEC)
}
}
@@ -10,32 +10,67 @@ package com.cobblemon.mod.common.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.util.DataKeys
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import io.netty.buffer.ByteBuf
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.network.codec.StreamCodec
class IVs : PokemonStats() {
override val acceptableRange = 0..MAX_VALUE
override val defaultValue = 0
// TODO: Hyper training
val hyperTrainedIVs = mutableMapOf<Stat, Int>()
fun isHyperTrained(stat: Stat): Boolean {
return hyperTrainedIVs.containsKey(stat)
}
fun setHyperTrainedIV(stat: Stat, value: Int) {
if (value in acceptableRange) {
if(value == this[stat]) {
//not hypertrained any more if it ends up being the same as natural IV
if (hyperTrainedIVs.remove(stat) == null)
return //this means nothing has changed; no hypertraining did actually happen, e.g. you hypertrain to 31 HP when your normal IVs are already 31, its moot so no update needed
}
else {
hyperTrainedIVs[stat] = value
}
update()
} else {
throw IllegalArgumentException("Value $value is out of acceptable range $acceptableRange")
}
}
// Used to get the value that should be used for stat calculation.
fun getEffectiveBattleIV(stat: Stat): Int {
return (this.hyperTrainedIVs[stat] ?: this[stat]) as Int
}
companion object {
const val MAX_VALUE = 31
@JvmStatic
fun createRandomIVs(minPerfectIVs : Int = 0) : IVs = Cobblemon.statProvider.createEmptyIVs(minPerfectIVs)
fun createRandomIVs(minPerfectIVs: Int = 0): IVs = Cobblemon.statProvider.createEmptyIVs(minPerfectIVs)
@JvmStatic
val CODEC: Codec<IVs> = Codec.unboundedMap(Stat.PERMANENT_ONLY_CODEC, Codec.intRange(0, MAX_VALUE))
.xmap(
{ map ->
val ivs = Cobblemon.statProvider.createEmptyIVs(0)
map.forEach { (stat, value) -> ivs[stat] = value }
return@xmap ivs
},
{ ivs ->
val map = hashMapOf<Stat, Int>()
ivs.forEach { (stat, value) -> map[stat] = value }
return@xmap map
val CODEC: Codec<IVs> = RecordCodecBuilder.create { instance ->
instance.group(
Codec.unboundedMap(Stat.PERMANENT_ONLY_CODEC, Codec.intRange(0, MAX_VALUE)).fieldOf(DataKeys.POKEMON_IVS_BASE).forGetter(IVs::stats),
Codec.unboundedMap(Stat.PERMANENT_ONLY_CODEC, Codec.intRange(0, MAX_VALUE)).fieldOf(DataKeys.POKEMON_IVS_HYPERTRAINED).forGetter(IVs::hyperTrainedIVs)
).apply(instance) { stats, hyperTrained ->
val ivs = IVs()
stats.entries.forEach {
ivs[it.key] = it.value
}
)
hyperTrained.entries.forEach {
ivs.setHyperTrainedIV(it.key, it.value)
}
ivs
}
}
@JvmStatic
val STREAM_CODEC: StreamCodec<ByteBuf, IVs> = ByteBufCodecs.fromCodec(CODEC)
}
}
@@ -216,6 +216,7 @@ open class Pokemon : ShowdownIdentifiable {
field = value
value.changeFunction = oldChangeFunction
}
var evs = EVs.createEmpty().also { it.changeFunction = { onChange(EVsUpdatePacket({ this }, it as EVs)) } }
internal set(value) {
val oldChangeFunction = field.changeFunction
@@ -232,6 +233,14 @@ open class Pokemon : ShowdownIdentifiable {
}
}
fun hyperTrainIV(stat: Stat, value: Int) {
val quotient = clamp(currentHealth / maxHealth.toFloat(), 0F, 1F)
ivs.setHyperTrainedIV(stat, value)
if (stat == Stats.HP) {
updateHP(quotient)
}
}
fun setEV(stat: Stat, value : Int) {
val quotient = clamp(currentHealth / maxHealth.toFloat(), 0F, 1F)
evs[stat] = value
@@ -1263,6 +1272,9 @@ open class Pokemon : ShowdownIdentifiable {
other.ivs.forEach {
this.ivs[it.key] = it.value
}
other.ivs.hyperTrainedIVs.forEach {
(stat, value) -> this.ivs.setHyperTrainedIV(stat, value)
}
}
this.evs.doWithoutEmitting {
other.evs.forEach {
@@ -37,7 +37,7 @@ abstract class PokemonStats : Iterable<Map.Entry<Stat, Int>> {
/** Run whenever anything changes. */
var changeFunction: (PokemonStats) -> Unit = {}
private val stats = mutableMapOf<Stat, Int>()
protected val stats = mutableMapOf<Stat, Int>()
private var emit = true
fun doWithoutEmitting(action: () -> Unit) {
@@ -67,65 +67,5 @@ abstract class PokemonStats : Iterable<Map.Entry<Stat, Int>> {
protected open fun canSet(stat: Stat, value: Int) = value in acceptableRange
fun saveToNBT(nbt: CompoundTag): CompoundTag {
this.stats.forEach { (stat, value) ->
// don't waste space if default
if (value != this.defaultValue) {
nbt.putShort(this.cleanStatIdentifier(stat.identifier), value.toShort())
}
}
return nbt
}
fun loadFromNBT(nbt: CompoundTag): PokemonStats {
stats.clear()
Stats.PERMANENT.forEach { stat ->
val identifier = this.cleanStatIdentifier(stat.identifier)
this[stat] = nbt.getShort(identifier).toInt().coerceIn(this.acceptableRange)
}
return this
}
fun saveToJSON(json: JsonObject): JsonObject {
this.stats.forEach { (stat, value) ->
// don't waste space if default
if (value != this.defaultValue) {
json.addProperty(this.cleanStatIdentifier(stat.identifier), value)
}
}
return json
}
fun loadFromJSON(json: JsonObject): PokemonStats {
stats.clear()
Stats.PERMANENT.forEach { stat ->
val identifier = this.cleanStatIdentifier(stat.identifier)
json.get(identifier)?.asInt?.coerceIn(this.acceptableRange)?.let { this.stats[stat] = it }
}
return this
}
fun saveToBuffer(buffer: RegistryFriendlyByteBuf) {
buffer.writeSizedInt(IntSize.U_BYTE, stats.size)
for ((stat, value) in stats) {
Cobblemon.statProvider.encode(buffer, stat)
buffer.writeSizedInt(IntSize.U_SHORT, value)
}
}
fun loadFromBuffer(buffer: RegistryFriendlyByteBuf) {
stats.clear()
repeat(times = buffer.readUnsignedByte().toInt()) {
val stat = Cobblemon.statProvider.decode(buffer)
val value = buffer.readUnsignedShort()
stats[stat] = value
}
}
fun getOrDefault(stat: Stat) = this[stat] ?: this.defaultValue
// util to prevent unnecessary long identifiers, usually vanilla defaults to Minecraft but in our context defaulting to cobblemon makes more sense
private fun cleanStatIdentifier(identifier: ResourceLocation): String = identifier.toString().substringAfter("${Cobblemon.MODID}:")
}
@@ -86,7 +86,7 @@ object CobblemonStatProvider : StatProvider {
}
override fun getStatForPokemon(pokemon: Pokemon, stat: Stat): Int {
val iv = pokemon.ivs.getOrDefault(stat)
val iv = pokemon.ivs.getEffectiveBattleIV(stat)
val base = pokemon.form.baseStats[stat]!!
val ev = pokemon.evs.getOrDefault(stat)
val level = pokemon.level
@@ -21,6 +21,8 @@ object DataKeys {
const val POKEMON_FULLNESS = "Fullness"
const val POKEMON_IVS = "IVs"
const val POKEMON_IVS_BASE = "Base"
const val POKEMON_IVS_HYPERTRAINED = "HyperTrained"
const val POKEMON_EVS = "EVs"
const val POKEMON_HEALTH = "Health"
const val POKEMON_SCALE_MODIFIER = "ScaleModifier"
@@ -61,8 +61,11 @@ internal class ClientPokemonP1(
other.setFriendship(this.friendship)
// Applied before current health for calcs to take place
other.ivs.doWithoutEmitting {
this.ivs.forEach {
other.ivs[it.key] = it.value
this.ivs.forEach { (stat, value) ->
other.ivs[stat] = value
if(this.ivs.isHyperTrained(stat)) {
other.ivs.setHyperTrainedIV(stat, this.ivs.hyperTrainedIVs[stat]!!)
}
}
}
other.evs.doWithoutEmitting {
@@ -54,8 +54,11 @@ internal class PokemonP1(
other.setFriendship(this.friendship)
// Applied before current health for calcs to take place
other.ivs.doWithoutEmitting {
this.ivs.forEach {
other.ivs[it.key] = it.value
this.ivs.forEach { (stat, value) ->
other.ivs[stat] = value
if(this.ivs.isHyperTrained(stat)) {
other.ivs.setHyperTrainedIV(stat, this.ivs.hyperTrainedIVs[stat]!!)
}
}
}
other.evs.doWithoutEmitting {
@@ -789,10 +789,28 @@
"trim_pattern.cobblemon.automaton": "Automaton Armor Trim",
"cobblemon.tooltip.see_more_info": "Hold shift to see info",
"cobblemon.item_class.fishing_bait": "Bait Effects",
"cobblemon.item_class.seasoning": "Seasoning",
"cobblemon.item_class.mob_effect": "Mob Effects",
"cobblemon.item_class.food": "Food",
"cobblemon.item_class.fishing_bait": "Fishing Bait",
"cobblemon.item_class.cooking_pot_recipe_absorbs": "Absorbs",
"cobblemon.item_class.seasonings": "Seasonings",
"cobblemon.item_class.flavor_seasoning": "Flavor Seasoning",
"cobblemon.item_class.bait_effect_seasoning": "Bait Seasoning",
"cobblemon.item_class.mob_effect_seasoning": "Effect Seasoning",
"cobblemon.item_class.food_seasoning": "Food Seasoning",
"cobblemon.seasoning_bait_effect_header": "Bait",
"cobblemon.seasoning_mob_effect_header": "Effect",
"cobblemon.seasoning_food_header": "Food",
"cobblemon.seasoning_flavor_header": "Flavor",
"cobblemon.seasoning_bait_effect_info_header": "Bait Info:",
"cobblemon.seasoning_mob_effect_info_header": "Effect Info:",
"cobblemon.seasoning_food_info_header": "Food Info:",
"cobblemon.seasoning_flavor_info_header": "Flavor Info:",
"cobblemon.seasoning_flavor.spicy": "Spicy",
"cobblemon.seasoning_flavor.dry": "Dry",
"cobblemon.seasoning_flavor.sweet": "Sweet",
"cobblemon.seasoning_flavor.bitter": "Bitter",
"cobblemon.seasoning_flavor.sour": "Sour",
"cobblemon.cooking.cooking_quality": "Quality: %1$s",
"cobblemon.cooking.cooking_quality.low": "Low",
@@ -995,7 +1013,7 @@
"cobblemon.fishing.no_bite": "Not even a nibble.",
"cobblemon.fishing_bait_effect_header": "Bait Effects:",
"cobblemon.fishing_bait_effect_header": "Bait Info:",
"cobblemon.fishing_bait_effects.nature.tooltip": "%1$s%% - Attract %2$s Nature",
"cobblemon.fishing_bait_effects.iv.tooltip": "%1$s%% - %2$s IV +%3$s",
"cobblemon.fishing_bait_effects.ev.tooltip": "%1$s%% - Attract %2$s EV Yields",
@@ -1027,13 +1045,6 @@
"cobblemon.egg_group.dragon": "Dragon",
"cobblemon.egg_group.undiscovered": "Undiscovered",
"cobblemon.seasoning_flavor_header": "Seasoning Flavor:",
"cobblemon.seasoning_flavor.spicy": "Spicy",
"cobblemon.seasoning_flavor.dry": "Dry",
"cobblemon.seasoning_flavor.sweet": "Sweet",
"cobblemon.seasoning_flavor.bitter": "Bitter",
"cobblemon.seasoning_flavor.sour": "Sour",
"item.cobblemon.ponigiri": "Ponigiri",
"item.cobblemon.jelly_doughnut": "Jelly Donut",
"item.cobblemon.sinister_tea": "Sinister Tea",
@@ -1055,6 +1066,30 @@
"tooltip.cobblemon.poke_puff.friendship_increase": "+%1$s Friendship for %2$s natures",
"tooltip.cobblemon.poke_puff.friendship_decrease": "-%1$s Friendship for other natures",
"cobblemon.tooltip.poke_puff.friendship_effects": "Friendship Effects",
"item.cobblemon.health_candy": "Health Candy",
"item.cobblemon.health_candy.tooltip_1": "Raises the Pokémon's effective HP IV stat by 1",
"item.cobblemon.mighty_candy": "Mighty Candy",
"item.cobblemon.mighty_candy.tooltip_1": "Raises the Pokémon's effective Attack IV stat by 1",
"item.cobblemon.tough_candy": "Tough Candy",
"item.cobblemon.tough_candy.tooltip_1": "Raises the Pokémon's effective Defense IV stat by 1",
"item.cobblemon.smart_candy": "Smart Candy",
"item.cobblemon.smart_candy.tooltip_1": "Raises the Pokémon's effective Special Attack IV stat by 1",
"item.cobblemon.courage_candy": "Courage Candy",
"item.cobblemon.courage_candy.tooltip_1": "Raises the Pokémon's effective Special Defense IV stat by 1",
"item.cobblemon.quick_candy": "Quick Candy",
"item.cobblemon.quick_candy.tooltip_1": "Raises the Pokémon's effective Speed IV stat by 1",
"item.cobblemon.sickly_candy": "Sickly Candy",
"item.cobblemon.sickly_candy.tooltip_1": "Lowers the Pokémon's effective HP IV stat by 1",
"item.cobblemon.weak_candy": "Weak Candy",
"item.cobblemon.weak_candy.tooltip_1": "Lowers the Pokémon's effective Attack IV stat by 1",
"item.cobblemon.brittle_candy": "Brittle Candy",
"item.cobblemon.brittle_candy.tooltip_1": "Lowers the Pokémon's effective Defense IV stat by 1",
"item.cobblemon.numb_candy": "Numb Candy",
"item.cobblemon.numb_candy.tooltip_1": "Lowers the Pokémon's effective Special Attack IV stat by 1",
"item.cobblemon.coward_candy": "Coward Candy",
"item.cobblemon.coward_candy.tooltip_1": "Lowers the Pokémon's effective Special Defense IV stat by 1",
"item.cobblemon.slow_candy": "Slow Candy",
"item.cobblemon.slow_candy.tooltip_1": "Lowers the Pokémon's effective Speed IV stat by 1",
"entity.minecraft.villager.nurse": "Nurse",
"entity.minecraft.villager.cobblemon.nurse": "Nurse",
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/iv_candy/brittle_candy"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/iv_candy/courage_candy"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/iv_candy/coward_candy"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/iv_candy/health_candy"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/iv_candy/mighty_candy"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/iv_candy/numb_candy"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/iv_candy/quick_candy"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/iv_candy/sickly_candy"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/iv_candy/slow_candy"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/iv_candy/smart_candy"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/iv_candy/tough_candy"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "cobblemon:item/iv_candy/weak_candy"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

@@ -1,7 +1,7 @@
{
"type": "cobblemon:cooking_pot_shapeless",
"group": "aprijuice",
"category": "baits",
"category": "complex_dishes",
"ingredients": [
{
"item": "cobblemon:green_apricorn"
@@ -25,7 +25,6 @@
},
"seasoningProcessors": [
"spawn_bait",
"flavour",
"food_colour"
]
}