Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f5799e5df | ||
|
|
54ea4903ba | ||
|
|
28ad0cce83 | ||
|
|
7fe1929609 | ||
|
|
e03082a75e | ||
|
|
8d0c27f0e4 | ||
|
|
91fc2dc896 | ||
|
|
ab1ca295a3 |
No files matched your search
@@ -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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 524 B |
|
After Width: | Height: | Size: 529 B |
|
After Width: | Height: | Size: 524 B |
|
After Width: | Height: | Size: 529 B |
|
After Width: | Height: | Size: 529 B |
|
After Width: | Height: | Size: 524 B |
|
After Width: | Height: | Size: 529 B |
|
After Width: | Height: | Size: 524 B |
|
After Width: | Height: | Size: 524 B |
|
After Width: | Height: | Size: 529 B |
|
After Width: | Height: | Size: 529 B |
|
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"
|
||||
]
|
||||
}
|
||||