forked from mirrors/cobblemon
Merge branch 'feat/capture-qol-feature-parity' into 'main'
Critical capture feature parity See merge request cable-mc/cobblemon!1516
This commit is contained in:
@@ -32,6 +32,8 @@
|
||||
- Renamed Cobblemon's creative tabs to start with "Cobblemon: " to distinguish Cobblemon's tabs from tabs for other mods.
|
||||
- Various items now have a rarity value.
|
||||
|
||||
### Changes
|
||||
- When using the `cobblemon` or `generation_9` capture calculators a critical capture with a single shake will always play for successful captures when you've already registered the Pokémon as caught in your Pokédex.
|
||||
### 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)
|
||||
- The `requiredContext` for an item interaction evolution
|
||||
|
||||
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.events.pokemon
|
||||
import com.cobblemon.mod.common.api.events.Cancelable
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.EvSource
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stat
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
|
||||
/**
|
||||
* Fired during EV mutation.
|
||||
@@ -25,6 +26,11 @@ interface EvGainedEvent {
|
||||
*/
|
||||
val stat: Stat
|
||||
|
||||
/**
|
||||
* @see EvSource.pokemon
|
||||
*/
|
||||
val pokemon: Pokemon get() = this.source.pokemon
|
||||
|
||||
/**
|
||||
* The [EvSource] that fired this event.
|
||||
*/
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.pokeball.catching.calculators
|
||||
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.CaptureContext
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.pokedex
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* An influencer on capture results based on Pokedex data.
|
||||
*/
|
||||
interface PokedexStatusCaptureInfluencer {
|
||||
|
||||
/**
|
||||
* Attempts to influence the [context] of a capture.
|
||||
*
|
||||
* @param thrower The [LivingEntity] that threw the Pokeball
|
||||
* @param target The [PokemonEntity] attempting to capture.
|
||||
* @param context The calculated [CaptureContext].
|
||||
* @return The final [CaptureContext].
|
||||
*/
|
||||
fun influence(thrower: LivingEntity, target: PokemonEntity, context: CaptureContext): CaptureContext {
|
||||
if (!context.isSuccessfulCapture) {
|
||||
return context
|
||||
}
|
||||
val player = thrower as? ServerPlayer ?: return context
|
||||
val pokedex = player.pokedex()
|
||||
val speciesId = target.pokemon.species.resourceIdentifier
|
||||
val formName = target.pokemon.form.name
|
||||
val hasCaughtStatus = pokedex.getOrCreateSpeciesRecord(speciesId)
|
||||
.getOrCreateFormRecord(formName)
|
||||
.knowledge == PokedexEntryProgress.CAUGHT
|
||||
// In modern games when the catch is a success and the Pokémon has already been registered to the Pokédex
|
||||
// the catch becomes a critical capture and only requires one shake
|
||||
return if (hasCaughtStatus) {
|
||||
CaptureContext(
|
||||
numberOfShakes = 1,
|
||||
isSuccessfulCapture = true,
|
||||
isCriticalCapture = true
|
||||
)
|
||||
} else context
|
||||
}
|
||||
|
||||
}
|
||||
+13
-8
@@ -21,8 +21,9 @@ import kotlin.random.Random
|
||||
abstract class PokeBallPosableState : PosableState(), Schedulable {
|
||||
abstract val stateEmitter: SettableObservable<EmptyPokeBallEntity.CaptureState>
|
||||
abstract val shakeEmitter: Observable<Unit>
|
||||
private val group: String
|
||||
protected open val group: String
|
||||
get() = if (this.currentModel is AncientPokeBallModel) "ancient_poke_ball" else "poke_ball"
|
||||
protected open var lastKnownState: EmptyPokeBallEntity.CaptureState? = null
|
||||
|
||||
open fun initSubscriptions() {
|
||||
stateEmitter.subscribe { state ->
|
||||
@@ -44,9 +45,17 @@ abstract class PokeBallPosableState : PosableState(), Schedulable {
|
||||
}
|
||||
|
||||
EmptyPokeBallEntity.CaptureState.FALL -> {}
|
||||
EmptyPokeBallEntity.CaptureState.SHAKE -> {
|
||||
EmptyPokeBallEntity.CaptureState.CRITICAL -> {
|
||||
doLater {
|
||||
setActiveAnimations(currentModel!!.bedrockStateful(group, "bounce"))
|
||||
setActiveAnimations(currentModel!!.bedrockStateful(group, "critical"))
|
||||
}
|
||||
}
|
||||
EmptyPokeBallEntity.CaptureState.SHAKE -> {
|
||||
if (this.lastKnownState != EmptyPokeBallEntity.CaptureState.CRITICAL) {
|
||||
// Skip the bounce on a critical capture, the animation replaces this step
|
||||
doLater {
|
||||
setActiveAnimations(currentModel!!.bedrockStateful(group, "bounce"))
|
||||
}
|
||||
}
|
||||
shakeEmitter
|
||||
.pipe(Observable.emitWhile { stateEmitter.get() == EmptyPokeBallEntity.CaptureState.SHAKE })
|
||||
@@ -60,11 +69,6 @@ abstract class PokeBallPosableState : PosableState(), Schedulable {
|
||||
setActiveAnimations(currentModel!!.bedrockStateful(group, "capture"))
|
||||
}
|
||||
}
|
||||
EmptyPokeBallEntity.CaptureState.CAPTURED_CRITICAL -> {
|
||||
doLater {
|
||||
setActiveAnimations(currentModel!!.bedrockStateful(group, "critical"))
|
||||
}
|
||||
}
|
||||
EmptyPokeBallEntity.CaptureState.BROKEN_FREE -> {
|
||||
doLater {
|
||||
setActiveAnimations(currentModel!!.bedrockStateful(group, "break"))
|
||||
@@ -72,6 +76,7 @@ abstract class PokeBallPosableState : PosableState(), Schedulable {
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
this.lastKnownState = state
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
-15
@@ -72,16 +72,15 @@ import net.minecraft.world.phys.EntityHitResult
|
||||
import net.minecraft.world.phys.HitResult
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.compareTo
|
||||
|
||||
class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragModifier, Schedulable {
|
||||
enum class CaptureState {
|
||||
NOT,
|
||||
HIT,
|
||||
FALL,
|
||||
CRITICAL,
|
||||
SHAKE,
|
||||
CAPTURED,
|
||||
CAPTURED_CRITICAL,
|
||||
BROKEN_FREE
|
||||
}
|
||||
|
||||
@@ -138,14 +137,13 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
delegate.onSyncedDataUpdated(data)
|
||||
}
|
||||
if (data == CAPTURE_STATE) {
|
||||
val newState = entityData.get(CAPTURE_STATE)
|
||||
val newState = entityData[CAPTURE_STATE]
|
||||
when (CaptureState.entries[newState.toInt()]) {
|
||||
CaptureState.NOT -> setNoGravity(false)
|
||||
CaptureState.HIT -> {}
|
||||
CaptureState.FALL -> setNoGravity(false)
|
||||
CaptureState.SHAKE -> setNoGravity(true)
|
||||
CaptureState.CAPTURED, CaptureState.CAPTURED_CRITICAL -> {}
|
||||
CaptureState.BROKEN_FREE -> {}
|
||||
CaptureState.NOT, CaptureState.FALL -> isNoGravity = false
|
||||
CaptureState.CRITICAL, CaptureState.SHAKE -> isNoGravity = true
|
||||
CaptureState.HIT, CaptureState.CAPTURED, CaptureState.BROKEN_FREE -> {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
}
|
||||
dataTrackerEmitter.emit(data)
|
||||
@@ -339,14 +337,14 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
|
||||
if (rollsRemaining <= 0) {
|
||||
if (captureResult.isSuccessfulCapture) {
|
||||
captureState = if (captureResult.isCriticalCapture) CaptureState.CAPTURED_CRITICAL else CaptureState.CAPTURED
|
||||
captureState = CaptureState.CAPTURED
|
||||
// Do a capture
|
||||
val pokemon = capturingPokemon ?: return
|
||||
val player = this.owner as? ServerPlayer ?: return
|
||||
val captureTime = if (pokeBall.ancient == true) 1.8F else 1F
|
||||
|
||||
after(seconds = captureTime) {
|
||||
// Dupes occurred by double-adding Pokémon, this hopefully prevents it triple-condom style
|
||||
// wtf did I just read above me
|
||||
if (pokemon.pokemon.isWild() && pokemon.isAlive && !captureFuture.isDone) {
|
||||
pokemon.discard()
|
||||
discard()
|
||||
@@ -406,7 +404,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
pokemon.busyLocks.remove(this)
|
||||
captureFuture.complete(false)
|
||||
val ballType =
|
||||
this.pokeBall.name.path.toString().toLowerCase().replace("_", "")
|
||||
this.pokeBall.name.path.lowercase().replace("_", "")
|
||||
val mode = "casual"
|
||||
val sendflash =
|
||||
BedrockParticleOptionsRepository.getEffect(cobblemonResource("${ballType}/${mode}/sendflash"))
|
||||
@@ -473,7 +471,6 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
override fun onHit(hitResult: HitResult) {
|
||||
super.onHit(hitResult)
|
||||
if (captureState == CaptureState.FALL && hitResult.type == HitResult.Type.BLOCK) {
|
||||
captureState = CaptureState.SHAKE
|
||||
if (level().isServerSide()) {
|
||||
beginCapture()
|
||||
}
|
||||
@@ -494,12 +491,26 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
if (rollsRemaining == 4) {
|
||||
rollsRemaining--
|
||||
}
|
||||
|
||||
var animatedCritical = false
|
||||
// Critical capture despite their name actually animate first
|
||||
// They can still fail and don't actually signify a success
|
||||
val iterations: Int = if (captureResult.isCriticalCapture) {
|
||||
captureState = CaptureState.CRITICAL
|
||||
captureResult.numberOfShakes + 2
|
||||
} else {
|
||||
captureState = CaptureState.SHAKE
|
||||
captureResult.numberOfShakes + 1
|
||||
}
|
||||
taskBuilder()
|
||||
.iterations(captureResult.numberOfShakes + 1)
|
||||
.iterations(iterations)
|
||||
.delay(SECONDS_BEFORE_SHAKE)
|
||||
.interval(SECONDS_BETWEEN_SHAKES)
|
||||
.execute {
|
||||
if (captureResult.isCriticalCapture && !animatedCritical) {
|
||||
captureState = CaptureState.SHAKE
|
||||
animatedCritical = true
|
||||
return@execute
|
||||
}
|
||||
if (pokeBall.ancient && rollsRemaining > 1) {
|
||||
rollsRemaining = 1
|
||||
}
|
||||
|
||||
+7
-5
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.pokeball.catching.CaptureContext
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculator
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CriticalCaptureProvider
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.PokedexProgressCaptureMultiplierProvider
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.PokedexStatusCaptureInfluencer
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
|
||||
@@ -32,7 +33,7 @@ import kotlin.random.Random
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
object CobblemonCaptureCalculator: CaptureCalculator, CriticalCaptureProvider, PokedexProgressCaptureMultiplierProvider {
|
||||
object CobblemonCaptureCalculator: CaptureCalculator, CriticalCaptureProvider, PokedexProgressCaptureMultiplierProvider, PokedexStatusCaptureInfluencer {
|
||||
|
||||
override fun id(): String = "cobblemon"
|
||||
|
||||
@@ -54,9 +55,9 @@ object CobblemonCaptureCalculator: CaptureCalculator, CriticalCaptureProvider, P
|
||||
val pokeBall = pokeBallEntity.pokeBall
|
||||
val pokemon = target.pokemon
|
||||
if (pokeBall.catchRateModifier.isGuaranteed()) {
|
||||
return CaptureContext.successful()
|
||||
return this.influence(thrower, target, CaptureContext.successful())
|
||||
}
|
||||
// We don't have dark grass so we're just gonna pretend everything is that. Scratch that, without the pokedex it has issues.
|
||||
// We don't have dark grass so we're just going to pretend everything is that. Scratch that, without the Pokédex it has issues.
|
||||
val darkGrass = 1F //if (thrower is ServerPlayer) this.caughtMultiplierFor(thrower).roundToInt() else 1
|
||||
val inBattleModifier = if (target.battleId != null) 1F else 0.5F
|
||||
val catchRate = getCatchRate(thrower, pokeBallEntity, target, pokemon.form.catchRate.toFloat())
|
||||
@@ -94,8 +95,8 @@ object CobblemonCaptureCalculator: CaptureCalculator, CriticalCaptureProvider, P
|
||||
return CaptureContext(numberOfShakes = 1, isSuccessfulCapture = shakes == 1, isCriticalCapture = true)
|
||||
}
|
||||
}
|
||||
// ToDo once pokedex is implemented if the target is registered and it's a success shorten to 1 shake and become critical capture
|
||||
return CaptureContext(numberOfShakes = shakes, isSuccessfulCapture = shakes == 4, isCriticalCapture = false)
|
||||
return this.influence(thrower, target, CaptureContext(numberOfShakes = shakes, isSuccessfulCapture = shakes == 4, isCriticalCapture = false))
|
||||
|
||||
}
|
||||
|
||||
private fun findHighestThrowerLevel(player: ServerPlayer, pokemon: Pokemon): Int? {
|
||||
@@ -109,4 +110,5 @@ object CobblemonCaptureCalculator: CaptureCalculator, CriticalCaptureProvider, P
|
||||
} ?: return null
|
||||
return actor.getSide().getOppositeSide().activePokemon.maxOfOrNull { it.battlePokemon?.effectedPokemon?.level ?: 1 }
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.pokeball.catching.CaptureContext
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculator
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CriticalCaptureProvider
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.PokedexProgressCaptureMultiplierProvider
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.PokedexStatusCaptureInfluencer
|
||||
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.BurnStatus
|
||||
@@ -34,7 +35,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
* @author Licious
|
||||
* @since January 29th, 2022
|
||||
*/
|
||||
object Gen9CaptureCalculator : CaptureCalculator, CriticalCaptureProvider, PokedexProgressCaptureMultiplierProvider {
|
||||
object Gen9CaptureCalculator : CaptureCalculator, CriticalCaptureProvider, PokedexProgressCaptureMultiplierProvider, PokedexStatusCaptureInfluencer {
|
||||
|
||||
override fun id(): String = "generation_9"
|
||||
|
||||
@@ -42,7 +43,7 @@ object Gen9CaptureCalculator : CaptureCalculator, CriticalCaptureProvider, Poked
|
||||
val pokeBall = pokeBallEntity.pokeBall
|
||||
val pokemon = target.pokemon
|
||||
if (pokeBall.catchRateModifier.isGuaranteed()) {
|
||||
return CaptureContext.successful()
|
||||
return this.influence(thrower, target, CaptureContext.successful())
|
||||
}
|
||||
// We don't have dark grass so we're just gonna pretend everything is that.
|
||||
val darkGrass = if (thrower is ServerPlayer) this.caughtMultiplierFor(thrower).roundToInt() else 1
|
||||
@@ -70,8 +71,7 @@ object Gen9CaptureCalculator : CaptureCalculator, CriticalCaptureProvider, Poked
|
||||
return CaptureContext(numberOfShakes = 1, isSuccessfulCapture = shakes == 1, isCriticalCapture = true)
|
||||
}
|
||||
}
|
||||
// ToDo once pokedex is implemented if the target is registered and it's a success shorten to 1 shake and become critical capture
|
||||
return CaptureContext(numberOfShakes = shakes, isSuccessfulCapture = shakes == 4, isCriticalCapture = false)
|
||||
return this.influence(thrower, target, CaptureContext(numberOfShakes = shakes, isSuccessfulCapture = shakes == 4, isCriticalCapture = false))
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user