Moved empty moveset verification mapping thing to upgrade script bc i wanted to

This commit is contained in:
Bertie690 2025-04-21 12:51:28 -04:00
parent 2746a658df
commit 102554cdb7
10 changed files with 139 additions and 122 deletions

View File

@ -3273,13 +3273,13 @@ export class ConditionalUserFieldStatusEffectImmunityAbAttr extends UserFieldSta
/** /**
* Conditionally provides immunity to stat drop effects to the user's field. * Conditionally provides immunity to stat drop effects to the user's field.
* *
* Used by {@linkcode Abilities.FLOWER_VEIL | Flower Veil}. * Used by {@linkcode Abilities.FLOWER_VEIL | Flower Veil}.
*/ */
export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbAttr { export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbAttr {
/** {@linkcode BattleStat} to protect or `undefined` if **all** {@linkcode BattleStat} are protected */ /** {@linkcode BattleStat} to protect or `undefined` if **all** {@linkcode BattleStat} are protected */
protected protectedStat?: BattleStat; protected protectedStat?: BattleStat;
/** If the method evaluates to true, the stat will be protected. */ /** If the method evaluates to true, the stat will be protected. */
protected condition: (target: Pokemon) => boolean; protected condition: (target: Pokemon) => boolean;
@ -3296,7 +3296,7 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA
* @param stat The stat being affected * @param stat The stat being affected
* @param cancelled Holds whether the stat change was already prevented. * @param cancelled Holds whether the stat change was already prevented.
* @param args Args[0] is the target pokemon of the stat change. * @param args Args[0] is the target pokemon of the stat change.
* @returns * @returns
*/ */
override canApplyPreStatStageChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: [Pokemon, ...any]): boolean { override canApplyPreStatStageChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: [Pokemon, ...any]): boolean {
const target = args[0]; const target = args[0];
@ -3428,7 +3428,7 @@ export class BonusCritAbAttr extends AbAttr {
/** /**
* Apply the bonus crit ability by increasing the value in the provided number holder by 1 * Apply the bonus crit ability by increasing the value in the provided number holder by 1
* *
* @param pokemon The pokemon with the BonusCrit ability (unused) * @param pokemon The pokemon with the BonusCrit ability (unused)
* @param passive Unused * @param passive Unused
* @param simulated Unused * @param simulated Unused
@ -3581,7 +3581,7 @@ export class PreWeatherEffectAbAttr extends AbAttr {
args: any[]): boolean { args: any[]): boolean {
return true; return true;
} }
applyPreWeatherEffect( applyPreWeatherEffect(
pokemon: Pokemon, pokemon: Pokemon,
passive: boolean, passive: boolean,
@ -4143,25 +4143,24 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
* Used by {@linkcode Abilities.CUD_CHEW}. * Used by {@linkcode Abilities.CUD_CHEW}.
*/ */
export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr { export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr {
constructor() { // no need for constructor; all it does is set `showAbility` which we override before triggering anyways
super(true);
}
/** /**
* @returns `true` if the pokemon ate anything last turn * @returns `true` if the pokemon ate anything last turn
*/ */
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
this.showAbility = true; // force ability popup if ability triggers
return !!pokemon.summonData.berriesEatenLast.length; return !!pokemon.summonData.berriesEatenLast.length;
} }
/** /**
* Cause this {@linkcode Pokemon} to regurgitate and eat all berries * Cause this {@linkcode Pokemon} to regurgitate and eat all berries
* inside its `berriesEatenLast` array. * inside its `berriesEatenLast` array.
* @param pokemon The pokemon having the tummy ache * @param pokemon - The pokemon having the tummy ache
* @param _passive N/A * @param _passive - N/A
* @param _simulated N/A * @param _simulated - N/A
* @param _cancelled N/A * @param _cancelled - N/A
* @param _args N/A * @param _args - N/A
*/ */
override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder | null, _args: any[]): void { override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder | null, _args: any[]): void {
// play berry animation // play berry animation
@ -4182,6 +4181,7 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr {
* @returns `true` if the pokemon ate anything this turn (we move it into `battleData`) * @returns `true` if the pokemon ate anything this turn (we move it into `battleData`)
*/ */
override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
this.showAbility = false; // don't show popup for turn end berry moving (should ideally be hidden)
return !!pokemon.turnData.berriesEaten.length; return !!pokemon.turnData.berriesEaten.length;
} }

View File

@ -2694,7 +2694,9 @@ export class EatBerryAttr extends MoveEffectAttr {
if (!preserve.value) { if (!preserve.value) {
this.reduceBerryModifier(target); this.reduceBerryModifier(target);
} }
this.eatBerry(target);
// Don't update harvest for berries preserved via Berry pouch (no item dupes lol)
this.eatBerry(target, undefined, !preserve.value);
return true; return true;
} }
@ -2711,15 +2713,21 @@ export class EatBerryAttr extends MoveEffectAttr {
globalScene.updateModifiers(target.isPlayer()); globalScene.updateModifiers(target.isPlayer());
} }
eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer) {
// consumer eats berry, owner triggers unburden and similar effects /**
// These are the same under normal circumstances * Internal function to apply berry effects.
*
* @param consumer - The {@linkcode Pokemon} eating the berry; assumed to also be owner if `berryOwner` is omitted
* @param berryOwner - The {@linkcode Pokemon} whose berry is being eaten; defaults to `consumer` if not specified.
* @param updateHarvest - Whether to prevent harvest from tracking berries;
* defaults to whether `consumer` equals `berryOwner` (i.e. consuming own berry).
*/
eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) {
// consumer eats berry, owner triggers unburden and similar effects
getBerryEffectFunc(this.chosenBerry.berryType)(consumer); getBerryEffectFunc(this.chosenBerry.berryType)(consumer);
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner, false); applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner, false);
applyAbAttrs(HealFromBerryUseAbAttr, consumer, new BooleanHolder(false)); applyAbAttrs(HealFromBerryUseAbAttr, consumer, new BooleanHolder(false));
consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest);
// Harvest doesn't track berries eaten by other pokemon
consumer.recordEatenBerry(this.chosenBerry.berryType, berryOwner !== consumer);
} }
} }

