From f1e500400ac367ed1415daee61340e0c3d4424aa Mon Sep 17 00:00:00 2001 From: Jimmybald1 <122436263+Jimmybald1@users.noreply.github.com> Date: Tue, 14 Jan 2025 23:45:11 +0100 Subject: [PATCH 01/14] [Balance][Bug] Fix off by one error in Generate random biome and Daily Mode now has its own function (#5121) * [Balance] Allow Island and Laboratory in Generate Random Biome * [Bug] Fix off by one error in Generate Random Biome * [Balance] Daily Mode now has its own Generate Random Starting Biome * [Misc] Filtering out Town and End specifically instead of assuming enum value stays consistent forever --------- Co-authored-by: Jimmybald1 <147992650+IBBCalc@users.noreply.github.com> --- src/battle-scene.ts | 8 ++--- src/data/daily-run.ts | 74 +++++++++++++++++++++++++++++++++++++++++++ src/game-mode.ts | 3 +- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 6cc33dc476d..6db9311bac8 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1865,7 +1865,7 @@ export default class BattleScene extends SceneBase { generateRandomBiome(waveIndex: integer): Biome { const relWave = waveIndex % 250; - const biomes = Utils.getEnumValues(Biome).slice(1, Utils.getEnumValues(Biome).filter(b => b >= 40).length * -1); + const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END); const maxDepth = biomeDepths[Biome.END][0] - 2; const depthWeights = new Array(maxDepth + 1).fill(null) .map((_, i: integer) => ((1 - Math.min(Math.abs((i / (maxDepth - 1)) - (relWave / 250)) + 0.25, 1)) / 0.75) * 250); @@ -1878,9 +1878,9 @@ export default class BattleScene extends SceneBase { const randInt = Utils.randSeedInt(totalWeight); - for (const biome of biomes) { - if (randInt < biomeThresholds[biome]) { - return biome; + for (let i = 0; i < biomes.length; i++) { + if (randInt < biomeThresholds[i]) { + return biomes[i]; } } diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index b0ce38cebd2..2a4a78a9caf 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -8,6 +8,7 @@ import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import { Biome } from "#app/enums/biome"; export interface DailyRunConfig { seed: integer; @@ -71,3 +72,76 @@ function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLeve pokemon.destroy(); return starter; } + +interface BiomeWeights { + [key: integer]: integer +} + +// Initially weighted by amount of exits each biome has +// Town and End are set to 0 however +// And some other biomes were balanced +1/-1 based on average size of the total daily. +const dailyBiomeWeights: BiomeWeights = { + [Biome.CAVE]: 3, + [Biome.LAKE]: 3, + [Biome.PLAINS]: 3, + [Biome.SNOWY_FOREST]: 3, + [Biome.SWAMP]: 3, // 2 -> 3 + [Biome.TALL_GRASS]: 3, // 2 -> 3 + + [Biome.ABYSS]: 2, // 3 -> 2 + [Biome.RUINS]: 2, + [Biome.BADLANDS]: 2, + [Biome.BEACH]: 2, + [Biome.CONSTRUCTION_SITE]: 2, + [Biome.DESERT]: 2, + [Biome.DOJO]: 2, // 3 -> 2 + [Biome.FACTORY]: 2, + [Biome.FAIRY_CAVE]: 2, + [Biome.FOREST]: 2, + [Biome.GRASS]: 2, // 1 -> 2 + [Biome.MEADOW]: 2, + [Biome.MOUNTAIN]: 2, // 3 -> 2 + [Biome.SEA]: 2, + [Biome.SEABED]: 2, + [Biome.SLUM]: 2, + [Biome.TEMPLE]: 2, // 3 -> 2 + [Biome.VOLCANO]: 2, + + [Biome.GRAVEYARD]: 1, + [Biome.ICE_CAVE]: 1, + [Biome.ISLAND]: 1, + [Biome.JUNGLE]: 1, + [Biome.LABORATORY]: 1, + [Biome.METROPOLIS]: 1, + [Biome.POWER_PLANT]: 1, + [Biome.SPACE]: 1, + [Biome.WASTELAND]: 1, + + [Biome.TOWN]: 0, + [Biome.END]: 0, +}; + +export function getDailyStartingBiome(): Biome { + const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END); + + let totalWeight = 0; + const biomeThresholds: integer[] = []; + for (const biome of biomes) { + // Keep track of the total weight + totalWeight += dailyBiomeWeights[biome]; + + // Keep track of each biomes cumulative weight + biomeThresholds.push(totalWeight); + } + + const randInt = Utils.randSeedInt(totalWeight); + + for (let i = 0; i < biomes.length; i++) { + if (randInt < biomeThresholds[i]) { + return biomes[i]; + } + } + + // Fallback in case something went wrong + return biomes[Utils.randSeedInt(biomes.length)]; +} diff --git a/src/game-mode.ts b/src/game-mode.ts index 4e0f5715851..78a65a54890 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -12,6 +12,7 @@ import { Biome } from "#enums/biome"; import { Species } from "#enums/species"; import { Challenges } from "./enums/challenges"; import { globalScene } from "#app/global-scene"; +import { getDailyStartingBiome } from "./data/daily-run"; export enum GameModes { CLASSIC, @@ -120,7 +121,7 @@ export class GameMode implements GameModeConfig { getStartingBiome(): Biome { switch (this.modeId) { case GameModes.DAILY: - return globalScene.generateRandomBiome(this.getWaveForDifficulty(1)); + return getDailyStartingBiome(); default: return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN; } From 001b61c1c713d4d481904d0cb7af0490f0819824 Mon Sep 17 00:00:00 2001 From: schmidtc1 <62030095+schmidtc1@users.noreply.github.com> Date: Tue, 14 Jan 2025 18:26:35 -0500 Subject: [PATCH 02/14] [Bug][Move] Refactor moves that call a random move (#3380) * Combine moveset from allies and uses it to get a move * Clearer implementation of combining user and teammates' moves * Refactor assist and sleep talk to use metronome's attribute for calling a move * Refactor move filtering in RandomMovesetMoveAttr, creates arrays with invalid moves for assist/sleep talk * Refactor RandomMoveAttr to set moveId in condition, places reused code in callMove in RandomMoveAttr * Correct invalid move lists, adds Max/Z moves to metronome's list * Remove ignoresVirtual from beta merge * Remove Max/Z moves per frutescens' comment * Fix bug with metronome/copycat/assist/sleep talk targeting ally * Experimental async/await to be tested * Refactor other attributes to extend CallMoveAttr * Replace QueuedMove with TurnMove, refactor to attempt two-turn move fix for metronome * Fix Swallow test due to TurnMove refactor * Further fixes for TurnMove refactor * Fix metronome two turn moves for enemy pokemon * Replace nested ternary with if-else block per DayKev's comment * Minor fixes * Adjust command phase args handling * Create metronome test, refactor RandomMoveAttr for easier testing * Add unit test for recharge moves * Refactor Copycat and Mirror Move, adjust move targeting * Add unit test for ally targeting with Aromatic Mist * Add tests for secondary effects and recharge moves for metronome * Add test for Roar, remove test for Acupressure * Create test for Assist * Add test for assist failing * Add sleep talk unit test coverage * Adjust move-phase to better track last move for copycat, write and update unit tests for assist/copycat * Create moveHistory in Battle to track all moves used, adjust mirror move to use this, writes unit tests * Correct mirror move implementation, rewrite unit test to adjust * Add docs to attrs, update assist to only grab allies sets * Update assist unit test to match expected functionality * Update metronome unit test to use getMoveOverride * Update copycat unit test to use metronome getMoveOverride mock * Fix phase interception * Add docs from missed conversations * Update assist tests to use manual moveset overrides Minor fixes to other tests * Remove `export` from `CallMoveAttr` * Add missing `.unimplemented()` to some Max- and Z-Moves --------- Co-authored-by: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle.ts | 14 +- src/data/battler-tags.ts | 2 +- src/data/move.ts | 864 ++++++++++++++++++----------- src/field/pokemon.ts | 35 +- src/phases/command-phase.ts | 28 +- src/phases/move-phase.ts | 15 +- src/test/moves/assist.test.ts | 105 ++++ src/test/moves/copycat.test.ts | 91 +++ src/test/moves/metronome.test.ts | 113 ++++ src/test/moves/mirror_move.test.ts | 84 +++ src/test/moves/sleep_talk.test.ts | 75 +++ src/test/moves/spit_up.test.ts | 6 +- src/test/moves/stockpile.test.ts | 2 +- src/test/moves/swallow.test.ts | 6 +- 14 files changed, 1069 insertions(+), 371 deletions(-) create mode 100644 src/test/moves/assist.test.ts create mode 100644 src/test/moves/copycat.test.ts create mode 100644 src/test/moves/metronome.test.ts create mode 100644 src/test/moves/mirror_move.test.ts create mode 100644 src/test/moves/sleep_talk.test.ts diff --git a/src/battle.ts b/src/battle.ts index 6dae845bfe1..b1196bb0139 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -7,7 +7,7 @@ import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/mod import type { PokeballType } from "#enums/pokeball"; import { trainerConfigs } from "#app/data/trainer-config"; import { SpeciesFormKey } from "#enums/species-form-key"; -import type { EnemyPokemon, PlayerPokemon, QueuedMove } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon, TurnMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattleSpec } from "#enums/battle-spec"; @@ -45,12 +45,12 @@ export enum BattlerIndex { } export interface TurnCommand { - command: Command; - cursor?: number; - move?: QueuedMove; - targets?: BattlerIndex[]; - skip?: boolean; - args?: any[]; + command: Command; + cursor?: number; + move?: TurnMove; + targets?: BattlerIndex[]; + skip?: boolean; + args?: any[]; } export interface FaintLogEntry { diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 2743c36e7b5..3a58ff4a99d 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -612,7 +612,7 @@ export class InterruptedTag extends BattlerTag { super.onAdd(pokemon); pokemon.getMoveQueue().shift(); - pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.OTHER }); + pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.OTHER, targets: []}); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { diff --git a/src/data/move.ts b/src/data/move.ts index 54b10a4ab80..f3a1f3aa119 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -87,7 +87,6 @@ export enum MoveFlags { NONE = 0, MAKES_CONTACT = 1 << 0, IGNORE_PROTECT = 1 << 1, - IGNORE_VIRTUAL = 1 << 2, /** * Sound-based moves have the following effects: * - Pokemon with the {@linkcode Abilities.SOUNDPROOF Soundproof Ability} are unaffected by other Pokemon's sound-based moves. @@ -98,35 +97,35 @@ export enum MoveFlags { * * cf https://bulbapedia.bulbagarden.net/wiki/Sound-based_move */ - SOUND_BASED = 1 << 3, - HIDE_USER = 1 << 4, - HIDE_TARGET = 1 << 5, - BITING_MOVE = 1 << 6, - PULSE_MOVE = 1 << 7, - PUNCHING_MOVE = 1 << 8, - SLICING_MOVE = 1 << 9, + SOUND_BASED = 1 << 2, + HIDE_USER = 1 << 3, + HIDE_TARGET = 1 << 4, + BITING_MOVE = 1 << 5, + PULSE_MOVE = 1 << 6, + PUNCHING_MOVE = 1 << 7, + SLICING_MOVE = 1 << 8, /** * Indicates a move should be affected by {@linkcode Abilities.RECKLESS} * @see {@linkcode Move.recklessMove()} */ - RECKLESS_MOVE = 1 << 10, + RECKLESS_MOVE = 1 << 9, /** Indicates a move should be affected by {@linkcode Abilities.BULLETPROOF} */ - BALLBOMB_MOVE = 1 << 11, + BALLBOMB_MOVE = 1 << 10, /** Grass types and pokemon with {@linkcode Abilities.OVERCOAT} are immune to powder moves */ - POWDER_MOVE = 1 << 12, + POWDER_MOVE = 1 << 11, /** Indicates a move should trigger {@linkcode Abilities.DANCER} */ - DANCE_MOVE = 1 << 13, + DANCE_MOVE = 1 << 12, /** Indicates a move should trigger {@linkcode Abilities.WIND_RIDER} */ - WIND_MOVE = 1 << 14, + WIND_MOVE = 1 << 13, /** Indicates a move should trigger {@linkcode Abilities.TRIAGE} */ - TRIAGE_MOVE = 1 << 15, - IGNORE_ABILITIES = 1 << 16, + TRIAGE_MOVE = 1 << 14, + IGNORE_ABILITIES = 1 << 15, /** Enables all hits of a multi-hit move to be accuracy checked individually */ - CHECK_ALL_HITS = 1 << 17, + CHECK_ALL_HITS = 1 << 16, /** Indicates a move is able to bypass its target's Substitute (if the target has one) */ - IGNORE_SUBSTITUTE = 1 << 18, + IGNORE_SUBSTITUTE = 1 << 17, /** Indicates a move is able to be redirected to allies in a double battle if the attacker faints */ - REDIRECT_COUNTER = 1 << 19, + REDIRECT_COUNTER = 1 << 18, } type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; @@ -441,16 +440,6 @@ export default class Move implements Localizable { return this; } - /** - * Sets the {@linkcode MoveFlags.IGNORE_VIRTUAL} flag for the calling Move - * @see {@linkcode Moves.NATURE_POWER} - * @returns The {@linkcode Move} that called this function - */ - ignoresVirtual(): this { - this.setFlag(MoveFlags.IGNORE_VIRTUAL, true); - return this; - } - /** * Sets the {@linkcode MoveFlags.SOUND_BASED} flag for the calling Move * @see {@linkcode Moves.UPROAR} @@ -1552,7 +1541,7 @@ export class RecoilAttr extends MoveEffectAttr { } // Chloroblast and Struggle should not deal recoil damage if the move was not successful - if (this.useHp && [ MoveResult.FAIL, MoveResult.MISS ].includes(user.getLastXMoves(1)[0]?.result)) { + if (this.useHp && [ MoveResult.FAIL, MoveResult.MISS ].includes(user.getLastXMoves(1)[0]?.result ?? MoveResult.FAIL)) { return false; } @@ -6483,52 +6472,46 @@ export class FirstMoveTypeAttr extends MoveEffectAttr { } } -export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { - private enemyMoveset: boolean | null; - - constructor(enemyMoveset?: boolean) { - super(); - - this.enemyMoveset = enemyMoveset!; // TODO: is this bang correct? - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const moveset = (!this.enemyMoveset ? user : target).getMoveset(); - const moves = moveset.filter(m => !m?.getMove().hasFlag(MoveFlags.IGNORE_VIRTUAL)); - if (moves.length) { - const move = moves[user.randSeedInt(moves.length)]; - const moveIndex = moveset.findIndex(m => m?.moveId === move?.moveId); - const moveTargets = getMoveTargets(user, move?.moveId!); // TODO: is this bang correct? - if (!moveTargets.targets.length) { - return false; - } - let selectTargets: BattlerIndex[]; - switch (true) { - case (moveTargets.multiple || moveTargets.targets.length === 1): { - selectTargets = moveTargets.targets; - break; - } - case (moveTargets.targets.indexOf(target.getBattlerIndex()) > -1): { - selectTargets = [ target.getBattlerIndex() ]; - break; - } - default: { - moveTargets.targets.splice(moveTargets.targets.indexOf(user.getAlly().getBattlerIndex())); - selectTargets = [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; - break; - } - } - const targets = selectTargets; - user.getMoveQueue().push({ move: move?.moveId!, targets: targets, ignorePP: true }); // TODO: is this bang correct? - globalScene.unshiftPhase(new MovePhase(user, targets, moveset[moveIndex]!, true)); // There's a PR to re-do the move(s) that use this Attr, gonna put `!` for now - return true; +/** + * Attribute used to call a move. + * Used by other move attributes: {@linkcode RandomMoveAttr}, {@linkcode RandomMovesetMoveAttr}, {@linkcode CopyMoveAttr} + * @see {@linkcode apply} for move call + * @extends OverrideMoveEffectAttr + */ +class CallMoveAttr extends OverrideMoveEffectAttr { + protected invalidMoves: Moves[]; + protected hasTarget: boolean; + async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + const replaceMoveTarget = move.moveTarget === MoveTarget.NEAR_OTHER ? MoveTarget.NEAR_ENEMY : undefined; + const moveTargets = getMoveTargets(user, move.id, replaceMoveTarget); + if (moveTargets.targets.length === 0) { + return false; } + const targets = moveTargets.multiple || moveTargets.targets.length === 1 + ? moveTargets.targets + : [ this.hasTarget ? target.getBattlerIndex() : moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; // account for Mirror Move having a target already + user.getMoveQueue().push({ move: move.id, targets: targets, virtual: true, ignorePP: true }); + globalScene.unshiftPhase(new MovePhase(user, targets, new PokemonMove(move.id, 0, 0, true), true, true)); - return false; + await Promise.resolve(initMoveAnim(move.id).then(() => { + loadMoveAnimAssets([ move.id ], true); + })); + return true; } } -export class RandomMoveAttr extends OverrideMoveEffectAttr { +/** + * Attribute used to call a random move. + * Used for {@linkcode Moves.METRONOME} + * @see {@linkcode apply} for move selection and move call + * @extends CallMoveAttr to call a selected move + */ +export class RandomMoveAttr extends CallMoveAttr { + constructor(invalidMoves: Moves[]) { + super(); + this.invalidMoves = invalidMoves; + } + /** * This function exists solely to allow tests to override the randomly selected move by mocking this function. */ @@ -6536,31 +6519,353 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr { return null; } + /** + * User calls a random moveId. + * + * Invalid moves are indicated by what is passed in to invalidMoves: {@linkcode invalidMetronomeMoves} + * @param user Pokemon that used the move and will call a random move + * @param target Pokemon that will be targeted by the random move (if single target) + * @param move Move being used + * @param args Unused + */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - return new Promise(resolve => { - const moveIds = Utils.getEnumValues(Moves).filter(m => !allMoves[m].hasFlag(MoveFlags.IGNORE_VIRTUAL) && !allMoves[m].name.endsWith(" (N)")); - const moveId = this.getMoveOverride() ?? moveIds[user.randSeedInt(moveIds.length)]; - - const moveTargets = getMoveTargets(user, moveId); - if (!moveTargets.targets.length) { - resolve(false); - return; - } - const targets = moveTargets.multiple || moveTargets.targets.length === 1 - ? moveTargets.targets - : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 - ? [ target.getBattlerIndex() ] - : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; - user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true }); - globalScene.unshiftPhase(new MovePhase(user, targets, new PokemonMove(moveId, 0, 0, true), true)); - initMoveAnim(moveId).then(() => { - loadMoveAnimAssets([ moveId ], true) - .then(() => resolve(true)); - }); - }); + const moveIds = Utils.getEnumValues(Moves).map(m => !this.invalidMoves.includes(m) && !allMoves[m].name.endsWith(" (N)") ? m : Moves.NONE); + let moveId: Moves = Moves.NONE; + do { + moveId = this.getMoveOverride() ?? moveIds[user.randSeedInt(moveIds.length)]; + } + while (moveId === Moves.NONE); + return super.apply(user, target, allMoves[moveId], args); } } +/** + * Attribute used to call a random move in the user or party's moveset. + * Used for {@linkcode Moves.ASSIST} and {@linkcode Moves.SLEEP_TALK} + * + * Fails if the user has no callable moves. + * + * Invalid moves are indicated by what is passed in to invalidMoves: {@linkcode invalidAssistMoves} or {@linkcode invalidSleepTalkMoves} + * @extends RandomMoveAttr to use the callMove function on a moveId + * @see {@linkcode getCondition} for move selection + */ +export class RandomMovesetMoveAttr extends CallMoveAttr { + private includeParty: boolean; + private moveId: number; + constructor(invalidMoves: Moves[], includeParty: boolean = false) { + super(); + this.includeParty = includeParty; + this.invalidMoves = invalidMoves; + } + + /** + * User calls a random moveId selected in {@linkcode getCondition} + * @param user Pokemon that used the move and will call a random move + * @param target Pokemon that will be targeted by the random move (if single target) + * @param move Move being used + * @param args Unused + */ + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + return super.apply(user, target, allMoves[this.moveId], args); + } + + getCondition(): MoveConditionFunc { + return (user, target, move) => { + // includeParty will be true for Assist, false for Sleep Talk + let allies: Pokemon[]; + if (this.includeParty) { + allies = user.isPlayer() ? globalScene.getPlayerParty().filter(p => p !== user) : globalScene.getEnemyParty().filter(p => p !== user); + } else { + allies = [ user ]; + } + const partyMoveset = allies.map(p => p.moveset).flat(); + const moves = partyMoveset.filter(m => !this.invalidMoves.includes(m!.moveId) && !m!.getMove().name.endsWith(" (N)")); + if (moves.length === 0) { + return false; + } + + this.moveId = moves[user.randSeedInt(moves.length)]!.moveId; + return true; + }; + } +} + +const invalidMetronomeMoves: Moves[] = [ + Moves.AFTER_YOU, + Moves.APPLE_ACID, + Moves.ARMOR_CANNON, + Moves.ASSIST, + Moves.ASTRAL_BARRAGE, + Moves.AURA_WHEEL, + Moves.BANEFUL_BUNKER, + Moves.BEAK_BLAST, + Moves.BEHEMOTH_BASH, + Moves.BEHEMOTH_BLADE, + Moves.BELCH, + Moves.BESTOW, + Moves.BLAZING_TORQUE, + Moves.BODY_PRESS, + Moves.BRANCH_POKE, + Moves.BREAKING_SWIPE, + Moves.CELEBRATE, + Moves.CHATTER, + Moves.CHILLING_WATER, + Moves.CHILLY_RECEPTION, + Moves.CLANGOROUS_SOUL, + Moves.COLLISION_COURSE, + Moves.COMBAT_TORQUE, + Moves.COMEUPPANCE, + Moves.COPYCAT, + Moves.COUNTER, + Moves.COVET, + Moves.CRAFTY_SHIELD, + Moves.DECORATE, + Moves.DESTINY_BOND, + Moves.DETECT, + Moves.DIAMOND_STORM, + Moves.DOODLE, + Moves.DOUBLE_IRON_BASH, + Moves.DOUBLE_SHOCK, + Moves.DRAGON_ASCENT, + Moves.DRAGON_ENERGY, + Moves.DRUM_BEATING, + Moves.DYNAMAX_CANNON, + Moves.ELECTRO_DRIFT, + Moves.ENDURE, + Moves.ETERNABEAM, + Moves.FALSE_SURRENDER, + Moves.FEINT, + Moves.FIERY_WRATH, + Moves.FILLET_AWAY, + Moves.FLEUR_CANNON, + Moves.FOCUS_PUNCH, + Moves.FOLLOW_ME, + Moves.FREEZE_SHOCK, + Moves.FREEZING_GLARE, + Moves.GLACIAL_LANCE, + Moves.GRAV_APPLE, + Moves.HELPING_HAND, + Moves.HOLD_HANDS, + Moves.HYPER_DRILL, + Moves.HYPERSPACE_FURY, + Moves.HYPERSPACE_HOLE, + Moves.ICE_BURN, + Moves.INSTRUCT, + Moves.JET_PUNCH, + Moves.JUNGLE_HEALING, + Moves.KINGS_SHIELD, + Moves.LIFE_DEW, + Moves.LIGHT_OF_RUIN, + Moves.MAKE_IT_RAIN, + Moves.MAGICAL_TORQUE, + Moves.MAT_BLOCK, + Moves.ME_FIRST, + Moves.METEOR_ASSAULT, + Moves.METRONOME, + Moves.MIMIC, + Moves.MIND_BLOWN, + Moves.MIRROR_COAT, + Moves.MIRROR_MOVE, + Moves.MOONGEIST_BEAM, + Moves.NATURE_POWER, + Moves.NATURES_MADNESS, + Moves.NOXIOUS_TORQUE, + Moves.OBSTRUCT, + Moves.ORDER_UP, + Moves.ORIGIN_PULSE, + Moves.OVERDRIVE, + Moves.PHOTON_GEYSER, + Moves.PLASMA_FISTS, + Moves.POPULATION_BOMB, + Moves.POUNCE, + Moves.POWER_SHIFT, + Moves.PRECIPICE_BLADES, + Moves.PROTECT, + Moves.PYRO_BALL, + Moves.QUASH, + Moves.QUICK_GUARD, + Moves.RAGE_FIST, + Moves.RAGE_POWDER, + Moves.RAGING_BULL, + Moves.RAGING_FURY, + Moves.RELIC_SONG, + Moves.REVIVAL_BLESSING, + Moves.RUINATION, + Moves.SALT_CURE, + Moves.SECRET_SWORD, + Moves.SHED_TAIL, + Moves.SHELL_TRAP, + Moves.SILK_TRAP, + Moves.SKETCH, + Moves.SLEEP_TALK, + Moves.SNAP_TRAP, + Moves.SNARL, + Moves.SNATCH, + Moves.SNORE, + Moves.SNOWSCAPE, + Moves.SPECTRAL_THIEF, + Moves.SPICY_EXTRACT, + Moves.SPIKY_SHIELD, + Moves.SPIRIT_BREAK, + Moves.SPOTLIGHT, + Moves.STEAM_ERUPTION, + Moves.STEEL_BEAM, + Moves.STRANGE_STEAM, + Moves.STRUGGLE, + Moves.SUNSTEEL_STRIKE, + Moves.SURGING_STRIKES, + Moves.SWITCHEROO, + Moves.TECHNO_BLAST, + Moves.TERA_STARSTORM, + Moves.THIEF, + Moves.THOUSAND_ARROWS, + Moves.THOUSAND_WAVES, + Moves.THUNDER_CAGE, + Moves.THUNDEROUS_KICK, + Moves.TIDY_UP, + Moves.TRAILBLAZE, + Moves.TRANSFORM, + Moves.TRICK, + Moves.TWIN_BEAM, + Moves.V_CREATE, + Moves.WICKED_BLOW, + Moves.WICKED_TORQUE, + Moves.WIDE_GUARD, +]; + +const invalidAssistMoves: Moves[] = [ + Moves.ASSIST, + Moves.BANEFUL_BUNKER, + Moves.BEAK_BLAST, + Moves.BELCH, + Moves.BESTOW, + Moves.BOUNCE, + Moves.CELEBRATE, + Moves.CHATTER, + Moves.CIRCLE_THROW, + Moves.COPYCAT, + Moves.COUNTER, + Moves.COVET, + Moves.DESTINY_BOND, + Moves.DETECT, + Moves.DIG, + Moves.DIVE, + Moves.DRAGON_TAIL, + Moves.ENDURE, + Moves.FEINT, + Moves.FLY, + Moves.FOCUS_PUNCH, + Moves.FOLLOW_ME, + Moves.HELPING_HAND, + Moves.HOLD_HANDS, + Moves.KINGS_SHIELD, + Moves.MAT_BLOCK, + Moves.ME_FIRST, + Moves.METRONOME, + Moves.MIMIC, + Moves.MIRROR_COAT, + Moves.MIRROR_MOVE, + Moves.NATURE_POWER, + Moves.PHANTOM_FORCE, + Moves.PROTECT, + Moves.RAGE_POWDER, + Moves.ROAR, + Moves.SHADOW_FORCE, + Moves.SHELL_TRAP, + Moves.SKETCH, + Moves.SKY_DROP, + Moves.SLEEP_TALK, + Moves.SNATCH, + Moves.SPIKY_SHIELD, + Moves.SPOTLIGHT, + Moves.STRUGGLE, + Moves.SWITCHEROO, + Moves.THIEF, + Moves.TRANSFORM, + Moves.TRICK, + Moves.WHIRLWIND, +]; + +const invalidSleepTalkMoves: Moves[] = [ + Moves.ASSIST, + Moves.BELCH, + Moves.BEAK_BLAST, + Moves.BIDE, + Moves.BOUNCE, + Moves.COPYCAT, + Moves.DIG, + Moves.DIVE, + Moves.DYNAMAX_CANNON, + Moves.FREEZE_SHOCK, + Moves.FLY, + Moves.FOCUS_PUNCH, + Moves.GEOMANCY, + Moves.ICE_BURN, + Moves.ME_FIRST, + Moves.METRONOME, + Moves.MIRROR_MOVE, + Moves.MIMIC, + Moves.PHANTOM_FORCE, + Moves.RAZOR_WIND, + Moves.SHADOW_FORCE, + Moves.SHELL_TRAP, + Moves.SKETCH, + Moves.SKULL_BASH, + Moves.SKY_ATTACK, + Moves.SKY_DROP, + Moves.SLEEP_TALK, + Moves.SOLAR_BLADE, + Moves.SOLAR_BEAM, + Moves.STRUGGLE, + Moves.UPROAR, +]; + +const invalidCopycatMoves = [ + Moves.ASSIST, + Moves.BANEFUL_BUNKER, + Moves.BEAK_BLAST, + Moves.BEHEMOTH_BASH, + Moves.BEHEMOTH_BLADE, + Moves.BESTOW, + Moves.CELEBRATE, + Moves.CHATTER, + Moves.CIRCLE_THROW, + Moves.COPYCAT, + Moves.COUNTER, + Moves.COVET, + Moves.DESTINY_BOND, + Moves.DETECT, + Moves.DRAGON_TAIL, + Moves.ENDURE, + Moves.FEINT, + Moves.FOCUS_PUNCH, + Moves.FOLLOW_ME, + Moves.HELPING_HAND, + Moves.HOLD_HANDS, + Moves.KINGS_SHIELD, + Moves.MAT_BLOCK, + Moves.ME_FIRST, + Moves.METRONOME, + Moves.MIMIC, + Moves.MIRROR_COAT, + Moves.MIRROR_MOVE, + Moves.PROTECT, + Moves.RAGE_POWDER, + Moves.ROAR, + Moves.SHELL_TRAP, + Moves.SKETCH, + Moves.SLEEP_TALK, + Moves.SNATCH, + Moves.SPIKY_SHIELD, + Moves.SPOTLIGHT, + Moves.STRUGGLE, + Moves.SWITCHEROO, + Moves.THIEF, + Moves.TRANSFORM, + Moves.TRICK, + Moves.WHIRLWIND, +]; + export class NaturePowerAttr extends OverrideMoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { return new Promise(resolve => { @@ -6704,45 +7009,35 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { } } -const lastMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { - const copiableMove = globalScene.currentBattle.lastMove; - - if (!copiableMove) { - return false; +/** + * Attribute used to copy a previously-used move. + * Used for {@linkcode Moves.COPYCAT} and {@linkcode Moves.MIRROR_MOVE} + * @see {@linkcode apply} for move selection and move call + * @extends CallMoveAttr to call a selected move + */ +export class CopyMoveAttr extends CallMoveAttr { + private mirrorMove: boolean; + constructor(mirrorMove: boolean, invalidMoves: Moves[] = []) { + super(); + this.mirrorMove = mirrorMove; + this.invalidMoves = invalidMoves; } - if (allMoves[copiableMove].isChargingMove()) { - return false; - } - - // TODO: Add last turn of Bide - - return true; -}; - -export class CopyMoveAttr extends OverrideMoveEffectAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const lastMove = globalScene.currentBattle.lastMove; - - const moveTargets = getMoveTargets(user, lastMove); - if (!moveTargets.targets.length) { - return false; - } - - const targets = moveTargets.multiple || moveTargets.targets.length === 1 - ? moveTargets.targets - : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 - ? [ target.getBattlerIndex() ] - : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; - user.getMoveQueue().push({ move: lastMove, targets: targets, ignorePP: true }); - - globalScene.unshiftPhase(new MovePhase(user as PlayerPokemon, targets, new PokemonMove(lastMove, 0, 0, true), true)); - - return true; + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + this.hasTarget = this.mirrorMove; + const lastMove = this.mirrorMove ? target.getLastXMoves()[0].move : globalScene.currentBattle.lastMove; + return super.apply(user, target, allMoves[lastMove], args); } getCondition(): MoveConditionFunc { - return lastMoveCopiableCondition; + return (user, target, move) => { + if (this.mirrorMove) { + return target.getMoveHistory().length !== 0; + } else { + const lastMove = globalScene.currentBattle.lastMove; + return lastMove !== undefined && !this.invalidMoves.includes(lastMove); + } + }; } } @@ -7896,11 +8191,20 @@ export type MoveTargetSet = { multiple: boolean; }; -export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet { +export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveTarget): MoveTargetSet { const variableTarget = new Utils.NumberHolder(0); user.getOpponents().forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget)); - const moveTarget = allMoves[move].hasAttr(VariableTargetAttr) ? variableTarget.value : move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : []; + let moveTarget: MoveTarget | undefined; + if (allMoves[move].hasAttr(VariableTargetAttr)) { + moveTarget = variableTarget.value; + } else if (replaceTarget !== undefined) { + moveTarget = replaceTarget; + } else if (move) { + moveTarget = allMoves[move].moveTarget; + } else if (move === undefined) { + moveTarget = MoveTarget.NEAR_ENEMY; + } const opponents = user.getOpponents(); let set: Pokemon[] = []; @@ -7992,7 +8296,6 @@ export function initMoves() { .chargeText(i18next.t("moveTriggers:whippedUpAWhirlwind", { pokemonName: "{USER}" })) .attr(HighCritAttr) .windMove() - .ignoresVirtual() .target(MoveTarget.ALL_NEAR_ENEMIES), new SelfStatusMove(Moves.SWORDS_DANCE, Type.NORMAL, -1, 20, -1, 0, 1) .attr(StatStageChangeAttr, [ Stat.ATK ], 2, true) @@ -8011,8 +8314,7 @@ export function initMoves() { new ChargingAttackMove(Moves.FLY, Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, -1, 0, 1) .chargeText(i18next.t("moveTriggers:flewUpHigh", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) - .condition(failOnGravityCondition) - .ignoresVirtual(), + .condition(failOnGravityCondition), new AttackMove(Moves.BIND, Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1) .attr(TrapAttr, BattlerTagType.BIND), new AttackMove(Moves.SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1), @@ -8161,8 +8463,7 @@ export function initMoves() { new ChargingAttackMove(Moves.SOLAR_BEAM, Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 1) .chargeText(i18next.t("moveTriggers:tookInSunlight", { pokemonName: "{USER}" })) .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.SUNNY, WeatherType.HARSH_SUN ]) - .attr(AntiSunlightPowerDecreaseAttr) - .ignoresVirtual(), + .attr(AntiSunlightPowerDecreaseAttr), new StatusMove(Moves.POISON_POWDER, Type.POISON, 75, 35, -1, 0, 1) .attr(StatusEffectAttr, StatusEffect.POISON) .powderMove(), @@ -8211,8 +8512,7 @@ export function initMoves() { .makesContact(false), new ChargingAttackMove(Moves.DIG, Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 1) .chargeText(i18next.t("moveTriggers:dugAHole", { pokemonName: "{USER}" })) - .chargeAttr(SemiInvulnerableAttr, BattlerTagType.UNDERGROUND) - .ignoresVirtual(), + .chargeAttr(SemiInvulnerableAttr, BattlerTagType.UNDERGROUND), new StatusMove(Moves.TOXIC, Type.POISON, 90, 10, -1, 0, 1) .attr(StatusEffectAttr, StatusEffect.TOXIC) .attr(ToxicAccuracyAttr), @@ -8236,8 +8536,7 @@ export function initMoves() { .attr(LevelDamageAttr), new StatusMove(Moves.MIMIC, Type.NORMAL, -1, 10, -1, 0, 1) .attr(MovesetCopyMoveAttr) - .ignoresSubstitute() - .ignoresVirtual(), + .ignoresSubstitute(), new StatusMove(Moves.SCREECH, Type.NORMAL, 85, 40, -1, 0, 1) .attr(StatStageChangeAttr, [ Stat.DEF ], -2) .soundBased(), @@ -8273,15 +8572,12 @@ export function initMoves() { new SelfStatusMove(Moves.FOCUS_ENERGY, Type.NORMAL, -1, 30, -1, 0, 1) .attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, true, true), new AttackMove(Moves.BIDE, Type.NORMAL, MoveCategory.PHYSICAL, -1, -1, 10, -1, 1, 1) - .ignoresVirtual() .target(MoveTarget.USER) .unimplemented(), new SelfStatusMove(Moves.METRONOME, Type.NORMAL, -1, 10, -1, 0, 1) - .attr(RandomMoveAttr) - .ignoresVirtual(), + .attr(RandomMoveAttr, invalidMetronomeMoves), new StatusMove(Moves.MIRROR_MOVE, Type.FLYING, -1, 20, -1, 0, 1) - .attr(CopyMoveAttr) - .ignoresVirtual(), + .attr(CopyMoveAttr, true), new AttackMove(Moves.SELF_DESTRUCT, Type.NORMAL, MoveCategory.PHYSICAL, 200, 100, 5, -1, 0, 1) .attr(SacrificialAttr) .makesContact(false) @@ -8309,8 +8605,7 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_ENEMIES), new ChargingAttackMove(Moves.SKULL_BASH, Type.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, -1, 0, 1) .chargeText(i18next.t("moveTriggers:loweredItsHead", { pokemonName: "{USER}" })) - .chargeAttr(StatStageChangeAttr, [ Stat.DEF ], 1, true) - .ignoresVirtual(), + .chargeAttr(StatStageChangeAttr, [ Stat.DEF ], 1, true), new AttackMove(Moves.SPIKE_CANNON, Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 15, -1, 0, 1) .attr(MultiHitAttr) .makesContact(false), @@ -8350,8 +8645,7 @@ export function initMoves() { .chargeText(i18next.t("moveTriggers:isGlowing", { pokemonName: "{USER}" })) .attr(HighCritAttr) .attr(FlinchAttr) - .makesContact(false) - .ignoresVirtual(), + .makesContact(false), new StatusMove(Moves.TRANSFORM, Type.NORMAL, -1, 10, -1, 0, 1) .attr(TransformAttr) // transforming from or into fusion pokemon causes various problems (such as crashes) @@ -8414,12 +8708,10 @@ export function initMoves() { new AttackMove(Moves.STRUGGLE, Type.NORMAL, MoveCategory.PHYSICAL, 50, -1, 1, -1, 0, 1) .attr(RecoilAttr, true, 0.25, true) .attr(TypelessAttr) - .ignoresVirtual() .target(MoveTarget.RANDOM_NEAR_ENEMY), new StatusMove(Moves.SKETCH, Type.NORMAL, -1, 1, -1, 0, 2) .ignoresSubstitute() - .attr(SketchAttr) - .ignoresVirtual(), + .attr(SketchAttr), new AttackMove(Moves.TRIPLE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 10, 90, 10, -1, 0, 2) .attr(MultiHitAttr, MultiHitType._3) .attr(MultiHitPowerIncrementAttr, 3) @@ -8572,10 +8864,9 @@ export function initMoves() { .condition((user, target, move) => user.isOppositeGender(target)), new SelfStatusMove(Moves.SLEEP_TALK, Type.NORMAL, -1, 10, -1, 0, 2) .attr(BypassSleepAttr) - .attr(RandomMovesetMoveAttr) + .attr(RandomMovesetMoveAttr, invalidSleepTalkMoves, false) .condition(userSleptOrComatoseCondition) - .target(MoveTarget.ALL_ENEMIES) - .ignoresVirtual(), + .target(MoveTarget.NEAR_ENEMY), new StatusMove(Moves.HEAL_BELL, Type.NORMAL, -1, 5, -1, 0, 2) .attr(PartyStatusCureAttr, i18next.t("moveTriggers:bellChimed"), Abilities.SOUNDPROOF) .soundBased() @@ -8700,7 +8991,6 @@ export function initMoves() { .attr(FlinchAttr) .condition(new FirstMoveCondition()), new AttackMove(Moves.UPROAR, Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, 0, 3) - .ignoresVirtual() .soundBased() .target(MoveTarget.RANDOM_NEAR_ENEMY) .partial(), // Does not lock the user, does not stop Pokemon from sleeping @@ -8743,7 +9033,6 @@ export function initMoves() { new AttackMove(Moves.FOCUS_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, -1, -3, 3) .attr(MessageHeaderAttr, (user, move) => i18next.t("moveTriggers:isTighteningFocus", { pokemonName: getPokemonNameWithAffix(user) })) .punchingMove() - .ignoresVirtual() .condition((user, target, move) => !user.turnData.attacksReceived.find(r => r.damage)), new AttackMove(Moves.SMELLING_SALTS, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 3) .attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1) @@ -8751,8 +9040,7 @@ export function initMoves() { new SelfStatusMove(Moves.FOLLOW_ME, Type.NORMAL, -1, 20, -1, 2, 3) .attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true), new StatusMove(Moves.NATURE_POWER, Type.NORMAL, -1, 20, -1, 0, 3) - .attr(NaturePowerAttr) - .ignoresVirtual(), + .attr(NaturePowerAttr), new SelfStatusMove(Moves.CHARGE, Type.ELECTRIC, -1, 20, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.SPDEF ], 1, true) .attr(AddBattlerTagAttr, BattlerTagType.CHARGED, true, false), @@ -8773,8 +9061,7 @@ export function initMoves() { .triageMove() .attr(AddArenaTagAttr, ArenaTagType.WISH, 2, true), new SelfStatusMove(Moves.ASSIST, Type.NORMAL, -1, 20, -1, 0, 3) - .attr(RandomMovesetMoveAttr, true) - .ignoresVirtual(), + .attr(RandomMovesetMoveAttr, invalidAssistMoves, true), new SelfStatusMove(Moves.INGRAIN, Type.GRASS, -1, 20, -1, 0, 3) .attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, true, true) @@ -8821,8 +9108,7 @@ export function initMoves() { new ChargingAttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3) .chargeText(i18next.t("moveTriggers:hidUnderwater", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.UNDERWATER) - .chargeAttr(GulpMissileTagAttr) - .ignoresVirtual(), + .chargeAttr(GulpMissileTagAttr), new AttackMove(Moves.ARM_THRUST, Type.FIGHTING, MoveCategory.PHYSICAL, 15, 100, 20, -1, 0, 3) .attr(MultiHitAttr), new SelfStatusMove(Moves.CAMOUFLAGE, Type.NORMAL, -1, 20, -1, 0, 3) @@ -8959,8 +9245,7 @@ export function initMoves() { .chargeText(i18next.t("moveTriggers:sprangUp", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) .attr(StatusEffectAttr, StatusEffect.PARALYSIS) - .condition(failOnGravityCondition) - .ignoresVirtual(), + .condition(failOnGravityCondition), new AttackMove(Moves.MUD_SHOT, Type.GROUND, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 3) .attr(StatStageChangeAttr, [ Stat.SPD ], -1), new AttackMove(Moves.POISON_TAIL, Type.POISON, MoveCategory.PHYSICAL, 50, 100, 25, 10, 0, 3) @@ -9087,12 +9372,10 @@ export function initMoves() { .target(MoveTarget.USER_SIDE), new StatusMove(Moves.ME_FIRST, Type.NORMAL, -1, 20, -1, 0, 4) .ignoresSubstitute() - .ignoresVirtual() .target(MoveTarget.NEAR_ENEMY) .unimplemented(), new SelfStatusMove(Moves.COPYCAT, Type.NORMAL, -1, 20, -1, 0, 4) - .attr(CopyMoveAttr) - .ignoresVirtual(), + .attr(CopyMoveAttr, false, invalidCopycatMoves), new StatusMove(Moves.POWER_SWAP, Type.PSYCHIC, -1, 10, 100, 0, 4) .attr(SwapStatStagesAttr, [ Stat.ATK, Stat.SPATK ]) .ignoresSubstitute(), @@ -9316,8 +9599,7 @@ export function initMoves() { new ChargingAttackMove(Moves.SHADOW_FORCE, Type.GHOST, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4) .chargeText(i18next.t("moveTriggers:vanishedInstantly", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.HIDDEN) - .ignoresProtect() - .ignoresVirtual(), + .ignoresProtect(), new SelfStatusMove(Moves.HONE_CLAWS, Type.DARK, -1, 15, -1, 0, 5) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.ACC ], 1, true), new StatusMove(Moves.WIDE_GUARD, Type.ROCK, -1, 10, -1, 3, 5) @@ -9444,7 +9726,6 @@ export function initMoves() { .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) .condition(failOnGravityCondition) .condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE)) - .ignoresVirtual() .partial(), // Should immobilize the target, Flying types should take no damage. cf https://bulbapedia.bulbagarden.net/wiki/Sky_Drop_(move) and https://www.smogon.com/dex/sv/moves/sky-drop/ new SelfStatusMove(Moves.SHIFT_GEAR, Type.STEEL, -1, 10, -1, 0, 5) .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true) @@ -9601,8 +9882,7 @@ export function initMoves() { .makesContact(false), new ChargingAttackMove(Moves.ICE_BURN, Type.ICE, MoveCategory.SPECIAL, 140, 90, 5, 30, 0, 5) .chargeText(i18next.t("moveTriggers:becameCloakedInFreezingAir", { pokemonName: "{USER}" })) - .attr(StatusEffectAttr, StatusEffect.BURN) - .ignoresVirtual(), + .attr(StatusEffectAttr, StatusEffect.BURN), new AttackMove(Moves.SNARL, Type.DARK, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5) .attr(StatStageChangeAttr, [ Stat.SPATK ], -1) .soundBased() @@ -9645,8 +9925,7 @@ export function initMoves() { new ChargingAttackMove(Moves.PHANTOM_FORCE, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6) .chargeText(i18next.t("moveTriggers:vanishedInstantly", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.HIDDEN) - .ignoresProtect() - .ignoresVirtual(), + .ignoresProtect(), new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6) .attr(AddTypeAttr, Type.GHOST), new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6) @@ -9755,8 +10034,7 @@ export function initMoves() { .powderMove(), new ChargingSelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6) .chargeText(i18next.t("moveTriggers:isChargingPower", { pokemonName: "{USER}" })) - .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true) - .ignoresVirtual(), + .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true), new StatusMove(Moves.MAGNETIC_FLUX, Type.ELECTRIC, -1, 20, -1, 0, 6) .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], 1, false, { condition: (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS ].find(a => target.hasAbility(a, false)) }) .ignoresSubstitute() @@ -9823,116 +10101,79 @@ export function initMoves() { .ignoresProtect(), /* Unused */ new AttackMove(Moves.BREAKNECK_BLITZ__PHYSICAL, Type.NORMAL, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.BREAKNECK_BLITZ__SPECIAL, Type.NORMAL, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.ALL_OUT_PUMMELING__PHYSICAL, Type.FIGHTING, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.ALL_OUT_PUMMELING__SPECIAL, Type.FIGHTING, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SUPERSONIC_SKYSTRIKE__PHYSICAL, Type.FLYING, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SUPERSONIC_SKYSTRIKE__SPECIAL, Type.FLYING, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.ACID_DOWNPOUR__PHYSICAL, Type.POISON, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.ACID_DOWNPOUR__SPECIAL, Type.POISON, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.TECTONIC_RAGE__PHYSICAL, Type.GROUND, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.TECTONIC_RAGE__SPECIAL, Type.GROUND, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.CONTINENTAL_CRUSH__PHYSICAL, Type.ROCK, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.CONTINENTAL_CRUSH__SPECIAL, Type.ROCK, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SAVAGE_SPIN_OUT__PHYSICAL, Type.BUG, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SAVAGE_SPIN_OUT__SPECIAL, Type.BUG, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.NEVER_ENDING_NIGHTMARE__PHYSICAL, Type.GHOST, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.NEVER_ENDING_NIGHTMARE__SPECIAL, Type.GHOST, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.CORKSCREW_CRASH__PHYSICAL, Type.STEEL, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.CORKSCREW_CRASH__SPECIAL, Type.STEEL, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.INFERNO_OVERDRIVE__PHYSICAL, Type.FIRE, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.INFERNO_OVERDRIVE__SPECIAL, Type.FIRE, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.HYDRO_VORTEX__PHYSICAL, Type.WATER, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.HYDRO_VORTEX__SPECIAL, Type.WATER, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.BLOOM_DOOM__PHYSICAL, Type.GRASS, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.BLOOM_DOOM__SPECIAL, Type.GRASS, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.GIGAVOLT_HAVOC__PHYSICAL, Type.ELECTRIC, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.GIGAVOLT_HAVOC__SPECIAL, Type.ELECTRIC, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SHATTERED_PSYCHE__PHYSICAL, Type.PSYCHIC, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SHATTERED_PSYCHE__SPECIAL, Type.PSYCHIC, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SUBZERO_SLAMMER__PHYSICAL, Type.ICE, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SUBZERO_SLAMMER__SPECIAL, Type.ICE, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.DEVASTATING_DRAKE__PHYSICAL, Type.DRAGON, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.DEVASTATING_DRAKE__SPECIAL, Type.DRAGON, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.BLACK_HOLE_ECLIPSE__PHYSICAL, Type.DARK, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.BLACK_HOLE_ECLIPSE__SPECIAL, Type.DARK, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.TWINKLE_TACKLE__PHYSICAL, Type.FAIRY, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.TWINKLE_TACKLE__SPECIAL, Type.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.CATASTROPIKA, Type.ELECTRIC, MoveCategory.PHYSICAL, 210, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), /* End Unused */ new SelfStatusMove(Moves.SHORE_UP, Type.GROUND, -1, 5, -1, 0, 7) .attr(SandHealAttr) @@ -10049,35 +10290,33 @@ export function initMoves() { .target(MoveTarget.USER_SIDE), /* Unused */ new AttackMove(Moves.SINISTER_ARROW_RAID, Type.GHOST, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7) + .unimplemented() .makesContact(false) - .edgeCase() // I assume it's because the user needs spirit shackle and decidueye - .ignoresVirtual(), + .edgeCase(), // I assume it's because the user needs spirit shackle and decidueye new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7) + .unimplemented() .attr(AlwaysHitMinimizeAttr) .attr(HitsTagAttr, BattlerTagType.MINIMIZED, true) - .edgeCase() // I assume it's because it needs darkest lariat and incineroar - .ignoresVirtual(), + .edgeCase(), // I assume it's because it needs darkest lariat and incineroar new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7) - .edgeCase() // I assume it's because it needs sparkling aria and primarina - .ignoresVirtual(), + .unimplemented() + .edgeCase(), // I assume it's because it needs sparkling aria and primarina new AttackMove(Moves.GUARDIAN_OF_ALOLA, Type.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SOUL_STEALING_7_STAR_STRIKE, Type.GHOST, MoveCategory.PHYSICAL, 195, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.STOKED_SPARKSURFER, Type.ELECTRIC, MoveCategory.SPECIAL, 175, -1, 1, 100, 0, 7) - .edgeCase() // I assume it's because it needs thunderbolt and Alola Raichu - .ignoresVirtual(), + .unimplemented() + .edgeCase(), // I assume it's because it needs thunderbolt and Alola Raichu new AttackMove(Moves.PULVERIZING_PANCAKE, Type.NORMAL, MoveCategory.PHYSICAL, 210, -1, 1, -1, 0, 7) - .edgeCase() // I assume it's because it needs giga impact and snorlax - .ignoresVirtual(), + .unimplemented() + .edgeCase(), // I assume it's because it needs giga impact and snorlax new SelfStatusMove(Moves.EXTREME_EVOBOOST, Type.NORMAL, -1, 1, -1, 0, 7) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true) - .ignoresVirtual(), + .unimplemented() + .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true), new AttackMove(Moves.GENESIS_SUPERNOVA, Type.PSYCHIC, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7) - .attr(TerrainChangeAttr, TerrainType.PSYCHIC) - .ignoresVirtual(), + .unimplemented() + .attr(TerrainChangeAttr, TerrainType.PSYCHIC), /* End Unused */ new AttackMove(Moves.SHELL_TRAP, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, -3, 7) .attr(AddBattlerTagHeaderAttr, BattlerTagType.SHELL_TRAP) @@ -10116,8 +10355,8 @@ export function initMoves() { .attr(FormChangeItemTypeAttr), /* Unused */ new AttackMove(Moves.TEN_MILLION_VOLT_THUNDERBOLT, Type.ELECTRIC, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7) - .edgeCase() // I assume it's because it needs thunderbolt and pikachu in a cap - .ignoresVirtual(), + .unimplemented() + .edgeCase(), // I assume it's because it needs thunderbolt and pikachu in a cap /* End Unused */ new AttackMove(Moves.MIND_BLOWN, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 7) .condition(failIfDampCondition) @@ -10131,28 +10370,28 @@ export function initMoves() { .ignoresAbilities(), /* Unused */ new AttackMove(Moves.LIGHT_THAT_BURNS_THE_SKY, Type.PSYCHIC, MoveCategory.SPECIAL, 200, -1, 1, -1, 0, 7) + .unimplemented() .attr(PhotonGeyserCategoryAttr) - .ignoresAbilities() - .ignoresVirtual(), + .ignoresAbilities(), new AttackMove(Moves.SEARING_SUNRAZE_SMASH, Type.STEEL, MoveCategory.PHYSICAL, 200, -1, 1, -1, 0, 7) - .ignoresAbilities() - .ignoresVirtual(), + .unimplemented() + .ignoresAbilities(), new AttackMove(Moves.MENACING_MOONRAZE_MAELSTROM, Type.GHOST, MoveCategory.SPECIAL, 200, -1, 1, -1, 0, 7) - .ignoresAbilities() - .ignoresVirtual(), + .unimplemented() + .ignoresAbilities(), new AttackMove(Moves.LETS_SNUGGLE_FOREVER, Type.FAIRY, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7) - .edgeCase() // I assume it needs play rough and mimikyu - .ignoresVirtual(), + .unimplemented() + .edgeCase(), // I assume it needs play rough and mimikyu new AttackMove(Moves.SPLINTERED_STORMSHARDS, Type.ROCK, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7) + .unimplemented() .attr(ClearTerrainAttr) - .makesContact(false) - .ignoresVirtual(), + .makesContact(false), new AttackMove(Moves.CLANGOROUS_SOULBLAZE, Type.DRAGON, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7) + .unimplemented() .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true, { firstTargetOnly: true }) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES) - .edgeCase() // I assume it needs clanging scales and Kommo-O - .ignoresVirtual(), + .edgeCase(), // I assume it needs clanging scales and Kommo-O /* End Unused */ new AttackMove(Moves.ZIPPY_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 50, 100, 15, -1, 2, 7) // LGPE Implementation .attr(CritOnlyAttr), @@ -10190,9 +10429,9 @@ export function initMoves() { .punchingMove(), /* Unused */ new SelfStatusMove(Moves.MAX_GUARD, Type.NORMAL, -1, 10, -1, 4, 8) + .unimplemented() .attr(ProtectAttr) - .condition(failIfLastCondition) - .ignoresVirtual(), + .condition(failIfLastCondition), /* End Unused */ new AttackMove(Moves.DYNAMAX_CANNON, Type.DRAGON, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8) .attr(MovePowerMultiplierAttr, (user, target, move) => { @@ -10205,8 +10444,7 @@ export function initMoves() { return 1; } }) - .attr(DiscourageFrequentUseAttr) - .ignoresVirtual(), + .attr(DiscourageFrequentUseAttr), new AttackMove(Moves.SNIPE_SHOT, Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 8) .attr(HighCritAttr) @@ -10252,76 +10490,58 @@ export function initMoves() { /* Unused */ new AttackMove(Moves.MAX_FLARE, Type.FIRE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_FLUTTERBY, Type.BUG, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_LIGHTNING, Type.ELECTRIC, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_STRIKE, Type.NORMAL, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_KNUCKLE, Type.FIGHTING, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_PHANTASM, Type.GHOST, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_HAILSTORM, Type.ICE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_OOZE, Type.POISON, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_GEYSER, Type.WATER, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_AIRSTREAM, Type.FLYING, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_STARFALL, Type.FAIRY, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_WYRMWIND, Type.DRAGON, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_MINDSTORM, Type.PSYCHIC, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_ROCKFALL, Type.ROCK, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_QUAKE, Type.GROUND, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_DARKNESS, Type.DARK, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_OVERGROWTH, Type.GRASS, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_STEELSPIKE, Type.STEEL, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), /* End Unused */ new SelfStatusMove(Moves.CLANGOROUS_SOUL, Type.DRAGON, 100, 5, -1, 0, 8) .attr(CutHpStatStageBoostAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, 3) @@ -10394,8 +10614,7 @@ export function initMoves() { .makesContact(false), new ChargingAttackMove(Moves.METEOR_BEAM, Type.ROCK, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 8) .chargeText(i18next.t("moveTriggers:isOverflowingWithSpacePower", { pokemonName: "{USER}" })) - .chargeAttr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) - .ignoresVirtual(), + .chargeAttr(StatStageChangeAttr, [ Stat.SPATK ], 1, true), new AttackMove(Moves.SHELL_SIDE_ARM, Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 20, 0, 8) .attr(ShellSideArmCategoryAttr) .attr(StatusEffectAttr, StatusEffect.POISON) @@ -10853,8 +11072,7 @@ export function initMoves() { new ChargingAttackMove(Moves.ELECTRO_SHOT, Type.ELECTRIC, MoveCategory.SPECIAL, 130, 100, 10, 100, 0, 9) .chargeText(i18next.t("moveTriggers:absorbedElectricity", { pokemonName: "{USER}" })) .chargeAttr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) - .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.RAIN, WeatherType.HEAVY_RAIN ]) - .ignoresVirtual(), + .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.RAIN, WeatherType.HEAVY_RAIN ]), new AttackMove(Moves.TERA_STARSTORM, Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) .attr(TeraMoveCategoryAttr) .attr(TeraStarstormTypeAttr) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index d40254c8a6b..8fc00e2ebeb 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3298,7 +3298,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - getMoveQueue(): QueuedMove[] { + getMoveQueue(): TurnMove[] { return this.summonData.moveQueue; } @@ -4810,17 +4810,19 @@ export class EnemyPokemon extends Pokemon { * the Pokemon the move will target. * @returns this Pokemon's next move in the format {move, moveTargets} */ - getNextMove(): QueuedMove { + getNextMove(): TurnMove { // If this Pokemon has a move already queued, return it. - const queuedMove = this.getMoveQueue().length - ? this.getMoveset().find(m => m?.moveId === this.getMoveQueue()[0].move) - : null; - if (queuedMove) { - if (queuedMove.isUsable(this, this.getMoveQueue()[0].ignorePP)) { - return { move: queuedMove.moveId, targets: this.getMoveQueue()[0].targets, ignorePP: this.getMoveQueue()[0].ignorePP }; - } else { - this.getMoveQueue().shift(); - return this.getNextMove(); + const moveQueue = this.getMoveQueue(); + if (moveQueue.length !== 0) { + const queuedMove = moveQueue[0]; + if (queuedMove) { + const moveIndex = this.getMoveset().findIndex(m => m?.moveId === queuedMove.move); + if ((moveIndex > -1 && this.getMoveset()[moveIndex]!.isUsable(this, queuedMove.ignorePP)) || queuedMove.virtual) { + return queuedMove; + } else { + this.getMoveQueue().shift(); + return this.getNextMove(); + } } } @@ -5242,15 +5244,10 @@ export class EnemyPokemon extends Pokemon { export interface TurnMove { move: Moves; - targets?: BattlerIndex[]; - result: MoveResult; + targets: BattlerIndex[]; + result?: MoveResult; virtual?: boolean; turn?: number; -} - -export interface QueuedMove { - move: Moves; - targets: BattlerIndex[]; ignorePP?: boolean; } @@ -5266,7 +5263,7 @@ export interface AttackMoveResult { export class PokemonSummonData { /** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */ public statStages: number[] = [ 0, 0, 0, 0, 0, 0, 0 ]; - public moveQueue: QueuedMove[] = []; + public moveQueue: TurnMove[] = []; public tags: BattlerTag[] = []; public abilitySuppressed: boolean = false; public abilitiesApplied: Abilities[] = []; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index d7293ec02fe..e2bad953fc5 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -11,7 +11,7 @@ import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Biome } from "#app/enums/biome"; import { Moves } from "#app/enums/moves"; import { PokeballType } from "#enums/pokeball"; -import type { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon, TurnMove } from "#app/field/pokemon"; import { FieldPosition } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { Command } from "#app/ui/command-ui-handler"; @@ -86,19 +86,19 @@ export class CommandPhase extends FieldPhase { const moveQueue = playerPokemon.getMoveQueue(); while (moveQueue.length && moveQueue[0] - && moveQueue[0].move && (!playerPokemon.getMoveset().find(m => m?.moveId === moveQueue[0].move) + && moveQueue[0].move && !moveQueue[0].virtual && (!playerPokemon.getMoveset().find(m => m?.moveId === moveQueue[0].move) || !playerPokemon.getMoveset()[playerPokemon.getMoveset().findIndex(m => m?.moveId === moveQueue[0].move)]!.isUsable(playerPokemon, moveQueue[0].ignorePP))) { // TODO: is the bang correct? moveQueue.shift(); } - if (moveQueue.length) { + if (moveQueue.length > 0) { const queuedMove = moveQueue[0]; if (!queuedMove.move) { - this.handleCommand(Command.FIGHT, -1, false); + this.handleCommand(Command.FIGHT, -1); } else { const moveIndex = playerPokemon.getMoveset().findIndex(m => m?.moveId === queuedMove.move); - if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) { // TODO: is the bang correct? - this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, { targets: queuedMove.targets, multiple: queuedMove.targets.length > 1 }); + if ((moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) || queuedMove.virtual) { // TODO: is the bang correct? + this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, queuedMove); } else { globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); } @@ -120,12 +120,24 @@ export class CommandPhase extends FieldPhase { switch (command) { case Command.FIGHT: let useStruggle = false; + const turnMove: TurnMove | undefined = (args.length === 2 ? (args[1] as TurnMove) : undefined); if (cursor === -1 || playerPokemon.trySelectMove(cursor, args[0] as boolean) || (useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) { - const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? + + let moveId: Moves; + if (useStruggle) { + moveId = Moves.STRUGGLE; + } else if (turnMove !== undefined) { + moveId = turnMove.move; + } else if (cursor > -1) { + moveId = playerPokemon.getMoveset()[cursor]!.moveId; + } else { + moveId = Moves.NONE; + } + const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; - const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; + const moveTargets: MoveTargetSet = turnMove === undefined ? getMoveTargets(playerPokemon, moveId) : { targets: turnMove.targets, multiple: turnMove.targets.length > 1 }; if (!moveId) { turnCommand.targets = [ this.fieldIndex ]; } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 0673ad3effe..5330540c8b2 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -296,11 +296,6 @@ export class MovePhase extends BattlePhase { globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); } - // Update the battle's "last move" pointer, unless we're currently mimicking a move. - if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { - globalScene.currentBattle.lastMove = this.move.moveId; - } - /** * Determine if the move is successful (meaning that its damage/effects can be attempted) * by checking that all of the following are true: @@ -324,6 +319,14 @@ export class MovePhase extends BattlePhase { const success = passesConditions && !failedDueToWeather && !failedDueToTerrain; + // Update the battle's "last move" pointer, unless we're currently mimicking a move. + if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { + // The last move used is unaffected by moves that fail + if (success) { + globalScene.currentBattle.lastMove = this.move.moveId; + } + } + /** * If the move has not failed, trigger ability-based user type changes and then execute it. * @@ -518,7 +521,7 @@ export class MovePhase extends BattlePhase { frenzyMissFunc(this.pokemon, this.move.getMove()); } - this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL }); + this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL, targets: this.targets }); this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); this.pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE); diff --git a/src/test/moves/assist.test.ts b/src/test/moves/assist.test.ts new file mode 100644 index 00000000000..81633d9a277 --- /dev/null +++ b/src/test/moves/assist.test.ts @@ -0,0 +1,105 @@ +import { BattlerIndex } from "#app/battle"; +import { Stat } from "#app/enums/stat"; +import { MoveResult } from "#app/field/pokemon"; +import { CommandPhase } from "#app/phases/command-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Assist", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + // Manual moveset overrides are required for the player pokemon in these tests + // because the normal moveset override doesn't allow for accurate testing of moveset changes + game.override + .ability(Abilities.BALL_FETCH) + .battleType("double") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyLevel(100) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should only use an ally's moves", async () => { + game.override.enemyMoveset(Moves.SWORDS_DANCE); + await game.classicMode.startBattle([ Species.FEEBAS, Species.SHUCKLE ]); + + const [ feebas, shuckle ] = game.scene.getPlayerField(); + // These are all moves Assist cannot call; Sketch will be used to test that it can call other moves properly + game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + game.move.changeMoveset(shuckle, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + + game.move.select(Moves.ASSIST, 0); + game.move.select(Moves.SKETCH, 1); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER ]); + // Player_2 uses Sketch, copies Swords Dance, Player_1 uses Assist, uses Player_2's Sketched Swords Dance + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(2); // Stat raised from Assist -> Swords Dance + }); + + it("should fail if there are no allies", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const feebas = game.scene.getPlayerPokemon()!; + game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + + game.move.select(Moves.ASSIST, 0); + await game.toNextTurn(); + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should fail if ally has no usable moves and user has usable moves", async () => { + game.override.enemyMoveset(Moves.SWORDS_DANCE); + await game.classicMode.startBattle([ Species.FEEBAS, Species.SHUCKLE ]); + + const [ feebas, shuckle ] = game.scene.getPlayerField(); + game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + game.move.changeMoveset(shuckle, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + + game.move.select(Moves.SKETCH, 0); + game.move.select(Moves.PROTECT, 1); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2 ]); + // Player uses Sketch to copy Swords Dance, Player_2 stalls a turn. Player will attempt Assist and should have no usable moves + await game.toNextTurn(); + game.move.select(Moves.ASSIST, 0); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.PROTECT, 1); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should apply secondary effects of a move", async () => { + game.override.moveset([ Moves.ASSIST, Moves.WOOD_HAMMER, Moves.WOOD_HAMMER, Moves.WOOD_HAMMER ]); + await game.classicMode.startBattle([ Species.FEEBAS, Species.SHUCKLE ]); + + const [ feebas, shuckle ] = game.scene.getPlayerField(); + game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + game.move.changeMoveset(shuckle, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + + game.move.select(Moves.ASSIST, 0); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.ASSIST, 1); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.isFullHp()).toBeFalsy(); // should receive recoil damage from Wood Hammer + }); +}); diff --git a/src/test/moves/copycat.test.ts b/src/test/moves/copycat.test.ts new file mode 100644 index 00000000000..d9e64289481 --- /dev/null +++ b/src/test/moves/copycat.test.ts @@ -0,0 +1,91 @@ +import { BattlerIndex } from "#app/battle"; +import { allMoves, RandomMoveAttr } from "#app/data/move"; +import { Stat } from "#app/enums/stat"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Copycat", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + const randomMoveAttr = allMoves[Moves.METRONOME].getAttrs(RandomMoveAttr)[0]; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.COPYCAT, Moves.SPIKY_SHIELD, Moves.SWORDS_DANCE, Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .starterSpecies(Species.FEEBAS) + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should copy the last move successfully executed", async () => { + game.override.enemyMoveset(Moves.SUCKER_PUNCH); + await game.classicMode.startBattle(); + + game.move.select(Moves.SWORDS_DANCE); + await game.toNextTurn(); + + game.move.select(Moves.COPYCAT); // Last successful move should be Swords Dance + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(4); + }); + + it("should fail when the last move used is not a valid Copycat move", async () => { + game.override.enemyMoveset(Moves.PROTECT); // Protect is not a valid move for Copycat to copy + await game.classicMode.startBattle(); + + game.move.select(Moves.SPIKY_SHIELD); // Spiky Shield is not a valid move for Copycat to copy + await game.toNextTurn(); + + game.move.select(Moves.COPYCAT); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should copy the called move when the last move successfully calls another", async () => { + game.override + .moveset([ Moves.SPLASH, Moves.METRONOME ]) + .enemyMoveset(Moves.COPYCAT); + await game.classicMode.startBattle(); + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.SWORDS_DANCE); + + game.move.select(Moves.METRONOME); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); // Player moves first, so enemy can copy Swords Dance + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(2); + }); + + it("should apply secondary effects of a move", async () => { + game.override.enemyMoveset(Moves.ACID_SPRAY); // Secondary effect lowers SpDef by 2 stages + await game.classicMode.startBattle(); + + game.move.select(Moves.COPYCAT); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPDEF)).toBe(-2); + }); +}); diff --git a/src/test/moves/metronome.test.ts b/src/test/moves/metronome.test.ts new file mode 100644 index 00000000000..946dc92de0f --- /dev/null +++ b/src/test/moves/metronome.test.ts @@ -0,0 +1,113 @@ +import { RechargingTag, SemiInvulnerableTag } from "#app/data/battler-tags"; +import { allMoves, RandomMoveAttr } from "#app/data/move"; +import { Abilities } from "#app/enums/abilities"; +import { Stat } from "#app/enums/stat"; +import { CommandPhase } from "#app/phases/command-phase"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; + +describe("Moves - Metronome", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + const randomMoveAttr = allMoves[Moves.METRONOME].getAttrs(RandomMoveAttr)[0]; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.METRONOME, Moves.SPLASH ]) + .battleType("single") + .startingLevel(100) + .starterSpecies(Species.REGIELEKI) + .enemyLevel(100) + .enemySpecies(Species.SHUCKLE) + .enemyMoveset(Moves.SPLASH) + .enemyAbility(Abilities.BALL_FETCH); + }); + + it("should have one semi-invulnerable turn and deal damage on the second turn when a semi-invulnerable move is called", async () => { + await game.classicMode.startBattle(); + const player = game.scene.getPlayerPokemon()!; + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.DIVE); + + game.move.select(Moves.METRONOME); + await game.toNextTurn(); + + expect(player.getTag(SemiInvulnerableTag)).toBeTruthy(); + + await game.toNextTurn(); + expect(player.getTag(SemiInvulnerableTag)).toBeFalsy(); + expect(enemy.isFullHp()).toBeFalsy(); + }); + + it("should apply secondary effects of a move", async () => { + await game.classicMode.startBattle(); + const player = game.scene.getPlayerPokemon()!; + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.WOOD_HAMMER); + + game.move.select(Moves.METRONOME); + await game.toNextTurn(); + + expect(player.isFullHp()).toBeFalsy(); + }); + + it("should recharge after using recharge move", async () => { + await game.classicMode.startBattle(); + const player = game.scene.getPlayerPokemon()!; + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.HYPER_BEAM); + vi.spyOn(allMoves[Moves.HYPER_BEAM], "accuracy", "get").mockReturnValue(100); + + game.move.select(Moves.METRONOME); + await game.toNextTurn(); + + expect(player.getTag(RechargingTag)).toBeTruthy(); + }); + + it("should only target ally for Aromatic Mist", async () => { + game.override.battleType("double"); + await game.classicMode.startBattle([ Species.REGIELEKI, Species.RATTATA ]); + const [ leftPlayer, rightPlayer ] = game.scene.getPlayerField(); + const [ leftOpp, rightOpp ] = game.scene.getEnemyField(); + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.AROMATIC_MIST); + + game.move.select(Moves.METRONOME, 0); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.SPLASH, 1); + await game.toNextTurn(); + + expect(rightPlayer.getStatStage(Stat.SPDEF)).toBe(1); + expect(leftPlayer.getStatStage(Stat.SPDEF)).toBe(0); + expect(leftOpp.getStatStage(Stat.SPDEF)).toBe(0); + expect(rightOpp.getStatStage(Stat.SPDEF)).toBe(0); + }); + + it("should cause opponent to flee, and not crash for Roar", async () => { + await game.classicMode.startBattle(); + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.ROAR); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.METRONOME); + await game.phaseInterceptor.to("BerryPhase"); + + const isVisible = enemyPokemon.visible; + const hasFled = enemyPokemon.switchOutStatus; + expect(!isVisible && hasFled).toBe(true); + + await game.phaseInterceptor.to("CommandPhase"); + }); +}); diff --git a/src/test/moves/mirror_move.test.ts b/src/test/moves/mirror_move.test.ts new file mode 100644 index 00000000000..e55c55038ae --- /dev/null +++ b/src/test/moves/mirror_move.test.ts @@ -0,0 +1,84 @@ +import { BattlerIndex } from "#app/battle"; +import { Stat } from "#app/enums/stat"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Mirror Move", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.MIRROR_MOVE, Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should use the last move that the target used on the user", async () => { + game.override + .battleType("double") + .enemyMoveset([ Moves.TACKLE, Moves.GROWL ]); + await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + + game.move.select(Moves.MIRROR_MOVE, 0, BattlerIndex.ENEMY); // target's last move is Tackle, enemy should receive damage from Mirror Move copying Tackle + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + await game.forceEnemyMove(Moves.GROWL, BattlerIndex.PLAYER_2); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyField()[0].isFullHp()).toBeFalsy(); + }); + + it("should apply secondary effects of a move", async () => { + game.override.enemyMoveset(Moves.ACID_SPRAY); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.MIRROR_MOVE); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPDEF)).toBe(-2); + }); + + it("should be able to copy status moves", async () => { + game.override.enemyMoveset(Moves.GROWL); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.MIRROR_MOVE); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(-1); + }); + + it("should fail if the target has not used any moves", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.MIRROR_MOVE); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); +}); diff --git a/src/test/moves/sleep_talk.test.ts b/src/test/moves/sleep_talk.test.ts new file mode 100644 index 00000000000..9ad2d23f903 --- /dev/null +++ b/src/test/moves/sleep_talk.test.ts @@ -0,0 +1,75 @@ +import { Stat } from "#app/enums/stat"; +import { StatusEffect } from "#app/enums/status-effect"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Sleep Talk", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH, Moves.SLEEP_TALK ]) + .statusEffect(StatusEffect.SLEEP) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .enemyLevel(100); + }); + + it("should fail when the user is not asleep", async () => { + game.override.statusEffect(StatusEffect.NONE); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SLEEP_TALK); + await game.toNextTurn(); + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should fail if the user has no valid moves", async () => { + game.override.moveset([ Moves.SLEEP_TALK, Moves.DIG, Moves.METRONOME, Moves.SOLAR_BEAM ]); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SLEEP_TALK); + await game.toNextTurn(); + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should call a random valid move if the user is asleep", async () => { + game.override.moveset([ Moves.SLEEP_TALK, Moves.DIG, Moves.FLY, Moves.SWORDS_DANCE ]); // Dig and Fly are invalid moves, Swords Dance should always be called + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SLEEP_TALK); + await game.toNextTurn(); + expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)); + }); + + it("should apply secondary effects of a move", async () => { + game.override.moveset([ Moves.SLEEP_TALK, Moves.DIG, Moves.FLY, Moves.WOOD_HAMMER ]); // Dig and Fly are invalid moves, Wood Hammer should always be called + await game.classicMode.startBattle(); + + game.move.select(Moves.SLEEP_TALK); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.isFullHp()).toBeFalsy(); // Wood Hammer recoil effect should be applied + }); +}); diff --git a/src/test/moves/spit_up.test.ts b/src/test/moves/spit_up.test.ts index fd21bb3c6c1..7f9dfaad38b 100644 --- a/src/test/moves/spit_up.test.ts +++ b/src/test/moves/spit_up.test.ts @@ -125,7 +125,7 @@ describe("Moves - Spit Up", () => { game.move.select(Moves.SPIT_UP); await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.FAIL }); + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.FAIL, targets: [ game.scene.getEnemyPokemon()!.getBattlerIndex() ]}); expect(spitUp.calculateBattlePower).not.toHaveBeenCalled(); }); @@ -148,7 +148,7 @@ describe("Moves - Spit Up", () => { await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS }); + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS, targets: [ game.scene.getEnemyPokemon()!.getBattlerIndex() ]}); expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce(); @@ -176,7 +176,7 @@ describe("Moves - Spit Up", () => { game.move.select(Moves.SPIT_UP); await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS }); + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS, targets: [ game.scene.getEnemyPokemon()!.getBattlerIndex() ]}); expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce(); diff --git a/src/test/moves/stockpile.test.ts b/src/test/moves/stockpile.test.ts index e50fe041b0a..f83459cd09d 100644 --- a/src/test/moves/stockpile.test.ts +++ b/src/test/moves/stockpile.test.ts @@ -72,7 +72,7 @@ describe("Moves - Stockpile", () => { expect(user.getStatStage(Stat.SPDEF)).toBe(3); expect(stockpilingTag).toBeDefined(); expect(stockpilingTag.stockpiledCount).toBe(3); - expect(user.getMoveHistory().at(-1)).toMatchObject({ result: MoveResult.FAIL, move: Moves.STOCKPILE }); + expect(user.getMoveHistory().at(-1)).toMatchObject({ result: MoveResult.FAIL, move: Moves.STOCKPILE, targets: [ user.getBattlerIndex() ]}); } } }); diff --git a/src/test/moves/swallow.test.ts b/src/test/moves/swallow.test.ts index c154d3c7c2c..b2435ba77b3 100644 --- a/src/test/moves/swallow.test.ts +++ b/src/test/moves/swallow.test.ts @@ -135,7 +135,7 @@ describe("Moves - Swallow", () => { game.move.select(Moves.SWALLOW); await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.FAIL }); + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.FAIL, targets: [ pokemon.getBattlerIndex() ]}); }); describe("restores stat stage boosts granted by stacks", () => { @@ -156,7 +156,7 @@ describe("Moves - Swallow", () => { await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.SUCCESS }); + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.SUCCESS, targets: [ pokemon.getBattlerIndex() ]}); expect(pokemon.getStatStage(Stat.DEF)).toBe(0); expect(pokemon.getStatStage(Stat.SPDEF)).toBe(0); @@ -183,7 +183,7 @@ describe("Moves - Swallow", () => { await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.SUCCESS }); + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.SUCCESS, targets: [ pokemon.getBattlerIndex() ]}); expect(pokemon.getStatStage(Stat.DEF)).toBe(1); expect(pokemon.getStatStage(Stat.SPDEF)).toBe(-2); From 6681a913fececeb7e4d5372676cd7ece1fce782b Mon Sep 17 00:00:00 2001 From: "Amani H." <109637146+xsn34kzx@users.noreply.github.com> Date: Tue, 14 Jan 2025 20:25:18 -0500 Subject: [PATCH 03/14] [Bug] Prevent Duplicate Signature Species in Trainer Battles (#5059) * [Bug] Prevent Duplicate Signature Species in Trainer Battles * Apply Kev's Suggestion Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: damocleas --- src/field/trainer.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/field/trainer.ts b/src/field/trainer.ts index fc12eb57abe..2b74c1e5069 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -428,7 +428,7 @@ export default class Trainer extends Phaser.GameObjects.Container { } // Prompts reroll of party member species if species already present in the enemy party - if (this.checkDuplicateSpecies(ret, baseSpecies)) { + if (this.checkDuplicateSpecies(baseSpecies.speciesId)) { console.log("Duplicate species detected, prompting reroll..."); retry = true; } @@ -443,17 +443,23 @@ export default class Trainer extends Phaser.GameObjects.Container { /** * Checks if the enemy trainer already has the Pokemon species in their party - * @param {PokemonSpecies} species {@linkcode PokemonSpecies} - * @param {PokemonSpecies} baseSpecies {@linkcode PokemonSpecies} - baseSpecies of the Pokemon if species is forced to evolve + * @param baseSpecies - The base {@linkcode Species} of the current Pokemon * @returns `true` if the species is already present in the party */ - checkDuplicateSpecies(species: PokemonSpecies, baseSpecies: PokemonSpecies): boolean { - const staticPartyPokemon = (signatureSpecies[TrainerType[this.config.trainerType]] ?? []).flat(1); - - const currentPartySpecies = globalScene.getEnemyParty().map(p => { - return p.species.speciesId; + checkDuplicateSpecies(baseSpecies: Species): boolean { + const staticSpecies = (signatureSpecies[TrainerType[this.config.trainerType]] ?? []).flat(1).map(s => { + let root = s; + while (pokemonPrevolutions.hasOwnProperty(root)) { + root = pokemonPrevolutions[root]; + } + return root; }); - return currentPartySpecies.includes(species.speciesId) || staticPartyPokemon.includes(baseSpecies.speciesId); + + const currentSpecies = globalScene.getEnemyParty().map(p => { + return p.species.getRootSpeciesId(); + }); + + return currentSpecies.includes(baseSpecies) || staticSpecies.includes(baseSpecies); } getPartyMemberMatchupScores(trainerSlot: TrainerSlot = TrainerSlot.NONE, forSwitch: boolean = false): [integer, integer][] { From d5f84cf3df7b5d5e0dcd9fffb9b8f49cb5ca0c74 Mon Sep 17 00:00:00 2001 From: damocleas Date: Tue, 14 Jan 2025 21:07:09 -0500 Subject: [PATCH 04/14] Change Archen HA from Emergency Exit to Wimp Out (#5124) --- src/data/pokemon-species.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 84486b30372..bd041fe7559 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -1834,7 +1834,7 @@ export function initSpecies() { new PokemonSpecies(Species.COFAGRIGUS, 5, false, false, false, "Coffin Pokémon", Type.GHOST, null, 1.7, 76.5, Abilities.MUMMY, Abilities.NONE, Abilities.NONE, 483, 58, 50, 145, 95, 105, 30, 90, 50, 169, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.TIRTOUGA, 5, false, false, false, "Prototurtle Pokémon", Type.WATER, Type.ROCK, 0.7, 16.5, Abilities.SOLID_ROCK, Abilities.STURDY, Abilities.SWIFT_SWIM, 355, 54, 78, 103, 53, 45, 22, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), new PokemonSpecies(Species.CARRACOSTA, 5, false, false, false, "Prototurtle Pokémon", Type.WATER, Type.ROCK, 1.2, 81, Abilities.SOLID_ROCK, Abilities.STURDY, Abilities.SWIFT_SWIM, 495, 74, 108, 133, 83, 65, 32, 45, 50, 173, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(Species.ARCHEN, 5, false, false, false, "First Bird Pokémon", Type.ROCK, Type.FLYING, 0.5, 9.5, Abilities.DEFEATIST, Abilities.NONE, Abilities.EMERGENCY_EXIT, 401, 55, 112, 45, 74, 45, 70, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden + new PokemonSpecies(Species.ARCHEN, 5, false, false, false, "First Bird Pokémon", Type.ROCK, Type.FLYING, 0.5, 9.5, Abilities.DEFEATIST, Abilities.NONE, Abilities.WIMP_OUT, 401, 55, 112, 45, 74, 45, 70, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden new PokemonSpecies(Species.ARCHEOPS, 5, false, false, false, "First Bird Pokémon", Type.ROCK, Type.FLYING, 1.4, 32, Abilities.DEFEATIST, Abilities.NONE, Abilities.EMERGENCY_EXIT, 567, 75, 140, 65, 112, 65, 110, 45, 50, 177, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden new PokemonSpecies(Species.TRUBBISH, 5, false, false, false, "Trash Bag Pokémon", Type.POISON, null, 0.6, 31, Abilities.STENCH, Abilities.STICKY_HOLD, Abilities.AFTERMATH, 329, 50, 50, 62, 40, 62, 65, 190, 50, 66, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.GARBODOR, 5, false, false, false, "Trash Heap Pokémon", Type.POISON, null, 1.9, 107.3, Abilities.STENCH, Abilities.WEAK_ARMOR, Abilities.AFTERMATH, 474, 80, 95, 82, 60, 82, 75, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, true, From 7ae216f0d6b4d2b11f6d757e7056969d014c87ca Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 15 Jan 2025 07:18:24 +0100 Subject: [PATCH 05/14] [UI/UX] Shop cursor freedom (#5110) * Allowing cursor in shop to cycle horizontally * Improved cycling of commands --------- Co-authored-by: damocleas --- src/ui/modifier-select-ui-handler.ts | 38 +++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 05740a349c6..0cca087ce8d 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -364,6 +364,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { success = this.setCursor(0); } else if (this.rowCursor < this.shopOptionsRows.length + 1) { success = this.setRowCursor(this.rowCursor + 1); + } else { + success = this.setRowCursor(0); } break; case Button.DOWN: @@ -371,13 +373,15 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { success = this.setRowCursor(this.rowCursor - 1); } else if (this.lockRarityButtonContainer.visible && this.cursor === 0) { success = this.setCursor(3); + } else { + success = this.setRowCursor(this.shopOptionsRows.length + 1); } break; case Button.LEFT: if (!this.rowCursor) { switch (this.cursor) { case 0: - success = false; + success = this.setCursor(2); break; case 1: if (this.lockRarityButtonContainer.visible) { @@ -395,11 +399,21 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { success = false; } break; + case 3: + if (this.lockRarityButtonContainer.visible) { + success = this.setCursor(2); + } else { + success = false; + } } } else if (this.cursor) { success = this.setCursor(this.cursor - 1); - } else if (this.rowCursor === 1 && this.rerollButtonContainer.visible) { - success = this.setRowCursor(0); + } else { + if (this.rowCursor === 1 && this.options.length === 0) { + success = false; + } else { + success = this.setCursor(this.getRowItems(this.rowCursor) - 1); + } } break; case Button.RIGHT: @@ -416,7 +430,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { success = this.setCursor(2); break; case 2: - success = false; + success = this.setCursor(0); break; case 3: if (this.transferButtonContainer.visible) { @@ -428,8 +442,12 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } } else if (this.cursor < this.getRowItems(this.rowCursor) - 1) { success = this.setCursor(this.cursor + 1); - } else if (this.rowCursor === 1 && this.transferButtonContainer.visible) { - success = this.setRowCursor(0); + } else { + if (this.rowCursor === 1 && this.options.length === 0) { + success = this.setRowCursor(0); + } else { + success = this.setCursor(0); + } } break; } @@ -519,6 +537,14 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { newCursor = 2; } } + // Allows to find lock rarity button when looping from the top + if (rowCursor === 0 && lastRowCursor > 1 && newCursor === 0 && this.lockRarityButtonContainer.visible) { + newCursor = 3; + } + // Allows to loop to top when lock rarity button is shown + if (rowCursor === this.shopOptionsRows.length + 1 && lastRowCursor === 0 && this.cursor === 3) { + newCursor = 0; + } this.cursor = -1; this.setCursor(newCursor); return true; From d2a3e4bb2d62164a2564d5799e35069bf2703ae8 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 15 Jan 2025 08:23:38 +0100 Subject: [PATCH 06/14] [UI/UX] Looping cursor in save slot selection screen (#5109) * Save slot selection allows looping * Removed debug logs --------- Co-authored-by: damocleas --- src/ui/save-slot-select-ui-handler.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 13f5020e5ad..fe2ac9e1221 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -157,6 +157,12 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { success = (this.cursor === 0) ? this.setCursor(this.cursor) : this.setCursor(this.cursor - 1, cursorPosition); } else if (this.scrollCursor) { success = this.setScrollCursor(this.scrollCursor - 1, cursorPosition); + } else if ((this.cursor === 0) && (this.scrollCursor === 0)) { + this.setScrollCursor(SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN); + // Revert to avoid an extra session slot sticking out + this.revertSessionSlot(SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN); + this.setCursor(SLOTS_ON_SCREEN - 1); + success = true; } break; case Button.DOWN: @@ -164,6 +170,11 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { success = this.setCursor(this.cursor + 1, cursorPosition); } else if (this.scrollCursor < SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN) { success = this.setScrollCursor(this.scrollCursor + 1, cursorPosition); + } else if ((this.cursor === SLOTS_ON_SCREEN - 1) && (this.scrollCursor === SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN)) { + this.setScrollCursor(0); + this.revertSessionSlot(SLOTS_ON_SCREEN - 1); + this.setCursor(0); + success = true; } break; case Button.RIGHT: From e2c6bec418e7d4e48ab3c37a0b51aadb860320f4 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:53:16 -0500 Subject: [PATCH 07/14] [Bug] Fix Poltergeist message displaying before move use (#5040) --- src/data/move.ts | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index f3a1f3aa119..572fbf4c2ac 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7878,31 +7878,6 @@ export class LastResortAttr extends MoveAttr { } } - -/** - * The move only works if the target has a transferable held item - * @extends MoveAttr - * @see {@linkcode getCondition} - */ -export class AttackedByItemAttr extends MoveAttr { - /** - * @returns the {@linkcode MoveConditionFunc} for this {@linkcode Move} - */ - getCondition(): MoveConditionFunc { - return (user: Pokemon, target: Pokemon, move: Move) => { - const heldItems = target.getHeldItems().filter(i => i.isTransferable); - if (heldItems.length === 0) { - return false; - } - - const itemName = heldItems[0]?.type?.name ?? "item"; - globalScene.queueMessage(i18next.t("moveTriggers:attackedByItem", { pokemonName: getPokemonNameWithAffix(target), itemName: itemName })); - - return true; - }; - } -} - export class VariableTargetAttr extends MoveAttr { private targetChangeFunc: (user: Pokemon, target: Pokemon, move: Move) => number; @@ -7976,6 +7951,18 @@ const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Po const failIfGhostTypeCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => !target.isOfType(Type.GHOST); +const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.getHeldItems().filter(i => i.isTransferable)?.length > 0; + +const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { + const heldItems = target.getHeldItems().filter(i => i.isTransferable); + if (heldItems.length === 0) { + return ""; + } + const itemName = heldItems[0]?.type?.name ?? "item"; + const message: string = i18next.t("moveTriggers:attackedByItem", { pokemonName: getPokemonNameWithAffix(target), itemName: itemName }); + return message; +}; + export type MoveAttrFilter = (attr: MoveAttr) => boolean; function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise { @@ -10641,7 +10628,8 @@ export function initMoves() { new AttackMove(Moves.LASH_OUT, Type.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8) .attr(MovePowerMultiplierAttr, (user, _target, _move) => user.turnData.statStagesDecreased ? 2 : 1), new AttackMove(Moves.POLTERGEIST, Type.GHOST, MoveCategory.PHYSICAL, 110, 90, 5, -1, 0, 8) - .attr(AttackedByItemAttr) + .condition(failIfNoTargetHeldItemsCondition) + .attr(PreMoveMessageAttr, attackedByItemMessageFunc) .makesContact(false), new StatusMove(Moves.CORROSIVE_GAS, Type.POISON, 100, 40, -1, 0, 8) .target(MoveTarget.ALL_NEAR_OTHERS) From 39b4d74e9592b76ae8159dfb43046b3ce7366ce3 Mon Sep 17 00:00:00 2001 From: Dean <69436131+emdeann@users.noreply.github.com> Date: Wed, 15 Jan 2025 01:06:09 -0800 Subject: [PATCH 08/14] [Bug] Fix #5029 Memory leak when saving and exiting (#5128) * Add destroy function to ui handlers * Implement destroy() for StarterSelectUiHandler * Update battlescene to free memory when resetting * Document destroy for starter select --------- Co-authored-by: damocleas --- src/battle-scene.ts | 3 +++ src/ui/settings/navigationMenu.ts | 7 +++++++ src/ui/starter-select-ui-handler.ts | 5 +++++ src/ui/ui-handler.ts | 5 +++++ src/ui/ui.ts | 11 +++++++++++ 5 files changed, 31 insertions(+) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 6db9311bac8..e9d5a97ab8d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1191,6 +1191,9 @@ export default class BattleScene extends SceneBase { onComplete: () => { this.clearPhaseQueue(); + this.ui.freeUIData(); + this.uiContainer.remove(this.ui, true); + this.uiContainer.destroy(); this.children.removeAll(true); this.game.domContainer.innerHTML = ""; this.launchBattle(); diff --git a/src/ui/settings/navigationMenu.ts b/src/ui/settings/navigationMenu.ts index eeb6da319ef..5fa53b7c270 100644 --- a/src/ui/settings/navigationMenu.ts +++ b/src/ui/settings/navigationMenu.ts @@ -89,6 +89,13 @@ export class NavigationManager { } } + /** + * Removes menus from the manager in preparation for reset + */ + public clearNavigationMenus() { + this.navigationMenus.length = 0; + } + } export default class NavigationMenu extends Phaser.GameObjects.Container { diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 29c58d7087e..40325d24af7 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2698,6 +2698,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.updateScroll(); }; + override destroy(): void { + // Without this the reference gets hung up and no startercontainers get GCd + this.starterContainers = []; + } + updateScroll = () => { const maxColumns = 9; const maxRows = 9; diff --git a/src/ui/ui-handler.ts b/src/ui/ui-handler.ts index 1f0155aef8b..89f8d9e65b6 100644 --- a/src/ui/ui-handler.ts +++ b/src/ui/ui-handler.ts @@ -62,4 +62,9 @@ export default abstract class UiHandler { clear() { this.active = false; } + /** + * To be implemented by individual handlers when necessary to free memory + * Called when {@linkcode BattleScene} is reset + */ + destroy(): void {} } diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 6d44997f649..9e8c52b1d24 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -53,6 +53,7 @@ import TestDialogueUiHandler from "#app/ui/test-dialogue-ui-handler"; import AutoCompleteUiHandler from "./autocomplete-ui-handler"; import { Device } from "#enums/devices"; import MysteryEncounterUiHandler from "./mystery-encounter-ui-handler"; +import { NavigationManager } from "./settings/navigationMenu"; export enum Mode { MESSAGE, @@ -614,4 +615,14 @@ export default class UI extends Phaser.GameObjects.Container { return globalScene.inputMethod; } } + + /** + * Attempts to free memory held by UI handlers + * and clears menus from {@linkcode NavigationManager} to prepare for reset + */ + public freeUIData(): void { + this.handlers.forEach(h => h.destroy()); + this.handlers = []; + NavigationManager.getInstance().clearNavigationMenus(); + } } From d6247335659224a58674952af584158f090d9c59 Mon Sep 17 00:00:00 2001 From: Jimmybald1 <122436263+Jimmybald1@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:12:50 +0100 Subject: [PATCH 09/14] [Bug] Fix #5034 removed unnecessary caught data block for certain forms (#5119) Co-authored-by: Jimmybald1 <147992650+IBBCalc@users.noreply.github.com> Co-authored-by: damocleas --- src/data/pokemon-species.ts | 13 ------------- src/system/game-data.ts | 5 +---- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index bd041fe7559..285c2a70236 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -948,19 +948,6 @@ export class PokemonForm extends PokemonSpeciesForm { } } -export const noStarterFormKeys: string[] = [ - SpeciesFormKey.MEGA, - SpeciesFormKey.MEGA_X, - SpeciesFormKey.MEGA_Y, - SpeciesFormKey.PRIMAL, - SpeciesFormKey.ORIGIN, - SpeciesFormKey.THERIAN, - SpeciesFormKey.GIGANTAMAX, - SpeciesFormKey.GIGANTAMAX_RAPID, - SpeciesFormKey.GIGANTAMAX_SINGLE, - SpeciesFormKey.ETERNAMAX -].map(k => k.toString()); - /** * Method to get the daily list of starters with Pokerus. * @returns A list of starters with Pokerus diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 11b98d3fee6..58d416eb468 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -6,7 +6,7 @@ import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpecies, noStarterFormKeys } from "#app/data/pokemon-species"; +import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import * as Utils from "#app/utils"; import Overrides from "#app/overrides"; @@ -1619,9 +1619,6 @@ export class GameData { const dexEntry = this.dexData[species.speciesId]; const caughtAttr = dexEntry.caughtAttr; const formIndex = pokemon.formIndex; - if (noStarterFormKeys.includes(pokemon.getFormKey())) { - pokemon.formIndex = 0; - } const dexAttr = pokemon.getDexAttr(); pokemon.formIndex = formIndex; From 95c6f4cd52b2a22c5099636d8de7e95d814b440b Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Wed, 15 Jan 2025 03:33:52 -0600 Subject: [PATCH 10/14] Update locales --- public/locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales b/public/locales index 4928231e22a..0d4d614a759 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 4928231e22a06dce2b55d9b04cd2b283c2ee4afb +Subproject commit 0d4d614a75998086e9bb10d9328d18b29b133f5b From 608a92b70c638c36e885f8868722fdd94219fd61 Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Wed, 15 Jan 2025 03:39:52 -0600 Subject: [PATCH 11/14] Revert "Update locales" This reverts commit 95c6f4cd52b2a22c5099636d8de7e95d814b440b. --- public/locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales b/public/locales index 0d4d614a759..4928231e22a 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 0d4d614a75998086e9bb10d9328d18b29b133f5b +Subproject commit 4928231e22a06dce2b55d9b04cd2b283c2ee4afb From ee6115f49dc895eb13a88f7e092a6cc4211032ce Mon Sep 17 00:00:00 2001 From: Scooom <97370685+Scoooom@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:55:14 -0600 Subject: [PATCH 12/14] [Challenge] Flip Stats Challenge (#5087) * Implement Flip Stat Challange * Add Achivement * Update challenge code to block other challenges. * Add Achievment Image * Add FLIP_STAT to enum ChallengeType * Fix comment for FlipStatChallenge * Add applyFlipStat override to Challenge Class, and add override inside of FlipStatsChallenge * Add ChallengeType.FLIP_STAT case to export function applyChallenges (Master Switch Function) * Properly block other challange achviements * Change the way achivements are blocked by challenge modes to a more flexible method * Adjust the image for Flip Stat, and add an additional achivement for completing both Flip and Inverse * Fix FLIP_INVERSE achivement to check ALL challanges are met, not SOME * Remove outdated image * Fix FlipStat applyChallenges inside calculateBaseStats * Update locales --------- Co-authored-by: Scooom Co-authored-by: Scooom Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- public/locales | 2 +- src/data/challenge.ts | 51 +++++++++++++++++++++++++++++++ src/enums/challenges.ts | 1 + src/field/pokemon.ts | 1 + src/system/achv.ts | 67 +++++++++++++++++++++++------------------ 5 files changed, 91 insertions(+), 31 deletions(-) diff --git a/public/locales b/public/locales index 4928231e22a..acad8499a4c 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 4928231e22a06dce2b55d9b04cd2b283c2ee4afb +Subproject commit acad8499a4ca488a9871902de140f635235f309a diff --git a/src/data/challenge.ts b/src/data/challenge.ts index b5eca55cb71..a01ceab8aa3 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -88,6 +88,11 @@ export enum ChallengeType { * Modifies what weight AI pokemon have when generating movesets. UNIMPLEMENTED. */ MOVE_WEIGHT, + /** + * Modifies what the pokemon stats for Flip Stat Mode. + */ + FLIP_STAT, + } /** @@ -405,6 +410,16 @@ export abstract class Challenge { applyMoveWeight(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.IntegerHolder): boolean { return false; } + + /** + * An apply function for FlipStats. Derived classes should alter this. + * @param pokemon {@link Pokemon} What pokemon would learn the move. + * @param baseStats What are the stats to flip. + * @returns {@link boolean} Whether this function did anything. + */ + applyFlipStat(pokemon: Pokemon, baseStats: number[]) { + return false; + } } type ChallengeCondition = (data: GameData) => boolean; @@ -705,6 +720,33 @@ export class InverseBattleChallenge extends Challenge { } } +/** + * Implements a flip stat challenge. + */ +export class FlipStatChallenge extends Challenge { + constructor() { + super(Challenges.FLIP_STAT, 1); + } + + override applyFlipStat(pokemon: Pokemon, baseStats: number[]) { + const origStats = Utils.deepCopy(baseStats); + baseStats[0] = origStats[5]; + baseStats[1] = origStats[4]; + baseStats[2] = origStats[3]; + baseStats[3] = origStats[2]; + baseStats[4] = origStats[1]; + baseStats[5] = origStats[0]; + return true; + } + + static loadChallenge(source: FlipStatChallenge | any): FlipStatChallenge { + const newChallenge = new FlipStatChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + /** * Lowers the amount of starter points available. */ @@ -890,6 +932,9 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType * @returns True if any challenge was successfully applied. */ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_WEIGHT, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, weight: Utils.IntegerHolder): boolean; + +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FLIP_STAT, pokemon: Pokemon, baseStats: number[]): boolean; + export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean { let ret = false; gameMode.challenges.forEach(c => { @@ -934,6 +979,9 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType case ChallengeType.MOVE_WEIGHT: ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]); break; + case ChallengeType.FLIP_STAT: + ret ||= c.applyFlipStat(args[0], args[1]); + break; } } }); @@ -959,6 +1007,8 @@ export function copyChallenge(source: Challenge | any): Challenge { return FreshStartChallenge.loadChallenge(source); case Challenges.INVERSE_BATTLE: return InverseBattleChallenge.loadChallenge(source); + case Challenges.FLIP_STAT: + return FlipStatChallenge.loadChallenge(source); } throw new Error("Unknown challenge copied"); } @@ -971,5 +1021,6 @@ export function initChallenges() { new SingleTypeChallenge(), new FreshStartChallenge(), new InverseBattleChallenge(), + new FlipStatChallenge() ); } diff --git a/src/enums/challenges.ts b/src/enums/challenges.ts index c4dc7460dfe..7b506a61a2f 100644 --- a/src/enums/challenges.ts +++ b/src/enums/challenges.ts @@ -5,4 +5,5 @@ export enum Challenges { LOWER_STARTER_POINTS, FRESH_START, INVERSE_BATTLE, + FLIP_STAT, } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8fc00e2ebeb..432f0a92fec 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1057,6 +1057,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { calculateBaseStats(): number[] { const baseStats = this.getSpeciesForm(true).baseStats.slice(0); + applyChallenges(globalScene.gameMode, ChallengeType.FLIP_STAT, this, baseStats); // Shuckle Juice globalScene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats); // Old Gateau diff --git a/src/system/achv.ts b/src/system/achv.ts index 1f5662dbdbe..fb17e7b1ced 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -5,7 +5,7 @@ import i18next from "i18next"; import * as Utils from "../utils"; import { PlayerGender } from "#enums/player-gender"; import type { Challenge } from "#app/data/challenge"; -import { FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge, InverseBattleChallenge } from "#app/data/challenge"; +import { FlipStatChallenge, FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge, InverseBattleChallenge } from "#app/data/challenge"; import type { ConditionFn } from "#app/@types/common"; import { Stat, getShortenedStatKey } from "#app/enums/stat"; import { Challenges } from "#app/enums/challenges"; @@ -280,6 +280,10 @@ export function getAchievementDescription(localizationKey: string): string { return i18next.t("achv:FRESH_START.description", { context: genderStr }); case "INVERSE_BATTLE": return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr }); + case "FLIP_STATS": + return i18next.t("achv:FLIP_STATS.description", { context: genderStr }); + case "FLIP_INVERSE": + return i18next.t("achv:FLIP_INVERSE.description", { context: genderStr }); case "BREEDERS_IN_SPACE": return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr }); default: @@ -288,6 +292,7 @@ export function getAchievementDescription(localizationKey: string): string { } + export const achvs = { _10K_MONEY: new MoneyAchv("10K_MONEY", "", 10000, "nugget", 10), _100K_MONEY: new MoneyAchv("100K_MONEY", "", 100000, "big_nugget", 25).setSecret(true), @@ -330,35 +335,37 @@ export const achvs = { PERFECT_IVS: new Achv("PERFECT_IVS", "", "PERFECT_IVS.description", "blunder_policy", 100), CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY", "", "CLASSIC_VICTORY.description", "relic_crown", 150, (_) => globalScene.gameData.gameStats.sessionsWon === 0), UNEVOLVED_CLASSIC_VICTORY: new Achv("UNEVOLVED_CLASSIC_VICTORY", "", "UNEVOLVED_CLASSIC_VICTORY.description", "eviolite", 175, (_) => globalScene.getPlayerParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)), - MONO_GEN_ONE_VICTORY: new ChallengeAchv("MONO_GEN_ONE", "", "MONO_GEN_ONE.description", "ribbon_gen1", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_TWO_VICTORY: new ChallengeAchv("MONO_GEN_TWO", "", "MONO_GEN_TWO.description", "ribbon_gen2", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_THREE_VICTORY: new ChallengeAchv("MONO_GEN_THREE", "", "MONO_GEN_THREE.description", "ribbon_gen3", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_FOUR_VICTORY: new ChallengeAchv("MONO_GEN_FOUR", "", "MONO_GEN_FOUR.description", "ribbon_gen4", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_FIVE_VICTORY: new ChallengeAchv("MONO_GEN_FIVE", "", "MONO_GEN_FIVE.description", "ribbon_gen5", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_SIX_VICTORY: new ChallengeAchv("MONO_GEN_SIX", "", "MONO_GEN_SIX.description", "ribbon_gen6", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_SEVEN_VICTORY: new ChallengeAchv("MONO_GEN_SEVEN", "", "MONO_GEN_SEVEN.description", "ribbon_gen7", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_EIGHT_VICTORY: new ChallengeAchv("MONO_GEN_EIGHT", "", "MONO_GEN_EIGHT.description", "ribbon_gen8", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_NINE_VICTORY: new ChallengeAchv("MONO_GEN_NINE", "", "MONO_GEN_NINE.description", "ribbon_gen9", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_NORMAL: new ChallengeAchv("MONO_NORMAL", "", "MONO_NORMAL.description", "silk_scarf", 100, (c) => c instanceof SingleTypeChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_FIGHTING: new ChallengeAchv("MONO_FIGHTING", "", "MONO_FIGHTING.description", "black_belt", 100, (c) => c instanceof SingleTypeChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_FLYING: new ChallengeAchv("MONO_FLYING", "", "MONO_FLYING.description", "sharp_beak", 100, (c) => c instanceof SingleTypeChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_POISON: new ChallengeAchv("MONO_POISON", "", "MONO_POISON.description", "poison_barb", 100, (c) => c instanceof SingleTypeChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GROUND: new ChallengeAchv("MONO_GROUND", "", "MONO_GROUND.description", "soft_sand", 100, (c) => c instanceof SingleTypeChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_ROCK: new ChallengeAchv("MONO_ROCK", "", "MONO_ROCK.description", "hard_stone", 100, (c) => c instanceof SingleTypeChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_BUG: new ChallengeAchv("MONO_BUG", "", "MONO_BUG.description", "silver_powder", 100, (c) => c instanceof SingleTypeChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GHOST: new ChallengeAchv("MONO_GHOST", "", "MONO_GHOST.description", "spell_tag", 100, (c) => c instanceof SingleTypeChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_STEEL: new ChallengeAchv("MONO_STEEL", "", "MONO_STEEL.description", "metal_coat", 100, (c) => c instanceof SingleTypeChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_FIRE: new ChallengeAchv("MONO_FIRE", "", "MONO_FIRE.description", "charcoal", 100, (c) => c instanceof SingleTypeChallenge && c.value === 10 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_WATER: new ChallengeAchv("MONO_WATER", "", "MONO_WATER.description", "mystic_water", 100, (c) => c instanceof SingleTypeChallenge && c.value === 11 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GRASS: new ChallengeAchv("MONO_GRASS", "", "MONO_GRASS.description", "miracle_seed", 100, (c) => c instanceof SingleTypeChallenge && c.value === 12 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_ELECTRIC: new ChallengeAchv("MONO_ELECTRIC", "", "MONO_ELECTRIC.description", "magnet", 100, (c) => c instanceof SingleTypeChallenge && c.value === 13 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_PSYCHIC: new ChallengeAchv("MONO_PSYCHIC", "", "MONO_PSYCHIC.description", "twisted_spoon", 100, (c) => c instanceof SingleTypeChallenge && c.value === 14 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_ICE: new ChallengeAchv("MONO_ICE", "", "MONO_ICE.description", "never_melt_ice", 100, (c) => c instanceof SingleTypeChallenge && c.value === 15 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_DRAGON: new ChallengeAchv("MONO_DRAGON", "", "MONO_DRAGON.description", "dragon_fang", 100, (c) => c instanceof SingleTypeChallenge && c.value === 16 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_DARK: new ChallengeAchv("MONO_DARK", "", "MONO_DARK.description", "black_glasses", 100, (c) => c instanceof SingleTypeChallenge && c.value === 17 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c) => c instanceof SingleTypeChallenge && c.value === 18 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c) => c instanceof FreshStartChallenge && c.value > 0 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, c => c instanceof InverseBattleChallenge && c.value > 0), + MONO_GEN_ONE_VICTORY: new ChallengeAchv("MONO_GEN_ONE", "", "MONO_GEN_ONE.description", "ribbon_gen1", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_TWO_VICTORY: new ChallengeAchv("MONO_GEN_TWO", "", "MONO_GEN_TWO.description", "ribbon_gen2", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_THREE_VICTORY: new ChallengeAchv("MONO_GEN_THREE", "", "MONO_GEN_THREE.description", "ribbon_gen3", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_FOUR_VICTORY: new ChallengeAchv("MONO_GEN_FOUR", "", "MONO_GEN_FOUR.description", "ribbon_gen4", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_FIVE_VICTORY: new ChallengeAchv("MONO_GEN_FIVE", "", "MONO_GEN_FIVE.description", "ribbon_gen5", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_SIX_VICTORY: new ChallengeAchv("MONO_GEN_SIX", "", "MONO_GEN_SIX.description", "ribbon_gen6", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_SEVEN_VICTORY: new ChallengeAchv("MONO_GEN_SEVEN", "", "MONO_GEN_SEVEN.description", "ribbon_gen7", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_EIGHT_VICTORY: new ChallengeAchv("MONO_GEN_EIGHT", "", "MONO_GEN_EIGHT.description", "ribbon_gen8", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_NINE_VICTORY: new ChallengeAchv("MONO_GEN_NINE", "", "MONO_GEN_NINE.description", "ribbon_gen9", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_NORMAL: new ChallengeAchv("MONO_NORMAL", "", "MONO_NORMAL.description", "silk_scarf", 100, (c) => c instanceof SingleTypeChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_FIGHTING: new ChallengeAchv("MONO_FIGHTING", "", "MONO_FIGHTING.description", "black_belt", 100, (c) => c instanceof SingleTypeChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_FLYING: new ChallengeAchv("MONO_FLYING", "", "MONO_FLYING.description", "sharp_beak", 100, (c) => c instanceof SingleTypeChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_POISON: new ChallengeAchv("MONO_POISON", "", "MONO_POISON.description", "poison_barb", 100, (c) => c instanceof SingleTypeChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GROUND: new ChallengeAchv("MONO_GROUND", "", "MONO_GROUND.description", "soft_sand", 100, (c) => c instanceof SingleTypeChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_ROCK: new ChallengeAchv("MONO_ROCK", "", "MONO_ROCK.description", "hard_stone", 100, (c) => c instanceof SingleTypeChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_BUG: new ChallengeAchv("MONO_BUG", "", "MONO_BUG.description", "silver_powder", 100, (c) => c instanceof SingleTypeChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GHOST: new ChallengeAchv("MONO_GHOST", "", "MONO_GHOST.description", "spell_tag", 100, (c) => c instanceof SingleTypeChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_STEEL: new ChallengeAchv("MONO_STEEL", "", "MONO_STEEL.description", "metal_coat", 100, (c) => c instanceof SingleTypeChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_FIRE: new ChallengeAchv("MONO_FIRE", "", "MONO_FIRE.description", "charcoal", 100, (c) => c instanceof SingleTypeChallenge && c.value === 10 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_WATER: new ChallengeAchv("MONO_WATER", "", "MONO_WATER.description", "mystic_water", 100, (c) => c instanceof SingleTypeChallenge && c.value === 11 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GRASS: new ChallengeAchv("MONO_GRASS", "", "MONO_GRASS.description", "miracle_seed", 100, (c) => c instanceof SingleTypeChallenge && c.value === 12 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_ELECTRIC: new ChallengeAchv("MONO_ELECTRIC", "", "MONO_ELECTRIC.description", "magnet", 100, (c) => c instanceof SingleTypeChallenge && c.value === 13 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_PSYCHIC: new ChallengeAchv("MONO_PSYCHIC", "", "MONO_PSYCHIC.description", "twisted_spoon", 100, (c) => c instanceof SingleTypeChallenge && c.value === 14 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_ICE: new ChallengeAchv("MONO_ICE", "", "MONO_ICE.description", "never_melt_ice", 100, (c) => c instanceof SingleTypeChallenge && c.value === 15 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_DRAGON: new ChallengeAchv("MONO_DRAGON", "", "MONO_DRAGON.description", "dragon_fang", 100, (c) => c instanceof SingleTypeChallenge && c.value === 16 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_DARK: new ChallengeAchv("MONO_DARK", "", "MONO_DARK.description", "black_glasses", 100, (c) => c instanceof SingleTypeChallenge && c.value === 17 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c) => c instanceof SingleTypeChallenge && c.value === 18 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c) => c instanceof FreshStartChallenge && c.value > 0 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, (c) => c instanceof InverseBattleChallenge && c.value > 0), + FLIP_STATS: new ChallengeAchv("FLIP_STATS", "", "FLIP_STATS.description", "dubious_disc", 100, (c) => c instanceof FlipStatChallenge && c.value > 0), + FLIP_INVERSE: new ChallengeAchv("FLIP_INVERSE", "", "FLIP_INVERSE.description", "cracked_pot", 100, (c) => c instanceof FlipStatChallenge && c.value > 0 && globalScene.gameMode.challenges.every(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 50).setSecret(), }; From c3641a370f66b26e1a2c6f3ed6a2d7d11ecd61b1 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:11:19 -0500 Subject: [PATCH 13/14] Add BW crit throw sound (#5131) --- public/audio/se/crit_throw.wav | Bin 0 -> 134444 bytes src/loading-scene.ts | 1 + src/phases/attempt-capture-phase.ts | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 public/audio/se/crit_throw.wav diff --git a/public/audio/se/crit_throw.wav b/public/audio/se/crit_throw.wav new file mode 100644 index 0000000000000000000000000000000000000000..a737410e7ce3857ea6369f1f828d555d476b5567 GIT binary patch literal 134444 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 { globalScene.field.moveBelow(this.pokeball as Phaser.GameObjects.GameObject, pokemon); }); From 188664f389aff4318c0a558339c8eebf2b7d10a6 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 15 Jan 2025 19:25:08 -0800 Subject: [PATCH 14/14] [Sprite] Fix game not waiting for variant data to finish loading (#5130) Co-authored-by: Moka Co-authored-by: damocleas --- src/battle-scene.ts | 30 +++++++++++++++------------- src/data/pokemon-species.ts | 3 +-- src/field/mystery-encounter-intro.ts | 5 +++-- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index e9d5a97ab8d..9b578c1e977 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -363,28 +363,30 @@ export default class BattleScene extends SceneBase { /** * Load the variant assets for the given sprite and stores them in {@linkcode variantColorCache} */ - loadPokemonVariantAssets(spriteKey: string, fileRoot: string, variant?: Variant) { + public async loadPokemonVariantAssets(spriteKey: string, fileRoot: string, variant?: Variant): Promise { const useExpSprite = this.experimentalSprites && this.hasExpSprite(spriteKey); if (useExpSprite) { fileRoot = `exp/${fileRoot}`; } let variantConfig = variantData; - fileRoot.split("/").map(p => variantConfig ? variantConfig = variantConfig[p] : null); + fileRoot.split("/").map((p) => (variantConfig ? (variantConfig = variantConfig[p]) : null)); const variantSet = variantConfig as VariantSet; - if (variantSet && (variant !== undefined && variantSet[variant] === 1)) { - const populateVariantColors = (key: string): Promise => { - return new Promise(resolve => { - if (variantColorCache.hasOwnProperty(key)) { - return resolve(); - } - this.cachedFetch(`./images/pokemon/variant/${fileRoot}.json`).then(res => res.json()).then(c => { - variantColorCache[key] = c; + + return new Promise((resolve) => { + if (variantSet && variant !== undefined && variantSet[variant] === 1) { + if (variantColorCache.hasOwnProperty(spriteKey)) { + return resolve(); + } + this.cachedFetch(`./images/pokemon/variant/${fileRoot}.json`) + .then((res) => res.json()) + .then((c) => { + variantColorCache[spriteKey] = c; resolve(); }); - }); - }; - populateVariantColors(spriteKey); - } + } else { + resolve(); + } + }); } async preload() { diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 285c2a70236..574c2a67f65 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -516,8 +516,7 @@ export abstract class PokemonSpeciesForm { globalScene.anims.get(spriteKey).frameRate = 10; } const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, ""); - globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant); - resolve(); + globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant).then(() => resolve()); }); if (startLoad) { if (!globalScene.load.isLoading()) { diff --git a/src/field/mystery-encounter-intro.ts b/src/field/mystery-encounter-intro.ts index 4fce9b1dfc9..0110dabc7a9 100644 --- a/src/field/mystery-encounter-intro.ts +++ b/src/field/mystery-encounter-intro.ts @@ -215,11 +215,12 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con resolve(); } + const shinyPromises: Promise[] = []; this.spriteConfigs.forEach((config) => { if (config.isPokemon) { globalScene.loadPokemonAtlas(config.spriteKey, config.fileRoot); if (config.isShiny) { - globalScene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant); + shinyPromises.push(globalScene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant)); } } else if (config.isItem) { globalScene.loadAtlas("items", ""); @@ -254,7 +255,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con return true; }); - resolve(); + Promise.all(shinyPromises).then(() => resolve()); }); if (!globalScene.load.isLoading()) {