[Balance] Shiny mons get a 2x catch bonus, can be boosted by events (#6694)

* Shiny mons get a 2x catch bonus

* Add catch bonus to safari zone as well

* Add logging for catch rate calculation

* Disabled debug catch % logging during tests
This commit is contained in:
Austin Fontaine 2025-10-27 17:47:23 -04:00 committed by GitHub
parent 3d5a46b1ec
commit 41b2f2f4fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 28 additions and 3 deletions

View File

@ -51,3 +51,6 @@ export const BOOSTED_RARE_EGGMOVE_RATES: readonly number[] = [16, 12, 6, 3];
// The chance x/10 of a shiny being a variant, then of being specifically an epic variant
export const SHINY_VARIANT_CHANCE = 4;
export const SHINY_EPIC_CHANCE = 1;
// The catch rate bonus for shiny mons, introduced in Z-A. Can be boosted in events.
export const SHINY_CATCH_RATE_MULTIPLIER = 2;

View File

@ -1,3 +1,4 @@
import { timedEventManager } from "#app/global-event-manager";
import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages";
import { speciesStarterCosts } from "#balance/starters";
@ -452,7 +453,8 @@ export function trainerThrowPokeball(
const catchRate = pokemon.species.catchRate;
const pokeballMultiplier = getPokeballCatchMultiplier(pokeballType);
const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1;
const x = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier);
const shinyMultiplier = pokemon.isShiny() ? timedEventManager.getShinyCatchMultiplier() : 1;
const x = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier * shinyMultiplier);
ballTwitchRate = Math.round(65536 / Math.sqrt(Math.sqrt(255 / x)));
}

View File

@ -1,6 +1,8 @@
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
import { timedEventManager } from "#app/global-event-manager";
import { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages";
import { isBeta, isDev } from "#constants/app-constants";
import { SubstituteTag } from "#data/battler-tags";
import { Gender } from "#data/gender";
import {
@ -64,9 +66,26 @@ export class AttemptCapturePhase extends PokemonPhase {
const catchRate = pokemon.species.catchRate;
const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType);
const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1;
const modifiedCatchRate = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier);
const shinyMultiplier = pokemon.isShiny() ? timedEventManager.getShinyCatchMultiplier() : 1;
const modifiedCatchRate = Math.round(
(((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier * shinyMultiplier,
);
const shakeProbability = Math.round(65536 / Math.pow(255 / modifiedCatchRate, 0.1875)); // Formula taken from gen 6
const criticalCaptureChance = getCriticalCaptureChance(modifiedCatchRate);
if ((isBeta || isDev) && import.meta.env.NODE_ENV !== "test") {
console.log(
"Base Catch Rate: %d\nBall Mult: %d\nStatus Mult: %d\nShiny Bonus: %d\nModified Catch Rate: %d\nShake Probability: %d\nCritical Catch Chance: %d",
catchRate,
pokeballMultiplier,
statusMultiplier,
shinyMultiplier,
modifiedCatchRate,
shakeProbability,
criticalCaptureChance,
);
}
const isCritical = pokemon.randBattleSeedInt(256) < criticalCaptureChance;
const fpOffset = pokemon.getFieldPositionOffset();

View File

@ -1,4 +1,5 @@
import { globalScene } from "#app/global-scene";
import { SHINY_CATCH_RATE_MULTIPLIER } from "#balance/rates";
import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER } from "#balance/starters";
import type { WeatherPoolEntry } from "#data/weather";
import { Challenges } from "#enums/challenges";
@ -432,7 +433,7 @@ export class TimedEventManager {
* @returns the shiny catch multiplier
*/
getShinyCatchMultiplier(): number {
return this.activeEvent()?.shinyCatchMultiplier ?? 1;
return this.activeEvent()?.shinyCatchMultiplier ?? SHINY_CATCH_RATE_MULTIPLIER;
}
getEventBannerFilename(): string {