Compare commits

...
4 Commits
7 changed files with 96 additions and 36 deletions
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.block.MintBlock
import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.client.pokedex.PokedexType
import com.cobblemon.mod.common.block.campfirepot.CampfirePotColor
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.item.*
import com.cobblemon.mod.common.item.armor.CobblemonArmorTrims
@@ -42,6 +43,7 @@ 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.Pokemon
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.Registry
@@ -50,6 +52,7 @@ import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.effect.MobEffectInstance
import net.minecraft.world.effect.MobEffects
import net.minecraft.world.entity.LivingEntity
@@ -66,6 +69,7 @@ import net.minecraft.world.item.SignItem
import net.minecraft.world.item.SmithingTemplateItem
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import kotlin.math.ceil
@Suppress("unused", "SameParameterValue")
object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<Item>>, Item>() {
@@ -614,7 +618,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
// Medicine
@JvmField
val RARE_CANDY = candyItem("rare_candy", Rarity.RARE) { _, pokemon -> pokemon.getExperienceToNextLevel() }
val RARE_CANDY = candyItem("rare_candy", Rarity.RARE) { player, pokemon ->
if (pokemon != null) return@candyItem pokemon.getExperienceToNextLevel()
return@candyItem -1
}
@JvmField
val EXPERIENCE_CANDY_XS = candyItem("exp_candy_xs") { _, _ -> CandyItem.DEFAULT_XS_CANDY_YIELD }
@JvmField
@@ -1476,7 +1484,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
return item
}
private fun candyItem(name: String, rarity: Rarity = Rarity.COMMON, calculator: CandyItem.Calculator): CandyItem = this.create(name, CandyItem(rarity, calculator))
private fun candyItem(name: String, rarity: Rarity = Rarity.COMMON, calculator: (ServerPlayer, Pokemon?) -> Int): CandyItem = this.create(name, CandyItem(rarity, calculator))
private fun pokerodItem(pokeRodId: ResourceLocation, rarity: Rarity = Rarity.COMMON): PokerodItem {
val settings = Item.Properties().stacksTo(1).durability(256).rarity(rarity)
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import net.minecraft.commands.CommandSourceStack
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.item.ItemStack
/**
@@ -25,8 +26,13 @@ interface ExperienceSource {
fun isInteraction() = this is CandyExperienceSource
fun isCommand() = this is CommandExperienceSource
fun isSidemod() = this is SidemodExperienceSource
fun isMob() = this is MobExperienceSource
}
open class MobExperienceSource(
mob: LivingEntity,
) : ExperienceSource
open class SidemodExperienceSource(
val sidemodId: String
) : ExperienceSource
@@ -344,6 +344,9 @@ class CobblemonConfig {
@CobblemonConfigField(Category.Spawning, lang = "despawner_max_age_ticks", SERVER)
var despawnerMaxAgeTicks = 3600
@CobblemonConfigField(Category.World, lang = "mob_experience_conversion_rate", SERVER)
var mobExperienceConversionRate = 50.0
fun clone(): CobblemonConfig {
val newConfig = CobblemonConfig()
CobblemonConfig::class.memberProperties.forEach { property ->
@@ -38,6 +38,7 @@ import com.cobblemon.mod.common.api.net.serializers.PoseTypeDataSerializer
import com.cobblemon.mod.common.api.net.serializers.RideBoostsDataSerializer
import com.cobblemon.mod.common.api.net.serializers.StringSetDataSerializer
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.pokemon.experience.MobExperienceSource
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
@@ -1259,7 +1260,18 @@ open class PokemonEntity(
override fun doHurtTarget(target: Entity): Boolean {
if (beamMode != 0) return false
return super.doHurtTarget(target)
val result = super.doHurtTarget(target)
if (result && !target.isAlive && target is LivingEntity && !level().isClientSide) {
val experience = target.getExperienceReward(level() as ServerLevel, this)
val source = MobExperienceSource(mob = target)
if (this.owner is ServerPlayer) {
pokemon.addExperienceWithPlayer(this.owner as ServerPlayer, source, (experience * Cobblemon.config.mobExperienceConversionRate).toInt())
}
else {
pokemon.addExperience(source, (experience * Cobblemon.config.mobExperienceConversionRate).toInt())
}
}
return result
}
override fun hurt(source: DamageSource, amount: Float): Boolean {
@@ -8,17 +8,24 @@
package com.cobblemon.mod.common.item.interactive
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.interaction.ExperienceCandyUseEvent
import com.cobblemon.mod.common.api.item.PokemonSelectingItem
import com.cobblemon.mod.common.api.pokemon.experience.CandyExperienceSource
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.entity.ExperienceOrb
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.food.FoodProperties
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Rarity
import net.minecraft.world.level.Level
@@ -34,22 +41,59 @@ import net.minecraft.world.level.Level
*/
class CandyItem(
val item_rarity: Rarity,
val calculator: Calculator
val expYield: (ServerPlayer, Pokemon?) -> Int
) : CobblemonItem(Properties().apply {
rarity(item_rarity)
}), PokemonSelectingItem {
food(FoodProperties.Builder()
.alwaysEdible()
.nutrition(0)
.saturationModifier(0.0f)
.build())
}) {
override val bagItem = null
override fun finishUsingItem(stack: ItemStack, level: Level, livingEntity: LivingEntity): ItemStack? {
if (livingEntity !is Player) return super.finishUsingItem(stack, level, livingEntity)
override fun use(world: Level, user: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
if (user is ServerPlayer) {
return use(user, user.getItemInHand(hand))
/* Didn't find a good way to determine the XP needed until the beginning of the next level.
* This works but is a little jank. Feel free to investigate a better solution.
*/
if (level.isClientSide && this == CobblemonItems.RARE_CANDY) {
val volume = if (livingEntity.experienceLevel > 30) 1.0f else livingEntity.experienceLevel.toFloat() / 30.0f
livingEntity.playSound(SoundEvents.PLAYER_LEVELUP, volume * 0.75f, 1.0f)
}
return InteractionResultHolder.success(user.getItemInHand(hand))
if (livingEntity !is ServerPlayer) return super.finishUsingItem(stack, level, livingEntity)
if (this == CobblemonItems.RARE_CANDY) {
livingEntity.setExperienceLevels(livingEntity.experienceLevel + 1)
livingEntity.setExperiencePoints(0)
}
else {
val expYield = expYield(livingEntity, null)
val playerExperience = (expYield / Cobblemon.config.mobExperienceConversionRate).toInt().coerceAtLeast(1)
ExperienceOrb.award(livingEntity.serverLevel(), livingEntity.position(), playerExperience)
}
return super.finishUsingItem(stack, level, livingEntity)
}
override fun applyToPokemon(player: ServerPlayer, stack: ItemStack, pokemon: Pokemon): InteractionResultHolder<ItemStack>? {
val experience = this.calculator.calculate(player, pokemon)
override fun interactLivingEntity(
stack: ItemStack,
player: Player,
interactionTarget: LivingEntity,
usedHand: InteractionHand
): InteractionResult {
if (player.level().isClientSide) return InteractionResult.FAIL
if (player.isCrouching) return InteractionResult.FAIL
if (interactionTarget !is PokemonEntity) return InteractionResult.PASS
if (interactionTarget.pokemon.getOwnerPlayer() != player) return InteractionResult.PASS
return applyToPokemon(player as ServerPlayer, stack, interactionTarget.pokemon)?.result ?: InteractionResult.FAIL
}
fun applyToPokemon(player: ServerPlayer, stack: ItemStack, pokemon: Pokemon): InteractionResultHolder<ItemStack>? {
if (!pokemon.canLevelUpFurther()) {
return InteractionResultHolder.fail(stack)
}
val experience = expYield(player, pokemon)
CobblemonEvents.EXPERIENCE_CANDY_USE_PRE.postThen(
event = ExperienceCandyUseEvent.Pre(player, pokemon, this, experience, experience),
ifSucceeded = { preEvent ->
@@ -74,29 +118,6 @@ class CandyItem(
return InteractionResultHolder.fail(stack)
}
override fun canUseOnPokemon(stack: ItemStack, pokemon: Pokemon): Boolean {
return pokemon.isPlayerOwned()
}
/**
* Functional interface responsible for resolving the experience a candy will yield.
*
* @author Licious
* @since March 5th, 2022
*/
fun interface Calculator {
/**
* Resolves the experience the [CandyItem] will give.
*
* @param player The [ServerPlayer] using the candy.
* @param pokemon The [Pokemon] receiving experience.
* @return The experience that will be received
*/
fun calculate(player: ServerPlayer, pokemon: Pokemon): Int
}
companion object {
const val DEFAULT_XS_CANDY_YIELD = 100
const val DEFAULT_S_CANDY_YIELD = 800
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.CobblemonNetwork.sendPacketToPlayers
import com.cobblemon.mod.common.CobblemonSounds
@@ -148,6 +149,7 @@ import net.minecraft.util.Mth.ceil
import net.minecraft.util.Mth.clamp
import net.minecraft.util.StringRepresentable
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.ExperienceOrb
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.MobSpawnType
import net.minecraft.world.entity.player.Player
@@ -2016,6 +2018,12 @@ open class Pokemon : ShowdownIdentifiable {
}
)
val player = getOwnerPlayer()
if (player != null && player.inventory.getArmor(3).item == CobblemonItems.EXP_SHARE) {
val playerExperience = (appliedXP / Cobblemon.config.mobExperienceConversionRate * Cobblemon.config.experienceShareMultiplier).toInt().coerceAtLeast(1)
ExperienceOrb.award(player.serverLevel(), player.position(), playerExperience)
}
experience += appliedXP
// Bound is needed here as we can still be allowed to level up but go over the current cap
var newLevel = experienceGroup.getLevel(experience).coerceAtMost(Cobblemon.config.maxPokemonLevel)
@@ -2621,6 +2621,8 @@
"cobblemon.config.ui.infinite_ride_stamina.tooltip": "Whether rideable Pokémon have infinite stamina.",
"cobblemon.config.ui.remember_riding_camera": "Remember Riding Camera",
"cobblemon.config.ui.remember_riding_camera.tooltip": "Whether the camera mode will automatically switch to the last used camera mode when mounting a rideable Pokémon.",
"cobblemon.config.ui.mob_experience_conversion_rate": "Mob Experience Conversion Rate",
"cobblemon.config.ui.mob_experience_conversion_rate.tooltip": "The conversion rate for mob experience to pokemon experience when a Pokemon kills a mob.",
"cobblemon.config.ui.allow_starter_on_join": "Allow Starter on Join",
"cobblemon.config.ui.prompt_starter_once_only": "Prompt Starter Once Only",