diff --git a/play.pokemonshowdown.com/src/battle-tooltips.ts b/play.pokemonshowdown.com/src/battle-tooltips.ts index 3d9d51320..c1a7a4cee 100644 --- a/play.pokemonshowdown.com/src/battle-tooltips.ts +++ b/play.pokemonshowdown.com/src/battle-tooltips.ts @@ -8,7 +8,7 @@ * @license MIT */ -import { Pokemon, type Battle, type ServerPokemon } from "./battle"; +import { Pokemon, type Battle, type PPState, type ServerPokemon } from "./battle"; import { Dex, type ModdedDex, toID, type ID } from "./battle-dex"; import type { BattleScene } from "./battle-animations"; import { BattleLog } from "./battle-log"; @@ -993,8 +993,8 @@ export class BattleTooltips { }).length > 4) { text += `(More than 4 moves is usually a sign of Illusion Zoroark/Zorua.) `; } - if (this.battle.gen === 3) { - text += `(Pressure is not visible in Gen 3, so in certain situations, more PP may have been lost than shown here.) `; + if (this.battle.gen === 3 && clientPokemon.moveTrack.some(([_, pp]) => typeof pp !== 'number')) { + text += `(Pressure is not visible in Gen 3, so in certain situations, the exact amount of PP used may be unknown.) `; } if (this.pokemonHasClones(clientPokemon)) { text += `(Your opponent has two indistinguishable Pokémon, making it impossible for you to tell which one has which moves/ability/item.) `; @@ -1472,7 +1472,7 @@ export class BattleTooltips { return buf; } - getPPUseText(moveTrackRow: [string, number], showKnown?: boolean) { + getPPUseText(moveTrackRow: [string, PPState], showKnown?: boolean) { let [moveName, ppUsed] = moveTrackRow; let move; let maxpp; @@ -1490,7 +1490,11 @@ export class BattleTooltips { return `${bullet} ${move.name} (0/${maxpp})`; } if (ppUsed || moveName.startsWith('*')) { - return `${bullet} ${move.name} (${maxpp - ppUsed}/${maxpp})`; + if (typeof ppUsed === 'number') { + return `${bullet} ${move.name} (${maxpp - ppUsed}/${maxpp})`; + } else { + return `${bullet} ${move.name} (${maxpp - ppUsed[0]}/${maxpp} to ${maxpp - ppUsed[1]}/${maxpp})`; + } } return `${bullet} ${move.name} ${showKnown ? ' (revealed)' : ''}`; } @@ -1988,7 +1992,8 @@ export class BattleTooltips { value.set(20 + 20 * boostCount); } if (move.id === 'trumpcard') { - const ppLeft = 5 - this.ppUsed(move, pokemon); + const pp = this.ppUsed(move, pokemon); + const ppLeft = 5 - (typeof pp === 'number' ? pp : pp[1]); let basePower = 40; if (ppLeft === 1) basePower = 200; else if (ppLeft === 2) basePower = 80; diff --git a/play.pokemonshowdown.com/src/battle.ts b/play.pokemonshowdown.com/src/battle.ts index 27f2eb072..f80495c83 100644 --- a/play.pokemonshowdown.com/src/battle.ts +++ b/play.pokemonshowdown.com/src/battle.ts @@ -40,6 +40,7 @@ declare const app: { user: AnyObject, rooms: AnyObject, ignore?: AnyObject } | u export type EffectState = any[] & { 0: ID }; export type WeatherState = [name: string, minTimeLeft: number, maxTimeLeft: number]; export type HPColor = 'r' | 'y' | 'g'; +export type PPState = number | [number, number]; export class Pokemon implements PokemonDetails, PokemonHealth { name = ''; @@ -106,7 +107,7 @@ export class Pokemon implements PokemonDetails, PokemonHealth { lastMove = ''; /** [[moveName, ppUsed]] */ - moveTrack: [string, number][] = []; + moveTrack: [string, PPState][] = []; statusData = { sleepTurns: 0, toxicTurns: 0 }; timesAttacked = 0; @@ -340,7 +341,37 @@ export class Pokemon implements PokemonDetails, PokemonHealth { this.clearMovestatuses(); this.side.battle.scene.clearEffects(this); } - rememberMove(moveName: string, pp = 1, recursionSource?: string) { + private mergePP(entry: [string, PPState], pp: PPState): PPState { + let ppUsed = entry[1]; + if (typeof ppUsed === 'number') { + if (typeof pp === 'number') { + ppUsed += pp; + } else { + ppUsed = [ppUsed + pp[0], ppUsed + pp[1]]; + } + } else { + if (typeof pp === 'number') { + ppUsed[0] += pp; + ppUsed[1] += pp; + } else { + ppUsed[0] += pp[0]; + ppUsed[1] += pp[1]; + } + } + if (typeof ppUsed === 'number') { + if (ppUsed < 0) ppUsed = 0; + } else { + if (ppUsed[0] < 0) ppUsed[0] = 0; + if (ppUsed[1] < 0) ppUsed[1] = 0; + const move = this.side.battle.dex.moves.get(entry[0]); + const maxpp = (move.pp === 1 || move.noPPBoosts ? move.pp : move.pp * 8 / 5); + if (ppUsed[0] > maxpp) ppUsed[0] = maxpp; + if (ppUsed[0] < ppUsed[1]) ppUsed[0] = ppUsed[1]; + if (ppUsed[0] === ppUsed[1]) ppUsed = ppUsed[0]; + } + return ppUsed; + } + rememberMove(moveName: string, pp: PPState = 1, recursionSource?: string) { if (recursionSource === this.ident) return; moveName = Dex.moves.get(moveName).name; if (moveName.startsWith('*')) return; @@ -353,8 +384,7 @@ export class Pokemon implements PokemonDetails, PokemonHealth { } for (const entry of this.moveTrack) { if (moveName === entry[0]) { - entry[1] += pp; - if (entry[1] < 0) entry[1] = 0; + entry[1] = this.mergePP(entry, pp); return; } } @@ -1537,8 +1567,8 @@ export class Battle { } } } - let pp = 1; - if (this.abilityActive('Pressure') && move.id !== 'stickyweb') { + let pp: PPState = callerMoveForPressure ? 0 : 1; // 1 pp was already deducted from using the move itself + if ((this.abilityActive('Pressure') || this.gen === 3) && move.id !== 'stickyweb') { const foeTargets = []; const moveTarget = move.pressureTarget; @@ -1559,18 +1589,9 @@ export class Battle { } else if (target && target.side !== pokemon.side) { foeTargets.push(target); } - - for (const foe of foeTargets) { - if (foe && !foe.fainted && foe.effectiveAbility() === 'Pressure') { - pp += 1; - } - } - } - if (!callerMoveForPressure) { - pokemon.rememberMove(moveName, pp); - } else { - pokemon.rememberMove(callerMoveForPressure.name, pp - 1); // 1 pp was already deducted from using the move itself + pp = this.getPressurePP(pp, foeTargets.filter(foe => foe && !foe.fainted) as Pokemon[]); } + pokemon.rememberMove(callerMoveForPressure ? callerMoveForPressure.name : moveName, pp); } pokemon.lastMove = move.id; this.lastMove = move.id; @@ -1578,6 +1599,26 @@ export class Battle { pokemon.side.wisher = pokemon; } } + private getPressurePP(pp: PPState, foes: Pokemon[]) { + for (const foe of foes) { + const abilities = Object.values(this.dex.species.get(foe.speciesForme).abilities); + const canHavePressure = this.gen === 3 && abilities.includes('Pressure'); + if (foe.effectiveAbility() === 'Pressure' || (canHavePressure && abilities.length === 1)) { + if (typeof pp === 'number') { + pp += 1; + } else { + pp[0] += 1; + pp[1] += 1; + } + } else if (canHavePressure) { + if (typeof pp === 'number') { + pp = [pp, pp]; + } + pp[0] += 1; + } + } + return pp; + } animateMove(pokemon: Pokemon, move: Dex.Move, target: Pokemon | null, kwArgs: KWArgs) { this.activeMoveIsSpread = kwArgs.spread; if (this.seeking !== null || kwArgs.still) return;