diff --git a/public/images/pokemon/exp/662.png b/public/images/pokemon/exp/662.png index 090e6e4c91f..e47863f724b 100644 Binary files a/public/images/pokemon/exp/662.png and b/public/images/pokemon/exp/662.png differ diff --git a/public/images/pokemon/exp/back/662.png b/public/images/pokemon/exp/back/662.png index e2a3111062f..4e6a3c88277 100644 Binary files a/public/images/pokemon/exp/back/662.png and b/public/images/pokemon/exp/back/662.png differ diff --git a/public/images/pokemon/exp/back/shiny/662.png b/public/images/pokemon/exp/back/shiny/662.png index b4e6c703eab..e662b3198a6 100644 Binary files a/public/images/pokemon/exp/back/shiny/662.png and b/public/images/pokemon/exp/back/shiny/662.png differ diff --git a/public/images/pokemon/exp/shiny/662.png b/public/images/pokemon/exp/shiny/662.png index ec0100f3e96..e45e79556c5 100644 Binary files a/public/images/pokemon/exp/shiny/662.png and b/public/images/pokemon/exp/shiny/662.png differ diff --git a/public/locales b/public/locales index 87615556d8a..71390cba88f 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 87615556d8a2bd7eef7abac818f84423a8a13b03 +Subproject commit 71390cba88f4103d0d2273d59a6dd8340a4fa54f diff --git a/src/data/ability.ts b/src/data/ability.ts index d761657f5cd..01e99966ff8 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2453,16 +2453,19 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { pokemon.setStatStage(s, target.getStatStage(s)); } - pokemon.summonData.moveset = target.getMoveset().map(m => { - const pp = m?.getMove().pp ?? 0; - // if PP value is less than 5, do nothing. If greater, we need to reduce the value to 5 using a negative ppUp value. - const ppUp = pp <= 5 ? 0 : (5 - pp) / Math.max(Math.floor(pp / 5), 1); - return new PokemonMove(m?.moveId ?? Moves.NONE, 0, ppUp); + pokemon.summonData.moveset = target.getMoveset().map((m) => { + if (m) { + // If PP value is less than 5, do nothing. If greater, we need to reduce the value to 5. + return new PokemonMove(m.moveId, 0, 0, false, Math.min(m.getMove().pp, 5)); + } else { + console.warn(`Imposter: somehow iterating over a ${m} value when copying moveset!`); + return new PokemonMove(Moves.NONE); + } }); pokemon.summonData.types = target.getTypes(); promises.push(pokemon.updateInfo()); - pokemon.scene.queueMessage(i18next.t("abilityTriggers:postSummonTransform", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), targetName: target!.name, })); + pokemon.scene.queueMessage(i18next.t("abilityTriggers:postSummonTransform", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), targetName: target.name, })); pokemon.scene.playSound("battle_anims/PRSFX- Transform"); promises.push(pokemon.loadAssets(false).then(() => { pokemon.playAnim(); diff --git a/src/data/move.ts b/src/data/move.ts index efdd4568927..9979b24cc24 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -6668,11 +6668,14 @@ export class TransformAttr extends MoveEffectAttr { user.setStatStage(s, target.getStatStage(s)); } - user.summonData.moveset = target.getMoveset().map(m => { - const pp = m?.getMove().pp ?? 0; - // if PP value is less than 5, do nothing. If greater, we need to reduce the value to 5 using a negative ppUp value. - const ppUp = pp <= 5 ? 0 : (5 - pp) / Math.max(Math.floor(pp / 5), 1); - return new PokemonMove(m?.moveId!, 0, ppUp); + user.summonData.moveset = target.getMoveset().map((m) => { + if (m) { + // If PP value is less than 5, do nothing. If greater, we need to reduce the value to 5. + return new PokemonMove(m.moveId, 0, 0, false, Math.min(m.getMove().pp, 5)); + } else { + console.warn(`Transform: somehow iterating over a ${m} value when copying moveset!`); + return new PokemonMove(Moves.NONE); + } }); user.summonData.types = target.getTypes(); promises.push(user.updateInfo()); diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 96d1eb430fb..a93c35829ea 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -1864,7 +1864,7 @@ export function initSpecies() { new PokemonSpecies(Species.ALOMOMOLA, 5, false, false, false, "Caring Pokémon", Type.WATER, null, 1.2, 31.6, Abilities.HEALER, Abilities.HYDRATION, Abilities.REGENERATOR, 470, 165, 75, 80, 40, 45, 65, 75, 70, 165, GrowthRate.FAST, 50, false), new PokemonSpecies(Species.JOLTIK, 5, false, false, false, "Attaching Pokémon", Type.BUG, Type.ELECTRIC, 0.1, 0.6, Abilities.COMPOUND_EYES, Abilities.UNNERVE, Abilities.SWARM, 319, 50, 47, 50, 57, 50, 65, 190, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.GALVANTULA, 5, false, false, false, "EleSpider Pokémon", Type.BUG, Type.ELECTRIC, 0.8, 14.3, Abilities.COMPOUND_EYES, Abilities.UNNERVE, Abilities.SWARM, 472, 70, 77, 60, 97, 60, 108, 75, 50, 165, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(Species.FERROSEED, 5, false, false, false, "Thorn Seed Pokémon", Type.GRASS, Type.STEEL, 0.6, 18.8, Abilities.IRON_BARBS, Abilities.NONE, Abilities.IRON_BARBS, 305, 44, 50, 91, 24, 86, 10, 255, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(Species.FERROSEED, 5, false, false, false, "Thorn Seed Pokémon", Type.GRASS, Type.STEEL, 0.6, 18.8, Abilities.IRON_BARBS, Abilities.NONE, Abilities.ANTICIPATION, 305, 44, 50, 91, 24, 86, 10, 255, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.FERROTHORN, 5, false, false, false, "Thorn Pod Pokémon", Type.GRASS, Type.STEEL, 1, 110, Abilities.IRON_BARBS, Abilities.NONE, Abilities.ANTICIPATION, 489, 74, 94, 131, 54, 116, 20, 90, 50, 171, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.KLINK, 5, false, false, false, "Gear Pokémon", Type.STEEL, null, 0.3, 21, Abilities.PLUS, Abilities.MINUS, Abilities.CLEAR_BODY, 300, 40, 55, 70, 45, 60, 30, 130, 50, 60, GrowthRate.MEDIUM_SLOW, null, false), new PokemonSpecies(Species.KLANG, 5, false, false, false, "Gear Pokémon", Type.STEEL, null, 0.6, 51, Abilities.PLUS, Abilities.MINUS, Abilities.CLEAR_BODY, 440, 60, 80, 95, 70, 85, 50, 60, 50, 154, GrowthRate.MEDIUM_SLOW, null, false), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0a2834e7288..c6fc5793724 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4453,7 +4453,7 @@ export class PlayerPokemon extends Pokemon { this.scene.removePartyMemberModifiers(fusedPartyMemberIndex); this.scene.getParty().splice(fusedPartyMemberIndex, 1)[0]; const newPartyMemberIndex = this.scene.getParty().indexOf(this); - pokemon.getMoveset(true).map(m => this.scene.unshiftPhase(new LearnMovePhase(this.scene, newPartyMemberIndex, m!.getMove().id))); // TODO: is the bang correct? + pokemon.getMoveset(true).map((m: PokemonMove) => this.scene.unshiftPhase(new LearnMovePhase(this.scene, newPartyMemberIndex, m.getMove().id))); pokemon.destroy(); this.updateFusionPalette(); resolve(); @@ -4474,8 +4474,12 @@ export class PlayerPokemon extends Pokemon { /** Returns a deep copy of this Pokemon's moveset array */ copyMoveset(): PokemonMove[] { const newMoveset : PokemonMove[] = []; - this.moveset.forEach(move => - newMoveset.push(new PokemonMove(move!.moveId, 0, move!.ppUp, move!.virtual))); // TODO: are those bangs correct? + this.moveset.forEach((move) => { + // TODO: refactor `moveset` to not accept `null`s + if (move) { + newMoveset.push(new PokemonMove(move.moveId, 0, move.ppUp, move.virtual, move.maxPpOverride)); + } + }); return newMoveset; } @@ -5202,15 +5206,22 @@ export interface DamageCalculationResult { **/ export class PokemonMove { public moveId: Moves; - public ppUsed: integer; - public ppUp: integer; + public ppUsed: number; + public ppUp: number; public virtual: boolean; - constructor(moveId: Moves, ppUsed?: integer, ppUp?: integer, virtual?: boolean) { + /** + * If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform). + * This also nullifies all effects of `ppUp`. + */ + public maxPpOverride?: number; + + constructor(moveId: Moves, ppUsed: number = 0, ppUp: number = 0, virtual: boolean = false, maxPpOverride?: number) { this.moveId = moveId; - this.ppUsed = ppUsed || 0; - this.ppUp = ppUp || 0; - this.virtual = !!virtual; + this.ppUsed = ppUsed; + this.ppUp = ppUp; + this.virtual = virtual; + this.maxPpOverride = maxPpOverride; } /** @@ -5247,7 +5258,7 @@ export class PokemonMove { } getMovePp(): integer { - return this.getMove().pp + this.ppUp * Utils.toDmgValue(this.getMove().pp / 5); + return this.maxPpOverride || (this.getMove().pp + this.ppUp * Utils.toDmgValue(this.getMove().pp / 5)); } getPpRatio(): number { @@ -5264,6 +5275,6 @@ export class PokemonMove { * @return {PokemonMove} A valid pokemonmove object */ static loadMove(source: PokemonMove | any): PokemonMove { - return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.virtual); + return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.virtual, source.maxPpOverride); } } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index c3e7b6f6d4c..fe68dc126b1 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -402,7 +402,7 @@ export class PokemonPpUpModifierType extends PokemonMoveModifierType { (_pokemon: PlayerPokemon) => { return null; }, (pokemonMove: PokemonMove) => { - if (pokemonMove.getMove().pp < 5 || pokemonMove.ppUp >= 3) { + if (pokemonMove.getMove().pp < 5 || pokemonMove.ppUp >= 3 || pokemonMove.maxPpOverride) { return PartyUiHandler.NoEffectMessage; } return null; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 11f16f103a5..36f94b99b20 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2166,7 +2166,7 @@ export class PokemonPpUpModifier extends ConsumablePokemonMoveModifier { override apply(playerPokemon: PlayerPokemon): boolean { const move = playerPokemon.getMoveset()[this.moveIndex]; - if (move) { + if (move && !move.maxPpOverride) { move.ppUp = Math.min(move.ppUp + this.upPoints, 3); } diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 421739a9da1..c8756e4dd7f 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -135,7 +135,7 @@ export default class PokemonData { } } } else { - this.moveset = (source.moveset || [ new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.GROWL) ]).filter(m => m).map((m: any) => new PokemonMove(m.moveId, m.ppUsed, m.ppUp)); + this.moveset = (source.moveset || [ new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.GROWL) ]).filter(m => m).map((m: any) => new PokemonMove(m.moveId, m.ppUsed, m.ppUp, m.virtual, m.maxPpOverride)); if (!forHistory) { this.status = source.status ? new Status(source.status.effect, source.status.toxicTurnCount, source.status.sleepTurnsRemaining) diff --git a/src/test/abilities/imposter.test.ts b/src/test/abilities/imposter.test.ts index 7aaac5ca8c4..3445b3b322c 100644 --- a/src/test/abilities/imposter.test.ts +++ b/src/test/abilities/imposter.test.ts @@ -60,18 +60,19 @@ describe("Abilities - Imposter", () => { const playerMoveset = player.getMoveset(); const enemyMoveset = player.getMoveset(); + expect(playerMoveset.length).toBe(enemyMoveset.length); for (let i = 0; i < playerMoveset.length && i < enemyMoveset.length; i++) { - // TODO: Checks for 5 PP should be done here when that gets addressed expect(playerMoveset[i]?.moveId).toBe(enemyMoveset[i]?.moveId); } const playerTypes = player.getTypes(); const enemyTypes = enemy.getTypes(); + expect(playerTypes.length).toBe(enemyTypes.length); for (let i = 0; i < playerTypes.length && i < enemyTypes.length; i++) { expect(playerTypes[i]).toBe(enemyTypes[i]); } - }, 20000); + }); it("should copy in-battle overridden stats", async () => { game.override.enemyMoveset([ Moves.POWER_SPLIT ]); @@ -104,7 +105,15 @@ describe("Abilities - Imposter", () => { await game.phaseInterceptor.to(TurnEndPhase); player.getMoveset().forEach(move => { - expect(move!.getMovePp()).toBeLessThanOrEqual(5); + // Should set correct maximum PP without touching `ppUp` + if (move) { + if (move.moveId === Moves.SKETCH) { + expect(move.getMovePp()).toBe(1); + } else { + expect(move.getMovePp()).toBe(5); + } + expect(move.ppUp).toBe(0); + } }); }); }); diff --git a/src/test/moves/transform.test.ts b/src/test/moves/transform.test.ts index 8c0f5eda7b2..adb97b42af7 100644 --- a/src/test/moves/transform.test.ts +++ b/src/test/moves/transform.test.ts @@ -60,18 +60,19 @@ describe("Moves - Transform", () => { const playerMoveset = player.getMoveset(); const enemyMoveset = player.getMoveset(); + expect(playerMoveset.length).toBe(enemyMoveset.length); for (let i = 0; i < playerMoveset.length && i < enemyMoveset.length; i++) { - // TODO: Checks for 5 PP should be done here when that gets addressed expect(playerMoveset[i]?.moveId).toBe(enemyMoveset[i]?.moveId); } const playerTypes = player.getTypes(); const enemyTypes = enemy.getTypes(); + expect(playerTypes.length).toBe(enemyTypes.length); for (let i = 0; i < playerTypes.length && i < enemyTypes.length; i++) { expect(playerTypes[i]).toBe(enemyTypes[i]); } - }, 20000); + }); it("should copy in-battle overridden stats", async () => { game.override.enemyMoveset([ Moves.POWER_SPLIT ]); @@ -94,7 +95,7 @@ describe("Moves - Transform", () => { expect(enemy.getStat(Stat.SPATK, false)).toBe(avgSpAtk); }); - it ("should set each move's pp to a maximum of 5", async () => { + it("should set each move's pp to a maximum of 5", async () => { game.override.enemyMoveset([ Moves.SWORDS_DANCE, Moves.GROWL, Moves.SKETCH, Moves.RECOVER ]); await game.classicMode.startBattle([ Species.DITTO ]); @@ -104,7 +105,15 @@ describe("Moves - Transform", () => { await game.phaseInterceptor.to(TurnEndPhase); player.getMoveset().forEach(move => { - expect(move!.getMovePp()).toBeLessThanOrEqual(5); + // Should set correct maximum PP without touching `ppUp` + if (move) { + if (move.moveId === Moves.SKETCH) { + expect(move.getMovePp()).toBe(1); + } else { + expect(move.getMovePp()).toBe(5); + } + expect(move.ppUp).toBe(0); + } }); }); });