View File

@ -279,6 +279,12 @@ export enum FieldPosition {
} }
export default abstract class Pokemon extends Phaser.GameObjects.Container { export default abstract class Pokemon extends Phaser.GameObjects.Container {
/**
* This pokemon's {@link https://bulbapedia.bulbagarden.net/wiki/Personality_value | Personality value/PID},
* used to determine various parameters of this Pokemon.
* Represented as a random 32-bit unsigned integer.
* TODO: Stop treating this like a unique ID and stop treating 0 as no pokemon
*/
public id: number; public id: number;
public name: string; public name: string;
public nickname: string; public nickname: string;
@ -324,7 +330,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public fusionCustomPokemonData: CustomPokemonData | null; public fusionCustomPokemonData: CustomPokemonData | null;
public fusionTeraType: PokemonType; public fusionTeraType: PokemonType;
public customPokemonData: CustomPokemonData = new CustomPokemonData(); public customPokemonData: CustomPokemonData = new CustomPokemonData;
/** /**
* TODO: Figure out if we can remove this thing * TODO: Figure out if we can remove this thing
@ -7892,7 +7898,7 @@ export class PokemonSummonData {
public gender: Gender; public gender: Gender;
public fusionGender: Gender; public fusionGender: Gender;
public stats: number[] = [0, 0, 0, 0, 0, 0]; public stats: number[] = [0, 0, 0, 0, 0, 0];
public moveset: PokemonMove[]; public moveset: PokemonMove[] | null;
public illusionBroken: boolean = false; public illusionBroken: boolean = false;
// If not initialized this value will not be populated from save data. // If not initialized this value will not be populated from save data.
@ -7915,6 +7921,8 @@ export class PokemonSummonData {
constructor(source?: PokemonSummonData | Partial<PokemonSummonData>) { constructor(source?: PokemonSummonData | Partial<PokemonSummonData>) {
if (!isNullOrUndefined(source)) { if (!isNullOrUndefined(source)) {
Object.assign(this, source) Object.assign(this, source)
this.moveset &&= this.moveset.map(m => PokemonMove.loadMove(m))
this.tags &&= this.tags.map(t => loadBattlerTag(t))
} }
} }
} }

View File

@ -2,14 +2,14 @@ import { BattleType } from "#enums/battle-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { Gender } from "../data/gender"; import type { Gender } from "../data/gender";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species"; import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species";
import type { Status } from "../data/status-effect"; import type { Status } from "../data/status-effect";
import Pokemon, { EnemyPokemon, PokemonBattleData, PokemonMove, PokemonSummonData } from "../field/pokemon"; import Pokemon, { EnemyPokemon, PokemonBattleData, PokemonMove, PokemonSummonData } from "../field/pokemon";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import type { Variant } from "#app/sprites/variant"; import type { Variant } from "#app/sprites/variant";
import type { Biome } from "#enums/biome"; import type { Biome } from "#enums/biome";
import { Moves } from "#enums/moves"; import type { Moves } from "#enums/moves";
import type { Species } from "#enums/species"; import type { Species } from "#enums/species";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
@ -59,11 +59,11 @@ export default class PokemonData {
public fusionTeraType: PokemonType; public fusionTeraType: PokemonType;
public boss: boolean; public boss: boolean;
public bossSegments?: number; public bossSegments: number;
// Effects that need to be preserved between waves // Effects that need to be preserved between waves
public summonData: PokemonSummonData = new PokemonSummonData(); public summonData: PokemonSummonData;
public battleData: PokemonBattleData = new PokemonBattleData(); public battleData: PokemonBattleData;
public summonDataSpeciesFormIndex: number; public summonDataSpeciesFormIndex: number;
public customPokemonData: CustomPokemonData; public customPokemonData: CustomPokemonData;
@ -87,7 +87,7 @@ export default class PokemonData {
this.passive = source.passive; this.passive = source.passive;
this.shiny = sourcePokemon?.isShiny() ?? source.shiny; this.shiny = sourcePokemon?.isShiny() ?? source.shiny;
this.variant = sourcePokemon?.getVariant() ?? source.variant; this.variant = sourcePokemon?.getVariant() ?? source.variant;
this.pokeball = source.pokeball; this.pokeball = source.pokeball ?? PokeballType.POKEBALL;
this.level = source.level; this.level = source.level;
this.exp = source.exp; this.exp = source.exp;
this.levelExp = source.levelExp; this.levelExp = source.levelExp;
@ -98,7 +98,7 @@ export default class PokemonData {
// TODO: Can't we move some of this verification stuff to an upgrade script? // TODO: Can't we move some of this verification stuff to an upgrade script?
this.nature = source.nature ?? Nature.HARDY; this.nature = source.nature ?? Nature.HARDY;
this.moveset = source.moveset ?? [new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.GROWL)]; this.moveset = source.moveset.map((m: any) => PokemonMove.loadMove(m));
this.status = source.status ?? null; this.status = source.status ?? null;
this.friendship = source.friendship ?? getPokemonSpecies(this.species).baseFriendship; this.friendship = source.friendship ?? getPokemonSpecies(this.species).baseFriendship;
this.metLevel = source.metLevel || 5; this.metLevel = source.metLevel || 5;

View File

@ -1,11 +1,9 @@
import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator";
import { loadBattlerTag } from "#app/data/battler-tags";
import { Status } from "#app/data/status-effect"; import { Status } from "#app/data/status-effect";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon";
import type { SessionSaveData } from "#app/system/game-data"; import type { SessionSaveData } from "#app/system/game-data";
import PokemonData from "#app/system/pokemon-data"; import type PokemonData from "#app/system/pokemon-data";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { PokeballType } from "#enums/pokeball";
/** /**
* Migrate all lingering rage fist data inside `CustomPokemonData`, * Migrate all lingering rage fist data inside `CustomPokemonData`,
@ -23,23 +21,26 @@ const migratePartyData: SessionSaveMigrator = {
pkmnData.status.sleepTurnsRemaining, pkmnData.status.sleepTurnsRemaining,
); );
// remove empty moves from moveset // remove empty moves from moveset
pkmnData.moveset = pkmnData.moveset.filter(m => !!m) ?? [ pkmnData.moveset = (pkmnData.moveset ?? [new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.GROWL)]).filter(
new PokemonMove(Moves.TACKLE), m => !!m,
new PokemonMove(Moves.GROWL), );
]; // only edit summondata moveset if exists
pkmnData.pokeball ??= PokeballType.POKEBALL; pkmnData.summonData.moveset &&= pkmnData.summonData.moveset.filter(m => !!m);
pkmnData.summonData.tags = pkmnData.summonData.tags.map((t: any) => loadBattlerTag(t));
if ( if (
pkmnData.customPokemonData &&
"hitsRecCount" in pkmnData.customPokemonData && "hitsRecCount" in pkmnData.customPokemonData &&
typeof pkmnData.customPokemonData["hitsRecCount"] === "number" typeof pkmnData.customPokemonData["hitsRecCount"] === "number"
) { ) {
pkmnData.battleData.hitCount = pkmnData.customPokemonData?.["hitsRecCount"]; // transfer old hit count stat to battleData.
// No need to reset it as new Pokemon
pkmnData.battleData.hitCount = pkmnData.customPokemonData["hitsRecCount"];
} }
pkmnData = new PokemonData(pkmnData); return pkmnData;
}; };
data.party.forEach(mapParty); data.party = data.party.map(mapParty);
data.enemyParty.forEach(mapParty); data.enemyParty = data.enemyParty.map(mapParty);
}, },
}; };

View File

@ -1,6 +1,7 @@
import { RepeatBerryNextTurnAbAttr } from "#app/data/abilities/ability"; import { RepeatBerryNextTurnAbAttr } from "#app/data/abilities/ability";
import { getBerryEffectFunc } from "#app/data/berry"; import { getBerryEffectFunc } from "#app/data/berry";
import Pokemon from "#app/field/pokemon"; import Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
@ -65,6 +66,37 @@ describe("Abilities - Cud Chew", () => {
expect(farigiraf.turnData.berriesEaten).toEqual([]); expect(farigiraf.turnData.berriesEaten).toEqual([]);
}); });
it("shouldn't show ability popup for end-of-turn storage", async () => {
const abDisplaySpy = vi.spyOn(globalScene, "queueAbilityDisplay");
await game.classicMode.startBattle([Species.FARIGIRAF]);
const farigiraf = game.scene.getPlayerPokemon()!;
farigiraf.hp = 1; // needed to allow sitrus procs
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
// doesn't trigger since cud chew hasn't eaten berry yet
expect(abDisplaySpy).not.toHaveBeenCalledWith(farigiraf);
await game.toNextTurn();
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase");
// globalScene.queueAbilityDisplay should be called twice: once to show cud chew before regurgitating berries,
// once to hide after finishing application
expect(abDisplaySpy).toBeCalledTimes(2);
expect(abDisplaySpy.mock.calls[0][0]).toBe(farigiraf);
expect(abDisplaySpy.mock.calls[0][2]).toBe(true);
expect(abDisplaySpy.mock.calls[1][0]).toBe(farigiraf);
expect(abDisplaySpy.mock.calls[1][2]).toBe(false);
await game.phaseInterceptor.to("TurnEndPhase");
// not called again at turn end
expect(abDisplaySpy).toBeCalledTimes(2);
});
it("can store multiple berries across 2 turns with teatime", async () => { it("can store multiple berries across 2 turns with teatime", async () => {
// always eat first berry for stuff cheeks & company // always eat first berry for stuff cheeks & company
vi.spyOn(Pokemon.prototype, "randSeedInt").mockReturnValue(0); vi.spyOn(Pokemon.prototype, "randSeedInt").mockReturnValue(0);

View File

@ -254,7 +254,7 @@ describe("Abilities - Harvest", () => {
game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase", false); await game.phaseInterceptor.to("TurnEndPhase", false);
// won;t trigger harvest since we didn't lose the berry (it just doesn't ever add it to the array) // won't trigger harvest since we didn't lose the berry (it just doesn't ever add it to the array)
expect(game.scene.getPlayerPokemon()?.battleData.berriesEaten).toEqual([]); expect(game.scene.getPlayerPokemon()?.battleData.berriesEaten).toEqual([]);
expectBerriesContaining(...initBerries); expectBerriesContaining(...initBerries);
}); });

View File

@ -6,9 +6,6 @@ import type BattleScene from "#app/battle-scene";
import { Moves } from "#app/enums/moves"; import { Moves } from "#app/enums/moves";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import PokemonData from "#app/system/pokemon-data";
import { PlayerPokemon } from "#app/field/pokemon";
import { getPokemonSpecies } from "#app/data/pokemon-species";
describe("Spec - Pokemon", () => { describe("Spec - Pokemon", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -212,29 +209,4 @@ describe("Spec - Pokemon", () => {
expect(types[1]).toBe(PokemonType.DARK); expect(types[1]).toBe(PokemonType.DARK);
}); });
}); });
// TODO: Remove/rework after save data overhaul
it("should preserve common fields when converting to and from PokemonData", async () => {
await game.classicMode.startBattle([Species.ALAKAZAM]);
const alakazam = game.scene.getPlayerPokemon()!;
expect(alakazam).toBeDefined();
alakazam.hp = 5;
const alakaData = new PokemonData(alakazam);
const alaka2 = new PlayerPokemon(
getPokemonSpecies(Species.ALAKAZAM),
5,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
alakaData,
);
for (const key of Object.keys(alakazam).filter(k => k in alakaData)) {
expect(alakazam[key]).toEqual(alaka2[key]);
}
});
}); });

View File

@ -32,6 +32,7 @@ describe("Moves - Rage Fist", () => {
.moveset([Moves.RAGE_FIST, Moves.SPLASH, Moves.SUBSTITUTE, Moves.TIDY_UP]) .moveset([Moves.RAGE_FIST, Moves.SPLASH, Moves.SUBSTITUTE, Moves.TIDY_UP])
.startingLevel(100) .startingLevel(100)
.enemyLevel(1) .enemyLevel(1)
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH) .enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.DOUBLE_KICK); .enemyMoveset(Moves.DOUBLE_KICK);
@ -39,9 +40,7 @@ describe("Moves - Rage Fist", () => {
}); });
it("should gain power per hit taken", async () => { it("should gain power per hit taken", async () => {
game.override.enemySpecies(Species.MAGIKARP); await game.classicMode.startBattle([Species.FEEBAS]);
await game.classicMode.startBattle([Species.MAGIKARP]);
game.move.select(Moves.RAGE_FIST); game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
@ -51,9 +50,7 @@ describe("Moves - Rage Fist", () => {
}); });
it("caps at 6 hits taken", async () => { it("caps at 6 hits taken", async () => {
game.override.enemySpecies(Species.MAGIKARP); await game.classicMode.startBattle([Species.FEEBAS]);
await game.classicMode.startBattle([Species.MAGIKARP]);
// spam splash against magikarp hitting us 2 times per turn // spam splash against magikarp hitting us 2 times per turn
game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH);
@ -72,10 +69,10 @@ describe("Moves - Rage Fist", () => {
expect(move.calculateBattlePower).toHaveLastReturnedWith(350); expect(move.calculateBattlePower).toHaveLastReturnedWith(350);
}); });
it("should not count subsitute hits or confusion damage", async () => { it("should not count substitute hits or confusion damage", async () => {
game.override.enemySpecies(Species.SHUCKLE).enemyMoveset([Moves.CONFUSE_RAY, Moves.DOUBLE_KICK]); game.override.enemySpecies(Species.SHUCKLE).enemyMoveset([Moves.CONFUSE_RAY, Moves.DOUBLE_KICK]);
await game.classicMode.startBattle([Species.MAGIKARP]); await game.classicMode.startBattle([Species.REGIROCK]);
game.move.select(Moves.SUBSTITUTE); game.move.select(Moves.SUBSTITUTE);
await game.forceEnemyMove(Moves.DOUBLE_KICK); await game.forceEnemyMove(Moves.DOUBLE_KICK);
@ -92,68 +89,52 @@ describe("Moves - Rage Fist", () => {
await game.toNextTurn(); await game.toNextTurn();
game.move.select(Moves.RAGE_FIST); game.move.select(Moves.RAGE_FIST);
await game.forceEnemyMove(Moves.CONFUSE_RAY);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.move.forceConfusionActivation(true); await game.move.forceConfusionActivation(true);
await game.forceEnemyMove(Moves.DOUBLE_KICK); await game.toNextTurn();
await game.phaseInterceptor.to("BerryPhase");
// didn't go up // didn't go up from hitting ourself
expect(game.scene.getPlayerPokemon()?.battleData.hitCount).toBe(0); expect(game.scene.getPlayerPokemon()?.battleData.hitCount).toBe(0);
await game.toNextTurn();
game.move.select(Moves.RAGE_FIST);
await game.forceEnemyMove(Moves.DOUBLE_KICK);
await game.move.forceConfusionActivation(false);
await game.toNextTurn();
expect(move.calculateBattlePower).toHaveLastReturnedWith(150);
expect(game.scene.getPlayerPokemon()?.battleData.hitCount).toBe(2);
}); });
it("should maintain hits recieved between wild waves", async () => { it("should maintain hits recieved between wild waves", async () => {
game.override.enemySpecies(Species.MAGIKARP).startingWave(1); await game.classicMode.startBattle([Species.FEEBAS]);
await game.classicMode.startBattle([Species.MAGIKARP]);
game.move.select(Moves.RAGE_FIST); game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.toNextWave(); await game.toNextWave();
game.move.select(Moves.RAGE_FIST); expect(game.scene.getPlayerPokemon()?.battleData.hitCount).toBe(2);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("BerryPhase", false);
expect(move.calculateBattlePower).toHaveLastReturnedWith(250);
expect(game.scene.getPlayerPokemon()?.battleData.hitCount).toBe(4);
});
it("should reset hits recieved before trainer battles", async () => {
game.override.enemySpecies(Species.MAGIKARP).moveset(Moves.DOUBLE_IRON_BASH);
await game.classicMode.startBattle([Species.MARSHADOW]);
const marshadow = game.scene.getPlayerPokemon()!;
expect(marshadow).toBeDefined();
// beat up a magikarp
game.move.select(Moves.RAGE_FIST); game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("TurnEndPhase"); await game.phaseInterceptor.to("TurnEndPhase");
expect(game.scene.getPlayerPokemon()?.battleData.hitCount).toBe(4);
expect(move.calculateBattlePower).toHaveLastReturnedWith(250);
});
it("should reset hits recieved before trainer battles", async () => {
await game.classicMode.startBattle([Species.IRON_HANDS]);
const ironHands = game.scene.getPlayerPokemon()!;
expect(ironHands).toBeDefined();
// beat up a magikarp
game.move.select(Moves.RAGE_FIST);
await game.forceEnemyMove(Moves.DOUBLE_KICK);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("TurnEndPhase");
expect(game.isVictory()).toBe(true); expect(game.isVictory()).toBe(true);
expect(marshadow.battleData.hitCount).toBe(2); expect(ironHands.battleData.hitCount).toBe(2);
expect(move.calculateBattlePower).toHaveLastReturnedWith(150); expect(move.calculateBattlePower).toHaveLastReturnedWith(150);
game.override.battleType(BattleType.TRAINER); game.override.battleType(BattleType.TRAINER);
await game.toNextWave(); await game.toNextWave();
expect(game.scene.lastEnemyTrainer).not.toBeNull(); expect(ironHands.battleData.hitCount).toBe(0);
expect(marshadow.battleData.hitCount).toBe(0);
game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("TurnEndPhase");
expect(move.calculateBattlePower).toHaveLastReturnedWith(150);
}); });
it("should reset the hitRecCounter if we enter new biome", async () => { it("should reset the hitRecCounter if we enter new biome", async () => {
@ -173,24 +154,39 @@ describe("Moves - Rage Fist", () => {
}); });
it("should not reset the hitRecCounter if switched out", async () => { it("should not reset the hitRecCounter if switched out", async () => {
game.override.enemySpecies(Species.MAGIKARP).startingWave(1).enemyMoveset(Moves.TACKLE); game.override.enemyMoveset(Moves.TACKLE);
const getPartyHitCount = () =>
game.scene
.getPlayerParty()
.filter(p => !!p)
.map(m => m.battleData.hitCount);
await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
// Charizard hit
game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.toNextTurn(); await game.toNextTurn();
expect(getPartyHitCount()).toEqual([1, 0]);
// blastoise switched in & hit
game.doSwitchPokemon(1); game.doSwitchPokemon(1);
await game.toNextTurn(); await game.toNextTurn();
expect(getPartyHitCount()).toEqual([1, 1]);
// charizard switched in & hit
game.doSwitchPokemon(1); game.doSwitchPokemon(1);
await game.toNextTurn(); await game.toNextTurn();
expect(getPartyHitCount()).toEqual([2, 1]);
// Charizard rage fist
game.move.select(Moves.RAGE_FIST); game.move.select(Moves.RAGE_FIST);
await game.phaseInterceptor.to("MoveEndPhase"); await game.phaseInterceptor.to("MoveEndPhase");
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(Species.CHARIZARD); const charizard = game.scene.getPlayerPokemon()!;
expect(charizard).toBeDefined();
expect(charizard.species.speciesId).toBe(Species.CHARIZARD);
expect(move.calculateBattlePower).toHaveLastReturnedWith(150); expect(move.calculateBattlePower).toHaveLastReturnedWith(150);
}); });
}); });

View File

@ -576,7 +576,7 @@ export default class GameManager {
* Intercepts `TurnStartPhase` and mocks {@linkcode TurnStartPhase.getSpeedOrder}'s return value. * Intercepts `TurnStartPhase` and mocks {@linkcode TurnStartPhase.getSpeedOrder}'s return value.
* Used to manually modify Pokemon turn order. * Used to manually modify Pokemon turn order.
* Note: This *DOES NOT* account for priority, only speed. * Note: This *DOES NOT* account for priority, only speed.
* @param {BattlerIndex[]} order The turn order to set * @param order - The turn order to set as an array of {@linkcode BattlerIndex}es.
* @example * @example
* ```ts * ```ts
* await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2]); * await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2]);