diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 58907179c02..4c527fad1db 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -30,7 +30,7 @@ import { Weather, WeatherType, getRandomWeatherType, getWeatherDamageMessage, ge import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTagType } from "./data/enums/arena-tag-type"; -import { Abilities, CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability"; +import { Abilities, CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; @@ -978,6 +978,8 @@ export class SwitchSummonPhase extends SummonPhase { if (!this.batonPass) (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); + applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, pokemon); + this.scene.ui.showText(this.player ? `Come back, ${pokemon.name}!` : `${this.scene.currentBattle.trainer.getName()}\nwithdrew ${pokemon.name}!`); this.scene.playSound('pb_rel'); pokemon.hideInfo(); diff --git a/src/data/ability.ts b/src/data/ability.ts index e1e5019140f..1253b92edda 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -679,6 +679,28 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { } } +export class PreSwitchOutAbAttr extends AbAttr { + constructor() { + super(true); + } + + applyPreSwitchOut(pokemon: Pokemon, args: any[]): boolean | Promise { + return false; + } +} + +export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { + applyPreSwitchOut(pokemon: Pokemon, args: any[]): boolean | Promise { + if (pokemon.status) { + pokemon.resetStatus(); + pokemon.updateInfo(); + return true; + } + + return false; + } +} + export class PreStatChangeAbAttr extends AbAttr { applyPreStatChange(pokemon: Pokemon, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { return false; @@ -991,6 +1013,67 @@ export class MaxMultiHitAbAttr extends AbAttr { } } +export class ReduceStatusEffectDurationAbAttr extends AbAttr { + private statusEffect: StatusEffect; + + constructor(statusEffect: StatusEffect) { + super(true); + + this.statusEffect = statusEffect; + } + + apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (args[0] === this.statusEffect) { + (args[1] as Utils.IntegerHolder).value = Math.floor((args[1] as Utils.IntegerHolder).value / 2); + return true; + } + + return false; + } +} + +export class FlinchEffectAbAttr extends AbAttr { + constructor() { + super(true); + } +} + +export class FlinchStatChangeAbAttr extends FlinchEffectAbAttr { + private stats: BattleStat[]; + private levels: integer; + + constructor(stats: BattleStat | BattleStat[], levels: integer) { + super(); + + this.stats = Array.isArray(stats) + ? stats + : [ stats ]; + this.levels = levels; + } + + apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean { + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels)); + return true; + } +} + +export class ReduceBerryUseThresholdAbAttr extends AbAttr { + constructor() { + super(true); + } + + apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean { + const hpRatio = pokemon.getHpRatio(); + + if (args[0].value < hpRatio) { + args[0].value *= 2; + return args[0].value >= hpRatio; + } + + return false; + } +} + export class WeightMultiplierAbAttr extends AbAttr { private multiplier: integer; @@ -1108,6 +1191,11 @@ export function applyPostSummonAbAttrs(attrType: { new(...args: any[]): PostSumm return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPostSummon(pokemon, args)); } +export function applyPreSwitchOutAbAttrs(attrType: { new(...args: any[]): PreSwitchOutAbAttr }, + pokemon: Pokemon, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPreSwitchOut(pokemon, args), false, true); +} + export function applyPreStatChangeAbAttrs(attrType: { new(...args: any[]): PreStatChangeAbAttr }, pokemon: Pokemon, stat: BattleStat, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPreStatChange(pokemon, stat, cancelled, args)); @@ -1523,7 +1611,8 @@ export function initAbilities() { .attr(SyncEncounterNatureAbAttr), new Ability(Abilities.CLEAR_BODY, "Clear Body", "Prevents other Pokémon's moves or Abilities from lowering the Pokémon's stats.", 3) .attr(ProtectStatAbAttr), - new Ability(Abilities.NATURAL_CURE, "Natural Cure (N)", "All status conditions heal when the Pokémon switches out.", 3), + new Ability(Abilities.NATURAL_CURE, "Natural Cure", "All status conditions heal when the Pokémon switches out.", 3) + .attr(PreSwitchOutResetStatusAbAttr), new Ability(Abilities.LIGHTNING_ROD, "Lightning Rod", "The Pokémon draws in all Electric-type moves. Instead of being hit by Electric-type moves, it boosts its Sp. Atk.", 3) .attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPATK, 1), new Ability(Abilities.SERENE_GRACE, "Serene Grace (N)", "Boosts the likelihood of additional effects occurring when attacking.", 3), @@ -1560,7 +1649,8 @@ export function initAbilities() { new Ability(Abilities.THICK_FAT, "Thick Fat", "The Pokémon is protected by a layer of thick fat, which halves the damage taken from Fire- and Ice-type moves.", 3) .attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(ReceivedTypeDamageMultiplierAbAttr, Type.ICE, 0.5), - new Ability(Abilities.EARLY_BIRD, "Early Bird (N)", "The Pokémon awakens from sleep twice as fast as other Pokémon.", 3), + new Ability(Abilities.EARLY_BIRD, "Early Bird", "The Pokémon awakens from sleep twice as fast as other Pokémon.", 3) + .attr(ReduceStatusEffectDurationAbAttr, StatusEffect.SLEEP), new Ability(Abilities.FLAME_BODY, "Flame Body", "Contact with the Pokémon may burn the attacker.", 3) .attr(PostDefendContactApplyStatusEffectAbAttr, 30, StatusEffect.BURN), new Ability(Abilities.RUN_AWAY, "Run Away", "Enables a sure getaway from wild Pokémon.", 3) @@ -1616,11 +1706,13 @@ export function initAbilities() { new Ability(Abilities.MOTOR_DRIVE, "Motor Drive", "Boosts its Speed stat if hit by an Electric-type move instead of taking damage.", 4) .attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPD, 1), new Ability(Abilities.RIVALRY, "Rivalry (N)", "Becomes competitive and deals more damage to Pokémon of the same gender, but deals less to Pokémon of the opposite gender.", 4), - new Ability(Abilities.STEADFAST, "Steadfast (N)", "The Pokémon's determination boosts the Speed stat each time the Pokémon flinches.", 4), + new Ability(Abilities.STEADFAST, "Steadfast", "The Pokémon's determination boosts the Speed stat each time the Pokémon flinches.", 4) + .attr(FlinchStatChangeAbAttr, BattleStat.SPD, 1), new Ability(Abilities.SNOW_CLOAK, "Snow Cloak", "Boosts evasiveness in a hailstorm.", 4) .attr(BattleStatMultiplierAbAttr, BattleStat.EVA, 1.2) .attr(BlockWeatherDamageAttr, WeatherType.HAIL), - new Ability(Abilities.GLUTTONY, "Gluttony (N)", "Makes the Pokémon eat a held Berry when its HP drops to half or less, which is sooner than usual.", 4), + new Ability(Abilities.GLUTTONY, "Gluttony", "Makes the Pokémon eat a held Berry when its HP drops to half or less, which is sooner than usual.", 4) + .attr(ReduceBerryUseThresholdAbAttr), new Ability(Abilities.ANGER_POINT, "Anger Point", "The Pokémon is angered when it takes a critical hit, and that maxes its Attack stat.", 4) .attr(PostDefendCritStatChangeAbAttr, BattleStat.ATK, 6), new Ability(Abilities.UNBURDEN, "Unburden (N)", "Boosts the Speed stat if the Pokémon's held item is used or lost.", 4), diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 31412db13da..93076b4bb7f 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -8,7 +8,7 @@ import * as Utils from "../utils"; import { Moves } from "./enums/moves"; import { ChargeAttr, allMoves } from "./move"; import { Type } from "./type"; -import { Abilities } from "./ability"; +import { Abilities, FlinchEffectAbAttr, applyAbAttrs } from "./ability"; import { BattlerTagType } from "./enums/battler-tag-type"; export enum BattlerTagLapseType { @@ -123,6 +123,12 @@ export class FlinchedTag extends BattlerTag { super(BattlerTagType.FLINCHED, BattlerTagLapseType.MOVE, 0, sourceMove); } + onAdd(pokemon: Pokemon): void { + super.onAdd(pokemon); + + applyAbAttrs(FlinchEffectAbAttr, pokemon, null); + } + lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { super.lapse(pokemon, lapseType); diff --git a/src/data/berry.ts b/src/data/berry.ts index 2ac971a7639..9a5c67734d6 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -6,7 +6,7 @@ import { BattleStat } from "./battle-stat"; import { BattlerTagType } from "./enums/battler-tag-type"; import { getStatusEffectHealText } from "./status-effect"; import * as Utils from "../utils"; -import { DoubleBerryEffectAbAttr, applyAbAttrs } from "./ability"; +import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./ability"; export enum BerryType { SITRUS, @@ -63,13 +63,23 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate { case BerryType.APICOT: case BerryType.SALAC: return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); const battleStat = (berryType - BerryType.LIECHI) as BattleStat; - return pokemon.getHpRatio() < 0.25 && pokemon.summonData.battleStats[battleStat] < 6; + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold); + return pokemon.getHpRatio() < threshold.value && pokemon.summonData.battleStats[battleStat] < 6; }; case BerryType.LANSAT: - return (pokemon: Pokemon) => pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold); + return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); + } case BerryType.STARF: - return (pokemon: Pokemon) => pokemon.getHpRatio() < 0.25; + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold); + return pokemon.getHpRatio() < 0.25; + } } } diff --git a/src/pokemon.ts b/src/pokemon.ts index b7c58d596e9..2f3f84071d2 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -25,7 +25,7 @@ import { TempBattleStat } from './data/temp-battle-stat'; import { WeakenMoveTypeTag } from './data/arena-tag'; import { ArenaTagType } from "./data/enums/arena-tag-type"; import { Biome } from "./data/enums/biome"; -import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability'; +import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability'; import PokemonData from './system/pokemon-data'; import { BattlerIndex } from './battle'; import { BattleSpec } from "./enums/battle-spec"; @@ -1466,14 +1466,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (cancelled.value) return false; - let cureTurn: integer; + let cureTurn: Utils.IntegerHolder; if (effect === StatusEffect.SLEEP) { - cureTurn = this.randSeedIntRange(2, 4); + cureTurn = new Utils.IntegerHolder(this.randSeedIntRange(2, 4)); + applyAbAttrs(ReduceStatusEffectDurationAbAttr, this, null, effect, cureTurn); + this.setFrameRate(4); } - this.status = new Status(effect, 0, cureTurn); + this.status = new Status(effect, 0, cureTurn?.value); if (effect !== StatusEffect.FAINT) this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);